1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
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
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
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
93 """ Handler for registering in dispatcher for accepting transport authentication. """
94 if stanza.getName()=='handshake': self.handshake=1
95 else: self.handshake=-1
96
98 """ Implements SASL authentication. """
100 PlugIn.__init__(self)
101 self.username=username
102 self.password=password
103
110
120
127
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
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
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
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
213 """ Bind some JID to the current connection to allow router know of our location."""
215 PlugIn.__init__(self)
216 self.DBG_LINE='bind'
217 self.bound=None
218
225
229
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
266 """ ComponentBind some JID to the current connection to allow router know of our location."""
268 PlugIn.__init__(self)
269 self.DBG_LINE='bind'
270 self.bound=None
271 self.needsUnregister=None
272 self.sasl = sasl
273
285
290
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
325 self.bindresponse = bind
326 pass
327