Package pyxmpp :: Module streambase

[hide private]

# Source Code for Module pyxmpp.streambase

      1  #
      2  # (C) Copyright 2003-2010 Jacek Konieczny <jajcus@jajcus.net>
      3  #
      4  # This program is free software; you can redistribute it and/or modify
      5  # it under the terms of the GNU Lesser General Public License Version
      6  # 2.1 as published by the Free Software Foundation.
      7  #
      8  # This program is distributed in the hope that it will be useful,
      9  # but WITHOUT ANY WARRANTY; without even the implied warranty of
     10  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     11  # GNU Lesser General Public License for more details.
     12  #
     13  # You should have received a copy of the GNU Lesser General Public
     14  # License along with this program; if not, write to the Free Software
     15  # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
     16  #
     17  # pylint: disable-msg=W0201
     18
     19  """Core XMPP stream functionality.
     20
     21  Normative reference:
     22    - `RFC 3920 <http://www.ietf.org/rfc/rfc3920.txt>`__
     23  """
     24
     25  __docformat__="restructuredtext en"
     26
     27  import libxml2
     28  import socket
     29  import os
     30  import time
     31  import random
     32  import threading
     33  import errno
     34  import logging
     35
     36
     37  from pyxmpp import xmlextra
     38  from pyxmpp.expdict import ExpiringDictionary
     39  from pyxmpp.utils import to_utf8, from_utf8
     40  from pyxmpp.stanza import Stanza
     41  from pyxmpp.error import StreamErrorNode
     42  from pyxmpp.iq import Iq
     43  from pyxmpp.presence import Presence
     44  from pyxmpp.message import Message
     45  from pyxmpp.jid import JID
     46  from pyxmpp import resolver
     47  from pyxmpp.stanzaprocessor import StanzaProcessor
     48  from pyxmpp.exceptions import StreamError, StreamEncryptionRequired
     49  from pyxmpp.exceptions import HostMismatch, ProtocolError
     50  from pyxmpp.exceptions import DNSError, UnexpectedCNAMEError
     51  from pyxmpp.exceptions import FatalStreamError, StreamParseError, StreamAuthenticationError
     52
     53  STREAM_NS="http://etherx.jabber.org/streams"
     54  BIND_NS="urn:ietf:params:xml:ns:xmpp-bind"
     55
     56 -def stanza_factory(xmlnode, stream = None):
     57      """Creates Iq, Message or Presence object for XML stanza `xmlnode`"""
     58      if xmlnode.name=="iq":
     59          return Iq(xmlnode, stream = stream)
     60      if xmlnode.name=="message":
     61          return Message(xmlnode, stream = stream)
     62      if xmlnode.name=="presence":
     63          return Presence(xmlnode, stream = stream)
     64      else:
     65          return Stanza(xmlnode, stream = stream)
     66
     67 -class StreamBase(StanzaProcessor,xmlextra.StreamHandler):
     68      """Base class for a generic XMPP stream.
     69
     70      Responsible for establishing connection, parsing the stream, dispatching
     71      received stanzas to apopriate handlers and sending application's stanzas.
     72      This doesn't provide any authentication or encryption (both required by
     73      the XMPP specification) and is not usable on its own.
     74
     75      Whenever we say "stream" here we actually mean two streams
     76      (incoming and outgoing) of one connections, as defined by the XMPP
     77      specification.
     78
     79      :Ivariables:
     80          - `lock`: RLock object used to synchronize access to Stream object.
     81          - `features`: stream features as annouced by the initiator.
     82          - `me`: local stream endpoint JID.
     83          - `peer`: remote stream endpoint JID.
     84          - `process_all_stanzas`: when `True` then all stanzas received are
     85            considered local.
     86          - `initiator`: `True` if local stream endpoint is the initiating entity.
     87          - `owner`: `Client`, `Component` or similar object "owning" this stream.
     88          - `_reader`: the stream reader object (push parser) for the stream.
     89      """
     90 -    def __init__(self, default_ns, extra_ns = (), keepalive = 0, owner = None):
     91          """Initialize Stream object
     92
     93          :Parameters:
     94            - `default_ns`: stream's default namespace ("jabber:client" for
     95              client, "jabber:server" for server, etc.)
     96            - `extra_ns`: sequence of extra namespace URIs to be defined for
     97              the stream.
     98            - `keepalive`: keepalive output interval. 0 to disable.
     99            - `owner`: `Client`, `Component` or similar object "owning" this stream.
    100          """
    101          StanzaProcessor.__init__(self)
    102          xmlextra.StreamHandler.__init__(self)
    103          self.default_ns_uri=default_ns
    104          if extra_ns:
    105              self.extra_ns_uris=extra_ns
    106          else:
    107              self.extra_ns_uris=[]
    108          self.keepalive=keepalive
    109          self._reader_lock=threading.Lock()
    110          self.process_all_stanzas=False
    111          self.port=None
    112          self._reset()
    113          self.owner = owner
    114          self.__logger=logging.getLogger("pyxmpp.Stream")
    115
    116 -    def _reset(self):
    117          """Reset `Stream` object state making it ready to handle new
    118          connections."""
    119          self.doc_in=None
    120          self.doc_out=None
    121          self.socket=None
    122          self._reader=None
    123          self.addr=None
    124          self.default_ns=None
    125          self.extra_ns={}
    126          self.stream_ns=None
    127          self._reader=None
    128          self.ioreader=None
    129          self.me=None
    130          self.peer=None
    131          self.skip=False
    132          self.stream_id=None
    133          self._iq_response_handlers=ExpiringDictionary()
    134          self._iq_get_handlers={}
    135          self._iq_set_handlers={}
    136          self._message_handlers=[]
    137          self._presence_handlers=[]
    138          self.eof=False
    139          self.initiator=None
    140          self.features=None
    141          self.authenticated=False
    142          self.peer_authenticated=False
    143          self.auth_method_used=None
    144          self.version=None
    145          self.last_keepalive=False
    146
    147 -    def __del__(self):
    148          self.close()
    149
    150 -    def _connect_socket(self,sock,to=None):
    151          """Initialize stream on outgoing connection.
    152
    153          :Parameters:
    154            - `sock`: connected socket for the stream
    155            - `to`: name of the remote host
    156          """
    157          self.eof=0
    158          self.socket=sock
    159          if to:
    160              self.peer=JID(to)
    161          else:
    162              self.peer=None
    163          self.initiator=1
    164          self._send_stream_start()
    165          self._make_reader()
    166
    167 -    def connect(self,addr,port,service=None,to=None):
    168          """Establish XMPP connection with given address.
    169
    170          [initiating entity only]
    171
    172          :Parameters:
    173              - `addr`: peer name or IP address
    174              - `port`: port number to connect to
    175              - `service`: service name (to be resolved using SRV DNS records)
    176              - `to`: peer name if different than `addr`
    177          """
    178          self.lock.acquire()
    179          try:
    180              return self._connect(addr,port,service,to)
    181          finally:
    182              self.lock.release()
    183
    184 -    def _connect(self, addr, port, service = None, to = None):
    185          """Same as `Stream.connect` but assume `self.lock` is acquired."""
    186          if to is None:
    187              to = from_utf8(addr)
    188          allow_cname = True
    189          if service is not None:
    190              self.state_change("resolving srv", (addr, service))
    191              addrs = resolver.resolve_srv(addr, service)
    192              if not addrs:
    193                  addrs = [(addr, port)]
    194              else:
    195                  allow_cname = False
    196          else:
    197              addrs=[(addr, port)]
    198          exception = None
    199          for addr, port in addrs:
    200              if type(addr) in (str, unicode):
    201                  self.state_change("resolving", addr)
    202              s=None
    203              while True:
    204                  try:
    205                      addresses = resolver.getaddrinfo(addr, port, None,
    206                                  socket.SOCK_STREAM, allow_cname = allow_cname)
    207                      break
    208                  except UnexpectedCNAMEError, err:
    209                      self.__logger.warning(str(err))
    210                      allow_cname = True
    211                      continue
    212                  except DNSError, err:
    213                      self.__logger.debug(str(err))
    214                      exception = err
    215                      addresses = []
    216                      break
    217              for res in addresses:
    218                  family, socktype, proto, _unused, sockaddr = res
    219                  try:
    220                      s=socket.socket(family,socktype,proto)
    221                      self.state_change("connecting",sockaddr)
    222                      s.connect(sockaddr)
    223                      self.state_change("connected",sockaddr)
    224                  except socket.error, err:
    225                      exception = err
    226                      self.__logger.debug("Connect to %r failed" % (sockaddr,))
    227                      if s:
    228                          s.close()
    229                          s=None
    230                      continue
    231                  break
    232              if s:
    233                  break
    234          if not s:
    235              if exception:
    236                  raise exception
    237              else:
    238                  raise FatalStreamError,"Cannot connect"
    239
    240          self.addr=addr
    241          self.port=port
    242          self._connect_socket(s, to)
    243          self.last_keepalive=time.time()
    244
    245 -    def accept(self,sock,myname):
    246          """Accept incoming connection.
    247
    248          [receiving entity only]
    249
    250          :Parameters:
    251              - `sock`: a listening socket.
    252              - `myname`: local stream endpoint name."""
    253          self.lock.acquire()
    254          try:
    255              return self._accept(sock,myname)
    256          finally:
    257              self.lock.release()
    258
    259 -    def _accept(self,sock,myname):
    260          """Same as `Stream.accept` but assume `self.lock` is acquired."""
    261          self.eof=0
    262          self.socket,addr=sock.accept()
    263          self.__logger.debug("Connection from: %r" % (addr,))
    264          self.addr,self.port=addr
    265          if myname:
    266              self.me=JID(myname)
    267          else:
    268              self.me=None
    269          self.initiator=0
    270          self._make_reader()
    271          self.last_keepalive=time.time()
    272
    273 -    def disconnect(self):
    274          """Gracefully close the connection."""
    275          self.lock.acquire()
    276          try:
    277              return self._disconnect()
    278          finally:
    279              self.lock.release()
    280
    281 -    def _disconnect(self):
    282          """Same as `Stream.disconnect` but assume `self.lock` is acquired."""
    283          if self.doc_out:
    284              self._send_stream_end()
    285
    286 -    def _post_connect(self):
    287          """Called when connection is established.
    288
    289          This method is supposed to be overriden in derived classes."""
    290          pass
    291
    292 -    def _post_auth(self):
    293          """Called when connection is authenticated.
    294
    295          This method is supposed to be overriden in derived classes."""
    296          pass
    297
    298 -    def state_change(self,state,arg):
    299          """Called when connection state is changed.
    300
    301          This method is supposed to be overriden in derived classes
    302          or replaced by an application.
    303
    304          It may be used to display the connection progress."""
    305          self.__logger.debug("State: %s: %r" % (state,arg))
    306
    307 -    def close(self):
    308          """Forcibly close the connection and clear the stream state."""
    309          self.lock.acquire()
    310          try:
    311              return self._close()
    312          finally:
    313              self.lock.release()
    314
    315 -    def _close(self):
    316          """Same as `Stream.close` but assume `self.lock` is acquired."""
    317          self._disconnect()
    318          if self.doc_in:
    319              self.doc_in=None
    320          if self.features:
    321              self.features=None
    322          self._reader=None
    323          self.stream_id=None
    324          if self.socket:
    325              self.socket.close()
    326          self._reset()
    327
    328 -    def _make_reader(self):
    329          """Create ne `xmlextra.StreamReader` instace as `self._reader`."""
    330          self._reader=xmlextra.StreamReader(self)
    331
    332 -    def stream_start(self,doc):
    333          """Process <stream:stream> (stream start) tag received from peer.
    334
    335          :Parameters:
    336              - `doc`: document created by the parser"""
    337          self.doc_in=doc
    338          self.__logger.debug("input document: %r" % (self.doc_in.serialize(),))
    339
    340          try:
    341              r=self.doc_in.getRootElement()
    342              if r.ns().getContent() != STREAM_NS:
    343                  self._send_stream_error("invalid-namespace")
    344                  raise FatalStreamError,"Invalid namespace."
    345          except libxml2.treeError:
    346              self._send_stream_error("invalid-namespace")
    347              raise FatalStreamError,"Couldn't get the namespace."
    348
    349          self.version=r.prop("version")
    350          if self.version and self.version!="1.0":
    351              self._send_stream_error("unsupported-version")
    352              raise FatalStreamError,"Unsupported protocol version."
    353
    354          to_from_mismatch=0
    355          if self.initiator:
    356              self.stream_id=r.prop("id")
    357              peer = from_utf8(r.prop("from"))
    358              if peer:
    359                  peer = JID(peer)
    360              if self.peer:
    361                  if peer and peer!=self.peer:
    362                      self.__logger.debug("peer hostname mismatch:"
    363                          " %r != %r" % (peer,self.peer))
    364                      to_from_mismatch=1
    365              else:
    366                  self.peer=peer
    367          else:
    368              to = from_utf8(r.prop("to"))
    369              if to:
    370                  to=self.check_to(to)
    371                  if not to:
    372                      self._send_stream_error("host-unknown")
    373                      raise FatalStreamError,'Bad "to"'
    374                  self.me=JID(to)
    375              self._send_stream_start(self.generate_id())
    376              self._send_stream_features()
    377              self.state_change("fully connected",self.peer)
    378              self._post_connect()
    379
    380          if not self.version:
    381              self.state_change("fully connected",self.peer)
    382              self._post_connect()
    383
    384          if to_from_mismatch:
    385              raise HostMismatch
    386
    387 -    def stream_end(self, _unused):
    388          """Process </stream:stream> (stream end) tag received from peer.
    389
    390          :Parameters:
    391              - `_unused`: document created by the parser"""
    392          self.__logger.debug("Stream ended")
    393          self.eof=1
    394          if self.doc_out:
    395              self._send_stream_end()
    396          if self.doc_in:
    397              self.doc_in=None
    398              self._reader=None
    399              if self.features:
    400                  self.features=None
    401          self.state_change("disconnected",self.peer)
    402
    403 -    def stanza_start(self,doc,node):
    404          """Process stanza (first level child element of the stream) start tag
    405          -- do nothing.
    406
    407          :Parameters:
    408              - `doc`: parsed document
    409              - `node`: stanza's full XML
    410          """
    411          pass
    412
    413 -    def stanza(self, _unused, node):
    414          """Process stanza (first level child element of the stream).
    415
    416          :Parameters:
    417              - `_unused`: parsed document
    418              - `node`: stanza's full XML
    419          """
    420          self._process_node(node)
    421
    422 -    def error(self,descr):
    423          """Handle stream XML parse error.
    424
    425          :Parameters:
    426              - `descr`: error description
    427          """
    428          raise StreamParseError,descr
    429
    430 -    def _send_stream_end(self):
    431          """Send stream end tag."""
    432          self.doc_out.getRootElement().addContent(" ")
    433          s=self.doc_out.getRootElement().serialize(encoding="UTF-8")
    434          end=s.rindex("<")
    435          try:
    436              self._write_raw(s[end:])
    437          except (IOError,SystemError,socket.error),e:
    438              self.__logger.debug("Sending stream closing tag failed:"+str(e))
    439          self.doc_out.freeDoc()
    440          self.doc_out=None
    441          if self.features:
    442              self.features=None
    443
    444 -    def _send_stream_start(self,sid=None):
    445          """Send stream start tag."""
    446          if self.doc_out:
    447              raise StreamError,"Stream start already sent"
    448          self.doc_out=libxml2.newDoc("1.0")
    449          root=self.doc_out.newChild(None, "stream", None)
    450          self.stream_ns=root.newNs(STREAM_NS,"stream")
    451          root.setNs(self.stream_ns)
    452          self.default_ns=root.newNs(self.default_ns_uri,None)
    453          for prefix,uri in self.extra_ns:
    454              self.extra_ns[uri]=root.newNs(uri,prefix)
    455          if self.peer and self.initiator:
    456              root.setProp("to",self.peer.as_utf8())
    457          if self.me and not self.initiator:
    458              root.setProp("from",self.me.as_utf8())
    459          root.setProp("version","1.0")
    460          if sid:
    461              root.setProp("id",sid)
    462              self.stream_id=sid
    463          sr=self.doc_out.serialize(encoding="UTF-8")
    464          self._write_raw(sr[:sr.find("/>")]+">")
    465
    466 -    def _send_stream_error(self,condition):
    467          """Send stream error element.
    468
    469          :Parameters:
    470              - `condition`: stream error condition name, as defined in the
    471                XMPP specification."""
    472          if not self.doc_out:
    473              self._send_stream_start()
    474          e=StreamErrorNode(condition)
    475          e.xmlnode.setNs(self.stream_ns)
    476          self._write_raw(e.serialize())
    477          e.free()
    478          self._send_stream_end()
    479
    480 -    def _restart_stream(self):
    481          """Restart the stream as needed after SASL and StartTLS negotiation."""
    482          self._reader=None
    483          #self.doc_out.freeDoc()
    484          self.doc_out=None
    485          #self.doc_in.freeDoc() # memleak, but the node which caused the restart
    486                      # will be freed after this function returns
    487          self.doc_in=None
    488          self.features=None
    489          if self.initiator:
    490              self._send_stream_start(self.stream_id)
    491          self._make_reader()
    492
    493 -    def _make_stream_features(self):
    494          """Create the <features/> element for the stream.
    495
    496          [receving entity only]
    497
    498          :returns: new <features/> element node."""
    499          root=self.doc_out.getRootElement()
    500          features=root.newChild(root.ns(),"features",None)
    501          return features
    502
    503 -    def _send_stream_features(self):
    504          """Send stream <features/>.
    505
    506          [receiving entity only]"""
    507          self.features=self._make_stream_features()
    508          self._write_raw(self.features.serialize(encoding="UTF-8"))
    509
    510 -    def write_raw(self,data):
    511          """Write raw data to the stream socket.
    512
    513          :Parameters:
    514              - `data`: data to send"""
    515          self.lock.acquire()
    516          try:
    517              return self._write_raw(data)
    518          finally:
    519              self.lock.release()
    520
    521 -    def _write_raw(self,data):
    522          """Same as `Stream.write_raw` but assume `self.lock` is acquired."""
    523          logging.getLogger("pyxmpp.Stream.out").debug("OUT: %r",data)
    524          try:
    525              while data:
    526                  sent = self.socket.send(data)
    527                  data = data[sent:]
    528          except (IOError,OSError,socket.error),e:
    529              raise FatalStreamError("IO Error: "+str(e))
    530
    531 -    def _write_node(self,xmlnode):
    532          """Write XML `xmlnode` to the stream.
    533
    534          :Parameters:
    535              - `xmlnode`: XML node to send."""
    536          if self.eof or not self.socket or not self.doc_out:
    537              self.__logger.debug("Dropping stanza: %r" % (xmlnode,))
    538              return
    539          xmlnode=xmlnode.docCopyNode(self.doc_out,1)
    540          self.doc_out.addChild(xmlnode)
    541          try:
    542              ns = xmlnode.ns()
    543          except libxml2.treeError:
    544              ns = None
    545          if ns and ns.content == xmlextra.COMMON_NS:
    546              xmlextra.replace_ns(xmlnode, ns, self.default_ns)
    547          s = xmlextra.safe_serialize(xmlnode)
    548          self._write_raw(s)
    549          xmlnode.unlinkNode()
    550          xmlnode.freeNode()
    551
    552 -    def send(self,stanza):
    553          """Write stanza to the stream.
    554
    555          :Parameters:
    556              - `stanza`: XMPP stanza to send."""
    557          self.lock.acquire()
    558          try:
    559              return self._send(stanza)
    560          finally:
    561              self.lock.release()
    562
    563 -    def _send(self,stanza):
    564          """Same as `Stream.send` but assume `self.lock` is acquired."""
    565          if not self.version:
    566              try:
    567                  err = stanza.get_error()
    568              except ProtocolError:
    569                  err = None
    570              if err:
    571                  err.downgrade()
    572          self.fix_out_stanza(stanza)
    573          self._write_node(stanza.xmlnode)
    574
    575 -    def idle(self):
    576          """Do some housekeeping (cache expiration, timeout handling).
    577
    578          This method should be called periodically from the application's
    579          main loop."""
    580          self.lock.acquire()
    581          try:
    582              return self._idle()
    583          finally:
    584              self.lock.release()
    585
    586 -    def _idle(self):
    587          """Same as `Stream.idle` but assume `self.lock` is acquired."""
    588          self._iq_response_handlers.expire()
    589          if not self.socket or self.eof:
    590              return
    591          now=time.time()
    592          if self.keepalive and now-self.last_keepalive>=self.keepalive:
    593              self._write_raw(" ")
    594              self.last_keepalive=now
    595
    596 -    def fileno(self):
    597          """Return filedescriptor of the stream socket."""
    598          self.lock.acquire()
    599          try:
    600              return self.socket.fileno()
    601          finally:
    602              self.lock.release()
    603
    604 -    def loop(self,timeout):
    605          """Simple "main loop" for the stream."""
    606          self.lock.acquire()
    607          try:
    608              while not self.eof and self.socket is not None:
    609                  act=self._loop_iter(timeout)
    610                  if not act:
    611                      self._idle()
    612          finally:
    613              self.lock.release()
    614
    615 -    def loop_iter(self,timeout):
    616          """Single iteration of a simple "main loop" for the stream."""
    617          self.lock.acquire()
    618          try:
    619              return self._loop_iter(timeout)
    620          finally:
    621              self.lock.release()
    622
    623 -    def _loop_iter(self,timeout):
    624          """Same as `Stream.loop_iter` but assume `self.lock` is acquired."""
    625          import select
    626          self.lock.release()
    627          try:
    628              if not self.socket:
    629                  time.sleep(timeout)
    630                  return False
    631              try:
    632                  ifd, _unused, efd = select.select( [self.socket], [], [self.socket], timeout )
    633              except select.error,e:
    634                  if e.args[0]!=errno.EINTR:
    635                      raise
    636                  ifd, _unused, efd=[], [], []
    637          finally:
    638              self.lock.acquire()
    639          if self.socket in ifd or self.socket in efd:
    640              self._process()
    641              return True
    642          else:
    643              return False
    644
    645 -    def process(self):
    646          """Process stream's pending events.
    647
    648          Should be called whenever there is input available
    649          on `self.fileno()` socket descriptor. Is called by
    650          `self.loop_iter`."""
    651          self.lock.acquire()
    652          try:
    653              self._process()
    654          finally:
    655              self.lock.release()
    656
    657 -    def _process(self):
    658          """Same as `Stream.process` but assume `self.lock` is acquired."""
    659          try:
    660              try:
    661                  self._read()
    662              except (xmlextra.error,),e:
    663                  self.__logger.exception("Exception during read()")
    664                  raise StreamParseError(unicode(e))
    665              except:
    666                  raise
    667          except (IOError,OSError,socket.error),e:
    668              self.close()
    669              raise FatalStreamError("IO Error: "+str(e))
    670          except (FatalStreamError,KeyboardInterrupt,SystemExit),e:
    671              self.close()
    672              raise
    673
    674 -    def _read(self):
    675          """Read data pending on the stream socket and pass it to the parser."""
    676          self.__logger.debug("StreamBase._read(), socket: %r",self.socket)
    677          if self.eof:
    678              return
    679          try:
    680              r=self.socket.recv(1024)
    681          except socket.error,e:
    682              if e.args[0]!=errno.EINTR:
    683                  raise
    684              return
    685          self._feed_reader(r)
    686
    687 -    def _feed_reader(self,data):
    688          """Feed the stream reader with data received.
    689
    690          If `data` is None or empty, then stream end (peer disconnected) is
    691          assumed and the stream is closed.
    692
    693          :Parameters:
    694              - `data`: data received from the stream socket.
    695          :Types:
    696              - `data`: `unicode`
    697          """
    698          logging.getLogger("pyxmpp.Stream.in").debug("IN: %r",data)
    699          if data:
    700              try:
    701                  r=self._reader.feed(data)
    702                  while r:
    703                      r=self._reader.feed("")
    704                  if r is None:
    705                      self.eof=1
    706                      self.disconnect()
    707              except StreamParseError:
    708                  self._send_stream_error("xml-not-well-formed")
    709                  raise
    710          else:
    711              self.eof=1
    712              self.disconnect()
    713          if self.eof:
    714              self.stream_end(None)
    715
    716 -    def _process_node(self,xmlnode):
    717          """Process first level element of the stream.
    718
    719          The element may be stream error or features, StartTLS
    720          request/response, SASL request/response or a stanza.
    721
    722          :Parameters:
    723              - `xmlnode`: XML node describing the element
    724          """
    725          ns_uri=xmlnode.ns().getContent()
    726          if ns_uri=="http://etherx.jabber.org/streams":
    727              self._process_stream_node(xmlnode)
    728              return
    729
    730          if ns_uri==self.default_ns_uri:
    731              stanza=stanza_factory(xmlnode, self)
    732              self.lock.release()
    733              try:
    734                  self.process_stanza(stanza)
    735              finally:
    736                  self.lock.acquire()
    737                  stanza.free()
    738          else:
    739              self.__logger.debug("Unhandled node: %r" % (xmlnode.serialize(),))
    740
    741 -    def _process_stream_node(self,xmlnode):
    742          """Process first level stream-namespaced element of the stream.
    743
    744          The element may be stream error or stream features.
    745
    746          :Parameters:
    747              - `xmlnode`: XML node describing the element
    748          """
    749          if xmlnode.name=="error":
    750              e=StreamErrorNode(xmlnode)
    751              self.lock.release()
    752              try:
    753                  self.process_stream_error(e)
    754              finally:
    755                  self.lock.acquire()
    756                  e.free()
    757              return
    758          elif xmlnode.name=="features":
    759              self.__logger.debug("Got stream features")
    760              self.__logger.debug("Node: %r" % (xmlnode,))
    761              self.features=xmlnode.copyNode(1)
    762              self.doc_in.addChild(self.features)
    763              self._got_features()
    764              return
    765
    766          self.__logger.debug("Unhandled stream node: %r" % (xmlnode.serialize(),))
    767
    768 -    def process_stream_error(self,err):
    769          """Process stream error element received.
    770
    771          :Types:
    772              - `err`: `StreamErrorNode`
    773
    774          :Parameters:
    775              - `err`: error received
    776          """
    777
    778          self.__logger.debug("Unhandled stream error: condition: %s %r"
    779                  % (err.get_condition().name,err.serialize()))
    780
    781 -    def check_to(self,to):
    782          """Check "to" attribute of received stream header.
    783
    784          :return: `to` if it is equal to `self.me`, None otherwise.
    785
    786          Should be overriden in derived classes which require other logic
    787          for handling that attribute."""
    788          if to!=self.me:
    789              return None
    790          return to
    791
    792 -    def generate_id(self):
    793          """Generate a random and unique stream ID.
    794
    795          :return: the id string generated."""
    796          return "%i-%i-%s" % (os.getpid(),time.time(),str(random.random())[2:])
    797
    798 -    def _got_features(self):
    799          """Process incoming <stream:features/> element.
    800
    801          [initiating entity only]
    802
    803          The received features node is available in `self.features`."""
    804          ctxt = self.doc_in.xpathNewContext()
    805          ctxt.setContextNode(self.features)
    806          ctxt.xpathRegisterNs("stream",STREAM_NS)
    807          ctxt.xpathRegisterNs("bind",BIND_NS)
    808          bind_n=None
    809          try:
    810              bind_n=ctxt.xpathEval("bind:bind")
    811          finally:
    812              ctxt.xpathFreeContext()
    813
    814          if self.authenticated:
    815              if bind_n:
    816                  self.bind(self.me.resource)
    817              else:
    818                  self.state_change("authorized",self.me)
    819
    820 -    def bind(self,resource):
    821          """Bind to a resource.
    822
    823          [initiating entity only]
    824
    825          :Parameters:
    826              - `resource`: the resource name to bind to.
    827
    828          XMPP stream is authenticated for bare JID only. To use
    829          the full JID it must be bound to a resource.
    830          """
    831          iq=Iq(stanza_type="set")
    832          q=iq.new_query(BIND_NS, u"bind")
    833          if resource:
    834              q.newTextChild(None,"resource",to_utf8(resource))
    835          self.state_change("binding",resource)
    836          self.set_response_handlers(iq,self._bind_success,self._bind_error)
    837          self.send(iq)
    838          iq.free()
    839
    840 -    def _bind_success(self,stanza):
    841          """Handle resource binding success.
    842
    843          [initiating entity only]
    844
    845          :Parameters:
    846              - `stanza`: <iq type="result"/> stanza received.
    847
    848          Set `self.me` to the full JID negotiated."""
    849          jid_n=stanza.xpath_eval("bind:bind/bind:jid",{"bind":BIND_NS})
    850          if jid_n:
    851              self.me=JID(jid_n[0].getContent().decode("utf-8"))
    852          self.state_change("authorized",self.me)
    853
    854 -    def _bind_error(self,stanza):
    855          """Handle resource binding success.
    856
    857          [initiating entity only]
    858
    859          :raise FatalStreamError:"""
    860          raise FatalStreamError,"Resource binding failed"
    861
    862 -    def connected(self):
    863          """Check if stream is connected.
    864
    865          :return: True if stream connection is active."""
    866          if self.doc_in and self.doc_out and not self.eof:
    867              return True
    868          else:
    869              return False
    870
    871  # vi: sts=4 et sw=4
    872