1
"""SocksiPy - Python SOCKS module.
4
Copyright 2006 Dan-Haim. All rights reserved.
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.
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.
28
This module provides a standard socket-like interface for Python
29
for tunneling connections through SOCKS proxies.
35
Minor modifications made by Christopher Gilbert (http://motomastyle.com/)
36
for use in PyLoris (http://pyloris.sourceforge.net/)
38
Minor modifications made by Mario Vilas (http://breakingcode.wordpress.com/)
39
mainly to merge bug fixes found in Sourceforge
52
_orgsocket = socket.socket
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
61
_generalerrors = ("success",
68
_socks5errors = ("succeeded",
69
"general SOCKS server failure",
70
"connection not allowed by ruleset",
71
"Network unreachable",
75
"Command not supported",
76
"Address type not supported",
79
_socks5autherrors = ("succeeded",
80
"authentication is required",
81
"all offered authentication methods were rejected",
82
"unknown username or invalid password",
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",
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.
97
_defaultproxy = (proxytype, addr, port, rdns, username, password)
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.
106
if _defaultproxy != None:
107
module.socket.socket = socksocket
109
raise GeneralProxyError((4, "no proxy specified"))
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.
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
123
self.__proxy = (None, None, None, None, None, None)
124
self.__proxysockname = None
125
self.__proxypeername = None
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.
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"))
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.
156
self.__proxy = (proxytype, addr, port, rdns, username, password)
158
def __negotiatesocks5(self, destaddr, destport):
159
"""__negotiatesocks5(self,destaddr,destport)
160
Negotiates a connection through a SOCKS5 server.
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))
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():
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
182
elif chosenauth[1:2] == chr(0x02).encode():
183
# Okay, we need to perform a basic username/password
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():
190
raise GeneralProxyError((1, _generalerrors[1]))
191
if authstat[1:2] != chr(0x00).encode():
192
# Authentication failed
194
raise Socks5AuthError((3, _socks5autherrors[3]))
195
# Authentication succeeded
197
# Reaching here is always bad
199
if chosenauth[1] == chr(0xFF).encode():
200
raise Socks5AuthError((2, _socks5autherrors[2]))
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.
208
ipaddr = socket.inet_aton(destaddr)
209
req = req + chr(0x01).encode() + ipaddr
211
# Well it's not an IP number, so it's probably a DNS name.
215
req = req + chr(0x03).encode() + chr(len(destaddr)).encode() + destaddr
218
ipaddr = socket.inet_aton(socket.gethostbyname(destaddr))
219
req = req + chr(0x01).encode() + ipaddr
220
req = req + struct.pack(">H", destport)
223
resp = self.__recvall(4)
224
if resp[0:1] != chr(0x05).encode():
226
raise GeneralProxyError((1, _generalerrors[1]))
227
elif resp[1:2] != chr(0x00).encode():
230
if ord(resp[1:2])<=8:
231
raise Socks5Error((ord(resp[1:2]), _socks5errors[ord(resp[1:2])]))
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]))
242
raise GeneralProxyError((1,_generalerrors[1]))
243
boundport = struct.unpack(">H", self.__recvall(2))[0]
244
self.__proxysockname = (boundaddr, boundport)
246
self.__proxypeername = (socket.inet_ntoa(ipaddr), destport)
248
self.__proxypeername = (destaddr, destport)
250
def getproxysockname(self):
251
"""getsockname() -> address info
252
Returns the bound IP address and port number at the proxy.
254
return self.__proxysockname
256
def getproxypeername(self):
257
"""getproxypeername() -> address info
258
Returns the IP and port number of the proxy.
260
return _orgsocket.getpeername(self)
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)
267
return self.__proxypeername
269
def __negotiatesocks4(self,destaddr,destport):
270
"""__negotiatesocks4(self,destaddr,destport)
271
Negotiates a connection through a SOCKS4 server.
273
# Check if the destination address provided is an IP address
276
ipaddr = socket.inet_aton(destaddr)
278
# It's a DNS name. Check where it should be resolved.
280
ipaddr = struct.pack("BBBB", 0x00, 0x00, 0x00, 0x01)
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.
294
req = req + destaddr + chr(0x00).encode()
296
# Get the response from the server
297
resp = self.__recvall(8)
298
if resp[0:1] != chr(0x00).encode():
301
raise GeneralProxyError((1,_generalerrors[1]))
302
if resp[1:2] != chr(0x5A).encode():
303
# Server returned an error
305
if ord(resp[1:2]) in (91, 92, 93):
307
raise Socks4Error((ord(resp[1:2]), _socks4errors[ord(resp[1:2]) - 90]))
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])
313
self.__proxypeername = (socket.inet_ntoa(ipaddr), destport)
315
self.__proxypeername = (destaddr, destport)
317
def __negotiatehttp(self, destaddr, destport):
318
"""__negotiatehttp(self,destaddr,destport)
319
Negotiates a connection through an HTTP server.
321
# If we need to resolve locally, we do this now
322
if not self.__proxy[3]:
323
addr = socket.gethostbyname(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"
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
333
statusline = resp.splitlines()[0].split(" ".encode(), 2)
334
if statusline[0] not in ("HTTP/1.0".encode(), "HTTP/1.1".encode()):
336
raise GeneralProxyError((1, _generalerrors[1]))
338
statuscode = int(statusline[1])
341
raise GeneralProxyError((1, _generalerrors[1]))
342
if statuscode != 200:
344
raise HTTPError((statuscode, statusline[2]))
345
self.__proxysockname = ("0.0.0.0", 0)
346
self.__proxypeername = (addr, destport)
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().
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]
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]
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]
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]))
382
raise GeneralProxyError((4, _generalerrors[4]))