Package nbxmpp :: Module auth_nb |
[hide private]
[frames] | no frames]

# Source Code for Module nbxmpp.auth_nb

      1  ##   auth_nb.py
      2  ##       based on auth.py, changes backported up to revision 1.41
      3  ##
      4  ##   Copyright (C) 2003-2005 Alexey "Snake" Nezhdanov
      5  ##       modified by Dimitur Kirov <dkirov@gmail.com>
      6  ##
      7  ##   This program is free software; you can redistribute it and/or modify
      8  ##   it under the terms of the GNU General Public License as published by
      9  ##   the Free Software Foundation; either version 2, or (at your option)
     10  ##   any later version.
     11  ##
     12  ##   This program is distributed in the hope that it will be useful,
     13  ##   but WITHOUT ANY WARRANTY; without even the implied warranty of
     14  ##   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     15  ##   GNU General Public License for more details.
     16
     17  """
     18  Provides plugs for SASL and NON-SASL authentication mechanisms.
     19  Can be used both for client and transport authentication
     20
     21  See client_nb.py
     22  """
     23
     24  from protocol import NS_SASL, NS_SESSION, NS_STREAMS, NS_BIND, NS_AUTH
     25  from protocol import NS_STREAM_MGMT
     26  from protocol import Node, NodeProcessed, isResultNode, Iq, Protocol, JID
     27  from plugin import PlugIn
     28  from smacks import Smacks
     29  import base64
     30  import random
     31  import itertools
     32  import dispatcher_nb
     33  import hashlib
     34  import hmac
     35  import hashlib
     36
     37  import logging
     38  log = logging.getLogger('nbxmpp.auth_nb')
     39
     40 -def HH(some): return hashlib.md5(some).hexdigest()
     41 -def H(some): return hashlib.md5(some).digest()
     42 -def C(some): return ':'.join(some)
     43
     44  try:
     45      kerberos = __import__('kerberos')
     46      have_kerberos = True
     47  except ImportError:
     48      have_kerberos = False
     49
     50  GSS_STATE_STEP = 0
     51  GSS_STATE_WRAP = 1
     52  SASL_FAILURE_IN_PROGRESS = 'failure-in-process'
     53  SASL_FAILURE = 'failure'
     54  SASL_SUCCESS = 'success'
     55  SASL_UNSUPPORTED = 'not-supported'
     56  SASL_IN_PROCESS = 'in-process'
     57
     58 -def challenge_splitter(data):
     59      """
     60      Helper function that creates a dict from challenge string
     61
     62      Sample challenge string:
     63        - username="example.org",realm="somerealm",
     64          nonce="OA6MG9tEQGm2hh",cnonce="OA6MHXh6VqTrRk",
     65          nc=00000001,qop="auth,auth-int,auth-conf",charset=utf-8
     66
     67      Expected result for challan:
     68        - dict['qop'] = ('auth','auth-int','auth-conf')
     69        - dict['realm'] = 'somerealm'
     70      """
     71      X_KEYWORD, X_VALUE, X_END = 0, 1, 2
     72      quotes_open = False
     73      keyword, value = '', ''
     74      dict_ = {}
     75      arr = None
     76
     77      expecting = X_KEYWORD
     78      for iter_ in range(len(data) + 1):
     79          end = False
     80          if iter_ == len(data):
     81              expecting = X_END
     82              end = True
     83          else:
     84              char = data[iter_]
     85          if expecting == X_KEYWORD:
     86              if char == '=':
     87                  expecting  = X_VALUE
     88              elif char in (',', ' ', '\t'):
     89                  pass
     90              else:
     91                  keyword = '%s%c' % (keyword, char)
     92          elif expecting == X_VALUE:
     93              if char == '"':
     94                  if quotes_open:
     95                      end = True
     96                  else:
     97                      quotes_open = True
     98              elif char in (',', ' ', '\t'):
     99                  if quotes_open:
    100                      if not arr:
    101                          arr = [value]
    102                      else:
    103                          arr.append(value)
    104                      value = ""
    105                  else:
    106                      end = True
    107              else:
    108                  value = '%s%c' % (value, char)
    109          if end:
    110              if arr:
    111                  arr.append(value)
    112                  dict_[keyword] = arr
    113                  arr = None
    114              else:
    115                  dict_[keyword] = value
    116              value, keyword = '', ''
    117              expecting = X_KEYWORD
    118              quotes_open = False
    119      return dict_
    120
    121 -def scram_parse(chatter):
    122      return dict(s.split('=', 1) for s in chatter.split(','))
    123
    124 -class SASL(PlugIn):
    125      """
    126      Implements SASL authentication. Can be plugged into NonBlockingClient
    127      to start authentication
    128      """
    129
    130 -    def __init__(self, username, password, on_sasl):
    131          """
    132          :param username: XMPP username
    133          :param password: XMPP password
    134          :param on_sasl: Callback, will be called after each SASL auth-step.
    135          """
    136          PlugIn.__init__(self)
    137          self.username = username
    138          self.password = password
    139          self.on_sasl = on_sasl
    140          self.realm = None
    141
    142 -    def plugin(self, owner):
    143          if 'version' not in self._owner.Dispatcher.Stream._document_attrs:
    144              self.startsasl = SASL_UNSUPPORTED
    145          elif self._owner.Dispatcher.Stream.features:
    146              try:
    147                  self.FeaturesHandler(self._owner.Dispatcher,
    148                      self._owner.Dispatcher.Stream.features)
    149              except NodeProcessed:
    150                  pass
    151          else:
    152              self.startsasl = None
    153
    154 -    def plugout(self):
    155          """
    156          Remove SASL handlers from owner's dispatcher. Used internally
    157          """
    158          if 'features' in  self._owner.__dict__:
    159              self._owner.UnregisterHandler('features', self.FeaturesHandler,
    160                  xmlns=NS_STREAMS)
    161          if 'challenge' in  self._owner.__dict__:
    162              self._owner.UnregisterHandler('challenge', self.SASLHandler,
    163                  xmlns=NS_SASL)
    164          if 'failure' in  self._owner.__dict__:
    165              self._owner.UnregisterHandler('failure', self.SASLHandler,
    166                  xmlns=NS_SASL)
    167          if 'success' in  self._owner.__dict__:
    168              self._owner.UnregisterHandler('success', self.SASLHandler,
    169                  xmlns=NS_SASL)
    170
    171 -    def auth(self):
    172          """
    173          Start authentication. Result can be obtained via "SASL.startsasl"
    174          attribute and will be either SASL_SUCCESS or SASL_FAILURE
    175
    176          Note that successfull auth will take at least two Dispatcher.Process()
    177          calls.
    178          """
    179          if self.startsasl:
    180              pass
    181          elif self._owner.Dispatcher.Stream.features:
    182              try:
    183                  self.FeaturesHandler(self._owner.Dispatcher,
    184                      self._owner.Dispatcher.Stream.features)
    185              except NodeProcessed:
    186                  pass
    187          else:
    188              self._owner.RegisterHandler('features',
    189                  self.FeaturesHandler, xmlns=NS_STREAMS)
    190
    191 -    def FeaturesHandler(self, conn, feats):
    192          """
    193          Used to determine if server supports SASL auth. Used internally
    194          """
    195          if not feats.getTag('mechanisms', namespace=NS_SASL):
    196              self.startsasl='not-supported'
    197              log.info('SASL not supported by server')
    198              return
    199          self.mecs = []
    200          for mec in feats.getTag('mechanisms', namespace=NS_SASL).getTags(
    201          'mechanism'):
    202              self.mecs.append(mec.getData())
    203
    204          self._owner.RegisterHandler('challenge', self.SASLHandler,
    205              xmlns=NS_SASL)
    206          self._owner.RegisterHandler('failure', self.SASLHandler, xmlns=NS_SASL)
    207          self._owner.RegisterHandler('success', self.SASLHandler, xmlns=NS_SASL)
    208          self.MechanismHandler()
    209
    210 -    def MechanismHandler(self):
    211          if 'ANONYMOUS' in self.mecs and self.username is None:
    212              self.mecs.remove('ANONYMOUS')
    213              node = Node('auth', attrs={'xmlns': NS_SASL,
    214                  'mechanism': 'ANONYMOUS'})
    215              self.mechanism = 'ANONYMOUS'
    216              self.startsasl = SASL_IN_PROCESS
    217              self._owner.send(str(node))
    218              raise NodeProcessed
    219          if "EXTERNAL" in self.mecs:
    220              self.mecs.remove('EXTERNAL')
    221              sasl_data = u'%s@%s' % (self.username, self._owner.Server)
    222              sasl_data = sasl_data.encode('utf-8').encode('base64').replace(
    223                  '\n', '')
    224              node = Node('auth', attrs={'xmlns': NS_SASL,
    225                  'mechanism': 'EXTERNAL'}, payload=[sasl_data])
    226              self.mechanism = 'EXTERNAL'
    227              self.startsasl = SASL_IN_PROCESS
    228              self._owner.send(str(node))
    229              raise NodeProcessed
    230          if 'GSSAPI' in self.mecs and have_kerberos:
    231              self.mecs.remove('GSSAPI')
    232              try:
    233                  self.gss_vc = kerberos.authGSSClientInit('xmpp@' + \
    234                      self._owner.xmpp_hostname)[1]
    235                  kerberos.authGSSClientStep(self.gss_vc, '')
    236                  response = kerberos.authGSSClientResponse(self.gss_vc)
    237                  node=Node('auth', attrs={'xmlns': NS_SASL,
    238                      'mechanism': 'GSSAPI'}, payload=(response or ''))
    239                  self.mechanism = 'GSSAPI'
    240                  self.gss_step = GSS_STATE_STEP
    241                  self.startsasl = SASL_IN_PROCESS
    242                  self._owner.send(str(node))
    243                  raise NodeProcessed
    244              except kerberos.GSSError, e:
    245                  log.info('GSSAPI authentication failed: %s' % str(e))
    246          if 'SCRAM-SHA-1' in self.mecs:
    247              self.mecs.remove('SCRAM-SHA-1')
    248              self.mechanism = 'SCRAM-SHA-1'
    249              self._owner._caller.get_password(self.set_password, self.mechanism)
    250              self.scram_step = 0
    251              self.startsasl = SASL_IN_PROCESS
    252              raise NodeProcessed
    253          if 'DIGEST-MD5' in self.mecs:
    254              self.mecs.remove('DIGEST-MD5')
    255              node = Node('auth', attrs={'xmlns': NS_SASL,
    256                  'mechanism': 'DIGEST-MD5'})
    257              self.mechanism = 'DIGEST-MD5'
    258              self.startsasl = SASL_IN_PROCESS
    259              self._owner.send(str(node))
    260              raise NodeProcessed
    261          if 'PLAIN' in self.mecs:
    262              self.mecs.remove('PLAIN')
    263              self.mechanism = 'PLAIN'
    264              self._owner._caller.get_password(self.set_password, self.mechanism)
    265              self.startsasl = SASL_IN_PROCESS
    266              raise NodeProcessed
    267          if 'X-MESSENGER-OAUTH2' in self.mecs:
    268              self.mecs.remove('X-MESSENGER-OAUTH2')
    269              self.mechanism = 'X-MESSENGER-OAUTH2'
    270              self._owner._caller.get_password(self.set_password, self.mechanism)
    271              self.startsasl = SASL_IN_PROCESS
    272              raise NodeProcessed
    273          self.startsasl = SASL_FAILURE
    274          log.info('I can only use EXTERNAL, SCRAM-SHA-1, DIGEST-MD5, GSSAPI and '
    275              'PLAIN mecanisms.')
    276          if self.on_sasl:
    277              self.on_sasl()
    278          return
    279
    280 -    def SASLHandler(self, conn, challenge):
    281          """
    282          Perform next SASL auth step. Used internally
    283          """
    284          if challenge.getNamespace() != NS_SASL:
    285              return
    286
    287          def scram_base64(s):
    288              return ''.join(s.encode('base64').split('\n'))
    289
    290          incoming_data = challenge.getData()
    291          data=base64.decodestring(incoming_data)
    292          ### Handle Auth result
    293          def on_auth_fail(reason):
    294              log.info('Failed SASL authentification: %s' % reason)
    295              self._owner.send(str(Node('abort', attrs={'xmlns': NS_SASL})))
    296              if len(self.mecs) > 0:
    297                  # There are other mechanisms to test, but wait for <failure>
    298                  # answer from server
    299                  self.startsasl = SASL_FAILURE_IN_PROGRESS
    300                  raise NodeProcessed
    301              if self.on_sasl:
    302                  self.on_sasl()
    303              raise NodeProcessed
    304
    305          if challenge.getName() == 'failure':
    306              if self.startsasl == SASL_FAILURE_IN_PROGRESS:
    307                  self.MechanismHandler()
    308                  raise NodeProcessed
    309              self.startsasl = SASL_FAILURE
    310              try:
    311                  reason = challenge.getChildren()[0]
    312              except Exception:
    313                  reason = challenge
    314              on_auth_fail(reason)
    315          elif challenge.getName() == 'success':
    316              if self.mechanism == 'SCRAM-SHA-1':
    317                  # check data-with-success
    318                  data = scram_parse(data)
    319                  if data['v'] != scram_base64(self.scram_ServerSignature):
    320                      on_auth_fail('ServerSignature is wrong')
    321
    322              self.startsasl = SASL_SUCCESS
    323              log.info('Successfully authenticated with remote server.')
    324              handlers = self._owner.Dispatcher.dumpHandlers()
    325
    326              # Bosh specific dispatcher replugging
    327              # save old features. They will be used in case we won't get response
    328              # on stream restart after SASL auth (happens with XMPP over BOSH
    329              # with Openfire)
    330              old_features = self._owner.Dispatcher.Stream.features
    331              self._owner.Dispatcher.PlugOut()
    332              dispatcher_nb.Dispatcher.get_instance().PlugIn(self._owner,
    333                  after_SASL=True, old_features=old_features)
    334              self._owner.Dispatcher.restoreHandlers(handlers)
    335              self._owner.User = self.username
    336
    337              if self.on_sasl:
    338                  self.on_sasl()
    339              raise NodeProcessed
    340
    341          ### Perform auth step
    342          log.info('Got challenge:' + data)
    343
    344          if self.mechanism == 'GSSAPI':
    345              if self.gss_step == GSS_STATE_STEP:
    346                  rc = kerberos.authGSSClientStep(self.gss_vc, incoming_data)
    347                  if rc != kerberos.AUTH_GSS_CONTINUE:
    348                      self.gss_step = GSS_STATE_WRAP
    349              elif self.gss_step == GSS_STATE_WRAP:
    350                  rc = kerberos.authGSSClientUnwrap(self.gss_vc, incoming_data)
    351                  response = kerberos.authGSSClientResponse(self.gss_vc)
    352                  rc = kerberos.authGSSClientWrap(self.gss_vc, response,
    353                      kerberos.authGSSClientUserName(self.gss_vc))
    354              response = kerberos.authGSSClientResponse(self.gss_vc)
    355              if not response:
    356                  response = ''
    357              self._owner.send(Node('response', attrs={'xmlns': NS_SASL},
    358                  payload=response).__str__())
    359              raise NodeProcessed
    360          if self.mechanism == 'SCRAM-SHA-1':
    361              hashfn = hashlib.sha1
    362
    363              def HMAC(k, s):
    364                  return hmac.HMAC(key=k, msg=s, digestmod=hashfn).digest()
    365
    366              def XOR(x, y):
    367                  r = (chr(ord(px) ^ ord(py)) for px, py in zip(x, y))
    368                  return ''.join(r)
    369
    370              def Hi(s, salt, iters):
    371                  ii = 1
    372                  try:
    373                      s = s.encode('utf-8')
    374                  except:
    375                      pass
    376                  ui_1 = HMAC(s, salt + '\0\0\0\01')
    377                  ui = ui_1
    378                  for i in range(iters - 1):
    379                      ii += 1
    380                      ui_1 = HMAC(s, ui_1)
    381                      ui = XOR(ui, ui_1)
    382                  return ui
    383
    384              def scram_H(s):
    385                  return hashfn(s).digest()
    386
    387              if self.scram_step == 0:
    388                  self.scram_step = 1
    389                  self.scram_soup += ',' + data + ','
    390                  data = scram_parse(data)
    391                  # TODO: Should check cnonce here.
    392                  # TODO: Channel binding data goes in here too.
    393                  r = 'c=' + scram_base64(self.scram_gs2)
    394                  r += ',r=' + data['r']
    395                  self.scram_soup += r
    396                  salt = data['s'].decode('base64')
    397                  iter = int(data['i'])
    398                  SaltedPassword = Hi(self.password, salt, iter)
    399                  # TODO: Could cache this, along with salt+iter.
    400                  ClientKey = HMAC(SaltedPassword, 'Client Key')
    401                  StoredKey = scram_H(ClientKey)
    402                  ClientSignature = HMAC(StoredKey, self.scram_soup)
    403                  ClientProof = XOR(ClientKey, ClientSignature)
    404                  r += ',p=' + scram_base64(ClientProof)
    405                  ServerKey = HMAC(SaltedPassword, 'Server Key')
    406                  self.scram_ServerSignature = HMAC(ServerKey, self.scram_soup)
    407                  sasl_data = scram_base64(r)
    408                  node = Node('response', attrs={'xmlns': NS_SASL},
    409                      payload=[sasl_data])
    410                  self._owner.send(str(node))
    411                  raise NodeProcessed
    412
    413              if self.scram_step == 1:
    414                  data = scram_parse(data)
    415                  if data['v'].decode('base64') != self.scram_ServerSignature:
    416                      # TODO: Not clear what to do here - need to abort.
    417                      raise Exception
    418                  node = Node('response', attrs={'xmlns': NS_SASL});
    419                  self._owner.send(str(node))
    420                  raise NodeProcessed
    421
    422          # magic foo...
    423          chal = challenge_splitter(data)
    424          if not self.realm and 'realm' in chal:
    425              self.realm = chal['realm']
    426          if 'qop' in chal and ((isinstance(chal['qop'], str) and \
    427          chal['qop'] =='auth') or (isinstance(chal['qop'], list) and 'auth' in \
    428          chal['qop'])):
    429              self.resp = {}
    430              self.resp['username'] = self.username
    431              if self.realm:
    432                  self.resp['realm'] = self.realm
    433              else:
    434                  self.resp['realm'] = self._owner.Server
    435              self.resp['nonce'] = chal['nonce']
    436              self.resp['cnonce'] = ''.join("%x" % randint(0, 2**28) for randint \
    437                  in itertools.repeat(random.randint, 7))
    438              self.resp['nc'] = ('00000001')
    439              self.resp['qop'] = 'auth'
    440              self.resp['digest-uri'] = 'xmpp/' + self._owner.Server
    441              self.resp['charset'] = 'utf-8'
    442              # Password is now required
    443              self._owner._caller.get_password(self.set_password, self.mechanism)
    444          elif 'rspauth' in chal:
    445              # Check rspauth value
    446              if chal['rspauth'] != self.digest_rspauth:
    447                  on_auth_fail('rspauth is wrong')
    448              self._owner.send(str(Node('response', attrs={'xmlns':NS_SASL})))
    449          else:
    450              self.startsasl = SASL_FAILURE
    451              log.info('Failed SASL authentification: unknown challenge')
    452          if self.on_sasl:
    453              self.on_sasl()
    454          raise NodeProcessed
    455
    456      @staticmethod
    457 -    def _convert_to_iso88591(string):
    458          try:
    459              string = string.decode('utf-8').encode('iso-8859-1')
    460          except UnicodeEncodeError:
    461              pass
    462          return string
    463
    464 -    def set_password(self, password):
    465          self.password = '' if password is None else password
    466          if self.mechanism == 'SCRAM-SHA-1':
    467              nonce = ''.join('%x' % randint(0, 2 ** 28) for randint in \
    468                  itertools.repeat(random.randint, 7))
    469              self.scram_soup = 'n=' + self.username + ',r=' + nonce
    470              self.scram_gs2 = 'n,,' # No CB yet.
    471              sasl_data = (self.scram_gs2 + self.scram_soup).encode('base64').\
    472                  replace('\n', '')
    473              node = Node('auth', attrs={'xmlns': NS_SASL,
    474                  'mechanism': self.mechanism}, payload=[sasl_data])
    475          elif self.mechanism == 'DIGEST-MD5':
    476              hash_username = self._convert_to_iso88591(self.resp['username'])
    477              hash_realm = self._convert_to_iso88591(self.resp['realm'])
    478              hash_password = self._convert_to_iso88591(self.password)
    479              A1 = C([H(C([hash_username, hash_realm, hash_password])),
    480                  self.resp['nonce'], self.resp['cnonce']])
    481              A2 = C(['AUTHENTICATE', self.resp['digest-uri']])
    482              response = HH(C([HH(A1), self.resp['nonce'], self.resp['nc'],
    483                  self.resp['cnonce'], self.resp['qop'], HH(A2)]))
    484              A2 = C(['', self.resp['digest-uri']])
    485              self.digest_rspauth = HH(C([HH(A1), self.resp['nonce'],
    486                  self.resp['nc'], self.resp['cnonce'], self.resp['qop'],
    487                  HH(A2)]))
    488              self.resp['response'] = response
    489              sasl_data = u''
    490              for key in ('charset', 'username', 'realm', 'nonce', 'nc', 'cnonce',
    491              'digest-uri', 'response', 'qop'):
    492                  if key in ('nc', 'qop', 'response', 'charset'):
    493                      sasl_data += u"%s=%s," % (key, self.resp[key])
    494                  else:
    495                      sasl_data += u'%s="%s",' % (key, self.resp[key])
    496              sasl_data = sasl_data[:-1].encode('utf-8').encode('base64').replace(
    497                  '\r', '').replace('\n', '')
    498              node = Node('response', attrs={'xmlns': NS_SASL},
    499                  payload=[sasl_data])
    500          elif self.mechanism == 'PLAIN':
    501              sasl_data = u'\x00%s\x00%s' % (self.username, self.password)
    502              sasl_data = sasl_data.encode('utf-8').encode('base64').replace(
    503                  '\n', '')
    504              node = Node('auth', attrs={'xmlns': NS_SASL, 'mechanism': 'PLAIN'},
    505                  payload=[sasl_data])
    506          elif self.mechanism == 'X-MESSENGER-OAUTH2':
    507              node = Node('auth', attrs={'xmlns': NS_SASL,
    508                  'mechanism': 'X-MESSENGER-OAUTH2'})
    509              node.addData(password)
    510          self._owner.send(str(node))
    511
    512
    513 -class NonBlockingNonSASL(PlugIn):
    514      """
    515      Implements old Non-SASL (JEP-0078) authentication used in jabberd1.4 and
    516      transport authentication
    517      """
    518
    519 -    def __init__(self, user, password, resource, on_auth):
    520          """
    521          Caches username, password and resource for auth
    522          """
    523          PlugIn.__init__(self)
    524          self.user = user
    525          if password is None:
    526              self.password = ''
    527          else:
    528              self.password = password
    529          self.resource = resource
    530          self.on_auth = on_auth
    531
    532 -    def plugin(self, owner):
    533          """
    534          Determine the best auth method (digest/0k/plain) and use it for auth.
    535          Returns used method name on success. Used internally
    536          """
    537          log.info('Querying server about possible auth methods')
    538          self.owner = owner
    539
    540          owner.Dispatcher.SendAndWaitForResponse(
    541              Iq('get', NS_AUTH, payload=[Node('username', payload=[self.user])]),
    542              func=self._on_username)
    543
    544 -    def _on_username(self, resp):
    545          if not isResultNode(resp):
    546              log.info('No result node arrived! Aborting...')
    547              return self.on_auth(None)
    548
    549          iq=Iq(typ='set', node=resp)
    550          query = iq.getTag('query')
    551          query.setTagData('username', self.user)
    552          query.setTagData('resource', self.resource)
    553
    554          if query.getTag('digest'):
    555              log.info("Performing digest authentication")
    556              query.setTagData('digest',
    557                  hashlib.sha1(self.owner.Dispatcher.Stream._document_attrs['id']
    558                  + self.password).hexdigest())
    559              if query.getTag('password'):
    560                  query.delChild('password')
    561              self._method = 'digest'
    562          elif query.getTag('token'):
    563              token = query.getTagData('token')
    564              seq = query.getTagData('sequence')
    565              log.info("Performing zero-k authentication")
    566
    567              def hasher(s):
    568                  return hashlib.sha1(s).hexdigest()
    569
    570              def hash_n_times(s, count):
    571                  return count and hasher(hash_n_times(s, count-1)) or s
    572
    573              hash_ = hash_n_times(hasher(hasher(self.password) + token),
    574                  int(seq))
    575              query.setTagData('hash', hash_)
    576              self._method='0k'
    577          else:
    578              log.warn("Secure methods unsupported, performing plain text \
    579                  authentication")
    580              self._method = 'plain'
    581              self._owner._caller.get_password(self._on_password, self._method)
    582              return
    583          resp = self.owner.Dispatcher.SendAndWaitForResponse(iq,
    584              func=self._on_auth)
    585
    586 -    def _on_password(self, password):
    587          self.password = '' if password is None else password
    588          iq=Iq('set', NS_AUTH)
    589          query = iq.getTag('query')
    590          query.setTagData('username', self.user)
    591          query.setTagData('resource', self.resource)
    592          query.setTagData('password', self.password)
    593          resp = self.owner.Dispatcher.SendAndWaitForResponse(iq,
    594              func=self._on_auth)
    595
    596 -    def _on_auth(self, resp):
    597          if isResultNode(resp):
    598              log.info('Sucessfully authenticated with remote host.')
    599              self.owner.User = self.user
    600              self.owner.Resource = self.resource
    601              self.owner._registered_name = self.owner.User + '@' + \
    602                  self.owner.Server+ '/' + self.owner.Resource
    603              return self.on_auth(self._method)
    604          log.info('Authentication failed!')
    605          return self.on_auth(None)
    606
    607
    608 -class NonBlockingBind(PlugIn):
    609      """
    610      Bind some JID to the current connection to allow router know of our
    611      location. Must be plugged after successful SASL auth
    612      """
    613
    614 -    def __init__(self):
    615          PlugIn.__init__(self)
    616          self.bound = None
    617          self.supports_sm = False
    618          self.resuming = False
    619
    620 -    def plugin(self, owner):
    621          ''' Start resource binding, if allowed at this time. Used internally. '''
    622          if self._owner.Dispatcher.Stream.features:
    623              try:
    624                  self.FeaturesHandler(self._owner.Dispatcher,
    625                      self._owner.Dispatcher.Stream.features)
    626              except NodeProcessed:
    627                  pass
    628          else:
    629              self._owner.RegisterHandler('features', self.FeaturesHandler,
    630                  xmlns=NS_STREAMS)
    631
    632 -    def FeaturesHandler(self, conn, feats):
    633          """
    634          Determine if server supports resource binding and set some internal
    635          attributes accordingly.
    636
    637          It also checks if server supports stream management
    638          """
    639
    640          if feats.getTag('sm', namespace=NS_STREAM_MGMT):
    641              self.supports_sm = True # server supports stream management
    642              if self.resuming:
    643                  self._owner._caller.sm.resume_request()
    644
    645          if not feats.getTag('bind', namespace=NS_BIND):
    646              log.info('Server does not requested binding.')
    647              # we try to bind resource anyway
    648              #self.bound='failure'
    649              self.bound = []
    650              return
    651          if feats.getTag('session', namespace=NS_SESSION):
    652              self.session = 1
    653          else:
    654              self.session = -1
    655          self.bound = []
    656
    657 -    def plugout(self):
    658          """
    659          Remove Bind handler from owner's dispatcher. Used internally
    660          """
    661          self._owner.UnregisterHandler('features', self.FeaturesHandler,
    662              xmlns=NS_STREAMS)
    663
    664 -    def NonBlockingBind(self, resource=None, on_bound=None):
    665          """
    666          Perform binding. Use provided resource name or random (if not provided).
    667          """
    668          if self.resuming: # We don't bind if we resume the stream
    669              return
    670          self.on_bound = on_bound
    671          self._resource = resource
    672          if self._resource:
    673              self._resource = [Node('resource', payload=[self._resource])]
    674          else:
    675              self._resource = []
    676
    677          self._owner.onreceive(None)
    678          self._owner.Dispatcher.SendAndWaitForResponse(
    679              Protocol('iq', typ='set', payload=[Node('bind',
    680              attrs={'xmlns': NS_BIND}, payload=self._resource)]),
    681              func=self._on_bound)
    682
    683 -    def _on_bound(self, resp):
    684          if isResultNode(resp):
    685              if resp.getTag('bind') and resp.getTag('bind').getTagData('jid'):
    686                  self.bound.append(resp.getTag('bind').getTagData('jid'))
    687                  log.info('Successfully bound %s.' % self.bound[-1])
    688                  jid = JID(resp.getTag('bind').getTagData('jid'))
    689                  self._owner.User = jid.getNode()
    690                  self._owner.Resource = jid.getResource()
    691                  # Only negociate stream management after bounded
    692                  sm = self._owner._caller.sm
    693                  if self.supports_sm:
    694                      # starts negociation
    695                      sm.supports_sm = True
    696                      sm.set_owner(self._owner)
    697                      sm.negociate()
    698                      self._owner.Dispatcher.sm = sm
    699
    700                  if hasattr(self, 'session') and self.session == -1:
    701                      # Server don't want us to initialize a session
    702                      log.info('No session required.')
    703                      self.on_bound('ok')
    704                  else:
    705                      self._owner.SendAndWaitForResponse(Protocol('iq', typ='set',
    706                          payload=[Node('session', attrs={'xmlns':NS_SESSION})]),
    707                          func=self._on_session)
    708                  return
    709          if resp:
    710              log.info('Binding failed: %s.' % resp.getTag('error'))
    711              self.on_bound(None)
    712          else:
    713              log.info('Binding failed: timeout expired.')
    714              self.on_bound(None)
    715
    716 -    def _on_session(self, resp):
    717          self._owner.onreceive(None)
    718          if isResultNode(resp):
    719              log.info('Successfully opened session.')
    720              self.session = 1
    721              self.on_bound('ok')
    722          else:
    723              log.error('Session open failed.')
    724              self.session = 0
    725              self.on_bound(None)
    726