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