socks.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  1. # This is free and unencumbered software released into the public domain.
  2. #
  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
  6. # means.
  7. #
  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.
  15. #
  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.
  23. #
  24. # For more information, please refer to <http://unlicense.org/>
  25. #
  26. # Example:
  27. # import socks
  28. # import ftplib
  29. # import socket
  30. #
  31. # socks.patch_socket()
  32. #
  33. # f = ftplib.FTP('ftp.kernel.org')
  34. # f.login()
  35. # print f.retrlines('LIST')
  36. # f.quit()
  37. #
  38. # s = socket.create_connection(('www.google.com', 80))
  39. # s.sendall('HEAD / HTTP/1.0\r\n\r\n')
  40. # print s.recv(1024)
  41. # s.close()
  42. from __future__ import unicode_literals
  43. import os
  44. import struct
  45. import socket
  46. import time
  47. __author__ = 'Timo Schmid <coding@timoschmid.de>'
  48. _orig_socket = socket.socket
  49. try:
  50. from collections import namedtuple
  51. except ImportError:
  52. from Collections import namedtuple
  53. try:
  54. from urllib.parse import urlparse
  55. except:
  56. from urlparse import urlparse
  57. try:
  58. from enum import Enum
  59. except ImportError:
  60. Enum = object
  61. class ProxyError(IOError): pass
  62. class Socks4Error(ProxyError):
  63. CODES = {
  64. 0x5B: 'request rejected or failed',
  65. 0x5C: 'request rejected becasue SOCKS server cannot connect to identd on the client',
  66. 0x5D: 'request rejected because the client program and identd report different user-ids'
  67. }
  68. def __init__(self, code=None, msg=None):
  69. if code is not None and msg is None:
  70. msg = self.CODES.get(code)
  71. if msg is None:
  72. msg = 'unknown error'
  73. super(Socks4Error, self).__init__(code, msg)
  74. class Socks5Error(Socks4Error):
  75. CODES = {
  76. 0x01: 'general SOCKS server failure',
  77. 0x02: 'connection not allowed by ruleset',
  78. 0x03: 'Network unreachable',
  79. 0x04: 'Host unreachable',
  80. 0x05: 'Connection refused',
  81. 0x06: 'TTL expired',
  82. 0x07: 'Command not supported',
  83. 0x08: 'Address type not supported',
  84. 0xFE: 'unknown username or invalid password',
  85. 0xFF: 'all offered authentication methods were rejected'
  86. }
  87. class ProxyType(Enum):
  88. SOCKS4 = 0
  89. SOCKS4A = 1
  90. SOCKS5 = 2
  91. Proxy = namedtuple('Proxy', ('type', 'host', 'port', 'username', 'password', 'remote_dns'))
  92. _default_proxy = None
  93. def setdefaultproxy(proxytype=None, addr=None, port=None, rdns=True, username=None, password=None, allow_env_override=True):
  94. global _default_proxy
  95. if allow_env_override:
  96. all_proxy = os.environ.get('ALL_PROXY', os.environ.get('all_proxy'))
  97. if all_proxy:
  98. all_proxy = urlparse(all_proxy)
  99. if all_proxy.scheme.startswith('socks'):
  100. if all_proxy.scheme == 'socks' or all_proxy.scheme == 'socks4':
  101. proxytype = ProxyType.SOCKS4
  102. elif all_proxy.scheme == 'socks4a':
  103. proxytype = ProxyType.SOCKS4A
  104. elif all_proxy.scheme == 'socks5':
  105. proxytype = ProxyType.SOCKS5
  106. addr = all_proxy.hostname
  107. port = all_proxy.port
  108. username = all_proxy.username
  109. password = all_proxy.password
  110. if proxytype is not None:
  111. _default_proxy = Proxy(proxytype, addr, port, username, password, rdns)
  112. def wrap_socket(sock):
  113. return socksocket(_sock=sock._sock)
  114. def wrap_module(module):
  115. if hasattr(module, 'socket'):
  116. sock = module.socket
  117. if isinstance(sock, socket.socket):
  118. module.socket = sockssocket
  119. elif hasattr(socket, 'socket'):
  120. socket.socket = sockssocket
  121. def patch_socket():
  122. import sys
  123. if 'socket' not in sys.modules:
  124. import socket
  125. sys.modules['socket'].socket = sockssocket
  126. class sockssocket(socket.socket):
  127. def __init__(self, *args, **kwargs):
  128. self.__proxy = None
  129. if 'proxy' in kwargs:
  130. self.__proxy = kwargs['proxy']
  131. del kwargs['proxy']
  132. super(sockssocket, self).__init__(*args, **kwargs)
  133. @property
  134. def _proxy(self):
  135. if self.__proxy:
  136. return self.__proxy
  137. return _default_proxy
  138. @property
  139. def _proxy_port(self):
  140. if self._proxy:
  141. if self._proxy.port:
  142. return self._proxy.port
  143. return 1080
  144. return None
  145. def setproxy(self, proxytype=None, addr=None, port=None, rdns=True, username=None, password=None):
  146. if proxytype is None:
  147. self.__proxy = None
  148. else:
  149. self.__proxy = Proxy(proxytype, addr, port, username, password, rdns)
  150. def recvall(self, cnt):
  151. data = b''
  152. while len(data) < cnt:
  153. cur = self.recv(cnt - len(data))
  154. if not cur:
  155. raise IOError("{0} bytes missing".format(cnt-len(data)))
  156. data += cur
  157. return data
  158. def _setup_socks4(self, address, is_4a=False):
  159. destaddr, port = address
  160. try:
  161. ipaddr = socket.inet_aton(destaddr)
  162. except socket.error:
  163. if is_4a and self._proxy.remote_dns:
  164. ipaddr = struct.pack('!BBBB', 0, 0, 0, 0xFF)
  165. else:
  166. ipaddr = socket.inet_aton(socket.gethostbyname(destaddr))
  167. packet = struct.pack('!BBH', 0x4, 0x1, port) + ipaddr
  168. if self._proxy.username:
  169. username = self._proxy.username
  170. if hasattr(username, 'encode'):
  171. username = username.encode()
  172. packet += struct.pack('!{0}s'.format(len(username)+1), username)
  173. else:
  174. packet += b'\x00'
  175. if is_4a and self._proxy.remote_dns:
  176. if hasattr(destaddr, 'encode'):
  177. destaddr = destaddr.encode()
  178. packet += struct.pack('!{0}s'.format(len(destaddr)+1), destaddr)
  179. self.sendall(packet)
  180. packet = self.recvall(8)
  181. nbyte, resp_code, dstport, dsthost = struct.unpack('!BBHI', packet)
  182. # check valid response
  183. if nbyte != 0x00:
  184. self.close()
  185. raise ProxyError(0, "Invalid response from server. Expected {0:02x} got {1:02x}".format(0, nbyte))
  186. # access granted
  187. if resp_code != 0x5a:
  188. self.close()
  189. raise Socks4Error(resp_code)
  190. def _setup_socks5(self, address):
  191. destaddr, port = address
  192. try:
  193. ipaddr = socket.inet_aton(destaddr)
  194. except socket.error:
  195. if self._proxy.remote_dns:
  196. ipaddr = None
  197. else:
  198. ipaddr = socket.inet_aton(socket.gethostbyname(destaddr))
  199. auth_methods = 1
  200. if self._proxy.username and self._proxy.password:
  201. # two auth methods available
  202. auth_methods = 2
  203. packet = struct.pack('!BBB', 0x5, auth_methods, 0x00) # no auth
  204. if self._proxy.username and self._proxy.password:
  205. packet += struct.pack('!B', 0x02) # user/pass auth
  206. self.sendall(packet)
  207. packet = self.recvall(2)
  208. version, method = struct.unpack('!BB', packet)
  209. # check valid response
  210. if version != 0x05:
  211. self.close()
  212. raise ProxyError(0, "Invalid response from server. Expected {0:02x} got {1:02x}".format(5, version))
  213. # no auth methods
  214. if method == 0xFF:
  215. self.close()
  216. raise Socks5Error(method)
  217. # user/pass auth
  218. if method == 0x01:
  219. username = self._proxy.username
  220. if hasattr(username, 'encode'):
  221. username = username.encode()
  222. password = self._proxy.password
  223. if hasattr(password, 'encode'):
  224. password = password.encode()
  225. packet = struct.pack('!BB', 1, len(username)) + username
  226. packet += struct.pack('!B', len(password)) + password
  227. self.sendall(packet)
  228. packet = self.recvall(2)
  229. version, status = struct.unpack('!BB', packet)
  230. if version != 0x01:
  231. self.close()
  232. raise ProxyError(0, "Invalid response from server. Expected {0:02x} got {1:02x}".format(1, version))
  233. if status != 0x00:
  234. self.close()
  235. raise Socks5Error(1)
  236. elif method == 0x00: # no auth
  237. pass
  238. packet = struct.pack('!BBB', 5, 1, 0)
  239. if ipaddr is None:
  240. if hasattr(destaddr, 'encode'):
  241. destaddr = destaddr.encode()
  242. packet += struct.pack('!BB', 3, len(destaddr)) + destaddr
  243. else:
  244. packet += struct.pack('!B', 1) + ipaddr
  245. packet += struct.pack('!H', port)
  246. self.sendall(packet)
  247. packet = self.recvall(4)
  248. version, status, _, atype = struct.unpack('!BBBB', packet)
  249. if version != 0x05:
  250. self.close()
  251. raise ProxyError(0, "Invalid response from server. Expected {0:02x} got {1:02x}".format(5, version))
  252. if status != 0x00:
  253. self.close()
  254. raise Socks5Error(status)
  255. if atype == 0x01:
  256. destaddr = self.recvall(4)
  257. elif atype == 0x03:
  258. alen = struct.unpack('!B', self.recv(1))[0]
  259. destaddr = self.recvall(alen)
  260. elif atype == 0x04:
  261. destaddr = self.recvall(16)
  262. destport = struct.unpack('!H', self.recvall(2))[0]
  263. def _make_proxy(self, connect_func, address):
  264. if self._proxy.type == ProxyType.SOCKS4:
  265. result = connect_func(self, (self._proxy.host, self._proxy_port))
  266. if result != 0 and result is not None:
  267. return result
  268. self._setup_socks4(address)
  269. elif self._proxy.type == ProxyType.SOCKS4A:
  270. result = connect_func(self, (self._proxy.host, self._proxy_port))
  271. if result != 0 and result is not None:
  272. return result
  273. self._setup_socks4(address, is_4a=True)
  274. elif self._proxy.type == ProxyType.SOCKS5:
  275. result = connect_func(self, (self._proxy.host, self._proxy_port))
  276. if result != 0 and result is not None:
  277. return result
  278. self._setup_socks5(address)
  279. else:
  280. return connect_func(self, address)
  281. def connect(self, address):
  282. self._make_proxy(_orig_socket.connect, address)
  283. def connect_ex(self, address):
  284. return self._make_proxy(_orig_socket.connect_ex, address)