]>
Raphaƫl G. Git Repositories - youtubedl/blob - youtube_dl/socks.py
104807242bd3b0f35e0423faa096540be29d0a45
   1 # Public Domain SOCKS proxy protocol implementation 
   2 # Adapted from https://gist.github.com/bluec0re/cafd3764412967417fd3 
   4 from __future__ 
import unicode_literals
 
   7 # SOCKS4 protocol http://www.openssh.com/txt/socks4.protocol 
   8 # SOCKS4A protocol http://www.openssh.com/txt/socks4a.protocol 
   9 # SOCKS5 protocol https://tools.ietf.org/html/rfc1928 
  10 # SOCKS5 username/password authentication https://tools.ietf.org/html/rfc1929 
  21 __author__ 
= 'Timo Schmid <coding@timoschmid.de>' 
  24 SOCKS4_REPLY_VERSION 
= 0x00 
  25 # Excerpt from SOCKS4A protocol: 
  26 # if the client cannot resolve the destination host's domain name to find its 
  27 # IP address, it should set the first three bytes of DSTIP to NULL and the last 
  28 # byte to a non-zero value. 
  29 SOCKS4_DEFAULT_DSTIP 
= compat_struct_pack('!BBBB', 0, 0, 0, 0xFF) 
  32 SOCKS5_USER_AUTH_VERSION 
= 0x01 
  33 SOCKS5_USER_AUTH_SUCCESS 
= 0x00 
  36 class Socks4Command(object): 
  41 class Socks5Command(Socks4Command
): 
  42     CMD_UDP_ASSOCIATE 
= 0x03 
  45 class Socks5Auth(object): 
  49     AUTH_NO_ACCEPTABLE 
= 0xFF  # For server response 
  52 class Socks5AddressType(object): 
  54     ATYP_DOMAINNAME 
= 0x03 
  58 class ProxyError(IOError): 
  61     def __init__(self
, code
=None, msg
=None): 
  62         if code 
is not None and msg 
is None: 
  63             msg 
= self
.CODES
.get(code
) and 'unknown error' 
  64         super(ProxyError
, self
).__init
__(code
, msg
) 
  67 class InvalidVersionError(ProxyError
): 
  68     def __init__(self
, expected_version
, got_version
): 
  69         msg 
= ('Invalid response version from server. Expected {0:02x} got ' 
  70                '{1:02x}'.format(expected_version
, got_version
)) 
  71         super(InvalidVersionError
, self
).__init
__(0, msg
) 
  74 class Socks4Error(ProxyError
): 
  78         91: 'request rejected or failed', 
  79         92: 'request rejected because SOCKS server cannot connect to identd on the client', 
  80         93: 'request rejected because the client program and identd report different user-ids' 
  84 class Socks5Error(ProxyError
): 
  85     ERR_GENERAL_FAILURE 
= 0x01 
  88         0x01: 'general SOCKS server failure', 
  89         0x02: 'connection not allowed by ruleset', 
  90         0x03: 'Network unreachable', 
  91         0x04: 'Host unreachable', 
  92         0x05: 'Connection refused', 
  94         0x07: 'Command not supported', 
  95         0x08: 'Address type not supported', 
  96         0xFE: 'unknown username or invalid password', 
  97         0xFF: 'all offered authentication methods were rejected' 
 101 class ProxyType(object): 
 106 Proxy 
= collections
.namedtuple('Proxy', ( 
 107     'type', 'host', 'port', 'username', 'password', 'remote_dns')) 
 110 class sockssocket(socket
.socket
): 
 111     def __init__(self
, *args
, **kwargs
): 
 113         super(sockssocket
, self
).__init
__(*args
, **kwargs
) 
 115     def setproxy(self
, proxytype
, addr
, port
, rdns
=True, username
=None, password
=None): 
 116         assert proxytype 
in (ProxyType
.SOCKS4
, ProxyType
.SOCKS4A
, ProxyType
.SOCKS5
) 
 118         self
._proxy 
= Proxy(proxytype
, addr
, port
, username
, password
, rdns
) 
 120     def recvall(self
, cnt
): 
 122         while len(data
) < cnt
: 
 123             cur 
= self
.recv(cnt 
- len(data
)) 
 125                 raise IOError('{0} bytes missing'.format(cnt 
- len(data
))) 
 129     def _recv_bytes(self
, cnt
): 
 130         data 
= self
.recvall(cnt
) 
 131         return compat_struct_unpack('!{0}B'.format(cnt
), data
) 
 134     def _len_and_data(data
): 
 135         return compat_struct_pack('!B', len(data
)) + data
 
 137     def _check_response_version(self
, expected_version
, got_version
): 
 138         if got_version 
!= expected_version
: 
 140             raise InvalidVersionError(expected_version
, got_version
) 
 142     def _resolve_address(self
, destaddr
, default
, use_remote_dns
): 
 144             return socket
.inet_aton(destaddr
) 
 146             if use_remote_dns 
and self
._proxy
.remote_dns
: 
 149                 return socket
.inet_aton(socket
.gethostbyname(destaddr
)) 
 151     def _setup_socks4(self
, address
, is_4a
=False): 
 152         destaddr
, port 
= address
 
 154         ipaddr 
= self
._resolve
_address
(destaddr
, SOCKS4_DEFAULT_DSTIP
, use_remote_dns
=is_4a
) 
 156         packet 
= compat_struct_pack('!BBH', SOCKS4_VERSION
, Socks4Command
.CMD_CONNECT
, port
) + ipaddr
 
 158         username 
= (self
._proxy
.username 
or '').encode('utf-8') 
 159         packet 
+= username 
+ b
'\x00' 
 161         if is_4a 
and self
._proxy
.remote_dns
: 
 162             packet 
+= destaddr
.encode('utf-8') + b
'\x00' 
 166         version
, resp_code
, dstport
, dsthost 
= compat_struct_unpack('!BBHI', self
.recvall(8)) 
 168         self
._check
_response
_version
(SOCKS4_REPLY_VERSION
, version
) 
 170         if resp_code 
!= Socks4Error
.ERR_SUCCESS
: 
 172             raise Socks4Error(resp_code
) 
 174         return (dsthost
, dstport
) 
 176     def _setup_socks4a(self
, address
): 
 177         self
._setup
_socks
4(address
, is_4a
=True) 
 179     def _socks5_auth(self
): 
 180         packet 
= compat_struct_pack('!B', SOCKS5_VERSION
) 
 182         auth_methods 
= [Socks5Auth
.AUTH_NONE
] 
 183         if self
._proxy
.username 
and self
._proxy
.password
: 
 184             auth_methods
.append(Socks5Auth
.AUTH_USER_PASS
) 
 186         packet 
+= compat_struct_pack('!B', len(auth_methods
)) 
 187         packet 
+= compat_struct_pack('!{0}B'.format(len(auth_methods
)), *auth_methods
) 
 191         version
, method 
= self
._recv
_bytes
(2) 
 193         self
._check
_response
_version
(SOCKS5_VERSION
, version
) 
 195         if method 
== Socks5Auth
.AUTH_NO_ACCEPTABLE
: 
 197             raise Socks5Error(method
) 
 199         if method 
== Socks5Auth
.AUTH_USER_PASS
: 
 200             username 
= self
._proxy
.username
.encode('utf-8') 
 201             password 
= self
._proxy
.password
.encode('utf-8') 
 202             packet 
= compat_struct_pack('!B', SOCKS5_USER_AUTH_VERSION
) 
 203             packet 
+= self
._len
_and
_data
(username
) + self
._len
_and
_data
(password
) 
 206             version
, status 
= self
._recv
_bytes
(2) 
 208             self
._check
_response
_version
(SOCKS5_USER_AUTH_VERSION
, version
) 
 210             if status 
!= SOCKS5_USER_AUTH_SUCCESS
: 
 212                 raise Socks5Error(Socks5Error
.ERR_GENERAL_FAILURE
) 
 214     def _setup_socks5(self
, address
): 
 215         destaddr
, port 
= address
 
 217         ipaddr 
= self
._resolve
_address
(destaddr
, None, use_remote_dns
=True) 
 222         packet 
= compat_struct_pack('!BBB', SOCKS5_VERSION
, Socks5Command
.CMD_CONNECT
, reserved
) 
 224             destaddr 
= destaddr
.encode('utf-8') 
 225             packet 
+= compat_struct_pack('!B', Socks5AddressType
.ATYP_DOMAINNAME
) 
 226             packet 
+= self
._len
_and
_data
(destaddr
) 
 228             packet 
+= compat_struct_pack('!B', Socks5AddressType
.ATYP_IPV4
) + ipaddr
 
 229         packet 
+= compat_struct_pack('!H', port
) 
 233         version
, status
, reserved
, atype 
= self
._recv
_bytes
(4) 
 235         self
._check
_response
_version
(SOCKS5_VERSION
, version
) 
 237         if status 
!= Socks5Error
.ERR_SUCCESS
: 
 239             raise Socks5Error(status
) 
 241         if atype 
== Socks5AddressType
.ATYP_IPV4
: 
 242             destaddr 
= self
.recvall(4) 
 243         elif atype 
== Socks5AddressType
.ATYP_DOMAINNAME
: 
 244             alen 
= compat_ord(self
.recv(1)) 
 245             destaddr 
= self
.recvall(alen
) 
 246         elif atype 
== Socks5AddressType
.ATYP_IPV6
: 
 247             destaddr 
= self
.recvall(16) 
 248         destport 
= compat_struct_unpack('!H', self
.recvall(2))[0] 
 250         return (destaddr
, destport
) 
 252     def _make_proxy(self
, connect_func
, address
): 
 254             return connect_func(self
, address
) 
 256         result 
= connect_func(self
, (self
._proxy
.host
, self
._proxy
.port
)) 
 257         if result 
!= 0 and result 
is not None: 
 260             ProxyType
.SOCKS4
: self
._setup
_socks
4, 
 261             ProxyType
.SOCKS4A
: self
._setup
_socks
4a
, 
 262             ProxyType
.SOCKS5
: self
._setup
_socks
5, 
 264         setup_funcs
[self
._proxy
.type](address
) 
 267     def connect(self
, address
): 
 268         self
._make
_proxy
(socket
.socket
.connect
, address
) 
 270     def connect_ex(self
, address
): 
 271         return self
._make
_proxy
(socket
.socket
.connect_ex
, address
)