Go to most recent revision | Details | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
2498 | - | 1 | /* |
2 | Copyright © 2002, The KPD-Team |
||
3 | All rights reserved. |
||
4 | http://www.mentalis.org/ |
||
5 | |||
6 | Redistribution and use in source and binary forms, with or without |
||
7 | modification, are permitted provided that the following conditions |
||
8 | are met: |
||
9 | |||
10 | - Redistributions of source code must retain the above copyright |
||
11 | notice, this list of conditions and the following disclaimer. |
||
12 | |||
13 | - Neither the name of the KPD-Team, nor the names of its contributors |
||
14 | may be used to endorse or promote products derived from this |
||
15 | software without specific prior written permission. |
||
16 | |||
17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||
18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||
19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS |
||
20 | FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL |
||
21 | THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, |
||
22 | INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
||
23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
||
24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
||
25 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, |
||
26 | STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
||
27 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED |
||
28 | OF THE POSSIBILITY OF SUCH DAMAGE. |
||
29 | */ |
||
30 | |||
31 | using System; |
||
32 | using System.Net; |
||
33 | using System.Net.Sockets; |
||
34 | using System.Text; |
||
35 | using Org.Mentalis.Network.ProxySocket.Authentication; |
||
36 | |||
37 | namespace Org.Mentalis.Network.ProxySocket { |
||
38 | /// <summary> |
||
39 | /// Implements the SOCKS5 protocol. |
||
40 | /// </summary> |
||
41 | internal sealed class Socks5Handler : SocksHandler { |
||
42 | /// <summary> |
||
43 | /// Initiliazes a new Socks5Handler instance. |
||
44 | /// </summary> |
||
45 | /// <param name="server">The socket connection with the proxy server.</param> |
||
46 | /// <exception cref="ArgumentNullException"><c>server</c> is null.</exception> |
||
47 | public Socks5Handler(Socket server) : this(server, "") {} |
||
48 | /// <summary> |
||
49 | /// Initiliazes a new Socks5Handler instance. |
||
50 | /// </summary> |
||
51 | /// <param name="server">The socket connection with the proxy server.</param> |
||
52 | /// <param name="user">The username to use.</param> |
||
53 | /// <exception cref="ArgumentNullException"><c>server</c> -or- <c>user</c> is null.</exception> |
||
54 | public Socks5Handler(Socket server, string user) : this(server, user, "") {} |
||
55 | /// <summary> |
||
56 | /// Initiliazes a new Socks5Handler instance. |
||
57 | /// </summary> |
||
58 | /// <param name="server">The socket connection with the proxy server.</param> |
||
59 | /// <param name="user">The username to use.</param> |
||
60 | /// <param name="pass">The password to use.</param> |
||
61 | /// <exception cref="ArgumentNullException"><c>server</c> -or- <c>user</c> -or- <c>pass</c> is null.</exception> |
||
62 | public Socks5Handler(Socket server, string user, string pass) : base(server, user) { |
||
63 | Password = pass; |
||
64 | } |
||
65 | /// <summary> |
||
66 | /// Starts the synchronous authentication process. |
||
67 | /// </summary> |
||
68 | /// <exception cref="ProxyException">Authentication with the proxy server failed.</exception> |
||
69 | /// <exception cref="ProtocolViolationException">The proxy server uses an invalid protocol.</exception> |
||
70 | /// <exception cref="SocketException">An operating system error occurs while accessing the Socket.</exception> |
||
71 | /// <exception cref="ObjectDisposedException">The Socket has been closed.</exception> |
||
72 | private void Authenticate() { |
||
73 | Server.Send(new byte [] {5, 2, 0, 2}); |
||
74 | byte[] buffer = ReadBytes(2); |
||
75 | if (buffer[1] == 255) |
||
76 | throw new ProxyException("No authentication method accepted."); |
||
77 | AuthMethod authenticate; |
||
78 | switch (buffer[1]) { |
||
79 | case 0: |
||
80 | authenticate = new AuthNone(Server); |
||
81 | break; |
||
82 | case 2: |
||
83 | authenticate = new AuthUserPass(Server, Username, Password); |
||
84 | break; |
||
85 | default: |
||
86 | throw new ProtocolViolationException(); |
||
87 | } |
||
88 | authenticate.Authenticate(); |
||
89 | } |
||
90 | /// <summary> |
||
91 | /// Creates an array of bytes that has to be sent when the user wants to connect to a specific host/port combination. |
||
92 | /// </summary> |
||
93 | /// <param name="host">The host to connect to.</param> |
||
94 | /// <param name="port">The port to connect to.</param> |
||
95 | /// <returns>An array of bytes that has to be sent when the user wants to connect to a specific host/port combination.</returns> |
||
96 | /// <exception cref="ArgumentNullException"><c>host</c> is null.</exception> |
||
97 | /// <exception cref="ArgumentException"><c>port</c> or <c>host</c> is invalid.</exception> |
||
98 | private byte[] GetHostPortBytes(string host, int port) { |
||
99 | if (host == null) |
||
100 | throw new ArgumentNullException(); |
||
101 | if (port <= 0 || port > 65535 || host.Length > 255) |
||
102 | throw new ArgumentException(); |
||
103 | byte [] connect = new byte[7 + host.Length]; |
||
104 | connect[0] = 5; |
||
105 | connect[1] = 1; |
||
106 | connect[2] = 0; //reserved |
||
107 | connect[3] = 3; |
||
108 | connect[4] = (byte)host.Length; |
||
109 | Array.Copy(Encoding.ASCII.GetBytes(host), 0, connect, 5, host.Length); |
||
110 | Array.Copy(PortToBytes(port), 0, connect, host.Length + 5, 2); |
||
111 | return connect; |
||
112 | } |
||
113 | /// <summary> |
||
114 | /// Creates an array of bytes that has to be sent when the user wants to connect to a specific IPEndPoint. |
||
115 | /// </summary> |
||
116 | /// <param name="remoteEP">The IPEndPoint to connect to.</param> |
||
117 | /// <returns>An array of bytes that has to be sent when the user wants to connect to a specific IPEndPoint.</returns> |
||
118 | /// <exception cref="ArgumentNullException"><c>remoteEP</c> is null.</exception> |
||
119 | private byte[] GetEndPointBytes(IPEndPoint remoteEP) { |
||
120 | if (remoteEP == null) |
||
121 | throw new ArgumentNullException(); |
||
122 | byte [] connect = new byte[10]; |
||
123 | connect[0] = 5; |
||
124 | connect[1] = 1; |
||
125 | connect[2] = 0; //reserved |
||
126 | connect[3] = 1; |
||
127 | |||
128 | Array.Copy(remoteEP.Address.GetAddressBytes(), 0, connect, 4, 4); |
||
129 | Array.Copy(PortToBytes(remoteEP.Port), 0, connect, 8, 2); |
||
130 | return connect; |
||
131 | } |
||
132 | /// <summary> |
||
133 | /// Starts negotiating with the SOCKS server. |
||
134 | /// </summary> |
||
135 | /// <param name="host">The host to connect to.</param> |
||
136 | /// <param name="port">The port to connect to.</param> |
||
137 | /// <exception cref="ArgumentNullException"><c>host</c> is null.</exception> |
||
138 | /// <exception cref="ArgumentException"><c>port</c> is invalid.</exception> |
||
139 | /// <exception cref="ProxyException">The proxy rejected the request.</exception> |
||
140 | /// <exception cref="SocketException">An operating system error occurs while accessing the Socket.</exception> |
||
141 | /// <exception cref="ObjectDisposedException">The Socket has been closed.</exception> |
||
142 | /// <exception cref="ProtocolViolationException">The proxy server uses an invalid protocol.</exception> |
||
143 | public override void Negotiate(string host, int port) { |
||
144 | Negotiate(GetHostPortBytes(host, port)); |
||
145 | } |
||
146 | /// <summary> |
||
147 | /// Starts negotiating with the SOCKS server. |
||
148 | /// </summary> |
||
149 | /// <param name="remoteEP">The IPEndPoint to connect to.</param> |
||
150 | /// <exception cref="ArgumentNullException"><c>remoteEP</c> is null.</exception> |
||
151 | /// <exception cref="ProxyException">The proxy rejected the request.</exception> |
||
152 | /// <exception cref="SocketException">An operating system error occurs while accessing the Socket.</exception> |
||
153 | /// <exception cref="ObjectDisposedException">The Socket has been closed.</exception> |
||
154 | /// <exception cref="ProtocolViolationException">The proxy server uses an invalid protocol.</exception> |
||
155 | public override void Negotiate(IPEndPoint remoteEP) { |
||
156 | Negotiate(GetEndPointBytes(remoteEP)); |
||
157 | } |
||
158 | /// <summary> |
||
159 | /// Starts negotiating with the SOCKS server. |
||
160 | /// </summary> |
||
161 | /// <param name="connect">The bytes to send when trying to authenticate.</param> |
||
162 | /// <exception cref="ArgumentNullException"><c>connect</c> is null.</exception> |
||
163 | /// <exception cref="ArgumentException"><c>connect</c> is too small.</exception> |
||
164 | /// <exception cref="ProxyException">The proxy rejected the request.</exception> |
||
165 | /// <exception cref="SocketException">An operating system error occurs while accessing the Socket.</exception> |
||
166 | /// <exception cref="ObjectDisposedException">The Socket has been closed.</exception> |
||
167 | /// <exception cref="ProtocolViolationException">The proxy server uses an invalid protocol.</exception> |
||
168 | private void Negotiate(byte[] connect) { |
||
169 | Authenticate(); |
||
170 | Server.Send(connect); |
||
171 | byte[] buffer = ReadBytes(4); |
||
172 | if (buffer[1] != 0) { |
||
173 | Server.Close(); |
||
174 | throw new ProxyException(buffer[1]); |
||
175 | } |
||
176 | switch(buffer[3]) { |
||
177 | case 1: |
||
178 | buffer = ReadBytes(6); //IPv4 address with port |
||
179 | break; |
||
180 | case 3: |
||
181 | buffer = ReadBytes(1); |
||
182 | buffer = ReadBytes(buffer[0] + 2); //domain name with port |
||
183 | break; |
||
184 | case 4: |
||
185 | buffer = ReadBytes(18); //IPv6 address with port |
||
186 | break; |
||
187 | default: |
||
188 | Server.Close(); |
||
189 | throw new ProtocolViolationException(); |
||
190 | } |
||
191 | } |
||
192 | /// <summary> |
||
193 | /// Starts negotiating asynchronously with the SOCKS server. |
||
194 | /// </summary> |
||
195 | /// <param name="host">The host to connect to.</param> |
||
196 | /// <param name="port">The port to connect to.</param> |
||
197 | /// <param name="callback">The method to call when the negotiation is complete.</param> |
||
198 | /// <param name="proxyEndPoint">The IPEndPoint of the SOCKS proxy server.</param> |
||
199 | /// <returns>An IAsyncProxyResult that references the asynchronous connection.</returns> |
||
200 | public override IAsyncProxyResult BeginNegotiate(string host, int port, HandShakeComplete callback, IPEndPoint proxyEndPoint) { |
||
201 | ProtocolComplete = callback; |
||
202 | HandShake = GetHostPortBytes(host, port); |
||
203 | Server.BeginConnect(proxyEndPoint, new AsyncCallback(this.OnConnect), Server); |
||
204 | AsyncResult = new IAsyncProxyResult(); |
||
205 | return AsyncResult; |
||
206 | } |
||
207 | /// <summary> |
||
208 | /// Starts negotiating asynchronously with the SOCKS server. |
||
209 | /// </summary> |
||
210 | /// <param name="remoteEP">An IPEndPoint that represents the remote device.</param> |
||
211 | /// <param name="callback">The method to call when the negotiation is complete.</param> |
||
212 | /// <param name="proxyEndPoint">The IPEndPoint of the SOCKS proxy server.</param> |
||
213 | /// <returns>An IAsyncProxyResult that references the asynchronous connection.</returns> |
||
214 | public override IAsyncProxyResult BeginNegotiate(IPEndPoint remoteEP, HandShakeComplete callback, IPEndPoint proxyEndPoint) { |
||
215 | ProtocolComplete = callback; |
||
216 | HandShake = GetEndPointBytes(remoteEP); |
||
217 | Server.BeginConnect(proxyEndPoint, new AsyncCallback(this.OnConnect), Server); |
||
218 | AsyncResult = new IAsyncProxyResult(); |
||
219 | return AsyncResult; |
||
220 | } |
||
221 | /// <summary> |
||
222 | /// Called when the socket is connected to the remote server. |
||
223 | /// </summary> |
||
224 | /// <param name="ar">Stores state information for this asynchronous operation as well as any user-defined data.</param> |
||
225 | private void OnConnect(IAsyncResult ar) { |
||
226 | try { |
||
227 | Server.EndConnect(ar); |
||
228 | } catch (Exception e) { |
||
229 | ProtocolComplete(e); |
||
230 | return; |
||
231 | } |
||
232 | try { |
||
233 | Server.BeginSend(new byte [] {5, 2, 0, 2}, 0, 4, SocketFlags.None, new AsyncCallback(this.OnAuthSent), Server); |
||
234 | } catch (Exception e) { |
||
235 | ProtocolComplete(e); |
||
236 | } |
||
237 | } |
||
238 | /// <summary> |
||
239 | /// Called when the authentication bytes have been sent. |
||
240 | /// </summary> |
||
241 | /// <param name="ar">Stores state information for this asynchronous operation as well as any user-defined data.</param> |
||
242 | private void OnAuthSent(IAsyncResult ar) { |
||
243 | try { |
||
244 | Server.EndSend(ar); |
||
245 | } catch (Exception e) { |
||
246 | ProtocolComplete(e); |
||
247 | return; |
||
248 | } |
||
249 | try { |
||
250 | Buffer = new byte[1024]; |
||
251 | Received = 0; |
||
252 | Server.BeginReceive(Buffer, 0, Buffer.Length, SocketFlags.None, new AsyncCallback(this.OnAuthReceive), Server); |
||
253 | } catch (Exception e) { |
||
254 | ProtocolComplete(e); |
||
255 | } |
||
256 | } |
||
257 | /// <summary> |
||
258 | /// Called when an authentication reply has been received. |
||
259 | /// </summary> |
||
260 | /// <param name="ar">Stores state information for this asynchronous operation as well as any user-defined data.</param> |
||
261 | private void OnAuthReceive(IAsyncResult ar) { |
||
262 | try { |
||
263 | Received += Server.EndReceive(ar); |
||
264 | if (Received <= 0) |
||
265 | throw new SocketException(); |
||
266 | } catch (Exception e) { |
||
267 | ProtocolComplete(e); |
||
268 | return; |
||
269 | } |
||
270 | try { |
||
271 | if (Received < 2) { |
||
272 | Server.BeginReceive(Buffer, Received, Buffer.Length - Received, SocketFlags.None, new AsyncCallback(this.OnAuthReceive), Server); |
||
273 | } else { |
||
274 | AuthMethod authenticate; |
||
275 | switch(Buffer[1]) { |
||
276 | case 0: |
||
277 | authenticate = new AuthNone(Server); |
||
278 | break; |
||
279 | case 2: |
||
280 | authenticate = new AuthUserPass(Server, Username, Password); |
||
281 | break; |
||
282 | default: |
||
283 | ProtocolComplete(new SocketException()); |
||
284 | return; |
||
285 | } |
||
286 | authenticate.BeginAuthenticate(new HandShakeComplete(this.OnAuthenticated)); |
||
287 | } |
||
288 | } catch (Exception e) { |
||
289 | ProtocolComplete(e); |
||
290 | } |
||
291 | } |
||
292 | /// <summary> |
||
293 | /// Called when the socket has been successfully authenticated with the server. |
||
294 | /// </summary> |
||
295 | /// <param name="e">The exception that has occured while authenticating, or <em>null</em> if no error occured.</param> |
||
296 | private void OnAuthenticated(Exception e) { |
||
297 | if (e != null) { |
||
298 | ProtocolComplete(e); |
||
299 | return; |
||
300 | } |
||
301 | try { |
||
302 | Server.BeginSend(HandShake, 0, HandShake.Length, SocketFlags.None, new AsyncCallback(this.OnSent), Server); |
||
303 | } catch (Exception ex) { |
||
304 | ProtocolComplete(ex); |
||
305 | } |
||
306 | } |
||
307 | /// <summary> |
||
308 | /// Called when the connection request has been sent. |
||
309 | /// </summary> |
||
310 | /// <param name="ar">Stores state information for this asynchronous operation as well as any user-defined data.</param> |
||
311 | private void OnSent(IAsyncResult ar) { |
||
312 | try { |
||
313 | Server.EndSend(ar); |
||
314 | } catch (Exception e) { |
||
315 | ProtocolComplete(e); |
||
316 | return; |
||
317 | } |
||
318 | try { |
||
319 | Buffer = new byte[5]; |
||
320 | Received = 0; |
||
321 | Server.BeginReceive(Buffer, 0, Buffer.Length, SocketFlags.None, new AsyncCallback(this.OnReceive), Server); |
||
322 | } catch (Exception e) { |
||
323 | ProtocolComplete(e); |
||
324 | } |
||
325 | } |
||
326 | /// <summary> |
||
327 | /// Called when a connection reply has been received. |
||
328 | /// </summary> |
||
329 | /// <param name="ar">Stores state information for this asynchronous operation as well as any user-defined data.</param> |
||
330 | private void OnReceive(IAsyncResult ar) { |
||
331 | try { |
||
332 | Received += Server.EndReceive(ar); |
||
333 | } catch (Exception e) { |
||
334 | ProtocolComplete(e); |
||
335 | return; |
||
336 | } |
||
337 | try { |
||
338 | if (Received == Buffer.Length) |
||
339 | ProcessReply(Buffer); |
||
340 | else |
||
341 | Server.BeginReceive(Buffer, Received, Buffer.Length - Received, SocketFlags.None, new AsyncCallback(this.OnReceive), Server); |
||
342 | } catch (Exception e) { |
||
343 | ProtocolComplete(e); |
||
344 | } |
||
345 | } |
||
346 | /// <summary> |
||
347 | /// Processes the received reply. |
||
348 | /// </summary> |
||
349 | /// <param name="buffer">The received reply</param> |
||
350 | /// <exception cref="ProtocolViolationException">The received reply is invalid.</exception> |
||
351 | private void ProcessReply(byte[] buffer) { |
||
352 | switch(buffer[3]) { |
||
353 | case 1: |
||
354 | Buffer = new byte[5]; //IPv4 address with port - 1 byte |
||
355 | break; |
||
356 | case 3: |
||
357 | Buffer = new byte[buffer[4] + 2]; //domain name with port |
||
358 | break; |
||
359 | case 4: |
||
360 | buffer = new byte[17]; //IPv6 address with port - 1 byte |
||
361 | break; |
||
362 | default: |
||
363 | throw new ProtocolViolationException(); |
||
364 | } |
||
365 | Received = 0; |
||
366 | Server.BeginReceive(Buffer, 0, Buffer.Length, SocketFlags.None, new AsyncCallback(this.OnReadLast), Server); |
||
367 | } |
||
368 | /// <summary> |
||
369 | /// Called when the last bytes are read from the socket. |
||
370 | /// </summary> |
||
371 | /// <param name="ar">Stores state information for this asynchronous operation as well as any user-defined data.</param> |
||
372 | private void OnReadLast(IAsyncResult ar) { |
||
373 | try { |
||
374 | Received += Server.EndReceive(ar); |
||
375 | } catch (Exception e) { |
||
376 | ProtocolComplete(e); |
||
377 | return; |
||
378 | } |
||
379 | try { |
||
380 | if (Received == Buffer.Length) |
||
381 | ProtocolComplete(null); |
||
382 | else |
||
383 | Server.BeginReceive(Buffer, Received, Buffer.Length - Received, SocketFlags.None, new AsyncCallback(this.OnReadLast), Server); |
||
384 | } catch (Exception e) { |
||
385 | ProtocolComplete(e); |
||
386 | } |
||
387 | } |
||
388 | /// <summary> |
||
389 | /// Gets or sets the password to use when authenticating with the SOCKS5 server. |
||
390 | /// </summary> |
||
391 | /// <value>The password to use when authenticating with the SOCKS5 server.</value> |
||
392 | private string Password { |
||
393 | get { |
||
394 | return m_Password; |
||
395 | } |
||
396 | set { |
||
397 | if (value == null) |
||
398 | throw new ArgumentNullException(); |
||
399 | m_Password = value; |
||
400 | } |
||
401 | } |
||
402 | /// <summary> |
||
403 | /// Gets or sets the bytes to use when sending a connect request to the proxy server. |
||
404 | /// </summary> |
||
405 | /// <value>The array of bytes to use when sending a connect request to the proxy server.</value> |
||
406 | private byte[] HandShake { |
||
407 | get { |
||
408 | return m_HandShake; |
||
409 | } |
||
410 | set { |
||
411 | m_HandShake = value; |
||
412 | } |
||
413 | } |
||
414 | // private variables |
||
415 | /// <summary>Holds the value of the Password property.</summary> |
||
416 | private string m_Password; |
||
417 | /// <summary>Holds the value of the HandShake property.</summary> |
||
418 | private byte[] m_HandShake; |
||
419 | } |
||
420 | } |