Package xmpp :: Module auth

[hide private]
[frames] | no frames]

# Source Code for Module xmpp.auth

      1  ##   auth.py
      2  ##
      3  ##   Copyright (C) 2003-2005 Alexey "Snake" Nezhdanov
      4  ##
      5  ##   This program is free software; you can redistribute it and/or modify
      6  ##   it under the terms of the GNU General Public License as published by
      7  ##   the Free Software Foundation; either version 2, or (at your option)
      8  ##   any later version.
      9  ##
     10  ##   This program is distributed in the hope that it will be useful,
     11  ##   but WITHOUT ANY WARRANTY; without even the implied warranty of
     12  ##   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     13  ##   GNU General Public License for more details.
     14
     15  # $Id: auth.py,v 1.38 2007/08/28 10:03:33 normanr Exp $
     16
     17  """
     18  Provides library with all Non-SASL and SASL authentication mechanisms.
     19  Can be used both for client and transport authentication.
     20  """
     21
     22  from protocol import *
     23  from client import PlugIn
     24  import sha,base64,random,dispatcher,re
     25
     26  import md5
     27 -def HH(some): return md5.new(some).hexdigest()
     28 -def H(some): return md5.new(some).digest()
     29 -def C(some): return ':'.join(some)
     30
     31 -class NonSASL(PlugIn):
     32      """ Implements old Non-SASL (JEP-0078) authentication used in jabberd1.4 and transport authentication."""
     33 -    def __init__(self,user,password,resource):
     34          """ Caches username, password and resource for auth. """
     35          PlugIn.__init__(self)
     36          self.DBG_LINE='gen_auth'
     37          self.user=user
     38          self.password=password
     39          self.resource=resource
     40
     41 -    def plugin(self,owner):
     42          """ Determine the best auth method (digest/0k/plain) and use it for auth.
     43              Returns used method name on success. Used internally. """
     44          if not self.resource: return self.authComponent(owner)
     45          self.DEBUG('Querying server about possible auth methods','start')
     46          resp=owner.Dispatcher.SendAndWaitForResponse(Iq('get',NS_AUTH,payload=[Node('username',payload=[self.user])]))
     47          if not isResultNode(resp):
     48              self.DEBUG('No result node arrived! Aborting...','error')
     49              return
     50          iq=Iq(typ='set',node=resp)
     51          query=iq.getTag('query')
     52          query.setTagData('username',self.user)
     53          query.setTagData('resource',self.resource)
     54
     55          if query.getTag('digest'):
     56              self.DEBUG("Performing digest authentication",'ok')
     57              query.setTagData('digest',sha.new(owner.Dispatcher.Stream._document_attrs['id']+self.password).hexdigest())
     58              if query.getTag('password'): query.delChild('password')
     59              method='digest'
     60          elif query.getTag('token'):
     61              token=query.getTagData('token')
     62              seq=query.getTagData('sequence')
     63              self.DEBUG("Performing zero-k authentication",'ok')
     64              hash = sha.new(sha.new(self.password).hexdigest()+token).hexdigest()
     65              for foo in xrange(int(seq)): hash = sha.new(hash).hexdigest()
     66              query.setTagData('hash',hash)
     67              method='0k'
     68          else:
     69              self.DEBUG("Sequre methods unsupported, performing plain text authentication",'warn')
     70              query.setTagData('password',self.password)
     71              method='plain'
     72          resp=owner.Dispatcher.SendAndWaitForResponse(iq)
     73          if isResultNode(resp):
     74              self.DEBUG('Sucessfully authenticated with remove host.','ok')
     75              owner.User=self.user
     76              owner.Resource=self.resource
     77              owner._registered_name=owner.User+'@'+owner.Server+'/'+owner.Resource
     78              return method
     79          self.DEBUG('Authentication failed!','error')
     80
     81 -    def authComponent(self,owner):
     82          """ Authenticate component. Send handshake stanza and wait for result. Returns "ok" on success. """
     83          self.handshake=0
     84          owner.send(Node(NS_COMPONENT_ACCEPT+' handshake',payload=[sha.new(owner.Dispatcher.Stream._document_attrs['id']+self.password).hexdigest()]))
     85          owner.RegisterHandler('handshake',self.handshakeHandler,xmlns=NS_COMPONENT_ACCEPT)
     86          while not self.handshake:
     87              self.DEBUG("waiting on handshake",'notify')
     88              owner.Process(1)
     89          owner._registered_name=self.user
     90          if self.handshake+1: return 'ok'
     91
     92 -    def handshakeHandler(self,disp,stanza):
     93          """ Handler for registering in dispatcher for accepting transport authentication. """
     94          if stanza.getName()=='handshake': self.handshake=1
     95          else: self.handshake=-1
     96
     97 -class SASL(PlugIn):
     98      """ Implements SASL authentication. """
     99 -    def __init__(self,username,password):
    100          PlugIn.__init__(self)
    101          self.username=username
    102          self.password=password
    103
    104 -    def plugin(self,owner):
    105          if not self._owner.Dispatcher.Stream._document_attrs.has_key('version'): self.startsasl='not-supported'
    106          elif self._owner.Dispatcher.Stream.features:
    107              try: self.FeaturesHandler(self._owner.Dispatcher,self._owner.Dispatcher.Stream.features)
    108              except NodeProcessed: pass
    109          else: self.startsasl=None
    110
    111 -    def auth(self):
    112          """ Start authentication. Result can be obtained via "SASL.startsasl" attribute and will be
    113              either "success" or "failure". Note that successfull auth will take at least
    114              two Dispatcher.Process() calls. """
    115          if self.startsasl: pass
    116          elif self._owner.Dispatcher.Stream.features:
    117              try: self.FeaturesHandler(self._owner.Dispatcher,self._owner.Dispatcher.Stream.features)
    118              except NodeProcessed: pass
    119          else: self._owner.RegisterHandler('features',self.FeaturesHandler,xmlns=NS_STREAMS)
    120
    121 -    def plugout(self):
    122          """ Remove SASL handlers from owner's dispatcher. Used internally. """
    123          if self._owner.__dict__.has_key('features'): self._owner.UnregisterHandler('features',self.FeaturesHandler,xmlns=NS_STREAMS)
    124          if self._owner.__dict__.has_key('challenge'): self._owner.UnregisterHandler('challenge',self.SASLHandler,xmlns=NS_SASL)
    125          if self._owner.__dict__.has_key('failure'): self._owner.UnregisterHandler('failure',self.SASLHandler,xmlns=NS_SASL)
    126          if self._owner.__dict__.has_key('success'): self._owner.UnregisterHandler('success',self.SASLHandler,xmlns=NS_SASL)
    127
    128 -    def FeaturesHandler(self,conn,feats):
    129          """ Used to determine if server supports SASL auth. Used internally. """
    130          if not feats.getTag('mechanisms',namespace=NS_SASL):
    131              self.startsasl='not-supported'
    132              self.DEBUG('SASL not supported by server','error')
    133              return
    134          mecs=[]
    135          for mec in feats.getTag('mechanisms',namespace=NS_SASL).getTags('mechanism'):
    136              mecs.append(mec.getData())
    137          self._owner.RegisterHandler('challenge',self.SASLHandler,xmlns=NS_SASL)
    138          self._owner.RegisterHandler('failure',self.SASLHandler,xmlns=NS_SASL)
    139          self._owner.RegisterHandler('success',self.SASLHandler,xmlns=NS_SASL)
    140          if "DIGEST-MD5" in mecs:
    141              node=Node('auth',attrs={'xmlns':NS_SASL,'mechanism':'DIGEST-MD5'})
    142          elif "PLAIN" in mecs:
    143              sasl_data='%s\x00%s\x00%s'%(self.username+'@'+self._owner.Server,self.username,self.password)
    144              node=Node('auth',attrs={'xmlns':NS_SASL,'mechanism':'PLAIN'},payload=[base64.encodestring(sasl_data)])
    145          else:
    146              self.startsasl='failure'
    147              self.DEBUG('I can only use DIGEST-MD5 and PLAIN mecanisms.','error')
    148              return
    149          self.startsasl='in-process'
    150          self._owner.send(node.__str__())
    151          raise NodeProcessed
    152
    153 -    def SASLHandler(self,conn,challenge):
    154          """ Perform next SASL auth step. Used internally. """
    155          if challenge.getNamespace()<>NS_SASL: return
    156          if challenge.getName()=='failure':
    157              self.startsasl='failure'
    158              try: reason=challenge.getChildren()[0]
    159              except: reason=challenge
    160              self.DEBUG('Failed SASL authentification: %s'%reason,'error')
    161              raise NodeProcessed
    162          elif challenge.getName()=='success':
    163              self.startsasl='success'
    164              self.DEBUG('Successfully authenticated with remote server.','ok')
    165              handlers=self._owner.Dispatcher.dumpHandlers()
    166              self._owner.Dispatcher.PlugOut()
    167              dispatcher.Dispatcher().PlugIn(self._owner)
    168              self._owner.Dispatcher.restoreHandlers(handlers)
    169              self._owner.User=self.username
    170              raise NodeProcessed
    171  ########################################3333
    172          incoming_data=challenge.getData()
    173          chal={}
    174          data=base64.decodestring(incoming_data)
    175          self.DEBUG('Got challenge:'+data,'ok')
    176          for pair in re.findall('(\w+=(?:"[^"]+")|(?:[^,]+))',data):
    177              key,value=pair.split('=', 1)
    178              if value[:1]=='"' and value[-1:]=='"': value=value[1:-1]
    179              chal[key]=value
    180          if chal.has_key('qop') and 'auth' in chal['qop'].split(','):
    181              resp={}
    182              resp['username']=self.username
    183              resp['realm']=self._owner.Server
    184              resp['nonce']=chal['nonce']
    185              cnonce=''
    186              for i in range(7):
    187                  cnonce+=hex(int(random.random()*65536*4096))[2:]
    188              resp['cnonce']=cnonce
    189              resp['nc']=('00000001')
    190              resp['qop']='auth'
    191              resp['digest-uri']='xmpp/'+self._owner.Server
    192              A1=C([H(C([resp['username'],resp['realm'],self.password])),resp['nonce'],resp['cnonce']])
    193              A2=C(['AUTHENTICATE',resp['digest-uri']])
    194              response= HH(C([HH(A1),resp['nonce'],resp['nc'],resp['cnonce'],resp['qop'],HH(A2)]))
    195              resp['response']=response
    196              resp['charset']='utf-8'
    197              sasl_data=''
    198              for key in ['charset','username','realm','nonce','nc','cnonce','digest-uri','response','qop']:
    199                  if key in ['nc','qop','response','charset']: sasl_data+="%s=%s,"%(key,resp[key])
    200                  else: sasl_data+='%s="%s",'%(key,resp[key])
    201  ########################################3333
    202              node=Node('response',attrs={'xmlns':NS_SASL},payload=[base64.encodestring(sasl_data[:-1]).replace('\r','').replace('\n','')])
    203              self._owner.send(node.__str__())
    204          elif chal.has_key('rspauth'): self._owner.send(Node('response',attrs={'xmlns':NS_SASL}).__str__())
    205          else:
    206              self.startsasl='failure'
    207              self.DEBUG('Failed SASL authentification: unknown challenge','error')
    208          raise NodeProcessed
    209
    210 -class Bind(PlugIn):
    211      """ Bind some JID to the current connection to allow router know of our location."""
    212 -    def __init__(self):
    213          PlugIn.__init__(self)
    214          self.DBG_LINE='bind'
    215          self.bound=None
    216
    217 -    def plugin(self,owner):
    218          """ Start resource binding, if allowed at this time. Used internally. """
    219          if self._owner.Dispatcher.Stream.features:
    220              try: self.FeaturesHandler(self._owner.Dispatcher,self._owner.Dispatcher.Stream.features)
    221              except NodeProcessed: pass
    222          else: self._owner.RegisterHandler('features',self.FeaturesHandler,xmlns=NS_STREAMS)
    223
    224 -    def plugout(self):
    225          """ Remove Bind handler from owner's dispatcher. Used internally. """
    226          self._owner.UnregisterHandler('features',self.FeaturesHandler,xmlns=NS_STREAMS)
    227
    228 -    def FeaturesHandler(self,conn,feats):
    229          """ Determine if server supports resource binding and set some internal attributes accordingly. """
    230          if not feats.getTag('bind',namespace=NS_BIND):
    231              self.bound='failure'
    232              self.DEBUG('Server does not requested binding.','error')
    233              return
    234          if feats.getTag('session',namespace=NS_SESSION): self.session=1
    235          else: self.session=-1
    236          self.bound=[]
    237
    238 -    def Bind(self,resource=None):
    239          """ Perform binding. Use provided resource name or random (if not provided). """
    240          while self.bound is None and self._owner.Process(1): pass
    241          if resource: resource=[Node('resource',payload=[resource])]
    242          else: resource=[]
    243          resp=self._owner.SendAndWaitForResponse(Protocol('iq',typ='set',payload=[Node('bind',attrs={'xmlns':NS_BIND},payload=resource)]))
    244          if isResultNode(resp):
    245              self.bound.append(resp.getTag('bind').getTagData('jid'))
    246              self.DEBUG('Successfully bound %s.'%self.bound[-1],'ok')
    247              jid=JID(resp.getTag('bind').getTagData('jid'))
    248              self._owner.User=jid.getNode()
    249              self._owner.Resource=jid.getResource()
    250              resp=self._owner.SendAndWaitForResponse(Protocol('iq',typ='set',payload=[Node('session',attrs={'xmlns':NS_SESSION})]))
    251              if isResultNode(resp):
    252                  self.DEBUG('Successfully opened session.','ok')
    253                  self.session=1
    254                  return 'ok'
    255              else:
    256                  self.DEBUG('Session open failed.','error')
    257                  self.session=0
    258          elif resp: self.DEBUG('Binding failed: %s.'%resp.getTag('error'),'error')
    259          else:
    260              self.DEBUG('Binding failed: timeout expired.','error')
    261              return ''
    262
    263 -class ComponentBind(PlugIn):
    264      """ ComponentBind some JID to the current connection to allow router know of our location."""
    265 -    def __init__(self, sasl):
    266          PlugIn.__init__(self)
    267          self.DBG_LINE='bind'
    268          self.bound=None
    269          self.needsUnregister=None
    270          self.sasl = sasl
    271
    272 -    def plugin(self,owner):
    273          """ Start resource binding, if allowed at this time. Used internally. """
    274          if not self.sasl:
    275              self.bound=[]
    276              return
    277          if self._owner.Dispatcher.Stream.features:
    278              try: self.FeaturesHandler(self._owner.Dispatcher,self._owner.Dispatcher.Stream.features)
    279              except NodeProcessed: pass
    280          else:
    281              self._owner.RegisterHandler('features',self.FeaturesHandler,xmlns=NS_STREAMS)
    282              self.needsUnregister=1
    283
    284 -    def plugout(self):
    285          """ Remove ComponentBind handler from owner's dispatcher. Used internally. """
    286          if self.needsUnregister:
    287              self._owner.UnregisterHandler('features',self.FeaturesHandler,xmlns=NS_STREAMS)
    288
    289 -    def FeaturesHandler(self,conn,feats):
    290          """ Determine if server supports resource binding and set some internal attributes accordingly. """
    291          if not feats.getTag('bind',namespace=NS_BIND):
    292              self.bound='failure'
    293              self.DEBUG('Server does not requested binding.','error')
    294              return
    295          if feats.getTag('session',namespace=NS_SESSION): self.session=1
    296          else: self.session=-1
    297          self.bound=[]
    298
    299 -    def Bind(self,domain=None):
    300          """ Perform binding. Use provided domain name (if not provided). """
    301          while self.bound is None and self._owner.Process(1): pass
    302          if self.sasl:
    303              xmlns = NS_COMPONENT_1
    304          else:
    305              xmlns = None
    306          self.bindresponse = None
    307          ttl = dispatcher.DefaultTimeout
    308          self._owner.RegisterHandler('bind',self.BindHandler,xmlns=xmlns)
    309          self._owner.send(Protocol('bind',attrs={'name':domain},xmlns=NS_COMPONENT_1))
    310          while self.bindresponse is None and self._owner.Process(1) and ttl > 0: ttl-=1
    311          self._owner.UnregisterHandler('bind',self.BindHandler,xmlns=xmlns)
    312          resp=self.bindresponse
    313          if resp and resp.getAttr('error'):
    314              self.DEBUG('Binding failed: %s.'%resp.getAttr('error'),'error')
    315          elif resp:
    316              self.DEBUG('Successfully bound.','ok')
    317              return 'ok'
    318          else:
    319              self.DEBUG('Binding failed: timeout expired.','error')
    320              return ''
    321
    322 -    def BindHandler(self,conn,bind):
    323          self.bindresponse = bind
    324          pass
    325