1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 """Client stream handling.
20
21 Normative reference:
22 - `RFC 3920 <http://www.ietf.org/rfc/rfc3920.txt>`__
23 """
24
25 __revision__="$Id: clientstream.py 714 2010-04-05 10:20:10Z jajcus $"
26 __docformat__="restructuredtext en"
27
28 import logging
29
30 from pyxmpp.stream import Stream
31 from pyxmpp.streambase import BIND_NS
32 from pyxmpp.streamsasl import SASLNotAvailable,SASLMechanismNotAvailable
33 from pyxmpp.jid import JID
34 from pyxmpp.utils import to_utf8
35 from pyxmpp.exceptions import StreamError,StreamAuthenticationError,FatalStreamError
36 from pyxmpp.exceptions import ClientStreamError, FatalClientStreamError
37
39 """Handles XMPP-IM client connection stream.
40
41 Both client and server side of the connection is supported. This class handles
42 client SASL authentication, authorisation and resource binding.
43
44 This class is not ready for handling of legacy Jabber servers, as it doesn't
45 provide legacy authentication.
46
47 :Ivariables:
48 - `my_jid`: requested local JID. Please notice that this may differ from
49 `me`, which is actual authorized JID after the resource binding.
50 - `server`: server to use.
51 - `port`: port number to use.
52 - `password`: user's password.
53 - `auth_methods`: allowed authentication methods.
54 :Types:
55 - `my_jid`: `pyxmpp.JID`
56 - `server`: `str`
57 - `port`: `int`
58 - `password`: `str`
59 - `auth_methods`: `list` of `str`
60 """
61 - def __init__(self, jid, password=None, server=None, port=None,
62 auth_methods = ("sasl:DIGEST-MD5",),
63 tls_settings = None, keepalive = 0, owner = None):
64 """Initialize the ClientStream object.
65
66 :Parameters:
67 - `jid`: local JID.
68 - `password`: user's password.
69 - `server`: server to use. If not given then address will be derived form the JID.
70 - `port`: port number to use. If not given then address will be derived form the JID.
71 - `auth_methods`: sallowed authentication methods. SASL authentication mechanisms
72 in the list should be prefixed with "sasl:" string.
73 - `tls_settings`: settings for StartTLS -- `TLSSettings` instance.
74 - `keepalive`: keepalive output interval. 0 to disable.
75 - `owner`: `Client`, `Component` or similar object "owning" this stream.
76 :Types:
77 - `jid`: `pyxmpp.JID`
78 - `password`: `unicode`
79 - `server`: `unicode`
80 - `port`: `int`
81 - `auth_methods`: sequence of `str`
82 - `tls_settings`: `pyxmpp.TLSSettings`
83 - `keepalive`: `int`
84 """
85 sasl_mechanisms=[]
86 for m in auth_methods:
87 if not m.startswith("sasl:"):
88 continue
89 m=m[5:].upper()
90 sasl_mechanisms.append(m)
91 Stream.__init__(self, "jabber:client",
92 sasl_mechanisms = sasl_mechanisms,
93 tls_settings = tls_settings,
94 keepalive = keepalive,
95 owner = owner)
96 self.server=server
97 self.port=port
98 self.password=password
99 self.auth_methods=auth_methods
100 self.my_jid=jid
101 self.me = None
102 self._auth_methods_left = None
103 self.__logger=logging.getLogger("pyxmpp.ClientStream")
104
106 """Reset `ClientStream` object state, making the object ready to handle
107 new connections."""
108 Stream._reset(self)
109 self._auth_methods_left=[]
110
111 - def connect(self,server=None,port=None):
112 """Establish a client connection to a server.
113
114 [client only]
115
116 :Parameters:
117 - `server`: name or address of the server to use. Not recommended -- proper value
118 should be derived automatically from the JID.
119 - `port`: port number of the server to use. Not recommended --
120 proper value should be derived automatically from the JID.
121
122 :Types:
123 - `server`: `unicode`
124 - `port`: `int`"""
125 self.lock.acquire()
126 try:
127 self._connect(server,port)
128 finally:
129 self.lock.release()
130
131 - def _connect(self,server=None,port=None):
132 """Same as `ClientStream.connect` but assume `self.lock` is acquired."""
133 if not self.my_jid.node or not self.my_jid.resource:
134 raise ClientStreamError,"Client JID must have username and resource"
135 if not server:
136 server=self.server
137 if not port:
138 port=self.port
139 if server:
140 self.__logger.debug("server: %r", (server,))
141 service=None
142 else:
143 service="xmpp-client"
144 if port is None:
145 port=5222
146 if server is None:
147 server=self.my_jid.domain
148 self.me=self.my_jid
149 Stream._connect(self,server,port,service,self.my_jid.domain)
150
152 """Accept an incoming client connection.
153
154 [server only]
155
156 :Parameters:
157 - `sock`: a listening socket."""
158 Stream.accept(self,sock,self.my_jid)
159
160 - def _post_connect(self):
161 """Initialize authentication when the connection is established
162 and we are the initiator."""
163 if self.initiator:
164 self._auth_methods_left=list(self.auth_methods)
165 self._try_auth()
166
168 """Try to authenticate using the first one of allowed authentication
169 methods left.
170
171 [client only]"""
172 if not self.doc_out:
173 self.__logger.debug("try_auth: disconnecting already?")
174 return
175 if self.authenticated:
176 self.__logger.debug("try_auth: already authenticated")
177 return
178 self.__logger.debug("trying auth: %r", (self._auth_methods_left,))
179 if not self._auth_methods_left:
180 raise StreamAuthenticationError,"No allowed authentication methods available"
181 method=self._auth_methods_left[0]
182 if method.startswith("sasl:"):
183 if self.version:
184 self._auth_methods_left.pop(0)
185 try:
186 self._sasl_authenticate(self.my_jid.node, None,
187 mechanism=method[5:].upper())
188 except (SASLMechanismNotAvailable,SASLNotAvailable):
189 self.__logger.debug("Skipping unavailable auth method: %s", (method,) )
190 return self._try_auth()
191 else:
192 self._auth_methods_left.pop(0)
193 self.__logger.debug("Skipping auth method %s as legacy protocol is in use",
194 (method,) )
195 return self._try_auth()
196 else:
197 self._auth_methods_left.pop(0)
198 self.__logger.debug("Skipping unknown auth method: %s", method)
199 return self._try_auth()
200
212
245
246 - def get_password(self, username, realm=None, acceptable_formats=("plain",)):
247 """Get a user password for the SASL authentication.
248
249 :Parameters:
250 - `username`: username used for authentication.
251 - `realm`: realm used for authentication.
252 - `acceptable_formats`: acceptable password encoding formats requested.
253 :Types:
254 - `username`: `unicode`
255 - `realm`: `unicode`
256 - `acceptable_formats`: `list` of `str`
257
258 :return: The password and the format name ('plain').
259 :returntype: (`unicode`,`str`)"""
260 _unused = realm
261 if self.initiator and self.my_jid.node==username and "plain" in acceptable_formats:
262 return self.password,"plain"
263 else:
264 return None,None
265
267 """Get realms available for client authentication.
268
269 [server only]
270
271 :return: list of realms.
272 :returntype: `list` of `unicode`"""
273 return [self.my_jid.domain]
274
276 """Choose authentication realm from the list provided by the server.
277
278 [client only]
279
280 Use domain of the own JID if no realm list was provided or the domain is on the list
281 or the first realm on the list otherwise.
282
283 :Parameters:
284 - `realm_list`: realm list provided by the server.
285 :Types:
286 - `realm_list`: `list` of `unicode`
287
288 :return: the realm chosen.
289 :returntype: `unicode`"""
290 if not realm_list:
291 return self.my_jid.domain
292 if self.my_jid.domain in realm_list:
293 return self.my_jid.domain
294 return realm_list[0]
295
297 """Check authorization id provided by the client.
298
299 [server only]
300
301 :Parameters:
302 - `authzid`: authorization id provided.
303 - `extra_info`: additional information about the user
304 from the authentication backend. This mapping will
305 usually contain at least 'username' item.
306 :Types:
307 - `authzid`: unicode
308 - `extra_info`: mapping
309
310 :return: `True` if user is authorized to use that `authzid`.
311 :returntype: `bool`"""
312 if not extra_info:
313 extra_info={}
314 if not authzid:
315 return 1
316 if not self.initiator:
317 jid=JID(authzid)
318 if not extra_info.has_key("username"):
319 ret=0
320 elif jid.node!=extra_info["username"]:
321 ret=0
322 elif jid.domain!=self.my_jid.domain:
323 ret=0
324 elif not jid.resource:
325 ret=0
326 else:
327 ret=1
328 else:
329 ret=0
330 return ret
331
333 """Get the server name for SASL authentication.
334
335 :return: 'xmpp'."""
336 return "xmpp"
337
339 """Get the service name for SASL authentication.
340
341 :return: domain of the own JID."""
342 return self.my_jid.domain
343
345 """Get the service host name for SASL authentication.
346
347 :return: domain of the own JID."""
348
349 return self.my_jid.domain
350
352 """Fix outgoing stanza.
353
354 On a client clear the sender JID. On a server set the sender
355 address to the own JID if the address is not set yet."""
356 if self.initiator:
357 stanza.set_from(None)
358 else:
359 if not stanza.get_from():
360 stanza.set_from(self.my_jid)
361
363 """Fix an incoming stanza.
364
365 Ona server replace the sender address with authorized client JID."""
366 if self.initiator:
367 Stream.fix_in_stanza(self,stanza)
368 else:
369 stanza.set_from(self.peer)
370
371
372