1
2
3
4
5
6
7 from xmpp import *
8 try:
9 from xmppd.xmppd import *
10 except:
11 from xmppd import *
12 import socket,thread
13 from tlslite.api import *
14 import hashlib
15
17 """ 3. <features/>
18 4. <starttls/>
19 5. <proceed/> / <failure/>
20 -- NEW STREAM / connection close --
21 """
22 NS=NS_TLS
28
51
55
83
103
115
116 import hashlib,base64,random
117
118 -def HH(some): return hashlib.md5(some).hexdigest()
119 -def H(some): return hashlib.md5(some).digest()
120 -def C(some): return ':'.join(some)
121
123 NS=NS_SASL
124 """ 3. <features/>
125 4. <auth/>
126 5. <challenge/> / <failure/>
127 6. <response/>
128 7. <challenge/> / <failure/>
129 8. <response/> / <abort/>
130 9. <success/> / <failure/>
131 feature SASL, unfeature TLS
132 -- NEW STREAM on success --
133
134 What to do on failure (remote server rejected us)?
135 Probably drop the stream, mark this server as unreachable for several hours and notify admin?
136 If client supplied wrong credentials allow him to retry (configurable number of times).
137 """
138
143
144 """ def startauth(self,session,username,password):
145 session.username=username
146 session.password=password
147 if session.Stream.features:
148 try: self.FeaturesHandler(session,session.Stream.features)
149 except NodeProcessed: pass
150
151 def FeaturesHandler(self,session,feats):
152 if session.feature_in_process: return # some other feature is already underway
153 if not session.__dict__.has_key('username'): return
154 if not feats.getTag('mechanisms',namespace=NS_SASL):
155 session.unfeature(NS_SASL)
156 self.DEBUG('SASL not supported by server','error')
157 return
158 mecs=[]
159 for mec in feats.getTag('mechanisms',namespace=NS_SASL).getTags('mechanism'):
160 mecs.append(mec.getData())
161 if "DIGEST-MD5" in mecs:
162 node=Node('auth',attrs={'xmlns':NS_SASL,'mechanism':'DIGEST-MD5'})
163 elif "PLAIN" in mecs:
164 sasl_data='%s\x00%s\x00%s'%(self.username+'@'+session.peer,self.username,self.password)
165 node=Node('auth',attrs={'xmlns':NS_SASL,'mechanism':'PLAIN'},payload=[base64.encodestring(sasl_data)])
166 else:
167 session.startsasl='failure'
168 self.DEBUG('I can only use DIGEST-MD5 and PLAIN mecanisms.','error')
169 return
170 session.startsasl='in-process'
171 session.send(node)
172 raise NodeProcessed
173 """
185
191
193 """simple username: servername _or_ node@servername : 6.1 (6)
194 """
195 if NS_SASL in session.features:
196 self.DEBUG('Already authorized. Ignoring SASL stanza.','error')
197 raise NodeProcessed
198 if not session.__dict__.has_key('sasl'):
199 session.sasl={'retries':3}
200 if not session.sasl.has_key('next'):
201 session.sasl={'retries':session.sasl['retries']}
202 if session.TYP=='server': session.sasl['next']=['auth']
203 else: session.sasl['next']=['challenge','success','failure']
204 if stanza.getName() not in session.sasl['next']:
205
206 session.terminate_stream(STREAM_BAD_REQUEST)
207 raise NodeProcessed
208
209 try: data=base64.decodestring(stanza.getData())
210 except:
211 session.terminate_stream(STREAM_BAD_REQUEST)
212 raise NodeProcessed
213 self.DEBUG('Got challenge: '+`data`,'ok')
214 for pair in data.split(','):
215 if pair.find('=')==-1:
216 session.sasl['otherdata']=pair
217 continue
218 key,value=pair.split('=',1)
219 if value[:1]=='"' and value[-1:]=='"': value=value[1:-1]
220 if key in ['qop','username','realm','nonce','cnonce','digest-uri',
221 'nc','response','charset','rspauth','algorithm']:
222 chal[key]=value
223
224 if stanza.getName()=='auth':
225 session.sasl['next']=['response','abort','auth']
226
227 mec=stanza['mechanism']
228 session.sasl['mechanism']=mec
229 if mec=='PLAIN':
230 """The mechanism consists of a single message from the client to the
231 server. The client sends the authorization identity (identity to
232 login as), followed by a NUL (U+0000) character, followed by the
233 authentication identity (identity whose password will be used),
234 followed by a NUL (U+0000) character, followed by the clear-text
235 password."""
236 if session.sasl.has_key('otherdata'): pack=session.sasl['otherdata'].split('\000')
237 else: pack=[]
238 authzid=session.peer
239 if len(pack)<>3: res=0
240 else:
241 authzid, authcid, passwd = pack
242 if not authzid:
243 authzid=authcid
244 if session.xmlns==NS_CLIENT: authzid+='@'+session.ourname
245 username,domain=(authzid.split('@',1)+[''])[:2]
246 res = ( passwd == self._owner.AUTH.getpassword(username, domain) )
247 if res: self.commit_auth(session,authzid)
248 else: self.reject_auth(session,authzid)
249 elif mec=='DIGEST-MD5': pass
250 else:
251 session.terminate_stream(Node('failure',{'xmlns':NS_SASL},[Node('invalid-mechanism')]))
252 raise NodeProcessed
253 """elif stanza.getName()=='challenge':
254 session.sasl['next']=['challenge','success','failure']
255 # DIGEST-MD5 only
256 if chal.has_key('qop') and chal['qop']=='auth':
257 resp={}
258 resp['username']=self.username
259 resp['realm']=self._owner.Server
260 resp['nonce']=chal['nonce']
261 cnonce=''
262 for i in range(7):
263 cnonce+=hex(int(random.random()*65536*4096))[2:]
264 resp['cnonce']=cnonce
265 resp['nc']=('00000001')
266 resp['qop']='auth'
267 resp['digest-uri']='xmpp/'
268 A1=C([H(C([resp['username'],resp['realm'],self.password])),resp['nonce'],resp['cnonce']])
269 A2=C(['AUTHENTICATE',resp['digest-uri']])
270 response= HH(C([HH(A1),resp['nonce'],resp['nc'],resp['cnonce'],resp['qop'],HH(A2)]))
271 resp['response']=response
272 resp['charset']='utf-8'
273 sasl_data=''
274 for key in ['charset','username','realm','nonce','nc','cnonce','digest-uri','response','qop']:
275 if key in ['nc','qop','response','charset']: sasl_data+="%s=%s,"%(key,resp[key])
276 else: sasl_data+='%s="%s",'%(key,resp[key])
277 node=Node('response',attrs={'xmlns':NS_SASL},payload=[base64.encodestring(sasl_data[:-1]).replace('\n','')])
278 self._owner.send(node)
279 elif chal.has_key('rspauth'): self._owner.send(Node('response',attrs={'xmlns':NS_SASL}))
280 """
281 elif stanza.getName()=='response':
282 session.sasl['next']=['response','abort']
283 elif stanza.getName()=='abort':
284 session.sasl['next']=['auth']
285 elif stanza.getName()=='success':
286 session.sasl['next']=[]
287 session.startsasl='success'
288 self.DEBUG('Successfully authenticated with remote server.','ok')
289 session.StartStream()
290 elif stanza.getName()=='failure':
291 session.sasl['next']=['challenge','success','failure']
292 session.startsasl='failure'
293 try: reason=challenge.getChildren()[0]
294 except: reason=challenge
295 self.DEBUG('Failed SASL authentification: %s'%reason,'error')
296 raise NodeProcessed
297
322
340
341
343 NS=NS_COMPONENT_ACCEPT
344
357
358
362
363
364
365
366
368 self.DEBUG('Handshake handler called','info')
369 if session._session_state >= SESSION_AUTHED:
370 session.terminate_stream(STREAM_NOT_AUTHORIZED)
371 self.DEBUG('Session already authed','warn')
372 return
373
374 handshake = str(stanza.getData())
375 for k,v in self.server.components.items():
376 try:
377 truehs = hashlib.sha1(str(session.ID)+v['password']).hexdigest()
378 if handshake == truehs:
379
380 session.peer=v['jid'].lower()
381 self.server.activatesession(session, v['jid'])
382 session.set_session_state(SESSION_AUTHED)
383 session.set_session_state(SESSION_OPENED)
384 node , dom = v["jid"].split(".",1)
385 self.server.DB.register_user(dom, node, v['password'], v['name'])
386 session.send(Node('handshake'))
387 self.DEBUG('Component %s authenticated'%(v['jid']), 'ok')
388 return
389 except:
390 self.DEBUG('Error authenticating handshake', 'error')
391 self.DEBUG("Unknown component","warn")
392 self.DEBUG("Components: "+str(self.server.components),"warn")
393 return
394