## 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: auth.py,v 1.35 2006/01/18 19:26:43 normanr Exp $ """ 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 sha,base64,random,dispatcher import md5 def HH(some): return md5.new(some).hexdigest() def H(some): return md5.new(some).digest() def C(some): return ':'.join(some) class NonSASL(PlugIn): """ Implements old Non-SASL (JEP-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',sha.new(owner.Dispatcher.Stream._document_attrs['id']+self.password).hexdigest()) 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') def hasher(s): return sha.new(s).hexdigest() def hash_n_times(s, count): return count and hasher(hash_n_times(s, count-1)) or s hash_ = hash_n_times(hasher(hasher(self.password)+token), int(seq)) 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=[sha.new(owner.Dispatcher.Stream._document_attrs['id']+self.password).hexdigest()])) 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. """ self._owner.UnregisterHandler('features',self.FeaturesHandler,xmlns=NS_STREAMS) self._owner.UnregisterHandler('challenge',self.SASLHandler,xmlns=NS_SASL) self._owner.UnregisterHandler('failure',self.SASLHandler,xmlns=NS_SASL) 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 "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=[base64.encodestring(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 Exception: 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.decodestring(incoming_data) self.DEBUG('Got challenge:'+data,'ok') for pair in data.split(','): key,value=pair.split('=', 1) if value.startswith('"') and value.endswith('"'): value=value[1:-1] chal[key]=value if 'qop' in chal and chal['qop']=='auth': resp={} resp['username']=self.username resp['realm']=self._owner.Server resp['nonce']=chal['nonce'] resp['cnonce'] = ''.join("%x" % randint(0, 2**28) for randint in itertools.repeat(random.randint, 7)) 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']='utf-8' 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=[base64.encodestring(sasl_data[:-1]).replace('\r','').replace('\n','')]) 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): PlugIn.__init__(self) self.DBG_LINE='bind' self.bound=None self.needsUnregister=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) 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 resp=self._owner.SendAndWaitForResponse(Protocol('bind',attrs={'name':domain},xmlns=NS_COMPONENT_1)) 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 '' # vim: se ts=3: