Subversion Repositories Projects

Rev

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
}