]>
Raphaƫl G. Git Repositories - youtubedl/blob - youtube_dl/socks.py
63d19b3a5214221afa71a6d43bde36a39c13cd4b
   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): 
 107 Proxy 
= collections
.namedtuple('Proxy', ( 
 108     'type', 'host', 'port', 'username', 'password', 'remote_dns')) 
 111 class sockssocket(socket
.socket
): 
 112     def __init__(self
, *args
, **kwargs
): 
 114         super(sockssocket
, self
).__init
__(*args
, **kwargs
) 
 116     def setproxy(self
, proxytype
, addr
, port
, rdns
=True, username
=None, password
=None): 
 117         assert proxytype 
in (ProxyType
.SOCKS4
, ProxyType
.SOCKS4A
, ProxyType
.SOCKS5
) 
 119         self
._proxy 
= Proxy(proxytype
, addr
, port
, username
, password
, rdns
) 
 121     def recvall(self
, cnt
): 
 123         while len(data
) < cnt
: 
 124             cur 
= self
.recv(cnt 
- len(data
)) 
 126                 raise IOError('{0} bytes missing'.format(cnt 
- len(data
))) 
 130     def _recv_bytes(self
, cnt
): 
 131         data 
= self
.recvall(cnt
) 
 132         return compat_struct_unpack('!{0}B'.format(cnt
), data
) 
 135     def _len_and_data(data
): 
 136         return compat_struct_pack('!B', len(data
)) + data
 
 138     def _check_response_version(self
, expected_version
, got_version
): 
 139         if got_version 
!= expected_version
: 
 141             raise InvalidVersionError(expected_version
, got_version
) 
 143     def _resolve_address(self
, destaddr
, default
, use_remote_dns
): 
 145             return socket
.inet_aton(destaddr
) 
 147             if use_remote_dns 
and self
._proxy
.remote_dns
: 
 150                 return socket
.inet_aton(socket
.gethostbyname(destaddr
)) 
 152     def _setup_socks4(self
, address
, is_4a
=False): 
 153         destaddr
, port 
= address
 
 155         ipaddr 
= self
._resolve
_address
(destaddr
, SOCKS4_DEFAULT_DSTIP
, use_remote_dns
=is_4a
) 
 157         packet 
= compat_struct_pack('!BBH', SOCKS4_VERSION
, Socks4Command
.CMD_CONNECT
, port
) + ipaddr
 
 159         username 
= (self
._proxy
.username 
or '').encode('utf-8') 
 160         packet 
+= username 
+ b
'\x00' 
 162         if is_4a 
and self
._proxy
.remote_dns
: 
 163             packet 
+= destaddr
.encode('utf-8') + b
'\x00' 
 167         version
, resp_code
, dstport
, dsthost 
= compat_struct_unpack('!BBHI', self
.recvall(8)) 
 169         self
._check
_response
_version
(SOCKS4_REPLY_VERSION
, version
) 
 171         if resp_code 
!= Socks4Error
.ERR_SUCCESS
: 
 173             raise Socks4Error(resp_code
) 
 175         return (dsthost
, dstport
) 
 177     def _setup_socks4a(self
, address
): 
 178         self
._setup
_socks
4(address
, is_4a
=True) 
 180     def _socks5_auth(self
): 
 181         packet 
= compat_struct_pack('!B', SOCKS5_VERSION
) 
 183         auth_methods 
= [Socks5Auth
.AUTH_NONE
] 
 184         if self
._proxy
.username 
and self
._proxy
.password
: 
 185             auth_methods
.append(Socks5Auth
.AUTH_USER_PASS
) 
 187         packet 
+= compat_struct_pack('!B', len(auth_methods
)) 
 188         packet 
+= compat_struct_pack('!{0}B'.format(len(auth_methods
)), *auth_methods
) 
 192         version
, method 
= self
._recv
_bytes
(2) 
 194         self
._check
_response
_version
(SOCKS5_VERSION
, version
) 
 196         if method 
== Socks5Auth
.AUTH_NO_ACCEPTABLE
: 
 198             raise Socks5Error(method
) 
 200         if method 
== Socks5Auth
.AUTH_USER_PASS
: 
 201             username 
= self
._proxy
.username
.encode('utf-8') 
 202             password 
= self
._proxy
.password
.encode('utf-8') 
 203             packet 
= compat_struct_pack('!B', SOCKS5_USER_AUTH_VERSION
) 
 204             packet 
+= self
._len
_and
_data
(username
) + self
._len
_and
_data
(password
) 
 207             version
, status 
= self
._recv
_bytes
(2) 
 209             self
._check
_response
_version
(SOCKS5_USER_AUTH_VERSION
, version
) 
 211             if status 
!= SOCKS5_USER_AUTH_SUCCESS
: 
 213                 raise Socks5Error(Socks5Error
.ERR_GENERAL_FAILURE
) 
 215     def _setup_socks5(self
, address
): 
 216         destaddr
, port 
= address
 
 218         ipaddr 
= self
._resolve
_address
(destaddr
, None, use_remote_dns
=True) 
 223         packet 
= compat_struct_pack('!BBB', SOCKS5_VERSION
, Socks5Command
.CMD_CONNECT
, reserved
) 
 225             destaddr 
= destaddr
.encode('utf-8') 
 226             packet 
+= compat_struct_pack('!B', Socks5AddressType
.ATYP_DOMAINNAME
) 
 227             packet 
+= self
._len
_and
_data
(destaddr
) 
 229             packet 
+= compat_struct_pack('!B', Socks5AddressType
.ATYP_IPV4
) + ipaddr
 
 230         packet 
+= compat_struct_pack('!H', port
) 
 234         version
, status
, reserved
, atype 
= self
._recv
_bytes
(4) 
 236         self
._check
_response
_version
(SOCKS5_VERSION
, version
) 
 238         if status 
!= Socks5Error
.ERR_SUCCESS
: 
 240             raise Socks5Error(status
) 
 242         if atype 
== Socks5AddressType
.ATYP_IPV4
: 
 243             destaddr 
= self
.recvall(4) 
 244         elif atype 
== Socks5AddressType
.ATYP_DOMAINNAME
: 
 245             alen 
= compat_ord(self
.recv(1)) 
 246             destaddr 
= self
.recvall(alen
) 
 247         elif atype 
== Socks5AddressType
.ATYP_IPV6
: 
 248             destaddr 
= self
.recvall(16) 
 249         destport 
= compat_struct_unpack('!H', self
.recvall(2))[0] 
 251         return (destaddr
, destport
) 
 253     def _make_proxy(self
, connect_func
, address
): 
 255             return connect_func(self
, address
) 
 257         result 
= connect_func(self
, (self
._proxy
.host
, self
._proxy
.port
)) 
 258         if result 
!= 0 and result 
is not None: 
 261             ProxyType
.SOCKS4
: self
._setup
_socks
4, 
 262             ProxyType
.SOCKS4A
: self
._setup
_socks
4a
, 
 263             ProxyType
.SOCKS5
: self
._setup
_socks
5, 
 265         setup_funcs
[self
._proxy
.type](address
) 
 268     def connect(self
, address
): 
 269         self
._make
_proxy
(socket
.socket
.connect
, address
) 
 271     def connect_ex(self
, address
): 
 272         return self
._make
_proxy
(socket
.socket
.connect_ex
, address
)