Package spade :: Package xmpp :: Module auth
[hide private]
[frames] | no frames]

Source Code for Module spade.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.35 2006/01/18 19:26:43 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 base64,random,dispatcher 
 25  import hashlib 
 26   
27 -def HH(some): return hashlib.md5(some).hexdigest()
28 -def H(some): return hashlab.md5(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',hashlib.sha1(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 = hashlib.sha1(hashlib.sha1(self.password).hexdigest()+token).hexdigest() 65 for foo in xrange(int(seq)): hash = hashlib.sha1(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=[hashlib.sha1(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 self._owner.UnregisterHandler('features',self.FeaturesHandler,xmlns=NS_STREAMS) 124 self._owner.UnregisterHandler('challenge',self.SASLHandler,xmlns=NS_SASL) 125 self._owner.UnregisterHandler('failure',self.SASLHandler,xmlns=NS_SASL) 126 self._owner.UnregisterHandler('success',self.SASLHandler,xmlns=NS_SASL)
127 128
129 - def FeaturesHandler(self,conn,feats):
130 """ Used to determine if server supports SASL auth. Used internally. """ 131 if not feats.getTag('mechanisms',namespace=NS_SASL): 132 self.startsasl='not-supported' 133 self.DEBUG('SASL not supported by server','error') 134 return 135 mecs=[] 136 for mec in feats.getTag('mechanisms',namespace=NS_SASL).getTags('mechanism'): 137 mecs.append(mec.getData()) 138 self._owner.RegisterHandler('challenge',self.SASLHandler,xmlns=NS_SASL) 139 self._owner.RegisterHandler('failure',self.SASLHandler,xmlns=NS_SASL) 140 self._owner.RegisterHandler('success',self.SASLHandler,xmlns=NS_SASL) 141 if "DIGEST-MD5" in mecs: 142 node=Node('auth',attrs={'xmlns':NS_SASL,'mechanism':'DIGEST-MD5'}) 143 elif "PLAIN" in mecs: 144 sasl_data='%s\x00%s\x00%s'%(self.username+'@'+self._owner.Server,self.username,self.password) 145 node=Node('auth',attrs={'xmlns':NS_SASL,'mechanism':'PLAIN'},payload=[base64.encodestring(sasl_data)]) 146 else: 147 self.startsasl='failure' 148 self.DEBUG('I can only use DIGEST-MD5 and PLAIN mecanisms.','error') 149 return 150 self.startsasl='in-process' 151 self._owner.send(node.__str__()) 152 raise NodeProcessed
153
154 - def SASLHandler(self,conn,challenge):
155 """ Perform next SASL auth step. Used internally. """ 156 if challenge.getNamespace()<>NS_SASL: return 157 if challenge.getName()=='failure': 158 self.startsasl='failure' 159 try: reason=challenge.getChildren()[0] 160 except: reason=challenge 161 self.DEBUG('Failed SASL authentification: %s'%reason,'error') 162 raise NodeProcessed 163 elif challenge.getName()=='success': 164 self.startsasl='success' 165 self.DEBUG('Successfully authenticated with remote server.','ok') 166 handlers=self._owner.Dispatcher.dumpHandlers() 167 self._owner.Dispatcher.PlugOut() 168 dispatcher.Dispatcher().PlugIn(self._owner) 169 self._owner.Dispatcher.restoreHandlers(handlers) 170 self._owner.User=self.username 171 raise NodeProcessed 172 ########################################3333 173 incoming_data=challenge.getData() 174 chal={} 175 data=base64.decodestring(incoming_data) 176 self.DEBUG('Got challenge:'+data,'ok') 177 for pair in data.split(','): 178 key,value=pair.split('=', 1) 179 if value[:1]=='"' and value[-1:]=='"': value=value[1:-1] 180 chal[key]=value 181 if chal.has_key('qop') and chal['qop']=='auth': 182 resp={} 183 resp['username']=self.username 184 resp['realm']=self._owner.Server 185 resp['nonce']=chal['nonce'] 186 cnonce='' 187 for i in range(7): 188 cnonce+=hex(int(random.random()*65536*4096))[2:] 189 resp['cnonce']=cnonce 190 resp['nc']=('00000001') 191 resp['qop']='auth' 192 resp['digest-uri']='xmpp/'+self._owner.Server 193 A1=C([H(C([resp['username'],resp['realm'],self.password])),resp['nonce'],resp['cnonce']]) 194 A2=C(['AUTHENTICATE',resp['digest-uri']]) 195 response= HH(C([HH(A1),resp['nonce'],resp['nc'],resp['cnonce'],resp['qop'],HH(A2)])) 196 resp['response']=response 197 resp['charset']='utf-8' 198 sasl_data='' 199 for key in ['charset','username','realm','nonce','nc','cnonce','digest-uri','response','qop']: 200 if key in ['nc','qop','response','charset']: sasl_data+="%s=%s,"%(key,resp[key]) 201 else: sasl_data+='%s="%s",'%(key,resp[key]) 202 ########################################3333 203 node=Node('response',attrs={'xmlns':NS_SASL},payload=[base64.encodestring(sasl_data[:-1]).replace('\r','').replace('\n','')]) 204 self._owner.send(node.__str__()) 205 elif chal.has_key('rspauth'): self._owner.send(Node('response',attrs={'xmlns':NS_SASL}).__str__()) 206 else: 207 self.startsasl='failure' 208 self.DEBUG('Failed SASL authentification: unknown challenge','error') 209 raise NodeProcessed
210
211 -class Bind(PlugIn):
212 """ Bind some JID to the current connection to allow router know of our location."""
213 - def __init__(self):
214 PlugIn.__init__(self) 215 self.DBG_LINE='bind' 216 self.bound=None
217
218 - def plugin(self,owner):
219 """ Start resource binding, if allowed at this time. Used internally. """ 220 if self._owner.Dispatcher.Stream.features: 221 try: self.FeaturesHandler(self._owner.Dispatcher,self._owner.Dispatcher.Stream.features) 222 except NodeProcessed: pass 223 else: self._owner.RegisterHandler('features',self.FeaturesHandler,xmlns=NS_STREAMS)
224
225 - def plugout(self):
226 """ Remove Bind handler from owner's dispatcher. Used internally. """ 227 self._owner.UnregisterHandler('features',self.FeaturesHandler,xmlns=NS_STREAMS)
228
229 - def FeaturesHandler(self,conn,feats):
230 """ Determine if server supports resource binding and set some internal attributes accordingly. """ 231 if not feats.getTag('bind',namespace=NS_BIND): 232 self.bound='failure' 233 self.DEBUG('Server does not requested binding.','error') 234 return 235 if feats.getTag('session',namespace=NS_SESSION): self.session=1 236 else: self.session=-1 237 self.bound=[]
238
239 - def Bind(self,resource=None):
240 """ Perform binding. Use provided resource name or random (if not provided). """ 241 while self.bound is None and self._owner.Process(1): pass 242 if resource: resource=[Node('resource',payload=[resource])] 243 else: resource=[] 244 resp=self._owner.SendAndWaitForResponse(Protocol('iq',typ='set',payload=[Node('bind',attrs={'xmlns':NS_BIND},payload=resource)])) 245 if isResultNode(resp): 246 self.bound.append(resp.getTag('bind').getTagData('jid')) 247 self.DEBUG('Successfully bound %s.'%self.bound[-1],'ok') 248 jid=JID(resp.getTag('bind').getTagData('jid')) 249 self._owner.User=jid.getNode() 250 self._owner.Resource=jid.getResource() 251 resp=self._owner.SendAndWaitForResponse(Protocol('iq',typ='set',payload=[Node('session',attrs={'xmlns':NS_SESSION})])) 252 if isResultNode(resp): 253 self.DEBUG('Successfully opened session.','ok') 254 self.session=1 255 return 'ok' 256 else: 257 self.DEBUG('Session open failed.','error') 258 self.session=0 259 elif resp: self.DEBUG('Binding failed: %s.'%resp.getTag('error'),'error') 260 else: 261 self.DEBUG('Binding failed: timeout expired.','error') 262 return ''
263
264 -class ComponentBind(PlugIn):
265 """ ComponentBind some JID to the current connection to allow router know of our location."""
266 - def __init__(self):
267 PlugIn.__init__(self) 268 self.DBG_LINE='bind' 269 self.bound=None 270 self.needsUnregister=None
271
272 - def plugin(self,owner):
273 """ Start resource binding, if allowed at this time. Used internally. """ 274 if self._owner.Dispatcher.Stream.features: 275 try: self.FeaturesHandler(self._owner.Dispatcher,self._owner.Dispatcher.Stream.features) 276 except NodeProcessed: pass 277 else: 278 self._owner.RegisterHandler('features',self.FeaturesHandler,xmlns=NS_STREAMS) 279 self.needsUnregister=1
280
281 - def plugout(self):
282 """ Remove ComponentBind handler from owner's dispatcher. Used internally. """ 283 if self.needsUnregister: 284 self._owner.UnregisterHandler('features',self.FeaturesHandler,xmlns=NS_STREAMS)
285
286 - def FeaturesHandler(self,conn,feats):
287 """ Determine if server supports resource binding and set some internal attributes accordingly. """ 288 if not feats.getTag('bind',namespace=NS_BIND): 289 self.bound='failure' 290 self.DEBUG('Server does not requested binding.','error') 291 return 292 if feats.getTag('session',namespace=NS_SESSION): self.session=1 293 else: self.session=-1 294 self.bound=[]
295
296 - def Bind(self,domain=None):
297 """ Perform binding. Use provided domain name (if not provided). """ 298 while self.bound is None and self._owner.Process(1): pass 299 resp=self._owner.SendAndWaitForResponse(Protocol('bind',attrs={'name':domain},xmlns=NS_COMPONENT_1)) 300 if resp and resp.getAttr('error'): 301 self.DEBUG('Binding failed: %s.'%resp.getAttr('error'),'error') 302 elif resp: 303 self.DEBUG('Successfully bound.','ok') 304 return 'ok' 305 else: 306 self.DEBUG('Binding failed: timeout expired.','error') 307 return ''
308