Package pyxmpp :: Module streamsasl
[hide private]

Source Code for Module pyxmpp.streamsasl

  1  # 
  2  # (C) Copyright 2003-2006 Jacek Konieczny <jajcus@jajcus.net> 
  3  # 
  4  # This program is free software; you can redistribute it and/or modify 
  5  # it under the terms of the GNU Lesser General Public License Version 
  6  # 2.1 as published by the Free Software Foundation. 
  7  # 
  8  # This program is distributed in the hope that it will be useful, 
  9  # but WITHOUT ANY WARRANTY; without even the implied warranty of 
 10  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 11  # GNU Lesser General Public License for more details. 
 12  # 
 13  # You should have received a copy of the GNU Lesser General Public 
 14  # License along with this program; if not, write to the Free Software 
 15  # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 
 16  # 
 17  # pylint: disable-msg=W0201 
 18   
 19  """SASL support XMPP streams. 
 20   
 21  Normative reference: 
 22    - `RFC 3920 <http://www.ietf.org/rfc/rfc3920.txt>`__ 
 23  """ 
 24   
 25  __revision__="$Id: streamsasl.py 678 2008-08-08 11:22:14Z jajcus $" 
 26  __docformat__="restructuredtext en" 
 27   
 28  import base64 
 29  import logging 
 30   
 31  from pyxmpp.jid import JID 
 32  from pyxmpp import sasl 
 33  from pyxmpp.exceptions import StreamAuthenticationError, SASLNotAvailable, SASLMechanismNotAvailable, SASLAuthenticationFailed 
 34   
 35  SASL_NS="urn:ietf:params:xml:ns:xmpp-sasl" 
 36   
37 -class StreamSASLMixIn(sasl.PasswordManager):
38 """SASL authentication mix-in class for XMPP stream."""
39 - def __init__(self,sasl_mechanisms=()):
40 """Initialize Stream object 41 42 :Parameters: 43 - `sasl_mechanisms`: sequence of SASL mechanisms allowed for 44 authentication. Currently "PLAIN", "DIGEST-MD5" and "GSSAPI" are supported. 45 """ 46 sasl.PasswordManager.__init__(self) 47 if sasl_mechanisms: 48 self.sasl_mechanisms=sasl_mechanisms 49 else: 50 self.sasl_mechanisms=[] 51 self.__logger=logging.getLogger("pyxmpp.StreamSASLMixIn")
52
53 - def _reset_sasl(self):
54 """Reset `StreamSASLMixIn` object state making it ready to handle new 55 connections.""" 56 self.peer_sasl_mechanisms=None 57 self.authenticator=None
58
59 - def _make_stream_sasl_features(self,features):
60 """Add SASL features to the <features/> element of the stream. 61 62 [receving entity only] 63 64 :returns: update <features/> element node.""" 65 if self.sasl_mechanisms and not self.authenticated: 66 ml=features.newChild(None,"mechanisms",None) 67 ns=ml.newNs(SASL_NS,None) 68 ml.setNs(ns) 69 for m in self.sasl_mechanisms: 70 if m in sasl.all_mechanisms: 71 ml.newTextChild(None,"mechanism",m) 72 return features
73
74 - def _handle_sasl_features(self):
75 """Process incoming <stream:features/> element. 76 77 [initiating entity only] 78 79 The received features node is available in `self.features`.""" 80 ctxt = self.doc_in.xpathNewContext() 81 ctxt.setContextNode(self.features) 82 ctxt.xpathRegisterNs("sasl",SASL_NS) 83 try: 84 sasl_mechanisms_n=ctxt.xpathEval("sasl:mechanisms/sasl:mechanism") 85 finally: 86 ctxt.xpathFreeContext() 87 88 if sasl_mechanisms_n: 89 self.__logger.debug("SASL support found") 90 self.peer_sasl_mechanisms=[] 91 for n in sasl_mechanisms_n: 92 self.peer_sasl_mechanisms.append(n.getContent())
93
94 - def _process_node_sasl(self,xmlnode):
95 """Process incoming stream element. Pass it to _process_sasl_node 96 if it is in the SASL namespace. 97 98 :return: `True` when the node was recognized as a SASL element. 99 :returntype: `bool`""" 100 ns_uri=xmlnode.ns().getContent() 101 if ns_uri==SASL_NS: 102 self._process_sasl_node(xmlnode) 103 return True 104 return False
105
106 - def _process_sasl_node(self,xmlnode):
107 """Process stream element in the SASL namespace. 108 109 :Parameters: 110 - `xmlnode`: the XML node received 111 """ 112 if self.initiator: 113 if not self.authenticator: 114 self.__logger.debug("Unexpected SASL response: %r" % (xmlnode.serialize())) 115 ret=False 116 elif xmlnode.name=="challenge": 117 ret=self._process_sasl_challenge(xmlnode.getContent()) 118 elif xmlnode.name=="success": 119 ret=self._process_sasl_success(xmlnode.getContent()) 120 elif xmlnode.name=="failure": 121 ret=self._process_sasl_failure(xmlnode) 122 else: 123 self.__logger.debug("Unexpected SASL node: %r" % (xmlnode.serialize())) 124 ret=False 125 else: 126 if xmlnode.name=="auth": 127 mechanism=xmlnode.prop("mechanism") 128 ret=self._process_sasl_auth(mechanism,xmlnode.getContent()) 129 if xmlnode.name=="response": 130 ret=self._process_sasl_response(xmlnode.getContent()) 131 if xmlnode.name=="abort": 132 ret=self._process_sasl_abort() 133 else: 134 self.__logger.debug("Unexpected SASL node: %r" % (xmlnode.serialize())) 135 ret=False 136 return ret
137
138 - def _process_sasl_auth(self,mechanism,content):
139 """Process incoming <sasl:auth/> element. 140 141 [receiving entity only] 142 143 :Parameters: 144 - `mechanism`: mechanism choosen by the peer. 145 - `content`: optional "initial response" included in the element. 146 """ 147 if self.authenticator: 148 self.__logger.debug("Authentication already started") 149 return False 150 151 self.auth_method_used="sasl:"+mechanism 152 self.authenticator=sasl.server_authenticator_factory(mechanism,self) 153 154 r=self.authenticator.start(base64.decodestring(content)) 155 156 if isinstance(r,sasl.Success): 157 el_name="success" 158 content=r.base64() 159 elif isinstance(r,sasl.Challenge): 160 el_name="challenge" 161 content=r.base64() 162 else: 163 el_name="failure" 164 content=None 165 166 root=self.doc_out.getRootElement() 167 xmlnode=root.newChild(None,el_name,None) 168 ns=xmlnode.newNs(SASL_NS,None) 169 xmlnode.setNs(ns) 170 if content: 171 xmlnode.setContent(content) 172 if isinstance(r,sasl.Failure): 173 xmlnode.newChild(None,r.reason,None) 174 175 self._write_raw(xmlnode.serialize(encoding="UTF-8")) 176 xmlnode.unlinkNode() 177 xmlnode.freeNode() 178 179 if isinstance(r,sasl.Success): 180 if r.authzid: 181 self.peer=JID(r.authzid) 182 else: 183 self.peer=JID(r.username,self.me.domain) 184 self.peer_authenticated=1 185 self.state_change("authenticated",self.peer) 186 self._post_auth() 187 188 if isinstance(r,sasl.Failure): 189 raise SASLAuthenticationFailed,"SASL authentication failed" 190 191 return True
192
193 - def _process_sasl_challenge(self,content):
194 """Process incoming <sasl:challenge/> element. 195 196 [initiating entity only] 197 198 :Parameters: 199 - `content`: the challenge data received (Base64-encoded). 200 """ 201 if not self.authenticator: 202 self.__logger.debug("Unexpected SASL challenge") 203 return False 204 205 r=self.authenticator.challenge(base64.decodestring(content)) 206 if isinstance(r,sasl.Response): 207 el_name="response" 208 content=r.base64() 209 else: 210 el_name="abort" 211 content=None 212 213 root=self.doc_out.getRootElement() 214 xmlnode=root.newChild(None,el_name,None) 215 ns=xmlnode.newNs(SASL_NS,None) 216 xmlnode.setNs(ns) 217 if content: 218 xmlnode.setContent(content) 219 220 self._write_raw(xmlnode.serialize(encoding="UTF-8")) 221 xmlnode.unlinkNode() 222 xmlnode.freeNode() 223 224 if isinstance(r,sasl.Failure): 225 raise SASLAuthenticationFailed,"SASL authentication failed" 226 227 return True
228
229 - def _process_sasl_response(self,content):
230 """Process incoming <sasl:response/> element. 231 232 [receiving entity only] 233 234 :Parameters: 235 - `content`: the response data received (Base64-encoded). 236 """ 237 if not self.authenticator: 238 self.__logger.debug("Unexpected SASL response") 239 return 0 240 241 r=self.authenticator.response(base64.decodestring(content)) 242 if isinstance(r,sasl.Success): 243 el_name="success" 244 content=r.base64() 245 elif isinstance(r,sasl.Challenge): 246 el_name="challenge" 247 content=r.base64() 248 else: 249 el_name="failure" 250 content=None 251 252 root=self.doc_out.getRootElement() 253 xmlnode=root.newChild(None,el_name,None) 254 ns=xmlnode.newNs(SASL_NS,None) 255 xmlnode.setNs(ns) 256 if content: 257 xmlnode.setContent(content) 258 if isinstance(r,sasl.Failure): 259 xmlnode.newChild(None,r.reason,None) 260 261 self._write_raw(xmlnode.serialize(encoding="UTF-8")) 262 xmlnode.unlinkNode() 263 xmlnode.freeNode() 264 265 if isinstance(r,sasl.Success): 266 authzid=r.authzid 267 if authzid: 268 self.peer=JID(r.authzid) 269 else: 270 self.peer=JID(r.username,self.me.domain) 271 self.peer_authenticated=1 272 self._restart_stream() 273 self.state_change("authenticated",self.peer) 274 self._post_auth() 275 276 if isinstance(r,sasl.Failure): 277 raise SASLAuthenticationFailed,"SASL authentication failed" 278 279 return 1
280
281 - def _process_sasl_success(self,content):
282 """Process incoming <sasl:success/> element. 283 284 [initiating entity only] 285 286 :Parameters: 287 - `content`: the "additional data with success" received (Base64-encoded). 288 """ 289 if not self.authenticator: 290 self.__logger.debug("Unexpected SASL response") 291 return False 292 293 r=self.authenticator.finish(base64.decodestring(content)) 294 if isinstance(r,sasl.Success): 295 self.__logger.debug("SASL authentication succeeded") 296 if r.authzid: 297 self.me=JID(r.authzid) 298 else: 299 self.me=self.me 300 self.authenticated=1 301 self._restart_stream() 302 self.state_change("authenticated",self.me) 303 self._post_auth() 304 else: 305 self.__logger.debug("SASL authentication failed") 306 raise SASLAuthenticationFailed,"Additional success data procesing failed" 307 return True
308
309 - def _process_sasl_failure(self,xmlnode):
310 """Process incoming <sasl:failure/> element. 311 312 [initiating entity only] 313 314 :Parameters: 315 - `xmlnode`: the XML node received. 316 """ 317 if not self.authenticator: 318 self.__logger.debug("Unexpected SASL response") 319 return False 320 321 self.__logger.debug("SASL authentication failed: %r" % (xmlnode.serialize(),)) 322 raise SASLAuthenticationFailed,"SASL authentication failed"
323
324 - def _process_sasl_abort(self):
325 """Process incoming <sasl:abort/> element. 326 327 [receiving entity only]""" 328 if not self.authenticator: 329 self.__logger.debug("Unexpected SASL response") 330 return False 331 332 self.authenticator=None 333 self.__logger.debug("SASL authentication aborted") 334 return True
335
336 - def _sasl_authenticate(self,username,authzid,mechanism=None):
337 """Start SASL authentication process. 338 339 [initiating entity only] 340 341 :Parameters: 342 - `username`: user name. 343 - `authzid`: authorization ID. 344 - `mechanism`: SASL mechanism to use.""" 345 if not self.initiator: 346 raise SASLAuthenticationFailed,"Only initiating entity start SASL authentication" 347 while not self.features: 348 self.__logger.debug("Waiting for features") 349 self._read() 350 if not self.peer_sasl_mechanisms: 351 raise SASLNotAvailable,"Peer doesn't support SASL" 352 353 if not mechanism: 354 mechanism=None 355 for m in self.sasl_mechanisms: 356 if m in self.peer_sasl_mechanisms: 357 mechanism=m 358 break 359 if not mechanism: 360 raise SASLMechanismNotAvailable,"Peer doesn't support any of our SASL mechanisms" 361 self.__logger.debug("Our mechanism: %r" % (mechanism,)) 362 else: 363 if mechanism not in self.peer_sasl_mechanisms: 364 raise SASLMechanismNotAvailable,"%s is not available" % (mechanism,) 365 366 self.auth_method_used="sasl:"+mechanism 367 368 self.authenticator=sasl.client_authenticator_factory(mechanism,self) 369 370 initial_response=self.authenticator.start(username,authzid) 371 if not isinstance(initial_response,sasl.Response): 372 raise SASLAuthenticationFailed,"SASL initiation failed" 373 374 root=self.doc_out.getRootElement() 375 xmlnode=root.newChild(None,"auth",None) 376 ns=xmlnode.newNs(SASL_NS,None) 377 xmlnode.setNs(ns) 378 xmlnode.setProp("mechanism",mechanism) 379 if initial_response.data: 380 xmlnode.setContent(initial_response.base64()) 381 382 self._write_raw(xmlnode.serialize(encoding="UTF-8")) 383 xmlnode.unlinkNode() 384 xmlnode.freeNode()
385 386 # vi: sts=4 et sw=4 387