Beispiel #1
0
class JabberClient(Client):
    """Base class for a Jabber client.

    :Ivariables:
        - `disco_items`: default Disco#items reply for a query to an empty node.
        - `disco_info`: default Disco#info reply for a query to an empty node --
          provides information about the client and its supported fetures.
        - `disco_identity`: default identity of the default `disco_info`.
        - `register`: when `True` than registration will be started instead of authentication.
    :Types:
        - `disco_items`: `DiscoItems`
        - `disco_info`: `DiscoInfo`
        - `register`: `bool`
    """
    def __init__(self,jid=None, password=None, server=None, port=5222,
            auth_methods=("sasl:DIGEST-MD5","digest"),
            tls_settings=None, keepalive=0,
            disco_name=u"pyxmpp based Jabber client", disco_category=u"client",
            disco_type=u"pc"):
        """Initialize a JabberClient object.

        :Parameters:
            - `jid`: user full JID for the connection.
            - `password`: user password.
            - `server`: server to use. If not given then address will be derived form the JID.
            - `port`: port number to use. If not given then address will be derived form the JID.
            - `auth_methods`: sallowed authentication methods. SASL authentication mechanisms
              in the list should be prefixed with "sasl:" string.
            - `tls_settings`: settings for StartTLS -- `TLSSettings` instance.
            - `keepalive`: keepalive output interval. 0 to disable.
            - `disco_name`: name of the client identity in the disco#info
              replies.
            - `disco_category`: category of the client identity in the disco#info
              replies. The default of u'client' should be the right choice in
              most cases.
            - `disco_type`: type of the client identity in the disco#info
              replies. Use `the types registered by Jabber Registrar <http://www.jabber.org/registrar/disco-categories.html>`__
        :Types:
            - `jid`: `pyxmpp.JID`
            - `password`: `unicode`
            - `server`: `unicode`
            - `port`: `int`
            - `auth_methods`: sequence of `str`
            - `tls_settings`: `pyxmpp.TLSSettings`
            - `keepalive`: `int`
            - `disco_name`: `unicode`
            - `disco_category`: `unicode`
            - `disco_type`: `unicode`
        """

        Client.__init__(self,jid,password,server,port,auth_methods,tls_settings,keepalive)
        self.stream_class = LegacyClientStream
        self.disco_items=DiscoItems()
        self.disco_info=DiscoInfo()
        self.disco_identity=DiscoIdentity(self.disco_info,
                            disco_name, disco_category, disco_type)
        self.register_feature(u"dnssrv")
        self.register_feature(u"stringprep")
        self.register_feature(u"urn:ietf:params:xml:ns:xmpp-sasl#c2s")
        self.cache = CacheSuite(max_items = 1000)
        self.__logger = logging.getLogger("pyxmpp.jabber.JabberClient")

# public methods

    def connect(self, register = False):
        """Connect to the server and set up the stream.

        Set `self.stream` and notify `self.state_changed` when connection
        succeeds. Additionally, initialize Disco items and info of the client.
        """
        Client.connect(self, register)
        if register:
            self.stream.registration_callback = self.process_registration_form

    def register_feature(self, feature_name):
        """Register a feature to be announced by Service Discovery.

        :Parameters:
            - `feature_name`: feature namespace or name.
        :Types:
            - `feature_name`: `unicode`"""
        self.disco_info.add_feature(feature_name)

    def unregister_feature(self, feature_name):
        """Unregister a feature to be announced by Service Discovery.

        :Parameters:
            - `feature_name`: feature namespace or name.
        :Types:
            - `feature_name`: `unicode`"""
        self.disco_info.remove_feature(feature_name)

    def submit_registration_form(self, form):
        """Submit a registration form

        :Parameters:
            - `form`: the form to submit
        :Types:
            - `form`: `pyxmpp.jabber.dataforms.Form`"""
        self.stream.submit_registration_form(form)

# private methods
    def __disco_info(self,iq):
        """Handle a disco#info request.

        `self.disco_get_info` method will be used to prepare the query response.

        :Parameters:
            - `iq`: the IQ stanza received.
        :Types:
            - `iq`: `pyxmpp.iq.Iq`"""
        q=iq.get_query()
        if q.hasProp("node"):
            node=from_utf8(q.prop("node"))
        else:
            node=None
        info=self.disco_get_info(node,iq)
        if isinstance(info,DiscoInfo):
            resp=iq.make_result_response()
            self.__logger.debug("Disco-info query: %s preparing response: %s with reply: %s"
                % (iq.serialize(),resp.serialize(),info.xmlnode.serialize()))
            resp.set_content(info.xmlnode.copyNode(1))
        elif isinstance(info,Stanza):
            resp=info
        else:
            resp=iq.make_error_response("item-not-found")
        self.__logger.debug("Disco-info response: %s" % (resp.serialize(),))
        self.stream.send(resp)

    def __disco_items(self,iq):
        """Handle a disco#items request.

        `self.disco_get_items` method will be used to prepare the query response.

        :Parameters:
            - `iq`: the IQ stanza received.
        :Types:
            - `iq`: `pyxmpp.iq.Iq`"""
        q=iq.get_query()
        if q.hasProp("node"):
            node=from_utf8(q.prop("node"))
        else:
            node=None
        items=self.disco_get_items(node,iq)
        if isinstance(items,DiscoItems):
            resp=iq.make_result_response()
            self.__logger.debug("Disco-items query: %s preparing response: %s with reply: %s"
                % (iq.serialize(),resp.serialize(),items.xmlnode.serialize()))
            resp.set_content(items.xmlnode.copyNode(1))
        elif isinstance(items,Stanza):
            resp=items
        else:
            resp=iq.make_error_response("item-not-found")
        self.__logger.debug("Disco-items response: %s" % (resp.serialize(),))
        self.stream.send(resp)

    def _session_started(self):
        """Called when session is started.
        
        Activates objects from `self.interface_provides` by installing
        their disco features."""
        Client._session_started(self)
        for ob in self.interface_providers:
            if IFeaturesProvider.providedBy(ob):
                for ns in ob.get_features():
                    self.register_feature(ns)

# methods to override

    def authorized(self):
        """Handle "authorized" event. May be overriden in derived classes.
        By default: request an IM session and setup Disco handlers."""
        Client.authorized(self)
        self.stream.set_iq_get_handler("query",DISCO_ITEMS_NS,self.__disco_items)
        self.stream.set_iq_get_handler("query",DISCO_INFO_NS,self.__disco_info)
        disco.register_disco_cache_fetchers(self.cache,self.stream)

    def disco_get_info(self,node,iq):
        """Return Disco#info data for a node.

        :Parameters:
            - `node`: the node queried.
            - `iq`: the request stanza received.
        :Types:
            - `node`: `unicode`
            - `iq`: `pyxmpp.iq.Iq`

        :return: self.disco_info if `node` is empty or `None` otherwise.
        :returntype: `DiscoInfo`"""
        to=iq.get_to()
        if to and to!=self.jid:
            return iq.make_error_response("recipient-unavailable")
        if not node and self.disco_info:
            return self.disco_info
        return None

    def disco_get_items(self,node,iq):
        """Return Disco#items data for a node.

        :Parameters:
            - `node`: the node queried.
            - `iq`: the request stanza received.
        :Types:
            - `node`: `unicode`
            - `iq`: `pyxmpp.iq.Iq`

        :return: self.disco_info if `node` is empty or `None` otherwise.
        :returntype: `DiscoInfo`"""
        to=iq.get_to()
        if to and to!=self.jid:
            return iq.make_error_response("recipient-unavailable")
        if not node and self.disco_items:
            return self.disco_items
        return None

    def process_registration_form(self, stanza, form):
        """Fill-in the registration form provided by the server.

        This default implementation fills-in "username" and "passwords"
        fields only and instantly submits the form.

        :Parameters:
            - `stanza`: the stanza received.
            - `form`: the registration form.
        :Types:
            - `stanza`: `pyxmpp.iq.Iq`
            - `form`: `pyxmpp.jabber.dataforms.Form`
        """
        _unused = stanza
        self.__logger.debug(u"default registration callback started. auto-filling-in the form...")
        if not 'FORM_TYPE' in form or 'jabber:iq:register' not in form['FORM_TYPE'].values:
            raise RuntimeError, "Unknown form type: %r %r" % (form, form['FORM_TYPE'])
        for field in form:
            if field.name == u"username":
                self.__logger.debug(u"Setting username to %r" % (self.jid.node,))
                field.value = self.jid.node
            elif field.name == u"password":
                self.__logger.debug_s(u"Setting password to %r.decode('rot13')" % (self.password.encode('rot13'),))
                field.value = self.password
            elif field.required:
                self.__logger.debug(u"Unknown required field: %r" % (field.name,))
                raise RuntimeError, "Unsupported required registration form field %r" % (field.name,)
            else:
                self.__logger.debug(u"Unknown field: %r" % (field.name,))
        self.submit_registration_form(form)
Beispiel #2
0
class JabberClient(Client):
    """Base class for a Jabber client.

    :Ivariables:
        - `disco_items`: default Disco#items reply for a query to an empty node.
        - `disco_info`: default Disco#info reply for a query to an empty node --
          provides information about the client and its supported fetures.
        - `disco_identity`: default identity of the default `disco_info`.
        - `register`: when `True` than registration will be started instead of authentication.
    :Types:
        - `disco_items`: `DiscoItems`
        - `disco_info`: `DiscoInfo`
        - `register`: `bool`
    """
    def __init__(self,
                 jid=None,
                 password=None,
                 server=None,
                 port=5222,
                 auth_methods=("sasl:DIGEST-MD5", "digest"),
                 tls_settings=None,
                 keepalive=0,
                 disco_name=u"pyxmpp based Jabber client",
                 disco_category=u"client",
                 disco_type=u"pc"):
        """Initialize a JabberClient object.

        :Parameters:
            - `jid`: user full JID for the connection.
            - `password`: user password.
            - `server`: server to use. If not given then address will be derived form the JID.
            - `port`: port number to use. If not given then address will be derived form the JID.
            - `auth_methods`: sallowed authentication methods. SASL authentication mechanisms
              in the list should be prefixed with "sasl:" string.
            - `tls_settings`: settings for StartTLS -- `TLSSettings` instance.
            - `keepalive`: keepalive output interval. 0 to disable.
            - `disco_name`: name of the client identity in the disco#info
              replies.
            - `disco_category`: category of the client identity in the disco#info
              replies. The default of u'client' should be the right choice in
              most cases.
            - `disco_type`: type of the client identity in the disco#info
              replies. Use `the types registered by Jabber Registrar <http://www.jabber.org/registrar/disco-categories.html>`__
        :Types:
            - `jid`: `pyxmpp.JID`
            - `password`: `unicode`
            - `server`: `unicode`
            - `port`: `int`
            - `auth_methods`: sequence of `str`
            - `tls_settings`: `pyxmpp.TLSSettings`
            - `keepalive`: `int`
            - `disco_name`: `unicode`
            - `disco_category`: `unicode`
            - `disco_type`: `unicode`
        """

        Client.__init__(self, jid, password, server, port, auth_methods,
                        tls_settings, keepalive)
        self.stream_class = LegacyClientStream
        self.disco_items = DiscoItems()
        self.disco_info = DiscoInfo()
        self.disco_identity = DiscoIdentity(self.disco_info, disco_name,
                                            disco_category, disco_type)
        self.register_feature(u"dnssrv")
        self.register_feature(u"stringprep")
        self.register_feature(u"urn:ietf:params:xml:ns:xmpp-sasl#c2s")
        self.cache = CacheSuite(max_items=1000)
        self.__logger = logging.getLogger("pyxmpp.jabber.JabberClient")

# public methods

    def connect(self, register=False):
        """Connect to the server and set up the stream.

        Set `self.stream` and notify `self.state_changed` when connection
        succeeds. Additionally, initialize Disco items and info of the client.
        """
        Client.connect(self, register)
        if register:
            self.stream.registration_callback = self.process_registration_form

    def register_feature(self, feature_name):
        """Register a feature to be announced by Service Discovery.

        :Parameters:
            - `feature_name`: feature namespace or name.
        :Types:
            - `feature_name`: `unicode`"""
        self.disco_info.add_feature(feature_name)

    def unregister_feature(self, feature_name):
        """Unregister a feature to be announced by Service Discovery.

        :Parameters:
            - `feature_name`: feature namespace or name.
        :Types:
            - `feature_name`: `unicode`"""
        self.disco_info.remove_feature(feature_name)

    def submit_registration_form(self, form):
        """Submit a registration form

        :Parameters:
            - `form`: the form to submit
        :Types:
            - `form`: `pyxmpp.jabber.dataforms.Form`"""
        self.stream.submit_registration_form(form)

# private methods

    def __disco_info(self, iq):
        """Handle a disco#info request.

        `self.disco_get_info` method will be used to prepare the query response.

        :Parameters:
            - `iq`: the IQ stanza received.
        :Types:
            - `iq`: `pyxmpp.iq.Iq`"""
        q = iq.get_query()
        if q.hasProp("node"):
            node = from_utf8(q.prop("node"))
        else:
            node = None
        info = self.disco_get_info(node, iq)
        if isinstance(info, DiscoInfo):
            resp = iq.make_result_response()
            self.__logger.debug(
                "Disco-info query: %s preparing response: %s with reply: %s" %
                (iq.serialize(), resp.serialize(), info.xmlnode.serialize()))
            resp.set_content(info.xmlnode.copyNode(1))
        elif isinstance(info, Stanza):
            resp = info
        else:
            resp = iq.make_error_response("item-not-found")
        self.__logger.debug("Disco-info response: %s" % (resp.serialize(), ))
        self.stream.send(resp)

    def __disco_items(self, iq):
        """Handle a disco#items request.

        `self.disco_get_items` method will be used to prepare the query response.

        :Parameters:
            - `iq`: the IQ stanza received.
        :Types:
            - `iq`: `pyxmpp.iq.Iq`"""
        q = iq.get_query()
        if q.hasProp("node"):
            node = from_utf8(q.prop("node"))
        else:
            node = None
        items = self.disco_get_items(node, iq)
        if isinstance(items, DiscoItems):
            resp = iq.make_result_response()
            self.__logger.debug(
                "Disco-items query: %s preparing response: %s with reply: %s" %
                (iq.serialize(), resp.serialize(), items.xmlnode.serialize()))
            resp.set_content(items.xmlnode.copyNode(1))
        elif isinstance(items, Stanza):
            resp = items
        else:
            resp = iq.make_error_response("item-not-found")
        self.__logger.debug("Disco-items response: %s" % (resp.serialize(), ))
        self.stream.send(resp)

    def _session_started(self):
        """Called when session is started.
        
        Activates objects from `self.interface_provides` by installing
        their disco features."""
        Client._session_started(self)
        for ob in self.interface_providers:
            if IFeaturesProvider.providedBy(ob):
                for ns in ob.get_features():
                    self.register_feature(ns)

# methods to override

    def authorized(self):
        """Handle "authorized" event. May be overriden in derived classes.
        By default: request an IM session and setup Disco handlers."""
        Client.authorized(self)
        self.stream.set_iq_get_handler("query", DISCO_ITEMS_NS,
                                       self.__disco_items)
        self.stream.set_iq_get_handler("query", DISCO_INFO_NS,
                                       self.__disco_info)
        disco.register_disco_cache_fetchers(self.cache, self.stream)

    def disco_get_info(self, node, iq):
        """Return Disco#info data for a node.

        :Parameters:
            - `node`: the node queried.
            - `iq`: the request stanza received.
        :Types:
            - `node`: `unicode`
            - `iq`: `pyxmpp.iq.Iq`

        :return: self.disco_info if `node` is empty or `None` otherwise.
        :returntype: `DiscoInfo`"""
        to = iq.get_to()
        if to and to != self.jid:
            return iq.make_error_response("recipient-unavailable")
        if not node and self.disco_info:
            return self.disco_info
        return None

    def disco_get_items(self, node, iq):
        """Return Disco#items data for a node.

        :Parameters:
            - `node`: the node queried.
            - `iq`: the request stanza received.
        :Types:
            - `node`: `unicode`
            - `iq`: `pyxmpp.iq.Iq`

        :return: self.disco_info if `node` is empty or `None` otherwise.
        :returntype: `DiscoInfo`"""
        to = iq.get_to()
        if to and to != self.jid:
            return iq.make_error_response("recipient-unavailable")
        if not node and self.disco_items:
            return self.disco_items
        return None

    def process_registration_form(self, stanza, form):
        """Fill-in the registration form provided by the server.

        This default implementation fills-in "username" and "passwords"
        fields only and instantly submits the form.

        :Parameters:
            - `stanza`: the stanza received.
            - `form`: the registration form.
        :Types:
            - `stanza`: `pyxmpp.iq.Iq`
            - `form`: `pyxmpp.jabber.dataforms.Form`
        """
        _unused = stanza
        self.__logger.debug(
            u"default registration callback started. auto-filling-in the form..."
        )
        if not 'FORM_TYPE' in form or 'jabber:iq:register' not in form[
                'FORM_TYPE'].values:
            raise RuntimeError, "Unknown form type: %r %r" % (
                form, form['FORM_TYPE'])
        for field in form:
            if field.name == u"username":
                self.__logger.debug(u"Setting username to %r" %
                                    (self.jid.node, ))
                field.value = self.jid.node
            elif field.name == u"password":
                self.__logger.debug_s(
                    u"Setting password to %r.decode('rot13')" %
                    (self.password.encode('rot13'), ))
                field.value = self.password
            elif field.required:
                self.__logger.debug(u"Unknown required field: %r" %
                                    (field.name, ))
                raise RuntimeError, "Unsupported required registration form field %r" % (
                    field.name, )
            else:
                self.__logger.debug(u"Unknown field: %r" % (field.name, ))
        self.submit_registration_form(form)
Beispiel #3
0
class Component:
    """Jabber external component ("jabber:component:accept" protocol) interface
    implementation.

    Override this class to build your components.

    :Ivariables:
        - `jid`: component JID (should contain only the domain part).
        - `secret`: the authentication secret.
        - `server`: server to which the commonent will connect.
        - `port`: port number on the server to which the commonent will
          connect.
        - `keepalive`: keepalive interval for the stream.
        - `stream`: the XMPP stream object for the active connection
          or `None` if no connection is active.
        - `disco_items`: disco items announced by the component. Created
          when a stream is connected.
        - `disco_info`: disco info announced by the component. Created
          when a stream is connected.
        - `disco_identity`: disco identity (part of disco info) announced by
          the component. Created when a stream is connected.
        - `disco_category`: disco category to be used to create
          `disco_identity`.
        - `disco_type`: disco type to be used to create `disco_identity`.

    :Types:
        - `jid`:  `pyxmpp.JID`
        - `secret`: `unicode`
        - `server`: `unicode`
        - `port`: `int`
        - `keepalive`: `int`
        - `stream`: `pyxmpp.jabberd.ComponentStream`
        - `disco_items`: `pyxmpp.jabber.DiscoItems`
        - `disco_info`: `pyxmpp.jabber.DiscoInfo`
        - `disco_identity`: `pyxmpp.jabber.DiscoIdentity`
        - `disco_category`: `str`
        - `disco_type`: `str`"""
    def __init__(self, jid=None, secret=None, server=None, port=5347,
            disco_name=u"PyXMPP based component", disco_category=u"x-service",
            disco_type=u"x-unknown", keepalive=0):
        """Initialize a `Component` object.

        :Parameters:
            - `jid`: component JID (should contain only the domain part).
            - `secret`: the authentication secret.
            - `server`: server name or address the component should connect.
            - `port`: port number on the server where the component should connect.
            - `disco_name`: disco identity name to be used in the
              disco#info responses.
            - `disco_category`: disco identity category to be used in the
              disco#info responses.  Use `the categories registered by Jabber Registrar <http://www.jabber.org/registrar/disco-categories.html>`__
            - `disco_type`: disco identity type to be used in the component's
              disco#info responses.  Use `the types registered by Jabber Registrar <http://www.jabber.org/registrar/disco-categories.html>`__
            - `keepalive`: keepalive interval for the stream.

        :Types:
            - `jid`:  `pyxmpp.JID`
            - `secret`: `unicode`
            - `server`: `str` or `unicode`
            - `port`: `int`
            - `disco_name`: `unicode`
            - `disco_category`: `unicode`
            - `disco_type`: `unicode`
            - `keepalive`: `int`"""
        self.jid=jid
        self.secret=secret
        self.server=server
        self.port=port
        self.keepalive=keepalive
        self.stream=None
        self.lock=threading.RLock()
        self.state_changed=threading.Condition(self.lock)
        self.stream_class=ComponentStream
        self.disco_items=DiscoItems()
        self.disco_info=DiscoInfo()
        self.disco_identity=DiscoIdentity(self.disco_info,
                            disco_name, disco_category, disco_type)
        self.register_feature("stringprep")
        self.__logger=logging.getLogger("pyxmpp.jabberd.Component")

# public methods

    def connect(self):
        """Establish a connection with the server.

        Set `self.stream` to the `pyxmpp.jabberd.ComponentStream` when
        initial connection succeeds.

        :raise ValueError: when some of the component properties
          (`self.jid`, `self.secret`,`self.server` or `self.port`) are wrong."""
        if not self.jid or self.jid.node or self.jid.resource:
            raise ValueError,"Cannot connect: no or bad JID given"
        if not self.secret:
            raise ValueError,"Cannot connect: no secret given"
        if not self.server:
            raise ValueError,"Cannot connect: no server given"
        if not self.port:
            raise ValueError,"Cannot connect: no port given"

        self.lock.acquire()
        try:
            stream=self.stream
            self.stream=None
            if stream:
                stream.close()

            self.__logger.debug("Creating component stream: %r" % (self.stream_class,))
            stream=self.stream_class(jid = self.jid,
                    secret = self.secret,
                    server = self.server,
                    port = self.port,
                    keepalive = self.keepalive,
                    owner = self)
            stream.process_stream_error=self.stream_error
            self.stream_created(stream)
            stream.state_change=self.__stream_state_change
            stream.connect()
            self.stream=stream
            self.state_changed.notify()
            self.state_changed.release()
        except:
            self.stream=None
            self.state_changed.release()
            raise

    def get_stream(self):
        """Get the stream of the component in a safe way.

        :return: Stream object for the component or `None` if no connection is
            active.
        :returntype: `pyxmpp.jabberd.ComponentStream`"""
        self.lock.acquire()
        stream=self.stream
        self.lock.release()
        return stream

    def disconnect(self):
        """Disconnect from the server."""
        stream=self.get_stream()
        if stream:
            stream.disconnect()

    def socket(self):
        """Get the socket of the connection to the server.

        :return: the socket.
        :returntype: `socket.socket`"""
        return self.stream.socket

    def loop(self,timeout=1):
        """Simple 'main loop' for a component.

        This usually will be replaced by something more sophisticated. E.g.
        handling of other input sources."""
        self.stream.loop(timeout)

    def register_feature(self, feature_name):
        """Register a feature to be announced by Service Discovery.

        :Parameters:
            - `feature_name`: feature namespace or name.
        :Types:
            - `feature_name`: `unicode`"""
        self.disco_info.add_feature(feature_name)

    def unregister_feature(self, feature_name):
        """Unregister a feature to be announced by Service Discovery.

        :Parameters:
            - `feature_name`: feature namespace or name.
        :Types:
            - `feature_name`: `unicode`"""
        self.disco_info.remove_feature(feature_name)


# private methods
    def __stream_state_change(self,state,arg):
        """Handle various stream state changes and call right
        methods of `self`.

        :Parameters:
            - `state`: state name.
            - `arg`: state parameter.
        :Types:
            - `state`: `string`
            - `arg`: any object"""
        self.stream_state_changed(state,arg)
        if state=="fully connected":
            self.connected()
        elif state=="authenticated":
            self.authenticated()
        elif state=="authorized":
            self.authorized()
        elif state=="disconnected":
            self.state_changed.acquire()
            try:
                if self.stream:
                    self.stream.close()
                self.stream_closed(self.stream)
                self.stream=None
                self.state_changed.notify()
            finally:
                self.state_changed.release()
            self.disconnected()

    def __disco_info(self,iq):
        """Handle a disco-info query.

        :Parameters:
            - `iq`: the stanza received.

        Types:
            - `iq`: `pyxmpp.Iq`"""
        q=iq.get_query()
        if q.hasProp("node"):
            node=from_utf8(q.prop("node"))
        else:
            node=None
        info=self.disco_get_info(node,iq)
        if isinstance(info,DiscoInfo):
            resp=iq.make_result_response()
            self.__logger.debug("Disco-info query: %s preparing response: %s with reply: %s"
                % (iq.serialize(),resp.serialize(),info.xmlnode.serialize()))
            resp.set_content(info.xmlnode.copyNode(1))
        elif isinstance(info,Stanza):
            resp=info
        else:
            resp=iq.make_error_response("item-not-found")
        self.__logger.debug("Disco-info response: %s" % (resp.serialize(),))
        self.stream.send(resp)

    def __disco_items(self,iq):
        """Handle a disco-items query.

        :Parameters:
            - `iq`: the stanza received.

        Types:
            - `iq`: `pyxmpp.Iq`"""
        q=iq.get_query()
        if q.hasProp("node"):
            node=from_utf8(q.prop("node"))
        else:
            node=None
        items=self.disco_get_items(node,iq)
        if isinstance(items,DiscoItems):
            resp=iq.make_result_response()
            self.__logger.debug("Disco-items query: %s preparing response: %s with reply: %s"
                % (iq.serialize(),resp.serialize(),items.xmlnode.serialize()))
            resp.set_content(items.xmlnode.copyNode(1))
        elif isinstance(items,Stanza):
            resp=items
        else:
            resp=iq.make_error_response("item-not-found")
        self.__logger.debug("Disco-items response: %s" % (resp.serialize(),))
        self.stream.send(resp)

# Method to override
    def idle(self):
        """Do some "housekeeping" work like <iq/> result expiration. Should be
        called on a regular basis, usually when the component is idle."""
        stream=self.get_stream()
        if stream:
            stream.idle()

    def stream_created(self,stream):
        """Handle stream creation event.

        [may be overriden in derived classes]

        By default: do nothing.

        :Parameters:
            - `stream`: the stream just created.
        :Types:
            - `stream`: `pyxmpp.jabberd.ComponentStream`"""
        pass

    def stream_closed(self,stream):
        """Handle stream closure event.

        [may be overriden in derived classes]

        By default: do nothing.

        :Parameters:
            - `stream`: the stream just created.
        :Types:
            - `stream`: `pyxmpp.jabberd.ComponentStream`"""
        pass

    def stream_error(self,err):
        """Handle a stream error received.

        [may be overriden in derived classes]

        By default: just log it. The stream will be closed anyway.

        :Parameters:
            - `err`: the error element received.
        :Types:
            - `err`: `pyxmpp.error.StreamErrorNode`"""
        self.__logger.debug("Stream error: condition: %s %r"
                % (err.get_condition().name,err.serialize()))

    def stream_state_changed(self,state,arg):
        """Handle a stream state change.

        [may be overriden in derived classes]

        By default: do nothing.

        :Parameters:
            - `state`: state name.
            - `arg`: state parameter.
        :Types:
            - `state`: `string`
            - `arg`: any object"""
        pass

    def connected(self):
        """Handle stream connection event.

        [may be overriden in derived classes]

        By default: do nothing."""
        pass

    def authenticated(self):
        """Handle successful authentication event.

        A good place to register stanza handlers and disco features.

        [should be overriden in derived classes]

        By default: set disco#info and disco#items handlers."""
        self.__logger.debug("Setting up Disco handlers...")
        self.stream.set_iq_get_handler("query","http://jabber.org/protocol/disco#items",
                                    self.__disco_items)
        self.stream.set_iq_get_handler("query","http://jabber.org/protocol/disco#info",
                                    self.__disco_info)

    def authorized(self):
        """Handle successful authorization event."""
        pass

    def disco_get_info(self,node,iq):
        """Get disco#info data for a node.

        [may be overriden in derived classes]

        By default: return `self.disco_info` if no specific node name
        is provided.

        :Parameters:
            - `node`: name of the node queried.
            - `iq`: the stanza received.
        :Types:
            - `node`: `unicode`
            - `iq`: `pyxmpp.Iq`"""
        to=iq.get_to()
        if to and to!=self.jid:
            return iq.make_error_response("recipient-unavailable")
        if not node and self.disco_info:
            return self.disco_info
        return None

    def disco_get_items(self,node,iq):
        """Get disco#items data for a node.

        [may be overriden in derived classes]

        By default: return `self.disco_items` if no specific node name
        is provided.

        :Parameters:
            - `node`: name of the node queried.
            - `iq`: the stanza received.
        :Types:
            - `node`: `unicode`
            - `iq`: `pyxmpp.Iq`"""
        to=iq.get_to()
        if to and to!=self.jid:
            return iq.make_error_response("recipient-unavailable")
        if not node and self.disco_items:
            return self.disco_items
        return None

    def disconnected(self):
        """Handle stream disconnection (connection closed by peer) event.

        [may be overriden in derived classes]

        By default: do nothing."""
        pass