/ src / network / socks4a.py
socks4a.py
  1  """
  2  SOCKS4a proxy module
  3  """
  4  # pylint: disable=attribute-defined-outside-init
  5  import logging
  6  import socket
  7  import struct
  8  
  9  from .proxy import GeneralProxyError, Proxy, ProxyError
 10  
 11  logger = logging.getLogger('default')
 12  
 13  
 14  class Socks4aError(ProxyError):
 15      """SOCKS4a error base class"""
 16      errorCodes = (
 17          "Request granted",
 18          "Request rejected or failed",
 19          "Request rejected because SOCKS server cannot connect to identd"
 20          " on the client",
 21          "Request rejected because the client program and identd report"
 22          " different user-ids",
 23          "Unknown error"
 24      )
 25  
 26  
 27  class Socks4a(Proxy):
 28      """SOCKS4a proxy class"""
 29      def __init__(self, address=None):
 30          Proxy.__init__(self, address)
 31          self.ipaddr = None
 32          self.destport = address[1]
 33  
 34      def state_init(self):
 35          """Protocol initialisation (before connection is established)"""
 36          self.set_state("auth_done", 0)
 37          return True
 38  
 39      def state_pre_connect(self):
 40          """Handle feedback from SOCKS4a while it is connecting on our behalf"""
 41          # Get the response
 42          if self.read_buf[0:1] != chr(0x00).encode():
 43              # bad data
 44              self.close()
 45              raise GeneralProxyError(1)
 46          elif self.read_buf[1:2] != chr(0x5A).encode():
 47              # Connection failed
 48              self.close()
 49              if ord(self.read_buf[1:2]) in (91, 92, 93):
 50                  # socks 4 error
 51                  raise Socks4aError(ord(self.read_buf[1:2]) - 90)
 52              else:
 53                  raise Socks4aError(4)
 54          # Get the bound address/port
 55          self.boundport = struct.unpack(">H", self.read_buf[2:4])[0]
 56          self.boundaddr = self.read_buf[4:]
 57          self.__proxysockname = (self.boundaddr, self.boundport)
 58          if self.ipaddr:
 59              self.__proxypeername = (
 60                  socket.inet_ntoa(self.ipaddr), self.destination[1])
 61          else:
 62              self.__proxypeername = (self.destination[0], self.destport)
 63          self.set_state("proxy_handshake_done", length=8)
 64          return True
 65  
 66      def proxy_sock_name(self):
 67          """
 68          Handle return value when using SOCKS4a for DNS resolving
 69          instead of connecting.
 70          """
 71          return socket.inet_ntoa(self.__proxysockname[0])
 72  
 73  
 74  class Socks4aConnection(Socks4a):
 75      """Child SOCKS4a class used for making outbound connections."""
 76      def __init__(self, address):
 77          Socks4a.__init__(self, address=address)
 78  
 79      def state_auth_done(self):
 80          """Request connection to be made"""
 81          # Now we can request the actual connection
 82          rmtrslv = False
 83          self.append_write_buf(
 84              struct.pack('>BBH', 0x04, 0x01, self.destination[1]))
 85          # If the given destination address is an IP address, we'll
 86          # use the IPv4 address request even if remote resolving was specified.
 87          try:
 88              self.ipaddr = socket.inet_aton(self.destination[0])
 89              self.append_write_buf(self.ipaddr)
 90          except socket.error:
 91              # Well it's not an IP number,  so it's probably a DNS name.
 92              if self._remote_dns:
 93                  # Resolve remotely
 94                  rmtrslv = True
 95                  self.ipaddr = None
 96                  self.append_write_buf(
 97                      struct.pack("BBBB", 0x00, 0x00, 0x00, 0x01))
 98              else:
 99                  # Resolve locally
100                  self.ipaddr = socket.inet_aton(
101                      socket.gethostbyname(self.destination[0]))
102                  self.append_write_buf(self.ipaddr)
103          if self._auth:
104              self.append_write_buf(self._auth[0])
105          self.append_write_buf(chr(0x00).encode())
106          if rmtrslv:
107              self.append_write_buf(self.destination[0] + chr(0x00).encode())
108          self.set_state("pre_connect", length=0, expectBytes=8)
109          return True
110  
111      def state_pre_connect(self):
112          """Tell SOCKS4a to initiate a connection"""
113          try:
114              return Socks4a.state_pre_connect(self)
115          except Socks4aError as e:
116              self.close_reason = e.message
117              self.set_state("close")
118  
119  
120  class Socks4aResolver(Socks4a):
121      """DNS resolver class using SOCKS4a"""
122      def __init__(self, host):
123          self.host = host
124          self.port = 8444
125          Socks4a.__init__(self, address=(self.host, self.port))
126  
127      def state_auth_done(self):
128          """Request connection to be made"""
129          # Now we can request the actual connection
130          self.append_write_buf(
131              struct.pack('>BBH', 0x04, 0xF0, self.destination[1]))
132          self.append_write_buf(struct.pack("BBBB", 0x00, 0x00, 0x00, 0x01))
133          if self._auth:
134              self.append_write_buf(self._auth[0])
135          self.append_write_buf(chr(0x00).encode())
136          self.append_write_buf(self.host + chr(0x00).encode())
137          self.set_state("pre_connect", length=0, expectBytes=8)
138          return True
139  
140      def resolved(self):
141          """
142          Resolving is done, process the return value. To use this within
143          PyBitmessage, a callback needs to be implemented which hasn't
144          been done yet.
145          """
146          logger.debug(
147              'Resolved %s as %s', self.host, self.proxy_sock_name())