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