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