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