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