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 652 2006-08-27 19:41:15Z 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 self.authenticated:
173 self.__logger.debug("try_auth: already authenticated")
174 return
175 self.__logger.debug("trying auth: %r", (self._auth_methods_left,))
176 if not self._auth_methods_left:
177 raise StreamAuthenticationError,"No allowed authentication methods available"
178 method=self._auth_methods_left[0]
179 if method.startswith("sasl:"):
180 if self.version:
181 self._auth_methods_left.pop(0)
182 try:
183 self._sasl_authenticate(self.my_jid.node, None,
184 mechanism=method[5:].upper())
185 except (SASLMechanismNotAvailable,SASLNotAvailable):
186 self.__logger.debug("Skipping unavailable auth method: %s", (method,) )
187 return self._try_auth()
188 else:
189 self._auth_methods_left.pop(0)
190 self.__logger.debug("Skipping auth method %s as legacy protocol is in use",
191 (method,) )
192 return self._try_auth()
193 else:
194 self._auth_methods_left.pop(0)
195 self.__logger.debug("Skipping unknown auth method: %s", method)
196 return self._try_auth()
197
209
242
243 - def get_password(self, username, realm=None, acceptable_formats=("plain",)):
244 """Get a user password for the SASL authentication.
245
246 :Parameters:
247 - `username`: username used for authentication.
248 - `realm`: realm used for authentication.
249 - `acceptable_formats`: acceptable password encoding formats requested.
250 :Types:
251 - `username`: `unicode`
252 - `realm`: `unicode`
253 - `acceptable_formats`: `list` of `str`
254
255 :return: The password and the format name ('plain').
256 :returntype: (`unicode`,`str`)"""
257 _unused = realm
258 if self.initiator and self.my_jid.node==username and "plain" in acceptable_formats:
259 return self.password,"plain"
260 else:
261 return None,None
262
264 """Get realms available for client authentication.
265
266 [server only]
267
268 :return: list of realms.
269 :returntype: `list` of `unicode`"""
270 return [self.my_jid.domain]
271
273 """Choose authentication realm from the list provided by the server.
274
275 [client only]
276
277 Use domain of the own JID if no realm list was provided or the domain is on the list
278 or the first realm on the list otherwise.
279
280 :Parameters:
281 - `realm_list`: realm list provided by the server.
282 :Types:
283 - `realm_list`: `list` of `unicode`
284
285 :return: the realm chosen.
286 :returntype: `unicode`"""
287 if not realm_list:
288 return self.my_jid.domain
289 if self.my_jid.domain in realm_list:
290 return self.my_jid.domain
291 return realm_list[0]
292
294 """Check authorization id provided by the client.
295
296 [server only]
297
298 :Parameters:
299 - `authzid`: authorization id provided.
300 - `extra_info`: additional information about the user
301 from the authentication backend. This mapping will
302 usually contain at least 'username' item.
303 :Types:
304 - `authzid`: unicode
305 - `extra_info`: mapping
306
307 :return: `True` if user is authorized to use that `authzid`.
308 :returntype: `bool`"""
309 if not extra_info:
310 extra_info={}
311 if not authzid:
312 return 1
313 if not self.initiator:
314 jid=JID(authzid)
315 if not extra_info.has_key("username"):
316 ret=0
317 elif jid.node!=extra_info["username"]:
318 ret=0
319 elif jid.domain!=self.my_jid.domain:
320 ret=0
321 elif not jid.resource:
322 ret=0
323 else:
324 ret=1
325 else:
326 ret=0
327 return ret
328
330 """Get the server name for SASL authentication.
331
332 :return: 'xmpp'."""
333 return "xmpp"
334
336 """Get the service name for SASL authentication.
337
338 :return: domain of the own JID."""
339 return self.my_jid.domain
340
342 """Get the service host name for SASL authentication.
343
344 :return: domain of the own JID."""
345
346 return self.my_jid.domain
347
349 """Fix outgoing stanza.
350
351 On a client clear the sender JID. On a server set the sender
352 address to the own JID if the address is not set yet."""
353 if self.initiator:
354 stanza.set_from(None)
355 else:
356 if not stanza.get_from():
357 stanza.set_from(self.my_jid)
358
360 """Fix an incoming stanza.
361
362 Ona server replace the sender address with authorized client JID."""
363 if self.initiator:
364 Stream.fix_in_stanza(self,stanza)
365 else:
366 stanza.set_from(self.peer)
367
368
369