123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340 |
- ## auth.py
- ##
- ## Copyright (C) 2003-2005 Alexey "Snake" Nezhdanov
- ##
- ## This program is free software; you can redistribute it and/or modify
- ## it under the terms of the GNU General Public License as published by
- ## the Free Software Foundation; either version 2, or (at your option)
- ## any later version.
- ##
- ## This program is distributed in the hope that it will be useful,
- ## but WITHOUT ANY WARRANTY; without even the implied warranty of
- ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- ## GNU General Public License for more details.
- # $Id$
- """
- Provides library with all Non-SASL and SASL authentication mechanisms.
- Can be used both for client and transport authentication.
- """
- from .protocol import *
- from .client import PlugIn
- import base64,random,re
- from . import dispatcher
- from hashlib import md5,sha1
- from six import ensure_str,ensure_binary
- CHARSET_ENCODING='utf-8'
- def HH(some):
- return md5(ensure_binary(some, CHARSET_ENCODING)).hexdigest()
- def H(some):
- return md5(ensure_binary(some, CHARSET_ENCODING)).digest()
- def C(some):
- some = [ensure_binary(x, CHARSET_ENCODING) for x in some]
- return b':'.join(some)
- def HHSHA1(some):
- return sha1(ensure_binary(some, CHARSET_ENCODING)).hexdigest()
- def B64(some):
- return ensure_str(base64.b64encode(ensure_binary(some,CHARSET_ENCODING)),CHARSET_ENCODING)
- class NonSASL(PlugIn):
- """ Implements old Non-SASL (XEP-0078) authentication used in jabberd1.4 and transport authentication."""
- def __init__(self,user,password,resource):
- """ Caches username, password and resource for auth. """
- PlugIn.__init__(self)
- self.DBG_LINE='gen_auth'
- self.user=user
- self.password=password
- self.resource=resource
- def plugin(self,owner):
- """ Determine the best auth method (digest/0k/plain) and use it for auth.
- Returns used method name on success. Used internally. """
- if not self.resource: return self.authComponent(owner)
- self.DEBUG('Querying server about possible auth methods','start')
- resp=owner.Dispatcher.SendAndWaitForResponse(Iq('get',NS_AUTH,payload=[Node('username',payload=[self.user])]))
- if not isResultNode(resp):
- self.DEBUG('No result node arrived! Aborting...','error')
- return
- iq=Iq(typ='set',node=resp)
- query=iq.getTag('query')
- query.setTagData('username',self.user)
- query.setTagData('resource',self.resource)
- if query.getTag('digest'):
- self.DEBUG("Performing digest authentication",'ok')
- query.setTagData('digest',HHSHA1(owner.Dispatcher.Stream._document_attrs['id']+self.password))
- if query.getTag('password'): query.delChild('password')
- method='digest'
- elif query.getTag('token'):
- token=query.getTagData('token')
- seq=query.getTagData('sequence')
- self.DEBUG("Performing zero-k authentication",'ok')
- hash = HHSHA1(HHSHA1(self.password)+token)
- for foo in range(int(seq)): hash = HHSHA1(hash)
- query.setTagData('hash',hash)
- method='0k'
- else:
- self.DEBUG("Sequre methods unsupported, performing plain text authentication",'warn')
- query.setTagData('password',self.password)
- method='plain'
- resp=owner.Dispatcher.SendAndWaitForResponse(iq)
- if isResultNode(resp):
- self.DEBUG('Sucessfully authenticated with remove host.','ok')
- owner.User=self.user
- owner.Resource=self.resource
- owner._registered_name=owner.User+'@'+owner.Server+'/'+owner.Resource
- return method
- self.DEBUG('Authentication failed!','error')
- def authComponent(self,owner):
- """ Authenticate component. Send handshake stanza and wait for result. Returns "ok" on success. """
- self.handshake=0
- owner.send(Node(NS_COMPONENT_ACCEPT+' handshake',payload=[HHSHA1(owner.Dispatcher.Stream._document_attrs['id']+self.password)]))
- owner.RegisterHandler('handshake',self.handshakeHandler,xmlns=NS_COMPONENT_ACCEPT)
- while not self.handshake:
- self.DEBUG("waiting on handshake",'notify')
- owner.Process(1)
- owner._registered_name=self.user
- if self.handshake+1: return 'ok'
- def handshakeHandler(self,disp,stanza):
- """ Handler for registering in dispatcher for accepting transport authentication. """
- if stanza.getName()=='handshake': self.handshake=1
- else: self.handshake=-1
- class SASL(PlugIn):
- """ Implements SASL authentication. """
- def __init__(self,username,password):
- PlugIn.__init__(self)
- self.username=username
- self.password=password
- def plugin(self,owner):
- if 'version' not in self._owner.Dispatcher.Stream._document_attrs: self.startsasl='not-supported'
- elif self._owner.Dispatcher.Stream.features:
- try: self.FeaturesHandler(self._owner.Dispatcher,self._owner.Dispatcher.Stream.features)
- except NodeProcessed: pass
- else: self.startsasl=None
- def auth(self):
- """ Start authentication. Result can be obtained via "SASL.startsasl" attribute and will be
- either "success" or "failure". Note that successfull auth will take at least
- two Dispatcher.Process() calls. """
- if self.startsasl: pass
- elif self._owner.Dispatcher.Stream.features:
- try: self.FeaturesHandler(self._owner.Dispatcher,self._owner.Dispatcher.Stream.features)
- except NodeProcessed: pass
- else: self._owner.RegisterHandler('features',self.FeaturesHandler,xmlns=NS_STREAMS)
- def plugout(self):
- """ Remove SASL handlers from owner's dispatcher. Used internally. """
- if 'features' in self._owner.__dict__: self._owner.UnregisterHandler('features',self.FeaturesHandler,xmlns=NS_STREAMS)
- if 'challenge' in self._owner.__dict__: self._owner.UnregisterHandler('challenge',self.SASLHandler,xmlns=NS_SASL)
- if 'failure' in self._owner.__dict__: self._owner.UnregisterHandler('failure',self.SASLHandler,xmlns=NS_SASL)
- if 'success' in self._owner.__dict__: self._owner.UnregisterHandler('success',self.SASLHandler,xmlns=NS_SASL)
- def FeaturesHandler(self,conn,feats):
- """ Used to determine if server supports SASL auth. Used internally. """
- if not feats.getTag('mechanisms',namespace=NS_SASL):
- self.startsasl='not-supported'
- self.DEBUG('SASL not supported by server','error')
- return
- mecs=[]
- for mec in feats.getTag('mechanisms',namespace=NS_SASL).getTags('mechanism'):
- mecs.append(mec.getData())
- self._owner.RegisterHandler('challenge',self.SASLHandler,xmlns=NS_SASL)
- self._owner.RegisterHandler('failure',self.SASLHandler,xmlns=NS_SASL)
- self._owner.RegisterHandler('success',self.SASLHandler,xmlns=NS_SASL)
- if "ANONYMOUS" in mecs and self.username == None:
- node=Node('auth',attrs={'xmlns':NS_SASL,'mechanism':'ANONYMOUS'})
- elif "DIGEST-MD5" in mecs:
- node=Node('auth',attrs={'xmlns':NS_SASL,'mechanism':'DIGEST-MD5'})
- elif "PLAIN" in mecs:
- sasl_data='%s\x00%s\x00%s'%(self.username+'@'+self._owner.Server,self.username,self.password)
- node=Node('auth',attrs={'xmlns':NS_SASL,'mechanism':'PLAIN'},payload=[B64(sasl_data)])
- else:
- self.startsasl='failure'
- self.DEBUG('I can only use DIGEST-MD5 and PLAIN mecanisms.','error')
- return
- self.startsasl='in-process'
- self._owner.send(node.__str__())
- raise NodeProcessed
- def SASLHandler(self,conn,challenge):
- """ Perform next SASL auth step. Used internally. """
- if challenge.getNamespace()!=NS_SASL: return
- if challenge.getName()=='failure':
- self.startsasl='failure'
- try: reason=challenge.getChildren()[0]
- except: reason=challenge
- self.DEBUG('Failed SASL authentification: %s'%reason,'error')
- raise NodeProcessed
- elif challenge.getName()=='success':
- self.startsasl='success'
- self.DEBUG('Successfully authenticated with remote server.','ok')
- handlers=self._owner.Dispatcher.dumpHandlers()
- self._owner.Dispatcher.PlugOut()
- dispatcher.Dispatcher().PlugIn(self._owner)
- self._owner.Dispatcher.restoreHandlers(handlers)
- self._owner.User=self.username
- raise NodeProcessed
- ########################################3333
- incoming_data=challenge.getData()
- chal={}
- data=base64.b64decode(incoming_data)
- data=ensure_str(data,CHARSET_ENCODING)
- self.DEBUG('Got challenge: '+data,'ok')
- for pair in re.findall('(\w+\s*=\s*(?:(?:"[^"]+")|(?:[^,]+)))',data):
- key,value=[x.strip() for x in pair.split('=', 1)]
- if value[:1]=='"' and value[-1:]=='"': value=value[1:-1]
- chal[key]=value
- if 'qop' in chal and 'auth' in [x.strip() for x in chal['qop'].split(',')]:
- resp={}
- resp['username']=self.username
- resp['realm']=self._owner.Server
- resp['nonce']=chal['nonce']
- cnonce=''
- for i in range(7):
- cnonce+=hex(int(random.random()*65536*4096))[2:]
- resp['cnonce']=cnonce
- resp['nc']=('00000001')
- resp['qop']='auth'
- resp['digest-uri']='xmpp/'+self._owner.Server
- A1=C([H(C([resp['username'],resp['realm'],self.password])),resp['nonce'],resp['cnonce']])
- A2=C(['AUTHENTICATE',resp['digest-uri']])
- response= HH(C([HH(A1),resp['nonce'],resp['nc'],resp['cnonce'],resp['qop'],HH(A2)]))
- resp['response']=response
- resp['charset']=CHARSET_ENCODING
- sasl_data=''
- for key in ['charset','username','realm','nonce','nc','cnonce','digest-uri','response','qop']:
- if key in ['nc','qop','response','charset']: sasl_data+="%s=%s,"%(key,resp[key])
- else: sasl_data+='%s="%s",'%(key,resp[key])
- ########################################3333
- node=Node('response',attrs={'xmlns':NS_SASL},payload=[B64(sasl_data[:-1])])
- self._owner.send(node.__str__())
- elif 'rspauth' in chal: self._owner.send(Node('response',attrs={'xmlns':NS_SASL}).__str__())
- else:
- self.startsasl='failure'
- self.DEBUG('Failed SASL authentification: unknown challenge','error')
- raise NodeProcessed
- class Bind(PlugIn):
- """ Bind some JID to the current connection to allow router know of our location."""
- def __init__(self):
- PlugIn.__init__(self)
- self.DBG_LINE='bind'
- self.bound=None
- def plugin(self,owner):
- """ Start resource binding, if allowed at this time. Used internally. """
- if self._owner.Dispatcher.Stream.features:
- try: self.FeaturesHandler(self._owner.Dispatcher,self._owner.Dispatcher.Stream.features)
- except NodeProcessed: pass
- else: self._owner.RegisterHandler('features',self.FeaturesHandler,xmlns=NS_STREAMS)
- def plugout(self):
- """ Remove Bind handler from owner's dispatcher. Used internally. """
- self._owner.UnregisterHandler('features',self.FeaturesHandler,xmlns=NS_STREAMS)
- def FeaturesHandler(self,conn,feats):
- """ Determine if server supports resource binding and set some internal attributes accordingly. """
- if not feats.getTag('bind',namespace=NS_BIND):
- self.bound='failure'
- self.DEBUG('Server does not requested binding.','error')
- return
- if feats.getTag('session',namespace=NS_SESSION): self.session=1
- else: self.session=-1
- self.bound=[]
- def Bind(self,resource=None):
- """ Perform binding. Use provided resource name or random (if not provided). """
- while self.bound is None and self._owner.Process(1): pass
- if resource: resource=[Node('resource',payload=[resource])]
- else: resource=[]
- resp=self._owner.SendAndWaitForResponse(Protocol('iq',typ='set',payload=[Node('bind',attrs={'xmlns':NS_BIND},payload=resource)]))
- if isResultNode(resp):
- self.bound.append(resp.getTag('bind').getTagData('jid'))
- self.DEBUG('Successfully bound %s.'%self.bound[-1],'ok')
- jid=JID(resp.getTag('bind').getTagData('jid'))
- self._owner.User=jid.getNode()
- self._owner.Resource=jid.getResource()
- resp=self._owner.SendAndWaitForResponse(Protocol('iq',typ='set',payload=[Node('session',attrs={'xmlns':NS_SESSION})]))
- if isResultNode(resp):
- self.DEBUG('Successfully opened session.','ok')
- self.session=1
- return 'ok'
- else:
- self.DEBUG('Session open failed.','error')
- self.session=0
- elif resp: self.DEBUG('Binding failed: %s.'%resp.getTag('error'),'error')
- else:
- self.DEBUG('Binding failed: timeout expired.','error')
- return ''
- class ComponentBind(PlugIn):
- """ ComponentBind some JID to the current connection to allow router know of our location."""
- def __init__(self, sasl):
- PlugIn.__init__(self)
- self.DBG_LINE='bind'
- self.bound=None
- self.needsUnregister=None
- self.sasl = sasl
- def plugin(self,owner):
- """ Start resource binding, if allowed at this time. Used internally. """
- if not self.sasl:
- self.bound=[]
- return
- if self._owner.Dispatcher.Stream.features:
- try: self.FeaturesHandler(self._owner.Dispatcher,self._owner.Dispatcher.Stream.features)
- except NodeProcessed: pass
- else:
- self._owner.RegisterHandler('features',self.FeaturesHandler,xmlns=NS_STREAMS)
- self.needsUnregister=1
- def plugout(self):
- """ Remove ComponentBind handler from owner's dispatcher. Used internally. """
- if self.needsUnregister:
- self._owner.UnregisterHandler('features',self.FeaturesHandler,xmlns=NS_STREAMS)
- def FeaturesHandler(self,conn,feats):
- """ Determine if server supports resource binding and set some internal attributes accordingly. """
- if not feats.getTag('bind',namespace=NS_BIND):
- self.bound='failure'
- self.DEBUG('Server does not requested binding.','error')
- return
- if feats.getTag('session',namespace=NS_SESSION): self.session=1
- else: self.session=-1
- self.bound=[]
- def Bind(self,domain=None):
- """ Perform binding. Use provided domain name (if not provided). """
- while self.bound is None and self._owner.Process(1): pass
- if self.sasl:
- xmlns = NS_COMPONENT_1
- else:
- xmlns = None
- self.bindresponse = None
- ttl = dispatcher.DefaultTimeout
- self._owner.RegisterHandler('bind',self.BindHandler,xmlns=xmlns)
- self._owner.send(Protocol('bind',attrs={'name':domain},xmlns=NS_COMPONENT_1))
- while self.bindresponse is None and self._owner.Process(1) and ttl > 0: ttl-=1
- self._owner.UnregisterHandler('bind',self.BindHandler,xmlns=xmlns)
- resp=self.bindresponse
- if resp and resp.getAttr('error'):
- self.DEBUG('Binding failed: %s.'%resp.getAttr('error'),'error')
- elif resp:
- self.DEBUG('Successfully bound.','ok')
- return 'ok'
- else:
- self.DEBUG('Binding failed: timeout expired.','error')
- return ''
- def BindHandler(self,conn,bind):
- self.bindresponse = bind
- pass
|