~ubuntu-branches/debian/stretch/electrum/stretch

« back to all changes in this revision

Viewing changes to lib/socks.py

  • Committer: Package Import Robot
  • Author(s): Vasudev Kamath
  • Date: 2013-06-19 21:44:47 UTC
  • Revision ID: package-import@ubuntu.com-20130619214447-8n0u7ryn1ftu25w8
Tags: upstream-1.8
ImportĀ upstreamĀ versionĀ 1.8

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
"""SocksiPy - Python SOCKS module.
 
2
Version 1.00
 
3
 
 
4
Copyright 2006 Dan-Haim. All rights reserved.
 
5
 
 
6
Redistribution and use in source and binary forms, with or without modification,
 
7
are permitted provided that the following conditions are met:
 
8
1. Redistributions of source code must retain the above copyright notice, this
 
9
   list of conditions and the following disclaimer.
 
10
2. Redistributions in binary form must reproduce the above copyright notice,
 
11
   this list of conditions and the following disclaimer in the documentation
 
12
   and/or other materials provided with the distribution.
 
13
3. Neither the name of Dan Haim nor the names of his contributors may be used
 
14
   to endorse or promote products derived from this software without specific
 
15
   prior written permission.
 
16
   
 
17
THIS SOFTWARE IS PROVIDED BY DAN HAIM "AS IS" AND ANY EXPRESS OR IMPLIED
 
18
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 
19
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
 
20
EVENT SHALL DAN HAIM OR HIS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 
21
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 
22
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA
 
23
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 
24
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 
25
OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMANGE.
 
26
 
 
27
 
 
28
This module provides a standard socket-like interface for Python
 
29
for tunneling connections through SOCKS proxies.
 
30
 
 
31
"""
 
32
 
 
33
"""
 
34
 
 
35
Minor modifications made by Christopher Gilbert (http://motomastyle.com/)
 
36
for use in PyLoris (http://pyloris.sourceforge.net/)
 
37
 
 
38
Minor modifications made by Mario Vilas (http://breakingcode.wordpress.com/)
 
39
mainly to merge bug fixes found in Sourceforge
 
40
 
 
41
"""
 
42
 
 
43
import socket
 
44
import struct
 
45
import sys
 
46
 
 
47
PROXY_TYPE_SOCKS4 = 1
 
48
PROXY_TYPE_SOCKS5 = 2
 
49
PROXY_TYPE_HTTP = 3
 
50
 
 
51
_defaultproxy = None
 
52
_orgsocket = socket.socket
 
53
 
 
54
class ProxyError(Exception): pass
 
55
class GeneralProxyError(ProxyError): pass
 
56
class Socks5AuthError(ProxyError): pass
 
57
class Socks5Error(ProxyError): pass
 
58
class Socks4Error(ProxyError): pass
 
59
class HTTPError(ProxyError): pass
 
60
 
 
61
_generalerrors = ("success",
 
62
    "invalid data",
 
63
    "not connected",
 
64
    "not available",
 
65
    "bad proxy type",
 
66
    "bad input")
 
67
 
 
68
_socks5errors = ("succeeded",
 
69
    "general SOCKS server failure",
 
70
    "connection not allowed by ruleset",
 
71
    "Network unreachable",
 
72
    "Host unreachable",
 
73
    "Connection refused",
 
74
    "TTL expired",
 
75
    "Command not supported",
 
76
    "Address type not supported",
 
77
    "Unknown error")
 
78
 
 
79
_socks5autherrors = ("succeeded",
 
80
    "authentication is required",
 
81
    "all offered authentication methods were rejected",
 
82
    "unknown username or invalid password",
 
83
    "unknown error")
 
84
 
 
85
_socks4errors = ("request granted",
 
86
    "request rejected or failed",
 
87
    "request rejected because SOCKS server cannot connect to identd on the client",
 
88
    "request rejected because the client program and identd report different user-ids",
 
89
    "unknown error")
 
90
 
 
91
def setdefaultproxy(proxytype=None, addr=None, port=None, rdns=True, username=None, password=None):
 
92
    """setdefaultproxy(proxytype, addr[, port[, rdns[, username[, password]]]])
 
93
    Sets a default proxy which all further socksocket objects will use,
 
94
    unless explicitly changed.
 
95
    """
 
96
    global _defaultproxy
 
97
    _defaultproxy = (proxytype, addr, port, rdns, username, password)
 
98
 
 
99
def wrapmodule(module):
 
100
    """wrapmodule(module)
 
101
    Attempts to replace a module's socket library with a SOCKS socket. Must set
 
102
    a default proxy using setdefaultproxy(...) first.
 
103
    This will only work on modules that import socket directly into the namespace;
 
104
    most of the Python Standard Library falls into this category.
 
105
    """
 
106
    if _defaultproxy != None:
 
107
        module.socket.socket = socksocket
 
108
    else:
 
109
        raise GeneralProxyError((4, "no proxy specified"))
 
110
 
 
111
class socksocket(socket.socket):
 
112
    """socksocket([family[, type[, proto]]]) -> socket object
 
113
    Open a SOCKS enabled socket. The parameters are the same as
 
114
    those of the standard socket init. In order for SOCKS to work,
 
115
    you must specify family=AF_INET, type=SOCK_STREAM and proto=0.
 
116
    """
 
117
 
 
118
    def __init__(self, family=socket.AF_INET, type=socket.SOCK_STREAM, proto=0, _sock=None):
 
119
        _orgsocket.__init__(self, family, type, proto, _sock)
 
120
        if _defaultproxy != None:
 
121
            self.__proxy = _defaultproxy
 
122
        else:
 
123
            self.__proxy = (None, None, None, None, None, None)
 
124
        self.__proxysockname = None
 
125
        self.__proxypeername = None
 
126
 
 
127
    def __recvall(self, count):
 
128
        """__recvall(count) -> data
 
129
        Receive EXACTLY the number of bytes requested from the socket.
 
130
        Blocks until the required number of bytes have been received.
 
131
        """
 
132
        data = self.recv(count)
 
133
        while len(data) < count:
 
134
            d = self.recv(count-len(data))
 
135
            if not d: raise GeneralProxyError((0, "connection closed unexpectedly"))
 
136
            data = data + d
 
137
        return data
 
138
 
 
139
    def setproxy(self, proxytype=None, addr=None, port=None, rdns=True, username=None, password=None):
 
140
        """setproxy(proxytype, addr[, port[, rdns[, username[, password]]]])
 
141
        Sets the proxy to be used.
 
142
        proxytype -    The type of the proxy to be used. Three types
 
143
                are supported: PROXY_TYPE_SOCKS4 (including socks4a),
 
144
                PROXY_TYPE_SOCKS5 and PROXY_TYPE_HTTP
 
145
        addr -        The address of the server (IP or DNS).
 
146
        port -        The port of the server. Defaults to 1080 for SOCKS
 
147
                servers and 8080 for HTTP proxy servers.
 
148
        rdns -        Should DNS queries be preformed on the remote side
 
149
                (rather than the local side). The default is True.
 
150
                Note: This has no effect with SOCKS4 servers.
 
151
        username -    Username to authenticate with to the server.
 
152
                The default is no authentication.
 
153
        password -    Password to authenticate with to the server.
 
154
                Only relevant when username is also provided.
 
155
        """
 
156
        self.__proxy = (proxytype, addr, port, rdns, username, password)
 
157
 
 
158
    def __negotiatesocks5(self, destaddr, destport):
 
159
        """__negotiatesocks5(self,destaddr,destport)
 
160
        Negotiates a connection through a SOCKS5 server.
 
161
        """
 
162
        # First we'll send the authentication packages we support.
 
163
        if (self.__proxy[4]!=None) and (self.__proxy[5]!=None):
 
164
            # The username/password details were supplied to the
 
165
            # setproxy method so we support the USERNAME/PASSWORD
 
166
            # authentication (in addition to the standard none).
 
167
            self.sendall(struct.pack('BBBB', 0x05, 0x02, 0x00, 0x02))
 
168
        else:
 
169
            # No username/password were entered, therefore we
 
170
            # only support connections with no authentication.
 
171
            self.sendall(struct.pack('BBB', 0x05, 0x01, 0x00))
 
172
        # We'll receive the server's response to determine which
 
173
        # method was selected
 
174
        chosenauth = self.__recvall(2)
 
175
        if chosenauth[0:1] != chr(0x05).encode():
 
176
            self.close()
 
177
            raise GeneralProxyError((1, _generalerrors[1]))
 
178
        # Check the chosen authentication method
 
179
        if chosenauth[1:2] == chr(0x00).encode():
 
180
            # No authentication is required
 
181
            pass
 
182
        elif chosenauth[1:2] == chr(0x02).encode():
 
183
            # Okay, we need to perform a basic username/password
 
184
            # authentication.
 
185
            self.sendall(chr(0x01).encode() + chr(len(self.__proxy[4])) + self.__proxy[4] + chr(len(self.__proxy[5])) + self.__proxy[5])
 
186
            authstat = self.__recvall(2)
 
187
            if authstat[0:1] != chr(0x01).encode():
 
188
                # Bad response
 
189
                self.close()
 
190
                raise GeneralProxyError((1, _generalerrors[1]))
 
191
            if authstat[1:2] != chr(0x00).encode():
 
192
                # Authentication failed
 
193
                self.close()
 
194
                raise Socks5AuthError((3, _socks5autherrors[3]))
 
195
            # Authentication succeeded
 
196
        else:
 
197
            # Reaching here is always bad
 
198
            self.close()
 
199
            if chosenauth[1] == chr(0xFF).encode():
 
200
                raise Socks5AuthError((2, _socks5autherrors[2]))
 
201
            else:
 
202
                raise GeneralProxyError((1, _generalerrors[1]))
 
203
        # Now we can request the actual connection
 
204
        req = struct.pack('BBB', 0x05, 0x01, 0x00)
 
205
        # If the given destination address is an IP address, we'll
 
206
        # use the IPv4 address request even if remote resolving was specified.
 
207
        try:
 
208
            ipaddr = socket.inet_aton(destaddr)
 
209
            req = req + chr(0x01).encode() + ipaddr
 
210
        except socket.error:
 
211
            # Well it's not an IP number,  so it's probably a DNS name.
 
212
            if self.__proxy[3]:
 
213
                # Resolve remotely
 
214
                ipaddr = None
 
215
                req = req + chr(0x03).encode() + chr(len(destaddr)).encode() + destaddr
 
216
            else:
 
217
                # Resolve locally
 
218
                ipaddr = socket.inet_aton(socket.gethostbyname(destaddr))
 
219
                req = req + chr(0x01).encode() + ipaddr
 
220
        req = req + struct.pack(">H", destport)
 
221
        self.sendall(req)
 
222
        # Get the response
 
223
        resp = self.__recvall(4)
 
224
        if resp[0:1] != chr(0x05).encode():
 
225
            self.close()
 
226
            raise GeneralProxyError((1, _generalerrors[1]))
 
227
        elif resp[1:2] != chr(0x00).encode():
 
228
            # Connection failed
 
229
            self.close()
 
230
            if ord(resp[1:2])<=8:
 
231
                raise Socks5Error((ord(resp[1:2]), _socks5errors[ord(resp[1:2])]))
 
232
            else:
 
233
                raise Socks5Error((9, _socks5errors[9]))
 
234
        # Get the bound address/port
 
235
        elif resp[3:4] == chr(0x01).encode():
 
236
            boundaddr = self.__recvall(4)
 
237
        elif resp[3:4] == chr(0x03).encode():
 
238
            resp = resp + self.recv(1)
 
239
            boundaddr = self.__recvall(ord(resp[4:5]))
 
240
        else:
 
241
            self.close()
 
242
            raise GeneralProxyError((1,_generalerrors[1]))
 
243
        boundport = struct.unpack(">H", self.__recvall(2))[0]
 
244
        self.__proxysockname = (boundaddr, boundport)
 
245
        if ipaddr != None:
 
246
            self.__proxypeername = (socket.inet_ntoa(ipaddr), destport)
 
247
        else:
 
248
            self.__proxypeername = (destaddr, destport)
 
249
 
 
250
    def getproxysockname(self):
 
251
        """getsockname() -> address info
 
252
        Returns the bound IP address and port number at the proxy.
 
253
        """
 
254
        return self.__proxysockname
 
255
 
 
256
    def getproxypeername(self):
 
257
        """getproxypeername() -> address info
 
258
        Returns the IP and port number of the proxy.
 
259
        """
 
260
        return _orgsocket.getpeername(self)
 
261
 
 
262
    def getpeername(self):
 
263
        """getpeername() -> address info
 
264
        Returns the IP address and port number of the destination
 
265
        machine (note: getproxypeername returns the proxy)
 
266
        """
 
267
        return self.__proxypeername
 
268
 
 
269
    def __negotiatesocks4(self,destaddr,destport):
 
270
        """__negotiatesocks4(self,destaddr,destport)
 
271
        Negotiates a connection through a SOCKS4 server.
 
272
        """
 
273
        # Check if the destination address provided is an IP address
 
274
        rmtrslv = False
 
275
        try:
 
276
            ipaddr = socket.inet_aton(destaddr)
 
277
        except socket.error:
 
278
            # It's a DNS name. Check where it should be resolved.
 
279
            if self.__proxy[3]:
 
280
                ipaddr = struct.pack("BBBB", 0x00, 0x00, 0x00, 0x01)
 
281
                rmtrslv = True
 
282
            else:
 
283
                ipaddr = socket.inet_aton(socket.gethostbyname(destaddr))
 
284
        # Construct the request packet
 
285
        req = struct.pack(">BBH", 0x04, 0x01, destport) + ipaddr
 
286
        # The username parameter is considered userid for SOCKS4
 
287
        if self.__proxy[4] != None:
 
288
            req = req + self.__proxy[4]
 
289
        req = req + chr(0x00).encode()
 
290
        # DNS name if remote resolving is required
 
291
        # NOTE: This is actually an extension to the SOCKS4 protocol
 
292
        # called SOCKS4A and may not be supported in all cases.
 
293
        if rmtrslv:
 
294
            req = req + destaddr + chr(0x00).encode()
 
295
        self.sendall(req)
 
296
        # Get the response from the server
 
297
        resp = self.__recvall(8)
 
298
        if resp[0:1] != chr(0x00).encode():
 
299
            # Bad data
 
300
            self.close()
 
301
            raise GeneralProxyError((1,_generalerrors[1]))
 
302
        if resp[1:2] != chr(0x5A).encode():
 
303
            # Server returned an error
 
304
            self.close()
 
305
            if ord(resp[1:2]) in (91, 92, 93):
 
306
                self.close()
 
307
                raise Socks4Error((ord(resp[1:2]), _socks4errors[ord(resp[1:2]) - 90]))
 
308
            else:
 
309
                raise Socks4Error((94, _socks4errors[4]))
 
310
        # Get the bound address/port
 
311
        self.__proxysockname = (socket.inet_ntoa(resp[4:]), struct.unpack(">H", resp[2:4])[0])
 
312
        if rmtrslv != None:
 
313
            self.__proxypeername = (socket.inet_ntoa(ipaddr), destport)
 
314
        else:
 
315
            self.__proxypeername = (destaddr, destport)
 
316
 
 
317
    def __negotiatehttp(self, destaddr, destport):
 
318
        """__negotiatehttp(self,destaddr,destport)
 
319
        Negotiates a connection through an HTTP server.
 
320
        """
 
321
        # If we need to resolve locally, we do this now
 
322
        if not self.__proxy[3]:
 
323
            addr = socket.gethostbyname(destaddr)
 
324
        else:
 
325
            addr = destaddr
 
326
        self.sendall(("CONNECT " + addr + ":" + str(destport) + " HTTP/1.1\r\n" + "Host: " + destaddr + "\r\n\r\n").encode())
 
327
        # We read the response until we get the string "\r\n\r\n"
 
328
        resp = self.recv(1)
 
329
        while resp.find("\r\n\r\n".encode()) == -1:
 
330
            resp = resp + self.recv(1)
 
331
        # We just need the first line to check if the connection
 
332
        # was successful
 
333
        statusline = resp.splitlines()[0].split(" ".encode(), 2)
 
334
        if statusline[0] not in ("HTTP/1.0".encode(), "HTTP/1.1".encode()):
 
335
            self.close()
 
336
            raise GeneralProxyError((1, _generalerrors[1]))
 
337
        try:
 
338
            statuscode = int(statusline[1])
 
339
        except ValueError:
 
340
            self.close()
 
341
            raise GeneralProxyError((1, _generalerrors[1]))
 
342
        if statuscode != 200:
 
343
            self.close()
 
344
            raise HTTPError((statuscode, statusline[2]))
 
345
        self.__proxysockname = ("0.0.0.0", 0)
 
346
        self.__proxypeername = (addr, destport)
 
347
 
 
348
    def connect(self, destpair):
 
349
        """connect(self, despair)
 
350
        Connects to the specified destination through a proxy.
 
351
        destpar - A tuple of the IP/DNS address and the port number.
 
352
        (identical to socket's connect).
 
353
        To select the proxy server use setproxy().
 
354
        """
 
355
        # Do a minimal input check first
 
356
        if (not type(destpair) in (list,tuple)) or (len(destpair) < 2) or (type(destpair[0]) != type('')) or (type(destpair[1]) != int):
 
357
            raise GeneralProxyError((5, _generalerrors[5]))
 
358
        if self.__proxy[0] == PROXY_TYPE_SOCKS5:
 
359
            if self.__proxy[2] != None:
 
360
                portnum = self.__proxy[2]
 
361
            else:
 
362
                portnum = 1080
 
363
            _orgsocket.connect(self, (self.__proxy[1], portnum))
 
364
            self.__negotiatesocks5(destpair[0], destpair[1])
 
365
        elif self.__proxy[0] == PROXY_TYPE_SOCKS4:
 
366
            if self.__proxy[2] != None:
 
367
                portnum = self.__proxy[2]
 
368
            else:
 
369
                portnum = 1080
 
370
            _orgsocket.connect(self,(self.__proxy[1], portnum))
 
371
            self.__negotiatesocks4(destpair[0], destpair[1])
 
372
        elif self.__proxy[0] == PROXY_TYPE_HTTP:
 
373
            if self.__proxy[2] != None:
 
374
                portnum = self.__proxy[2]
 
375
            else:
 
376
                portnum = 8080
 
377
            _orgsocket.connect(self,(self.__proxy[1], portnum))
 
378
            self.__negotiatehttp(destpair[0], destpair[1])
 
379
        elif self.__proxy[0] == None:
 
380
            _orgsocket.connect(self, (destpair[0], destpair[1]))
 
381
        else:
 
382
            raise GeneralProxyError((4, _generalerrors[4]))