electrum-personal-server

Maximally lightweight electrum server for a single user
git clone https://git.parazyd.org/electrum-personal-server
Log | Files | Refs | README

socks.py (16153B)


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