URI: 
       tsocks.py - electrum-personal-server - Maximally lightweight electrum server for a single user
  HTML git clone https://git.parazyd.org/electrum-personal-server
   DIR Log
   DIR Files
   DIR Refs
   DIR README
       ---
       tsocks.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]))