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