Package spade :: Package xmppd :: Package modules :: Module stream
[hide private]
[frames] | no frames]

Source Code for Module spade.xmppd.modules.stream

  1  # Distributed under the terms of GPL version 2 or any later 
  2  # Copyright (C) Kristopher Tate / BlueBridge Technologies Group 2005 
  3  # Copyright (C) Alexey Nezhdanov 2004 
  4   
  5  # Stream-level features for xmppd.py 
  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   
16 -class TLS(PlugIn):
17 """ 3. <features/> 18 4. <starttls/> 19 5. <proceed/> / <failure/> 20 -- NEW STREAM / connection close -- 21 """ 22 NS=NS_TLS
23 - def plugin(self,server):
24 server.Dispatcher.RegisterHandler('starttls',self.starttlsHandler,xmlns=NS_TLS) 25 server.Dispatcher.RegisterHandler('proceed',self.proceedfailureHandler,xmlns=NS_TLS) 26 server.Dispatcher.RegisterHandler('failure',self.proceedfailureHandler,xmlns=NS_TLS) 27 server.Dispatcher.RegisterHandler('features',self.FeaturesHandler,xmlns=NS_STREAMS)
28
29 - def starttlsHandler(self,session,stanza):
30 #print stanza 31 if 'tls' in session.features: 32 session.send(Node('failure',{'xmlns':NS_TLS})) 33 self.DEBUG('TLS startup failure: already started.','error') 34 session.unfeature(NS_TLS) 35 raise NodeProcessed 36 if self._owner.__dict__.has_key('sslcertfile'): certfile=self._owner.sslcertfile 37 else: certfile=None 38 if self._owner.__dict__.has_key('sslkeyfile'): keyfile=self._owner.sslkeyfile 39 else: keyfile=certfile 40 try: open(certfile) ; open(keyfile) 41 except: certfile=None 42 if not certfile or not keyfile: 43 session.send(Node('failure',{'xmlns':NS_TLS})) 44 self.DEBUG('TLS startup failure: can\'t find SSL cert/key file[s].','error') 45 session.unfeature(NS_TLS) # do not declare TLS anymore 46 session.stop_feature(NS_TLS) # TLS finished, let another features start 47 else: 48 session.send(Node('proceed',{'xmlns':NS_TLS})) 49 self.startservertls(session) 50 raise NodeProcessed
51
52 - def startservertls(self,session):
55
56 - def _startservertls(self,session):
57 try: 58 cert=open(self._owner.sslcertfile).read() 59 key=open(self._owner.sslkeyfile).read() 60 except: 61 session.unfeature(NS_TLS) 62 session.terminate_stream(STREAM_INTERNAL_SERVER_ERROR) 63 return 64 65 x509 = X509() 66 x509.parse(cert) 67 certChain = X509CertChain([x509]) 68 privateKey = parsePEMKey(key, private=True) 69 connection = TLSConnection(session._sock) 70 try: connection.handshakeServer(certChain=certChain, privateKey=privateKey, reqCert=False) 71 except: 72 session.terminate_stream(unregister=0) 73 return 74 75 session._sslObj = connection 76 session._recv = connection.read 77 session._send = connection.send 78 79 session.feature(NS_TLS) # TLS enabled, do not declare it anymore 80 session.stop_feature(NS_TLS) # TLS finished, let another features start 81 session.StartStream() 82 session._owner.registersession(session)
83
84 - def proceedfailureHandler(self,session,stanza):
85 if stanza.getName()<>'proceed': 86 self.DEBUG('TLS can not be started. Giving up.','error') 87 session.unfeature(NS_TLS) 88 raise NodeProcessed 89 self.DEBUG('Starting client-mode TLS.','ok') 90 try: session._sslObj = socket.ssl(session._sock, None, None) 91 except: 92 session.set_socket_state(SOCKET_DEAD) 93 self.DEBUG('TLS failed. Terminating session','error') 94 session.terminate_stream() 95 raise NodeProcessed 96 session._recv = session._sslObj.read 97 session._send = session._sslObj.write 98 99 session.feature(NS_TLS) # TLS enabled, do not declare it anymore 100 session.stop_feature(NS_TLS) # TLS finished, let another features start 101 session.StartStream() 102 raise NodeProcessed
103
104 - def FeaturesHandler(self,session,stanza):
105 if NS_TLS in session.features: return # already started. do nothing 106 if session.feature_in_process: return # some other feature is already underway 107 if not stanza.getTag('starttls',namespace=NS_TLS): 108 self.DEBUG("TLS unsupported by remote server; Doing nothing -- Prob. jabber.org?",'warn') 109 return 110 else: 111 self.DEBUG("TLS supported by remote server. Requesting TLS start.",'ok') 112 session.start_feature(NS_TLS) 113 session.send(Node('starttls',{'xmlns':NS_TLS})) 114 raise NodeProcessed
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
122 -class SASL(PlugIn):
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
139 - def plugin(self,server):
140 server.Dispatcher.RegisterNamespaceHandler(NS_SASL,self.SASLHandler) 141 # server.Dispatcher.RegisterHandler('features',self.FeaturesHandler,xmlns=NS_STREAMS) 142 self.mechanisms=['PLAIN']#,'DIGEST-MD5'] # for announce in <features/> tag
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 """
174 - def commit_auth(self,session,authzid):
175 session.send(Node('success',{'xmlns':NS_SASL})) 176 session.feature(NS_SASL) 177 session.unfeature(NS_TLS) 178 session.sasl['next']=[] 179 session.StartStream() 180 session.peer=authzid.lower() 181 if session.xmlns==NS_CLIENT: session.set_session_state(SESSION_AUTHED) 182 else: session.set_session_state(SESSION_BOUND) 183 self.DEBUG('Peer %s successfully authenticated'%authzid,'ok') 184 self._owner.activatesession(session,authzid)
185
186 - def reject_auth(self,session,authzid='unknown'):
187 session.send(Node('failure',{'xmlns':NS_SASL},[Node('not-authorized')])) 188 session.sasl['retries']=session.sasl['retries']-1 189 if session.sasl['retries']<=0: session.terminate_stream() 190 self.DEBUG('Peer %s failed to authenticate'%authzid,'error')
191
192 - def SASLHandler(self,session,stanza):
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 # screwed SASL implementation on the other side. terminating stream 206 session.terminate_stream(STREAM_BAD_REQUEST) 207 raise NodeProcessed 208 #=================== preparation =============================================== 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 #=================== SASL begin =============================================== 224 if stanza.getName()=='auth': 225 session.sasl['next']=['response','abort','auth'] 226 # client requested some mechanism. May be ever provided credentials already. 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
298 -class Bind(PlugIn):
299 NS=NS_BIND
300 - def plugin(self,server):
301 server.Dispatcher.RegisterHandler('iq',self.bindHandler,typ='set',ns=NS_BIND,xmlns=NS_CLIENT)
302
303 - def bindHandler(self,session,stanza):
304 if session.xmlns<>NS_CLIENT or session.__dict__.has_key('resource'): 305 session.send(Error(stanza,ERR_SERVICE_UNAVAILABLE)) 306 else: 307 if session._session_state<SESSION_AUTHED: 308 session.terminate_stream(STREAM_NOT_AUTHORIZED) 309 raise NodeProcessed 310 resource=stanza.getTag('bind',namespace=NS_BIND).T.resource.getData() 311 if not resource: resource=session.ID 312 fulljid="%s/%s"%(session.peer,resource) 313 session.peer=fulljid 314 s=self._owner.deactivatesession(fulljid) 315 if s: s.terminate_stream(STREAM_CONFLICT) 316 rep=stanza.buildReply('result') 317 rep.NT.bind.setNamespace(NS_BIND) 318 rep.T.bind.NT.jid=fulljid 319 session.send(rep) 320 session.set_session_state(SESSION_BOUND) 321 raise NodeProcessed
322
323 -class Session(PlugIn):
324 NS=NS_SESSION
325 - def plugin(self,server):
326 server.Dispatcher.RegisterHandler('iq',self.sessionHandler,typ='set',ns=NS_SESSION,xmlns=NS_CLIENT)
327
328 - def sessionHandler(self,session,stanza):
329 if session._session_state<SESSION_AUTHED: 330 session.terminate_stream(STREAM_NOT_AUTHORIZED) 331 raise NodeProcessed 332 if session.xmlns<>NS_CLIENT \ 333 or session._session_state<SESSION_BOUND \ 334 or self._owner.getsession(session.peer)==session: 335 session.send(Error(stanza,ERR_SERVICE_UNAVAILABLE)) 336 else: 337 session.set_session_state(SESSION_OPENED) 338 session.send(stanza.buildReply('result')) 339 raise NodeProcessed
340 341
342 -class Handshake(PlugIn):
343 NS=NS_COMPONENT_ACCEPT 344
345 - def namespaceChangerAndRedirector(self,session,stanza):
346 if stanza.getName() == "handshake": 347 self.handshakeHandler(session, stanza) 348 if stanza.getName() in ['message', 'iq']: 349 self.DEBUG("Redirecting stanza %s to router"%(stanza),"info") 350 stanza.setNamespace(NS_CLIENT) 351 self.server.Router.routerHandler(session,stanza) 352 elif stanza.getName() == "presence": 353 self.DEBUG("Redirecting stanza %s to router"%(stanza),"info") 354 stanza.setNamespace(NS_CLIENT) 355 self.server.Router.presenceHandler(session,stanza) 356 return
357 358
359 - def plugin(self,server):
362 #server.Dispatcher.RegisterHandler('handshake',self.handshakeHandler,xmlns=NS_COMPONENT_ACCEPT) 363 #server.Dispatcher.RegisterHandler('presence',self.namespaceChangerAndRedirector,xmlns=NS_COMPONENT_ACCEPT) 364 #server.Dispatcher.RegisterHandler('message',self.namespaceChangerAndRedirector,xmlns=NS_COMPONENT_ACCEPT) 365 #server.Dispatcher.RegisterHandler('iq',self.namespaceChangerAndRedirector,xmlns=NS_COMPONENT_ACCEPT) 366
367 - def handshakeHandler(self, session, stanza):
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 # We have a match!! It's THIS component!! 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