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 714 2010-04-05 10:20:10Z 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