Subversion Repositories Projects

Rev

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
 
35
// Implements a number of classes to allow Sockets to connect trough a firewall.
36
namespace Org.Mentalis.Network.ProxySocket
37
{
38
    /// <summary>
39
    /// Specifies the type of proxy servers that an instance of the ProxySocket class can use.
40
    /// </summary>
41
    internal enum ProxyTypes
42
    {
43
        /// <summary>No proxy server; the ProxySocket object behaves exactly like an ordinary Socket object.</summary>
44
        None,
45
        /// <summary>A SOCKS4[A] proxy server.</summary>
46
        Socks4,
47
        /// <summary>A SOCKS5 proxy server.</summary>
48
        Socks5
49
    }
50
 
51
    /// <summary>
52
    /// Implements a Socket class that can connect trough a SOCKS proxy server.
53
    /// </summary>
54
    /// <remarks>This class implements SOCKS4[A] and SOCKS5.<br>It does not, however, implement the BIND commands, so you cannot .</br></remarks>
55
    internal class ProxySocket : Socket
56
    {
57
        /// <summary>
58
        /// Initializes a new instance of the ProxySocket class.
59
        /// </summary>
60
        /// <param name="addressFamily">One of the AddressFamily values.</param>
61
        /// <param name="socketType">One of the SocketType values.</param>
62
        /// <param name="protocolType">One of the ProtocolType values.</param>
63
        /// <exception cref="SocketException">The combination of addressFamily, socketType, and protocolType results in an invalid socket.</exception>
64
        public ProxySocket(AddressFamily addressFamily, SocketType socketType, ProtocolType protocolType) : this(addressFamily, socketType, protocolType, "") { }
65
 
66
        /// <summary>
67
        /// Initializes a new instance of the ProxySocket class.
68
        /// </summary>
69
        /// <param name="addressFamily">One of the AddressFamily values.</param>
70
        /// <param name="socketType">One of the SocketType values.</param>
71
        /// <param name="protocolType">One of the ProtocolType values.</param>
72
        /// <param name="proxyUsername">The username to use when authenticating with the proxy server.</param>
73
        /// <exception cref="SocketException">The combination of addressFamily, socketType, and protocolType results in an invalid socket.</exception>
74
        /// <exception cref="ArgumentNullException"><c>proxyUsername</c> is null.</exception>
75
        public ProxySocket(AddressFamily addressFamily, SocketType socketType, ProtocolType protocolType, string proxyUsername) : this(addressFamily, socketType, protocolType, proxyUsername, "") { }
76
 
77
        /// <summary>
78
        /// Initializes a new instance of the ProxySocket class.
79
        /// </summary>
80
        /// <param name="addressFamily">One of the AddressFamily values.</param>
81
        /// <param name="socketType">One of the SocketType values.</param>
82
        /// <param name="protocolType">One of the ProtocolType values.</param>
83
        /// <param name="proxyUsername">The username to use when authenticating with the proxy server.</param>
84
        /// <param name="proxyPassword">The password to use when authenticating with the proxy server.</param>
85
        /// <exception cref="SocketException">The combination of addressFamily, socketType, and protocolType results in an invalid socket.</exception>
86
        /// <exception cref="ArgumentNullException"><c>proxyUsername</c> -or- <c>proxyPassword</c> is null.</exception>
87
        public ProxySocket(AddressFamily addressFamily, SocketType socketType, ProtocolType protocolType, string proxyUsername, string proxyPassword)
88
            : base(addressFamily, socketType, protocolType)
89
        {
90
            ProxyUser = proxyUsername;
91
            ProxyPass = proxyPassword;
92
            ToThrow = new InvalidOperationException();
93
        }
94
 
95
        /// <summary>
96
        /// Establishes a connection to a remote device.
97
        /// </summary>
98
        /// <param name="remoteEP">An EndPoint that represents the remote device.</param>
99
        /// <exception cref="ArgumentNullException">The remoteEP parameter is a null reference (Nothing in Visual Basic).</exception>
100
        /// <exception cref="SocketException">An operating system error occurs while accessing the Socket.</exception>
101
        /// <exception cref="ObjectDisposedException">The Socket has been closed.</exception>
102
        /// <exception cref="ProxyException">An error occured while talking to the proxy server.</exception>
103
        public new void Connect(EndPoint remoteEP)
104
        {
105
            if (remoteEP == null)
106
                throw new ArgumentNullException("<remoteEP> cannot be null.");
107
            if (this.ProtocolType != ProtocolType.Tcp || ProxyType == ProxyTypes.None || ProxyEndPoint == null)
108
                base.Connect(remoteEP);
109
            else
110
            {
111
                base.Connect(ProxyEndPoint);
112
                if (ProxyType == ProxyTypes.Socks4)
113
                    (new Socks4Handler(this, ProxyUser)).Negotiate((IPEndPoint)remoteEP);
114
                else if (ProxyType == ProxyTypes.Socks5)
115
                    (new Socks5Handler(this, ProxyUser, ProxyPass)).Negotiate((IPEndPoint)remoteEP);
116
            }
117
        }
118
 
119
        /// <summary>
120
        /// Establishes a connection to a remote device.
121
        /// </summary>
122
        /// <param name="host">The remote host to connect to.</param>
123
        /// <param name="port">The remote port to connect to.</param>
124
        /// <exception cref="ArgumentNullException">The host parameter is a null reference (Nothing in Visual Basic).</exception>
125
        /// <exception cref="ArgumentException">The port parameter is invalid.</exception>
126
        /// <exception cref="SocketException">An operating system error occurs while accessing the Socket.</exception>
127
        /// <exception cref="ObjectDisposedException">The Socket has been closed.</exception>
128
        /// <exception cref="ProxyException">An error occured while talking to the proxy server.</exception>
129
        /// <remarks>If you use this method with a SOCKS4 server, it will let the server resolve the hostname. Not all SOCKS4 servers support this 'remote DNS' though.</remarks>
130
        public void Connect(string host, int port)
131
        {
132
            if (host == null)
133
                throw new ArgumentNullException("<host> cannot be null.");
134
            if (port <= 0 || port > 65535)
135
                throw new ArgumentException("Invalid port.");
136
            if (this.ProtocolType != ProtocolType.Tcp || ProxyType == ProxyTypes.None || ProxyEndPoint == null)
137
                base.Connect(new IPEndPoint(Dns.GetHostEntry(host).AddressList[0], port));
138
            else
139
            {
140
                base.Connect(ProxyEndPoint);
141
                if (ProxyType == ProxyTypes.Socks4)
142
                    (new Socks4Handler(this, ProxyUser)).Negotiate(host, port);
143
                else if (ProxyType == ProxyTypes.Socks5)
144
                    (new Socks5Handler(this, ProxyUser, ProxyPass)).Negotiate(host, port);
145
            }
146
        }
147
 
148
        /// <summary>
149
        /// Begins an asynchronous request for a connection to a network device.
150
        /// </summary>
151
        /// <param name="remoteEP">An EndPoint that represents the remote device.</param>
152
        /// <param name="callback">The AsyncCallback delegate.</param>
153
        /// <param name="state">An object that contains state information for this request.</param>
154
        /// <returns>An IAsyncResult that references the asynchronous connection.</returns>
155
        /// <exception cref="ArgumentNullException">The remoteEP parameter is a null reference (Nothing in Visual Basic).</exception>
156
        /// <exception cref="SocketException">An operating system error occurs while creating the Socket.</exception>
157
        /// <exception cref="ObjectDisposedException">The Socket has been closed.</exception>
158
        public new IAsyncResult BeginConnect(EndPoint remoteEP, AsyncCallback callback, object state)
159
        {
160
            if (remoteEP == null || callback == null)
161
                throw new ArgumentNullException();
162
            if (this.ProtocolType != ProtocolType.Tcp || ProxyType == ProxyTypes.None || ProxyEndPoint == null)
163
            {
164
                return base.BeginConnect(remoteEP, callback, state);
165
            }
166
            else
167
            {
168
                CallBack = callback;
169
                if (ProxyType == ProxyTypes.Socks4)
170
                {
171
                    AsyncResult = (new Socks4Handler(this, ProxyUser)).BeginNegotiate((IPEndPoint)remoteEP, new HandShakeComplete(this.OnHandShakeComplete), ProxyEndPoint);
172
                    return AsyncResult;
173
                }
174
                else if (ProxyType == ProxyTypes.Socks5)
175
                {
176
                    AsyncResult = (new Socks5Handler(this, ProxyUser, ProxyPass)).BeginNegotiate((IPEndPoint)remoteEP, new HandShakeComplete(this.OnHandShakeComplete), ProxyEndPoint);
177
                    return AsyncResult;
178
                }
179
                return null;
180
            }
181
        }
182
 
183
        /// <summary>
184
        /// Begins an asynchronous request for a connection to a network device.
185
        /// </summary>
186
        /// <param name="host">The host to connect to.</param>
187
        /// <param name="port">The port on the remote host to connect to.</param>
188
        /// <param name="callback">The AsyncCallback delegate.</param>
189
        /// <param name="state">An object that contains state information for this request.</param>
190
        /// <returns>An IAsyncResult that references the asynchronous connection.</returns>
191
        /// <exception cref="ArgumentNullException">The host parameter is a null reference (Nothing in Visual Basic).</exception>
192
        /// <exception cref="ArgumentException">The port parameter is invalid.</exception>
193
        /// <exception cref="SocketException">An operating system error occurs while creating the Socket.</exception>
194
        /// <exception cref="ObjectDisposedException">The Socket has been closed.</exception>
195
        public IAsyncResult BeginConnect(string host, int port, AsyncCallback callback, object state)
196
        {
197
            if (host == null || callback == null)
198
                throw new ArgumentNullException();
199
            if (port <= 0 || port > 65535)
200
                throw new ArgumentException();
201
            CallBack = callback;
202
            if (this.ProtocolType != ProtocolType.Tcp || ProxyType == ProxyTypes.None || ProxyEndPoint == null)
203
            {
204
                RemotePort = port;
205
                AsyncResult = BeginDns(host, new HandShakeComplete(this.OnHandShakeComplete));
206
                return AsyncResult;
207
            }
208
            else
209
            {
210
                if (ProxyType == ProxyTypes.Socks4)
211
                {
212
                    AsyncResult = (new Socks4Handler(this, ProxyUser)).BeginNegotiate(host, port, new HandShakeComplete(this.OnHandShakeComplete), ProxyEndPoint);
213
                    return AsyncResult;
214
                }
215
                else if (ProxyType == ProxyTypes.Socks5)
216
                {
217
                    AsyncResult = (new Socks5Handler(this, ProxyUser, ProxyPass)).BeginNegotiate(host, port, new HandShakeComplete(this.OnHandShakeComplete), ProxyEndPoint);
218
                    return AsyncResult;
219
                }
220
                return null;
221
            }
222
        }
223
 
224
        /// <summary>
225
        /// Ends a pending asynchronous connection request.
226
        /// </summary>
227
        /// <param name="asyncResult">Stores state information for this asynchronous operation as well as any user-defined data.</param>
228
        /// <exception cref="ArgumentNullException">The asyncResult parameter is a null reference (Nothing in Visual Basic).</exception>
229
        /// <exception cref="ArgumentException">The asyncResult parameter was not returned by a call to the BeginConnect method.</exception>
230
        /// <exception cref="SocketException">An operating system error occurs while accessing the Socket.</exception>
231
        /// <exception cref="ObjectDisposedException">The Socket has been closed.</exception>
232
        /// <exception cref="InvalidOperationException">EndConnect was previously called for the asynchronous connection.</exception>
233
        /// <exception cref="ProxyException">The proxy server refused the connection.</exception>
234
        public new void EndConnect(IAsyncResult asyncResult)
235
        {
236
            if (asyncResult == null)
237
                throw new ArgumentNullException();
238
            if (!asyncResult.IsCompleted)
239
                throw new ArgumentException();
240
            if (ToThrow != null)
241
                throw ToThrow;
242
            return;
243
        }
244
 
245
        /// <summary>
246
        /// Begins an asynchronous request to resolve a DNS host name or IP address in dotted-quad notation to an IPAddress instance.
247
        /// </summary>
248
        /// <param name="host">The host to resolve.</param>
249
        /// <param name="callback">The method to call when the hostname has been resolved.</param>
250
        /// <returns>An IAsyncResult instance that references the asynchronous request.</returns>
251
        /// <exception cref="SocketException">There was an error while trying to resolve the host.</exception>
252
        internal IAsyncProxyResult BeginDns(string host, HandShakeComplete callback)
253
        {
254
            try
255
            {
256
                Dns.BeginGetHostEntry(host, new AsyncCallback(this.OnResolved), this);
257
                return new IAsyncProxyResult();
258
            }
259
            catch
260
            {
261
                throw new SocketException();
262
            }
263
        }
264
 
265
        /// <summary>
266
        /// Called when the specified hostname has been resolved.
267
        /// </summary>
268
        /// <param name="asyncResult">The result of the asynchronous operation.</param>
269
        private void OnResolved(IAsyncResult asyncResult)
270
        {
271
            try
272
            {
273
                IPHostEntry dns = Dns.EndGetHostEntry(asyncResult);
274
                base.BeginConnect(new IPEndPoint(dns.AddressList[0], RemotePort), new AsyncCallback(this.OnConnect), State);
275
            }
276
            catch (Exception e)
277
            {
278
                OnHandShakeComplete(e);
279
            }
280
        }
281
 
282
        /// <summary>
283
        /// Called when the Socket is connected to the remote host.
284
        /// </summary>
285
        /// <param name="asyncResult">The result of the asynchronous operation.</param>
286
        private void OnConnect(IAsyncResult asyncResult)
287
        {
288
            try
289
            {
290
                base.EndConnect(asyncResult);
291
                OnHandShakeComplete(null);
292
            }
293
            catch (Exception e)
294
            {
295
                OnHandShakeComplete(e);
296
            }
297
        }
298
 
299
        /// <summary>
300
        /// Called when the Socket has finished talking to the proxy server and is ready to relay data.
301
        /// </summary>
302
        /// <param name="error">The error to throw when the EndConnect method is called.</param>
303
        private void OnHandShakeComplete(Exception error)
304
        {
305
            if (error != null)
306
                this.Close();
307
            ToThrow = error;
308
            AsyncResult.Reset();
309
            if (CallBack != null)
310
                CallBack(AsyncResult);
311
        }
312
 
313
        /// <summary>
314
        /// Gets or sets the EndPoint of the proxy server.
315
        /// </summary>
316
        /// <value>An IPEndPoint object that holds the IP address and the port of the proxy server.</value>
317
        public IPEndPoint ProxyEndPoint
318
        {
319
            get
320
            {
321
                return m_ProxyEndPoint;
322
            }
323
            set
324
            {
325
                m_ProxyEndPoint = value;
326
            }
327
        }
328
 
329
        /// <summary>
330
        /// Gets or sets the type of proxy server to use.
331
        /// </summary>
332
        /// <value>One of the ProxyTypes values.</value>
333
        public ProxyTypes ProxyType
334
        {
335
            get
336
            {
337
                return m_ProxyType;
338
            }
339
            set
340
            {
341
                m_ProxyType = value;
342
            }
343
        }
344
 
345
        /// <summary>
346
        /// Gets or sets a user-defined object.
347
        /// </summary>
348
        /// <value>The user-defined object.</value>
349
        private object State
350
        {
351
            get
352
            {
353
                return m_State;
354
            }
355
            set
356
            {
357
                m_State = value;
358
            }
359
        }
360
 
361
        /// <summary>
362
        /// Gets or sets the username to use when authenticating with the proxy.
363
        /// </summary>
364
        /// <value>A string that holds the username that's used when authenticating with the proxy.</value>
365
        /// <exception cref="ArgumentNullException">The specified value is null.</exception>
366
        public string ProxyUser
367
        {
368
            get
369
            {
370
                return m_ProxyUser;
371
            }
372
            set
373
            {
374
                if (value == null)
375
                    throw new ArgumentNullException();
376
                m_ProxyUser = value;
377
            }
378
        }
379
 
380
        /// <summary>
381
        /// Gets or sets the password to use when authenticating with the proxy.
382
        /// </summary>
383
        /// <value>A string that holds the password that's used when authenticating with the proxy.</value>
384
        /// <exception cref="ArgumentNullException">The specified value is null.</exception>
385
        public string ProxyPass
386
        {
387
            get
388
            {
389
                return m_ProxyPass;
390
            }
391
            set
392
            {
393
                if (value == null)
394
                    throw new ArgumentNullException();
395
                m_ProxyPass = value;
396
            }
397
        }
398
 
399
        /// <summary>
400
        /// Gets or sets the asynchronous result object.
401
        /// </summary>
402
        /// <value>An instance of the IAsyncProxyResult class.</value>
403
        private IAsyncProxyResult AsyncResult
404
        {
405
            get
406
            {
407
                return m_AsyncResult;
408
            }
409
            set
410
            {
411
                m_AsyncResult = value;
412
            }
413
        }
414
 
415
        /// <summary>
416
        /// Gets or sets the exception to throw when the EndConnect method is called.
417
        /// </summary>
418
        /// <value>An instance of the Exception class (or subclasses of Exception).</value>
419
        private Exception ToThrow
420
        {
421
            get
422
            {
423
                return m_ToThrow;
424
            }
425
            set
426
            {
427
                m_ToThrow = value;
428
            }
429
        }
430
 
431
        /// <summary>
432
        /// Gets or sets the remote port the user wants to connect to.
433
        /// </summary>
434
        /// <value>An integer that specifies the port the user wants to connect to.</value>
435
        private int RemotePort
436
        {
437
            get
438
            {
439
                return m_RemotePort;
440
            }
441
            set
442
            {
443
                m_RemotePort = value;
444
            }
445
        }
446
 
447
        // private variables
448
        /// <summary>Holds the value of the State property.</summary>
449
        private object m_State;
450
        /// <summary>Holds the value of the ProxyEndPoint property.</summary>
451
        private IPEndPoint m_ProxyEndPoint = null;
452
        /// <summary>Holds the value of the ProxyType property.</summary>
453
        private ProxyTypes m_ProxyType = ProxyTypes.None;
454
        /// <summary>Holds the value of the ProxyUser property.</summary>
455
        private string m_ProxyUser = null;
456
        /// <summary>Holds the value of the ProxyPass property.</summary>
457
        private string m_ProxyPass = null;
458
        /// <summary>Holds a pointer to the method that should be called when the Socket is connected to the remote device.</summary>
459
        private AsyncCallback CallBack = null;
460
        /// <summary>Holds the value of the AsyncResult property.</summary>
461
        private IAsyncProxyResult m_AsyncResult;
462
        /// <summary>Holds the value of the ToThrow property.</summary>
463
        private Exception m_ToThrow = null;
464
        /// <summary>Holds the value of the RemotePort property.</summary>
465
        private int m_RemotePort;
466
    }
467
}