summaryrefslogtreecommitdiff
path: root/src/xmpppy-0.5.0rc1/xmpp/auth.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/xmpppy-0.5.0rc1/xmpp/auth.py')
-rw-r--r--src/xmpppy-0.5.0rc1/xmpp/auth.py326
1 files changed, 326 insertions, 0 deletions
diff --git a/src/xmpppy-0.5.0rc1/xmpp/auth.py b/src/xmpppy-0.5.0rc1/xmpp/auth.py
new file mode 100644
index 00000000..6e51d72b
--- /dev/null
+++ b/src/xmpppy-0.5.0rc1/xmpp/auth.py
@@ -0,0 +1,326 @@
+## 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.41 2008/09/13 21:45:21 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,re
+
+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')
+ hash = sha.new(sha.new(self.password).hexdigest()+token).hexdigest()
+ for foo in xrange(int(seq)): hash = sha.new(hash).hexdigest()
+ 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 not self._owner.Dispatcher.Stream._document_attrs.has_key('version'): 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 self._owner.__dict__.has_key('features'): self._owner.UnregisterHandler('features',self.FeaturesHandler,xmlns=NS_STREAMS)
+ if self._owner.__dict__.has_key('challenge'): self._owner.UnregisterHandler('challenge',self.SASLHandler,xmlns=NS_SASL)
+ if self._owner.__dict__.has_key('failure'): self._owner.UnregisterHandler('failure',self.SASLHandler,xmlns=NS_SASL)
+ if self._owner.__dict__.has_key('success'): 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=[base64.encodestring(sasl_data).replace('\r','').replace('\n','')])
+ 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.decodestring(incoming_data)
+ 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 chal.has_key('qop') 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']='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 chal.has_key('rspauth'): 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