Package nbxmpp :: Module proxy_connectors

[hide private]
[frames] | no frames]

# Source Code for Module nbxmpp.proxy_connectors

      1  ##   proxy_connectors.py
      2  ##       based on transports_nb.py
      3  ##
      4  ##   Copyright (C) 2003-2004 Alexey "Snake" Nezhdanov
      5  ##       modified by Dimitur Kirov <dkirov@gmail.com>
      6  ##       modified by Tomas Karasek <tom.to.the.k@gmail.com>
      7  ##
      8  ##   This program is free software; you can redistribute it and/or modify
      9  ##   it under the terms of the GNU General Public License as published by
     10  ##   the Free Software Foundation; either version 2, or (at your option)
     11  ##   any later version.
     12  ##
     13  ##   This program is distributed in the hope that it will be useful,
     14  ##   but WITHOUT ANY WARRANTY; without even the implied warranty of
     15  ##   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     16  ##   GNU General Public License for more details.
     17
     18  """
     19  Module containing classes for proxy connecting. So far its HTTP CONNECT and
     20  SOCKS5 proxy
     21
     22  Authentication to NTLM (Microsoft implementation) proxies can be next.
     23  """
     24
     25  import struct, socket, base64
     26  import logging
     27  log = logging.getLogger('nbxmpp.proxy_connectors')
     28
     29 -class ProxyConnector:
     30      """
     31      Interface for proxy-connecting object - when tunnneling XMPP over proxies,
     32      some connecting process usually has to be done before opening stream. Proxy
     33      connectors are used right after TCP connection is estabilished
     34      """
     35
     36 -    def __init__(self, send_method, onreceive, old_on_receive, on_success,
     37                      on_failure, xmpp_server, proxy_creds=(None, None)):
     38          """
     39          Creates proxy connector, starts connecting immediately and gives control
     40          back to transport afterwards
     41
     42          :param send_method: transport send method
     43          :param onreceive: method to set on_receive callbacks
     44          :param old_on_receive: on_receive callback that should be set when
     45                  proxy connection was successful
     46          :param on_success: called after proxy connection was successfully opened
     47          :param on_failure: called when errors occured while connecting
     48          :param xmpp_server: tuple of (hostname, port)
     49          :param proxy_creds: tuple of (proxy_user, proxy_credentials)
     50          """
     51          self.send = send_method
     52          self.onreceive = onreceive
     53          self.old_on_receive = old_on_receive
     54          self.on_success = on_success
     55          self.on_failure = on_failure
     56          self.xmpp_server = xmpp_server
     57          self.proxy_user, self.proxy_pass = proxy_creds
     58          self.old_on_receive = old_on_receive
     59
     60          self.start_connecting()
     61
     62      @classmethod
     63 -    def get_instance(cls, *args, **kwargs):
     64          """
     65          Factory Method for object creation
     66
     67          Use this instead of directly initializing the class in order to make unit
     68          testing much easier.
     69          """
     70          return cls(*args, **kwargs)
     71
     72 -    def start_connecting(self):
     73          raise NotImplementedError
     74
     75 -    def connecting_over(self):
     76          self.onreceive(self.old_on_receive)
     77          self.on_success()
     78
     79 -class HTTPCONNECTConnector(ProxyConnector):
     80 -    def start_connecting(self):
     81          """
     82          Connect to a proxy, supply login and password to it (if were specified
     83          while creating instance). Instruct proxy to make connection to the target
     84          server.
     85          """
     86          log.info('Proxy server contacted, performing authentification')
     87          connector = ['CONNECT %s:%s HTTP/1.1' % self.xmpp_server,
     88                  'Proxy-Connection: Keep-Alive',
     89                  'Pragma: no-cache',
     90                  'Host: %s:%s' % self.xmpp_server,
     91                  'User-Agent: Gajim']
     92          if self.proxy_user and self.proxy_pass:
     93              credentials = '%s:%s' % (self.proxy_user, self.proxy_pass)
     94              credentials = base64.encodestring(credentials).strip()
     95              connector.append('Proxy-Authorization: Basic '+credentials)
     96          connector.append('\r\n')
     97          self.onreceive(self._on_headers_sent)
     98          self.send('\r\n'.join(connector))
     99
    100 -    def _on_headers_sent(self, reply):
    101          if reply is None:
    102              return
    103          self.reply = reply.replace('\r', '')
    104          try:
    105              proto, code, desc = reply.split('\n')[0].split(' ', 2)
    106          except:
    107              log.error("_on_headers_sent:", exc_info=True)
    108              #traceback.print_exc()
    109              self.on_failure('Invalid proxy reply')
    110              return
    111          if code <> '200':
    112              log.error('Invalid proxy reply: %s %s %s' % (proto, code, desc))
    113              self.on_failure('Invalid proxy reply')
    114              return
    115          if len(reply) != 2:
    116              pass
    117          self.connecting_over()
    118
    119
    120 -class SOCKS5Connector(ProxyConnector):
    121      """
    122      SOCKS5 proxy connection class. Allows to use SOCKS5 proxies with
    123      (optionally) simple authentication (only USERNAME/PASSWORD auth)
    124      """
    125
    126 -    def start_connecting(self):
    127          log.info('Proxy server contacted, performing authentification')
    128          if self.proxy_user and self.proxy_pass:
    129              to_send = '\x05\x02\x00\x02'
    130          else:
    131              to_send = '\x05\x01\x00'
    132          self.onreceive(self._on_greeting_sent)
    133          self.send(to_send)
    134
    135 -    def _on_greeting_sent(self, reply):
    136          if reply is None:
    137              return
    138          if len(reply) != 2:
    139              self.on_failure('Invalid proxy reply')
    140              return
    141          if reply[0] != '\x05':
    142              log.info('Invalid proxy reply')
    143              self.on_failure('Invalid proxy reply')
    144              return
    145          if reply[1] == '\x00':
    146              return self._on_proxy_auth('\x01\x00')
    147          elif reply[1] == '\x02':
    148              to_send = '\x01' + chr(len(self.proxy_user)) + self.proxy_user +\
    149                      chr(len(self.proxy_pass)) + self.proxy_pass
    150              self.onreceive(self._on_proxy_auth)
    151              self.send(to_send)
    152          else:
    153              if reply[1] == '\xff':
    154                  log.error('Authentification to proxy impossible: no acceptable '
    155                          'auth method')
    156                  self.on_failure('Authentification to proxy impossible: no '
    157                          'acceptable authentification method')
    158                  return
    159              log.error('Invalid proxy reply')
    160              self.on_failure('Invalid proxy reply')
    161              return
    162
    163 -    def _on_proxy_auth(self, reply):
    164          if reply is None:
    165              return
    166          if len(reply) != 2:
    167              log.error('Invalid proxy reply')
    168              self.on_failure('Invalid proxy reply')
    169              return
    170          if reply[0] != '\x01':
    171              log.error('Invalid proxy reply')
    172              self.on_failure('Invalid proxy reply')
    173              return
    174          if reply[1] != '\x00':
    175              log.error('Authentification to proxy failed')
    176              self.on_failure('Authentification to proxy failed')
    177              return
    178          log.info('Authentification successfull. Jabber server contacted.')
    179          # Request connection
    180          req = "\x05\x01\x00"
    181          # If the given destination address is an IP address, we'll
    182          # use the IPv4 address request even if remote resolving was specified.
    183          try:
    184              self.ipaddr = socket.inet_aton(self.xmpp_server[0])
    185              req = req + "\x01" + self.ipaddr
    186          except socket.error:
    187              # Well it's not an IP number,  so it's probably a DNS name.
    188  #                       if self.__proxy[3]==True:
    189              # Resolve remotely
    190              self.ipaddr = None
    191              req = req + "\x03" + chr(len(self.xmpp_server[0])) + self.xmpp_server[0]
    192  #                       else:
    193  #                               # Resolve locally
    194  #                               self.ipaddr = socket.inet_aton(socket.gethostbyname(self.xmpp_server[0]))
    195  #                               req = req + "\x01" + ipaddr
    196          req = req + struct.pack(">H", self.xmpp_server[1])
    197          self.onreceive(self._on_req_sent)
    198          self.send(req)
    199
    200 -    def _on_req_sent(self, reply):
    201          if reply is None:
    202              return
    203          if len(reply) < 10:
    204              log.error('Invalid proxy reply')
    205              self.on_failure('Invalid proxy reply')
    206              return
    207          if reply[0] != '\x05':
    208              log.error('Invalid proxy reply')
    209              self.on_failure('Invalid proxy reply')
    210              return
    211          if reply[1] != "\x00":
    212              # Connection failed
    213              if ord(reply[1])<9:
    214                  errors = ['general SOCKS server failure',
    215                          'connection not allowed by ruleset',
    216                          'Network unreachable',
    217                          'Host unreachable',
    218                          'Connection refused',
    219                          'TTL expired',
    220                          'Command not supported',
    221                          'Address type not supported'
    222                  ]
    223                  txt = errors[ord(reply[1])-1]
    224              else:
    225                  txt = 'Invalid proxy reply'
    226              log.error(txt)
    227              self.on_failure(txt)
    228              return
    229          # Get the bound address/port
    230          elif reply[3] == "\x01":
    231              begin, end = 3, 7
    232          elif reply[3] == "\x03":
    233              begin, end = 4, 4 + reply[4]
    234          else:
    235              log.error('Invalid proxy reply')
    236              self.on_failure('Invalid proxy reply')
    237              return
    238          self.connecting_over()
    239