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 __docformat__="restructuredtext en"
26
27 import base64
28 import logging
29
30 from pyxmpp.jid import JID
31 from pyxmpp import sasl
32 from pyxmpp.exceptions import StreamAuthenticationError, SASLNotAvailable, SASLMechanismNotAvailable, SASLAuthenticationFailed
33
34 SASL_NS="urn:ietf:params:xml:ns:xmpp-sasl"
35
37 """SASL authentication mix-in class for XMPP stream."""
39 """Initialize Stream object
40
41 :Parameters:
42 - `sasl_mechanisms`: sequence of SASL mechanisms allowed for
43 authentication. Currently "PLAIN", "DIGEST-MD5" and "GSSAPI" are supported.
44 """
45 sasl.PasswordManager.__init__(self)
46 if sasl_mechanisms:
47 self.sasl_mechanisms=sasl_mechanisms
48 else:
49 self.sasl_mechanisms=[]
50 self.__logger=logging.getLogger("pyxmpp.StreamSASLMixIn")
51
53 """Reset `StreamSASLMixIn` object state making it ready to handle new
54 connections."""
55 self.peer_sasl_mechanisms=None
56 self.authenticator=None
57
59 """Add SASL features to the <features/> element of the stream.
60
61 [receving entity only]
62
63 :returns: update <features/> element node."""
64 if self.sasl_mechanisms and not self.authenticated:
65 ml=features.newChild(None,"mechanisms",None)
66 ns=ml.newNs(SASL_NS,None)
67 ml.setNs(ns)
68 for m in self.sasl_mechanisms:
69 if m in sasl.all_mechanisms:
70 ml.newTextChild(None,"mechanism",m)
71 return features
72
74 """Process incoming <stream:features/> element.
75
76 [initiating entity only]
77
78 The received features node is available in `self.features`."""
79 ctxt = self.doc_in.xpathNewContext()
80 ctxt.setContextNode(self.features)
81 ctxt.xpathRegisterNs("sasl",SASL_NS)
82 try:
83 sasl_mechanisms_n=ctxt.xpathEval("sasl:mechanisms/sasl:mechanism")
84 finally:
85 ctxt.xpathFreeContext()
86
87 if sasl_mechanisms_n:
88 self.__logger.debug("SASL support found")
89 self.peer_sasl_mechanisms=[]
90 for n in sasl_mechanisms_n:
91 self.peer_sasl_mechanisms.append(n.getContent())
92
94 """Process incoming stream element. Pass it to _process_sasl_node
95 if it is in the SASL namespace.
96
97 :return: `True` when the node was recognized as a SASL element.
98 :returntype: `bool`"""
99 ns_uri=xmlnode.ns().getContent()
100 if ns_uri==SASL_NS:
101 self._process_sasl_node(xmlnode)
102 return True
103 return False
104
106 """Process stream element in the SASL namespace.
107
108 :Parameters:
109 - `xmlnode`: the XML node received
110 """
111 if self.initiator:
112 if not self.authenticator:
113 self.__logger.debug("Unexpected SASL response: %r" % (xmlnode.serialize()))
114 ret=False
115 elif xmlnode.name=="challenge":
116 ret=self._process_sasl_challenge(xmlnode.getContent())
117 elif xmlnode.name=="success":
118 ret=self._process_sasl_success(xmlnode.getContent())
119 elif xmlnode.name=="failure":
120 ret=self._process_sasl_failure(xmlnode)
121 else:
122 self.__logger.debug("Unexpected SASL node: %r" % (xmlnode.serialize()))
123 ret=False
124 else:
125 if xmlnode.name=="auth":
126 mechanism=xmlnode.prop("mechanism")
127 ret=self._process_sasl_auth(mechanism,xmlnode.getContent())
128 if xmlnode.name=="response":
129 ret=self._process_sasl_response(xmlnode.getContent())
130 if xmlnode.name=="abort":
131 ret=self._process_sasl_abort()
132 else:
133 self.__logger.debug("Unexpected SASL node: %r" % (xmlnode.serialize()))
134 ret=False
135 return ret
136
138 """Process incoming <sasl:auth/> element.
139
140 [receiving entity only]
141
142 :Parameters:
143 - `mechanism`: mechanism choosen by the peer.
144 - `content`: optional "initial response" included in the element.
145 """
146 if self.authenticator:
147 self.__logger.debug("Authentication already started")
148 return False
149
150 self.auth_method_used="sasl:"+mechanism
151 self.authenticator=sasl.server_authenticator_factory(mechanism,self)
152
153 r=self.authenticator.start(base64.decodestring(content))
154
155 if isinstance(r,sasl.Success):
156 el_name="success"
157 content=r.base64()
158 elif isinstance(r,sasl.Challenge):
159 el_name="challenge"
160 content=r.base64()
161 else:
162 el_name="failure"
163 content=None
164
165 root=self.doc_out.getRootElement()
166 xmlnode=root.newChild(None,el_name,None)
167 ns=xmlnode.newNs(SASL_NS,None)
168 xmlnode.setNs(ns)
169 if content:
170 xmlnode.setContent(content)
171 if isinstance(r,sasl.Failure):
172 xmlnode.newChild(None,r.reason,None)
173
174 self._write_raw(xmlnode.serialize(encoding="UTF-8"))
175 xmlnode.unlinkNode()
176 xmlnode.freeNode()
177
178 if isinstance(r,sasl.Success):
179 if r.authzid:
180 self.peer=JID(r.authzid)
181 else:
182 self.peer=JID(r.username,self.me.domain)
183 self.peer_authenticated=1
184 self.state_change("authenticated",self.peer)
185 self._post_auth()
186
187 if isinstance(r,sasl.Failure):
188 raise SASLAuthenticationFailed,"SASL authentication failed"
189
190 return True
191
193 """Process incoming <sasl:challenge/> element.
194
195 [initiating entity only]
196
197 :Parameters:
198 - `content`: the challenge data received (Base64-encoded).
199 """
200 if not self.authenticator:
201 self.__logger.debug("Unexpected SASL challenge")
202 return False
203
204 r=self.authenticator.challenge(base64.decodestring(content))
205 if isinstance(r,sasl.Response):
206 el_name="response"
207 content=r.base64()
208 else:
209 el_name="abort"
210 content=None
211
212 root=self.doc_out.getRootElement()
213 xmlnode=root.newChild(None,el_name,None)
214 ns=xmlnode.newNs(SASL_NS,None)
215 xmlnode.setNs(ns)
216 if content:
217 xmlnode.setContent(content)
218
219 self._write_raw(xmlnode.serialize(encoding="UTF-8"))
220 xmlnode.unlinkNode()
221 xmlnode.freeNode()
222
223 if isinstance(r,sasl.Failure):
224 raise SASLAuthenticationFailed,"SASL authentication failed"
225
226 return True
227
229 """Process incoming <sasl:response/> element.
230
231 [receiving entity only]
232
233 :Parameters:
234 - `content`: the response data received (Base64-encoded).
235 """
236 if not self.authenticator:
237 self.__logger.debug("Unexpected SASL response")
238 return 0
239
240 r=self.authenticator.response(base64.decodestring(content))
241 if isinstance(r,sasl.Success):
242 el_name="success"
243 content=r.base64()
244 elif isinstance(r,sasl.Challenge):
245 el_name="challenge"
246 content=r.base64()
247 else:
248 el_name="failure"
249 content=None
250
251 root=self.doc_out.getRootElement()
252 xmlnode=root.newChild(None,el_name,None)
253 ns=xmlnode.newNs(SASL_NS,None)
254 xmlnode.setNs(ns)
255 if content:
256 xmlnode.setContent(content)
257 if isinstance(r,sasl.Failure):
258 xmlnode.newChild(None,r.reason,None)
259
260 self._write_raw(xmlnode.serialize(encoding="UTF-8"))
261 xmlnode.unlinkNode()
262 xmlnode.freeNode()
263
264 if isinstance(r,sasl.Success):
265 authzid=r.authzid
266 if authzid:
267 self.peer=JID(r.authzid)
268 else:
269 self.peer=JID(r.username,self.me.domain)
270 self.peer_authenticated=1
271 self._restart_stream()
272 self.state_change("authenticated",self.peer)
273 self._post_auth()
274
275 if isinstance(r,sasl.Failure):
276 raise SASLAuthenticationFailed,"SASL authentication failed"
277
278 return 1
279
281 """Process incoming <sasl:success/> element.
282
283 [initiating entity only]
284
285 :Parameters:
286 - `content`: the "additional data with success" received (Base64-encoded).
287 """
288 if not self.authenticator:
289 self.__logger.debug("Unexpected SASL response")
290 return False
291
292 r=self.authenticator.finish(base64.decodestring(content))
293 if isinstance(r,sasl.Success):
294 self.__logger.debug("SASL authentication succeeded")
295 if r.authzid:
296 self.me=JID(r.authzid)
297 else:
298 self.me=self.me
299 self.authenticated=1
300 self._restart_stream()
301 self.state_change("authenticated",self.me)
302 self._post_auth()
303 else:
304 self.__logger.debug("SASL authentication failed")
305 raise SASLAuthenticationFailed,"Additional success data procesing failed"
306 return True
307
309 """Process incoming <sasl:failure/> element.
310
311 [initiating entity only]
312
313 :Parameters:
314 - `xmlnode`: the XML node received.
315 """
316 if not self.authenticator:
317 self.__logger.debug("Unexpected SASL response")
318 return False
319
320 self.__logger.debug("SASL authentication failed: %r" % (xmlnode.serialize(),))
321 raise SASLAuthenticationFailed,"SASL authentication failed"
322
324 """Process incoming <sasl:abort/> element.
325
326 [receiving entity only]"""
327 if not self.authenticator:
328 self.__logger.debug("Unexpected SASL response")
329 return False
330
331 self.authenticator=None
332 self.__logger.debug("SASL authentication aborted")
333 return True
334
336 """Start SASL authentication process.
337
338 [initiating entity only]
339
340 :Parameters:
341 - `username`: user name.
342 - `authzid`: authorization ID.
343 - `mechanism`: SASL mechanism to use."""
344 if not self.initiator:
345 raise SASLAuthenticationFailed,"Only initiating entity start SASL authentication"
346 while not self.features:
347 self.__logger.debug("Waiting for features")
348 self._read()
349 if not self.peer_sasl_mechanisms:
350 raise SASLNotAvailable,"Peer doesn't support SASL"
351
352 if not mechanism:
353 mechanism=None
354 for m in self.sasl_mechanisms:
355 if m in self.peer_sasl_mechanisms:
356 mechanism=m
357 break
358 if not mechanism:
359 raise SASLMechanismNotAvailable,"Peer doesn't support any of our SASL mechanisms"
360 self.__logger.debug("Our mechanism: %r" % (mechanism,))
361 else:
362 if mechanism not in self.peer_sasl_mechanisms:
363 raise SASLMechanismNotAvailable,"%s is not available" % (mechanism,)
364
365 self.auth_method_used="sasl:"+mechanism
366
367 self.authenticator=sasl.client_authenticator_factory(mechanism,self)
368
369 initial_response=self.authenticator.start(username,authzid)
370 if not isinstance(initial_response,sasl.Response):
371 raise SASLAuthenticationFailed,"SASL initiation failed"
372
373 root=self.doc_out.getRootElement()
374 xmlnode=root.newChild(None,"auth",None)
375 ns=xmlnode.newNs(SASL_NS,None)
376 xmlnode.setNs(ns)
377 xmlnode.setProp("mechanism",mechanism)
378 if initial_response.data:
379 if initial_response.encode:
380 xmlnode.setContent(initial_response.base64())
381 else:
382 xmlnode.setContent(initial_response.data)
383
384 self._write_raw(xmlnode.serialize(encoding="UTF-8"))
385 xmlnode.unlinkNode()
386 xmlnode.freeNode()
387
388
389