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