1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 """XMPP stream support with fallback to legacy non-SASL Jabber authentication.
18
19 Normative reference:
20 - `JEP 78 <http://www.jabber.org/jeps/jep-0078.html>`__
21 """
22
23 __revision__="$Id: clientstream.py 703 2010-04-03 17:45:43Z jajcus $"
24 __docformat__="restructuredtext en"
25
26 import hashlib
27 import logging
28
29 from pyxmpp.iq import Iq
30 from pyxmpp.utils import to_utf8,from_utf8
31 from pyxmpp.jid import JID
32 from pyxmpp.clientstream import ClientStream
33 from pyxmpp.jabber.register import Register
34
35 from pyxmpp.exceptions import ClientStreamError, LegacyAuthenticationError, RegistrationError
36
38 """Handles Jabber (both XMPP and legacy protocol) client connection stream.
39
40 Both client and server side of the connection is supported. This class handles
41 client SASL and legacy authentication, authorisation and XMPP resource binding.
42 """
43 - def __init__(self, jid, password = None, server = None, port = 5222,
44 auth_methods = ("sasl:DIGEST-MD5", "digest"),
45 tls_settings = None, keepalive = 0, owner = None):
46 """Initialize a LegacyClientStream object.
47
48 :Parameters:
49 - `jid`: local JID.
50 - `password`: user's password.
51 - `server`: server to use. If not given then address will be derived form the JID.
52 - `port`: port number to use. If not given then address will be derived form the JID.
53 - `auth_methods`: sallowed authentication methods. SASL authentication mechanisms
54 in the list should be prefixed with "sasl:" string.
55 - `tls_settings`: settings for StartTLS -- `TLSSettings` instance.
56 - `keepalive`: keepalive output interval. 0 to disable.
57 - `owner`: `Client`, `Component` or similar object "owning" this stream.
58 :Types:
59 - `jid`: `pyxmpp.JID`
60 - `password`: `unicode`
61 - `server`: `unicode`
62 - `port`: `int`
63 - `auth_methods`: sequence of `str`
64 - `tls_settings`: `pyxmpp.TLSSettings`
65 - `keepalive`: `int`
66 """
67 (self.authenticated, self.available_auth_methods, self.auth_stanza,
68 self.peer_authenticated, self.auth_method_used,
69 self.registration_callback, self.registration_form, self.__register) = (None,) * 8
70 ClientStream.__init__(self, jid, password, server, port,
71 auth_methods, tls_settings, keepalive, owner)
72 self.__logger=logging.getLogger("pyxmpp.jabber.LegacyClientStream")
73
75 """Reset the `LegacyClientStream` object state, making the object ready
76 to handle new connections."""
77 ClientStream._reset(self)
78 self.available_auth_methods = None
79 self.auth_stanza = None
80 self.registration_callback = None
81
82 - def _post_connect(self):
83 """Initialize authentication when the connection is established
84 and we are the initiator."""
85 if not self.initiator:
86 if "plain" in self.auth_methods or "digest" in self.auth_methods:
87 self.set_iq_get_handler("query","jabber:iq:auth",
88 self.auth_in_stage1)
89 self.set_iq_set_handler("query","jabber:iq:auth",
90 self.auth_in_stage2)
91 elif self.registration_callback:
92 iq = Iq(stanza_type = "get")
93 iq.set_content(Register())
94 self.set_response_handlers(iq, self.registration_form_received, self.registration_error)
95 self.send(iq)
96 return
97 ClientStream._post_connect(self)
98
99 - def _post_auth(self):
100 """Unregister legacy authentication handlers after successfull
101 authentication."""
102 ClientStream._post_auth(self)
103 if not self.initiator:
104 self.unset_iq_get_handler("query","jabber:iq:auth")
105 self.unset_iq_set_handler("query","jabber:iq:auth")
106
108 """Try to authenticate using the first one of allowed authentication
109 methods left.
110
111 [client only]"""
112 if self.authenticated:
113 self.__logger.debug("try_auth: already authenticated")
114 return
115 self.__logger.debug("trying auth: %r" % (self._auth_methods_left,))
116 if not self._auth_methods_left:
117 raise LegacyAuthenticationError,"No allowed authentication methods available"
118 method=self._auth_methods_left[0]
119 if method.startswith("sasl:"):
120 return ClientStream._try_auth(self)
121 elif method not in ("plain","digest"):
122 self._auth_methods_left.pop(0)
123 self.__logger.debug("Skipping unknown auth method: %s" % method)
124 return self._try_auth()
125 elif self.available_auth_methods is not None:
126 if method in self.available_auth_methods:
127 self._auth_methods_left.pop(0)
128 self.auth_method_used=method
129 if method=="digest":
130 self._digest_auth_stage2(self.auth_stanza)
131 else:
132 self._plain_auth_stage2(self.auth_stanza)
133 self.auth_stanza=None
134 return
135 else:
136 self.__logger.debug("Skipping unavailable auth method: %s" % method)
137 else:
138 self._auth_stage1()
139
141 """Handle the first stage (<iq type='get'/>) of legacy ("plain" or
142 "digest") authentication.
143
144 [server only]"""
145 self.lock.acquire()
146 try:
147 if "plain" not in self.auth_methods and "digest" not in self.auth_methods:
148 iq=stanza.make_error_response("not-allowed")
149 self.send(iq)
150 return
151
152 iq=stanza.make_result_response()
153 q=iq.new_query("jabber:iq:auth")
154 q.newChild(None,"username",None)
155 q.newChild(None,"resource",None)
156 if "plain" in self.auth_methods:
157 q.newChild(None,"password",None)
158 if "digest" in self.auth_methods:
159 q.newChild(None,"digest",None)
160 self.send(iq)
161 iq.free()
162 finally:
163 self.lock.release()
164
205
219
221 """Handle legacy authentication timeout.
222
223 [client only]"""
224 self.lock.acquire()
225 try:
226 self.__logger.debug("Timeout while waiting for jabber:iq:auth result")
227 if self._auth_methods_left:
228 self._auth_methods_left.pop(0)
229 finally:
230 self.lock.release()
231
233 """Handle legacy authentication error.
234
235 [client only]"""
236 self.lock.acquire()
237 try:
238 err=stanza.get_error()
239 ae=err.xpath_eval("e:*",{"e":"jabber:iq:auth:error"})
240 if ae:
241 ae=ae[0].name
242 else:
243 ae=err.get_condition().name
244 raise LegacyAuthenticationError,("Authentication error condition: %s"
245 % (ae,))
246 finally:
247 self.lock.release()
248
250 """Handle the first stage authentication response (result of the <iq
251 type="get"/>).
252
253 [client only]"""
254 self.lock.acquire()
255 try:
256 self.__logger.debug("Procesing auth response...")
257 self.available_auth_methods=[]
258 if (stanza.xpath_eval("a:query/a:digest",{"a":"jabber:iq:auth"}) and self.stream_id):
259 self.available_auth_methods.append("digest")
260 if (stanza.xpath_eval("a:query/a:password",{"a":"jabber:iq:auth"})):
261 self.available_auth_methods.append("plain")
262 self.auth_stanza=stanza.copy()
263 self._try_auth()
264 finally:
265 self.lock.release()
266
280
308
325
363
365 """Handle success of the legacy authentication."""
366 self.lock.acquire()
367 try:
368 self.__logger.debug("Authenticated")
369 self.authenticated=True
370 self.state_change("authorized",self.my_jid)
371 self._post_auth()
372 finally:
373 self.lock.release()
374
376 """Handle in-band registration error.
377
378 [client only]
379
380 :Parameters:
381 - `stanza`: the error stanza received or `None` on timeout.
382 :Types:
383 - `stanza`: `pyxmpp.stanza.Stanza`"""
384 self.lock.acquire()
385 try:
386 err=stanza.get_error()
387 ae=err.xpath_eval("e:*",{"e":"jabber:iq:auth:error"})
388 if ae:
389 ae=ae[0].name
390 else:
391 ae=err.get_condition().name
392 raise RegistrationError,("Authentication error condition: %s" % (ae,))
393 finally:
394 self.lock.release()
395
415
439
441 """Handle registration success.
442
443 [client only]
444
445 Clean up registration stuff, change state to "registered" and initialize
446 authentication.
447
448 :Parameters:
449 - `stanza`: the stanza received.
450 :Types:
451 - `stanza`: `pyxmpp.iq.Iq`"""
452 _unused = stanza
453 self.lock.acquire()
454 try:
455 self.state_change("registered", self.registration_form)
456 if ('FORM_TYPE' in self.registration_form
457 and self.registration_form['FORM_TYPE'].value == 'jabber:iq:register'):
458 if 'username' in self.registration_form:
459 self.my_jid = JID(self.registration_form['username'].value,
460 self.my_jid.domain, self.my_jid.resource)
461 if 'password' in self.registration_form:
462 self.password = self.registration_form['password'].value
463 self.registration_callback = None
464 self._post_connect()
465 finally:
466 self.lock.release()
467
468
469