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