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