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.38 2007/08/28 10:03:33 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 "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
)])
146 self
.startsasl
='failure'
147 self
.DEBUG('I can only use DIGEST-MD5 and PLAIN mecanisms.','error')
149 self
.startsasl
='in-process'
150 self
._owner
.send(node
.__str
__())
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')
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
171 ########################################3333
172 incoming_data
=challenge
.getData()
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]
180 if chal
.has_key('qop') and 'auth' in chal
['qop'].split(','):
182 resp
['username']=self
.username
183 resp
['realm']=self
._owner
.Server
184 resp
['nonce']=chal
['nonce']
187 cnonce
+=hex(int(random
.random()*65536*4096))[2:]
188 resp
['cnonce']=cnonce
189 resp
['nc']=('00000001')
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'
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
__())
206 self
.startsasl
='failure'
207 self
.DEBUG('Failed SASL authentification: unknown challenge','error')
211 """ Bind some JID to the current connection to allow router know of our location."""
213 PlugIn
.__init
__(self
)
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
)
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
):
232 self
.DEBUG('Server does not requested binding.','error')
234 if feats
.getTag('session',namespace
=NS_SESSION
): self
.session
=1
235 else: self
.session
=-1
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
])]
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')
256 self
.DEBUG('Session open failed.','error')
258 elif resp
: self
.DEBUG('Binding failed: %s.'%resp
.getTag('error'),'error')
260 self
.DEBUG('Binding failed: timeout expired.','error')
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
)
269 self
.needsUnregister
=None
272 def plugin(self
,owner
):
273 """ Start resource binding, if allowed at this time. Used internally. """
277 if self
._owner
.Dispatcher
.Stream
.features
:
278 try: self
.FeaturesHandler(self
._owner
.Dispatcher
,self
._owner
.Dispatcher
.Stream
.features
)
279 except NodeProcessed
: pass
281 self
._owner
.RegisterHandler('features',self
.FeaturesHandler
,xmlns
=NS_STREAMS
)
282 self
.needsUnregister
=1
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
):
293 self
.DEBUG('Server does not requested binding.','error')
295 if feats
.getTag('session',namespace
=NS_SESSION
): self
.session
=1
296 else: self
.session
=-1
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
303 xmlns
= NS_COMPONENT_1
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')
316 self
.DEBUG('Successfully bound.','ok')
319 self
.DEBUG('Binding failed: timeout expired.','error')
322 def BindHandler(self
,conn
,bind
):
323 self
.bindresponse
= bind