Package pyxmpp :: Module streambase |
[hide private]

# Source Code for Module pyxmpp.streambase

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