1   
  2   
  3   
  4   
  5   
  6   
  7   
  8   
  9   
 10   
 11   
 12   
 13   
 14   
 15   
 16   
 17   
 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   
 38      """SASL authentication mix-in class for XMPP stream.""" 
 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   
 54          """Reset `StreamSASLMixIn` object state making it ready to handle new 
 55          connections.""" 
 56          self.peer_sasl_mechanisms=None 
 57          self.authenticator=None 
  58   
 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   
 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   
 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   
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   
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   
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   
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   
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   
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   
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   
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   
387