3 ## Copyright (C) 2003-2005 Alexey "Snake" Nezhdanov
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)
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.
15 # $Id: auth.py,v 1.41 2008/09/13 21:45:21 normanr Exp $
18 Provides library with all Non-SASL and SASL authentication mechanisms.
19 Can be used both for client and transport authentication.
22 from protocol import *
23 from client import PlugIn
24 import sha,base64,random,dispatcher,re
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)
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. """
36 self.DBG_LINE='gen_auth'
38 self.password=password
39 self.resource=resource
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')
50 iq=Iq(typ='set',node=resp)
51 query=iq.getTag('query')
52 query.setTagData('username',self.user)
53 query.setTagData('resource',self.resource)
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')
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)
69 self.DEBUG("Sequre methods unsupported, performing plain text authentication",'warn')
70 query.setTagData('password',self.password)
72 resp=owner.Dispatcher.SendAndWaitForResponse(iq)
73 if isResultNode(resp):
74 self.DEBUG('Sucessfully authenticated with remove host.','ok')
76 owner.Resource=self.resource
77 owner._registered_name=owner.User+'@'+owner.Server+'/'+owner.Resource
79 self.DEBUG('Authentication failed!','error')
81 def authComponent(self,owner):
82 """ Authenticate component. Send handshake stanza and wait for result. Returns "ok" on success. """
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')
89 owner._registered_name=self.user
90 if self.handshake+1: return 'ok'
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
98 """ Implements SASL authentication. """
99 def __init__(self,username,password):
100 PlugIn.__init__(self)
101 self.username=username
102 self.password=password
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
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)
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)
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')
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','')])
148 self.startsasl='failure'
149 self.DEBUG('I can only use DIGEST-MD5 and PLAIN mecanisms.','error')
151 self.startsasl='in-process'
152 self._owner.send(node.__str__())
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')
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
173 ########################################3333
174 incoming_data=challenge.getData()
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]
182 if chal.has_key('qop') and 'auth' in [x.strip() for x in chal['qop'].split(',')]:
184 resp['username']=self.username
185 resp['realm']=self._owner.Server
186 resp['nonce']=chal['nonce']
189 cnonce+=hex(int(random.random()*65536*4096))[2:]
190 resp['cnonce']=cnonce
191 resp['nc']=('00000001')
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'
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__())
208 self.startsasl='failure'
209 self.DEBUG('Failed SASL authentification: unknown challenge','error')
213 """ Bind some JID to the current connection to allow router know of our location."""
215 PlugIn.__init__(self)
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)
227 """ Remove Bind handler from owner's dispatcher. Used internally. """
228 self._owner.UnregisterHandler('features',self.FeaturesHandler,xmlns=NS_STREAMS)
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):
234 self.DEBUG('Server does not requested binding.','error')
236 if feats.getTag('session',namespace=NS_SESSION): self.session=1
237 else: self.session=-1
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])]
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')
258 self.DEBUG('Session open failed.','error')
260 elif resp: self.DEBUG('Binding failed: %s.'%resp.getTag('error'),'error')
262 self.DEBUG('Binding failed: timeout expired.','error')
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)
271 self.needsUnregister=None
274 def plugin(self,owner):
275 """ Start resource binding, if allowed at this time. Used internally. """
279 if self._owner.Dispatcher.Stream.features:
280 try: self.FeaturesHandler(self._owner.Dispatcher,self._owner.Dispatcher.Stream.features)
281 except NodeProcessed: pass
283 self._owner.RegisterHandler('features',self.FeaturesHandler,xmlns=NS_STREAMS)
284 self.needsUnregister=1
287 """ Remove ComponentBind handler from owner's dispatcher. Used internally. """
288 if self.needsUnregister:
289 self._owner.UnregisterHandler('features',self.FeaturesHandler,xmlns=NS_STREAMS)
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):
295 self.DEBUG('Server does not requested binding.','error')
297 if feats.getTag('session',namespace=NS_SESSION): self.session=1
298 else: self.session=-1
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
305 xmlns = NS_COMPONENT_1
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')
318 self.DEBUG('Successfully bound.','ok')
321 self.DEBUG('Binding failed: timeout expired.','error')
324 def BindHandler(self,conn,bind):
325 self.bindresponse = bind