1 # This is free and unencumbered software released into the public domain.
3 # Anyone is free to copy, modify, publish, use, compile, sell, or
4 # distribute this software, either in source code form or as a compiled
5 # binary, for any purpose, commercial or non-commercial, and by any
8 # In jurisdictions that recognize copyright laws, the author or authors
9 # of this software dedicate any and all copyright interest in the
10 # software to the public domain. We make this dedication for the benefit
11 # of the public at large and to the detriment of our heirs and
12 # successors. We intend this dedication to be an overt act of
13 # relinquishment in perpetuity of all present and future rights to this
14 # software under copyright law.
16 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19 # IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20 # OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21 # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 # OTHER DEALINGS IN THE SOFTWARE.
24 # For more information, please refer to <http://unlicense.org/>
31 # socks.patch_socket()
33 # f = ftplib.FTP('ftp.kernel.org')
35 # print f.retrlines('LIST')
38 # s = socket.create_connection(('www.google.com', 80))
39 # s.sendall('HEAD / HTTP/1.0\r\n\r\n')
42 from __future__ import unicode_literals
48 __author__ = 'Timo Schmid <coding@timoschmid.de>'
50 _orig_socket = socket.socket
53 from collections import namedtuple
55 from Collections import namedtuple
58 from urllib.parse import urlparse
60 from urlparse import urlparse
68 class ProxyError(IOError): pass
69 class Socks4Error(ProxyError):
71 0x5B: 'request rejected or failed',
72 0x5C: 'request rejected becasue SOCKS server cannot connect to identd on the client',
73 0x5D: 'request rejected because the client program and identd report different user-ids'
75 def __init__(self, code=None, msg=None):
76 if code is not None and msg is None:
77 msg = self.CODES.get(code)
80 super(Socks4Error, self).__init__(code, msg)
82 class Socks5Error(Socks4Error):
84 0x01: 'general SOCKS server failure',
85 0x02: 'connection not allowed by ruleset',
86 0x03: 'Network unreachable',
87 0x04: 'Host unreachable',
88 0x05: 'Connection refused',
90 0x07: 'Command not supported',
91 0x08: 'Address type not supported',
92 0xFE: 'unknown username or invalid password',
93 0xFF: 'all offered authentication methods were rejected'
96 class ProxyType(Enum):
101 Proxy = namedtuple('Proxy', ('type', 'host', 'port', 'username', 'password', 'remote_dns'))
103 _default_proxy = None
105 def setdefaultproxy(proxytype=None, addr=None, port=None, rdns=True, username=None, password=None, allow_env_override=True):
106 global _default_proxy
107 if allow_env_override:
108 all_proxy = os.environ.get('ALL_PROXY', os.environ.get('all_proxy'))
110 all_proxy = urlparse(all_proxy)
111 if all_proxy.scheme.startswith('socks'):
112 if all_proxy.scheme == 'socks' or all_proxy.scheme == 'socks4':
113 proxytype = ProxyType.SOCKS4
114 elif all_proxy.scheme == 'socks4a':
115 proxytype = ProxyType.SOCKS4A
116 elif all_proxy.scheme == 'socks5':
117 proxytype = ProxyType.SOCKS5
118 addr = all_proxy.hostname
119 port = all_proxy.port
120 username = all_proxy.username
121 password = all_proxy.password
123 if proxytype is not None:
124 _default_proxy = Proxy(proxytype, addr, port, username, password, rdns)
127 def wrap_socket(sock):
128 return socksocket(_sock=sock._sock)
130 def wrap_module(module):
131 if hasattr(module, 'socket'):
133 if isinstance(sock, socket.socket):
134 module.socket = sockssocket
135 elif hasattr(socket, 'socket'):
136 socket.socket = sockssocket
140 if 'socket' not in sys.modules:
142 sys.modules['socket'].socket = sockssocket
145 class sockssocket(socket.socket):
146 def __init__(self, *args, **kwargs):
148 if 'proxy' in kwargs:
149 self.__proxy = kwargs['proxy']
151 super(sockssocket, self).__init__(*args, **kwargs)
157 return _default_proxy
160 def _proxy_port(self):
163 return self._proxy.port
167 def setproxy(self, proxytype=None, addr=None, port=None, rdns=True, username=None, password=None):
168 if proxytype is None:
171 self.__proxy = Proxy(proxytype, addr, port, username, password, rdns)
173 def recvall(self, cnt):
175 while len(data) < cnt:
176 cur = self.recv(cnt - len(data))
178 raise IOError("{0} bytes missing".format(cnt-len(data)))
182 def _setup_socks4(self, address, is_4a=False):
183 destaddr, port = address
186 ipaddr = socket.inet_aton(destaddr)
188 if is_4a and self._proxy.remote_dns:
189 ipaddr = struct.pack('!BBBB', 0, 0, 0, 0xFF)
191 ipaddr = socket.inet_aton(socket.gethostbyname(destaddr))
193 packet = struct.pack('!BBH', 0x4, 0x1, port) + ipaddr
194 if self._proxy.username:
195 username = self._proxy.username
196 if hasattr(username, 'encode'):
197 username = username.encode()
198 packet += struct.pack('!{0}s'.format(len(username)+1), username)
202 if is_4a and self._proxy.remote_dns:
203 if hasattr(destaddr, 'encode'):
204 destaddr = destaddr.encode()
205 packet += struct.pack('!{0}s'.format(len(destaddr)+1), destaddr)
209 packet = self.recvall(8)
210 nbyte, resp_code, dstport, dsthost = struct.unpack('!BBHI', packet)
212 # check valid response
215 raise ProxyError(0, "Invalid response from server. Expected {0:02x} got {1:02x}".format(0, nbyte))
218 if resp_code != 0x5a:
220 raise Socks4Error(resp_code)
222 def _setup_socks5(self, address):
223 destaddr, port = address
226 ipaddr = socket.inet_aton(destaddr)
228 if self._proxy.remote_dns:
231 ipaddr = socket.inet_aton(socket.gethostbyname(destaddr))
234 if self._proxy.username and self._proxy.password:
235 # two auth methods available
237 packet = struct.pack('!BBB', 0x5, auth_methods, 0x00) # no auth
238 if self._proxy.username and self._proxy.password:
239 packet += struct.pack('!B', 0x02) # user/pass auth
243 packet = self.recvall(2)
244 version, method = struct.unpack('!BB', packet)
246 # check valid response
249 raise ProxyError(0, "Invalid response from server. Expected {0:02x} got {1:02x}".format(5, version))
254 raise Socks5Error(method)
258 username = self._proxy.username
259 if hasattr(username, 'encode'):
260 username = username.encode()
261 password = self._proxy.password
262 if hasattr(password, 'encode'):
263 password = password.encode()
264 packet = struct.pack('!BB', 1, len(username)) + username
265 packet += struct.pack('!B', len(password)) + password
268 packet = self.recvall(2)
269 version, status = struct.unpack('!BB', packet)
273 raise ProxyError(0, "Invalid response from server. Expected {0:02x} got {1:02x}".format(1, version))
278 elif method == 0x00: # no auth
282 packet = struct.pack('!BBB', 5, 1, 0)
284 if hasattr(destaddr, 'encode'):
285 destaddr = destaddr.encode()
286 packet += struct.pack('!BB', 3, len(destaddr)) + destaddr
288 packet += struct.pack('!B', 1) + ipaddr
289 packet += struct.pack('!H', port)
293 packet = self.recvall(4)
294 version, status, _, atype = struct.unpack('!BBBB', packet)
298 raise ProxyError(0, "Invalid response from server. Expected {0:02x} got {1:02x}".format(5, version))
302 raise Socks5Error(status)
305 destaddr = self.recvall(4)
307 alen = struct.unpack('!B', self.recv(1))[0]
308 destaddr = self.recvall(alen)
310 destaddr = self.recvall(16)
311 destport = struct.unpack('!H', self.recvall(2))[0]
313 def _make_proxy(self, connect_func, address):
314 if self._proxy.type == ProxyType.SOCKS4:
315 result = connect_func(self, (self._proxy.host, self._proxy_port))
316 if result != 0 and result is not None:
318 self._setup_socks4(address)
319 elif self._proxy.type == ProxyType.SOCKS4A:
320 result = connect_func(self, (self._proxy.host, self._proxy_port))
321 if result != 0 and result is not None:
323 self._setup_socks4(address, is_4a=True)
324 elif self._proxy.type == ProxyType.SOCKS5:
325 result = connect_func(self, (self._proxy.host, self._proxy_port))
326 if result != 0 and result is not None:
328 self._setup_socks5(address)
330 return connect_func(self, address)
332 def connect(self, address):
333 self._make_proxy(_orig_socket.connect, address)
335 def connect_ex(self, address):
336 return self._make_proxy(_orig_socket.connect_ex, address)