Merge branch 'master' into mutagen-branch
[pyTivo/wmcbrine.git] / xmpp / auth.py
blob93841c7ff344087ad81e8d80634fa8e12d3fe90f
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.
15 # $Id: auth.py,v 1.38 2007/08/28 10:03:33 normanr Exp $
17 """
18 Provides library with all Non-SASL and SASL authentication mechanisms.
19 Can be used both for client and transport authentication.
20 """
22 from protocol import *
23 from client import PlugIn
24 import sha,base64,random,dispatcher,re
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)
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
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)
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')
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'
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
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
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
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)
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)
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
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
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
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)
224 def plugout(self):
225 """ Remove Bind handler from owner's dispatcher. Used internally. """
226 self._owner.UnregisterHandler('features',self.FeaturesHandler,xmlns=NS_STREAMS)
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=[]
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 ''
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
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
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)
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=[]
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 ''
322 def BindHandler(self,conn,bind):
323 self.bindresponse = bind
324 pass