diff options
Diffstat (limited to 'sleekxmpp/componentxmpp.py')
-rw-r--r-- | sleekxmpp/componentxmpp.py | 162 |
1 files changed, 162 insertions, 0 deletions
diff --git a/sleekxmpp/componentxmpp.py b/sleekxmpp/componentxmpp.py new file mode 100644 index 00000000..5b16c5ef --- /dev/null +++ b/sleekxmpp/componentxmpp.py @@ -0,0 +1,162 @@ +# -*- coding: utf-8 -*- +""" + sleekxmpp.clientxmpp + ~~~~~~~~~~~~~~~~~~~~ + + This module provides XMPP functionality that + is specific to external server component connections. + + Part of SleekXMPP: The Sleek XMPP Library + + :copyright: (c) 2011 Nathanael C. Fritz + :license: MIT, see LICENSE for more details +""" + +from __future__ import absolute_import + +import logging +import base64 +import sys +import hashlib + +from sleekxmpp import plugins +from sleekxmpp import stanza +from sleekxmpp.basexmpp import BaseXMPP +from sleekxmpp.xmlstream import XMLStream, RestartStream +from sleekxmpp.xmlstream import StanzaBase, ET +from sleekxmpp.xmlstream.matcher import * +from sleekxmpp.xmlstream.handler import * + + +log = logging.getLogger(__name__) + + +class ComponentXMPP(BaseXMPP): + + """ + SleekXMPP's basic XMPP server component. + + Use only for good, not for evil. + + :param jid: The JID of the component. + :param secret: The secret or password for the component. + :param host: The server accepting the component. + :param port: The port used to connect to the server. + :param plugin_config: A dictionary of plugin configurations. + :param plugin_whitelist: A list of approved plugins that + will be loaded when calling + :meth:`~sleekxmpp.basexmpp.BaseXMPP.register_plugins()`. + :param use_jc_ns: Indicates if the ``'jabber:client'`` namespace + should be used instead of the standard + ``'jabber:component:accept'`` namespace. + Defaults to ``False``. + """ + + def __init__(self, jid, secret, host=None, port=None, + plugin_config={}, plugin_whitelist=[], use_jc_ns=False): + if use_jc_ns: + default_ns = 'jabber:client' + else: + default_ns = 'jabber:component:accept' + BaseXMPP.__init__(self, jid, default_ns) + + self.auto_authorize = None + self.stream_header = "<stream:stream %s %s to='%s'>" % ( + 'xmlns="jabber:component:accept"', + 'xmlns:stream="%s"' % self.stream_ns, + jid) + self.stream_footer = "</stream:stream>" + self.server_host = host + self.server_port = port + self.secret = secret + + self.plugin_config = plugin_config + self.plugin_whitelist = plugin_whitelist + self.is_component = True + + self.register_handler( + Callback('Handshake', + MatchXPath('{jabber:component:accept}handshake'), + self._handle_handshake)) + self.add_event_handler('presence_probe', + self._handle_probe) + + def connect(self, host=None, port=None, use_ssl=False, + use_tls=True, reattempt=True): + """Connect to the server. + + Setting ``reattempt`` to ``True`` will cause connection attempts to + be made every second until a successful connection is established. + + :param host: The name of the desired server for the connection. + Defaults to :attr:`server_host`. + :param port: Port to connect to on the server. + Defauts to :attr:`server_port`. + :param use_ssl: Flag indicating if SSL should be used by connecting + directly to a port using SSL. + :param use_tls: Flag indicating if TLS should be used, allowing for + connecting to a port without using SSL immediately and + later upgrading the connection. + :param reattempt: Flag indicating if the socket should reconnect + after disconnections. + """ + if host is None: + host = self.server_host + if port is None: + port = self.server_port + log.debug("Connecting to %s:%s", host, port) + return XMLStream.connect(self, host=host, port=port, + use_ssl=use_ssl, + use_tls=use_tls, + reattempt=reattempt) + + def incoming_filter(self, xml): + """ + Pre-process incoming XML stanzas by converting any + ``'jabber:client'`` namespaced elements to the component's + default namespace. + + :param xml: The XML stanza to pre-process. + """ + if xml.tag.startswith('{jabber:client}'): + xml.tag = xml.tag.replace('jabber:client', self.default_ns) + + # The incoming_filter call is only made on top level stanza + # elements. So we manually continue filtering on sub-elements. + for sub in xml: + self.incoming_filter(sub) + + return xml + + def start_stream_handler(self, xml): + """ + Once the streams are established, attempt to handshake + with the server to be accepted as a component. + + :param xml: The incoming stream's root element. + """ + BaseXMPP.start_stream_handler(self, xml) + + # Construct a hash of the stream ID and the component secret. + sid = xml.get('id', '') + pre_hash = '%s%s' % (sid, self.secret) + if sys.version_info >= (3, 0): + # Handle Unicode byte encoding in Python 3. + pre_hash = bytes(pre_hash, 'utf-8') + + handshake = ET.Element('{jabber:component:accept}handshake') + handshake.text = hashlib.sha1(pre_hash).hexdigest().lower() + self.send_xml(handshake, now=True) + + def _handle_handshake(self, xml): + """The handshake has been accepted. + + :param xml: The reply handshake stanza. + """ + self.session_started_event.set() + self.event("session_start") + + def _handle_probe(self, presence): + pto = presence['to'].bare + pfrom = presence['from'].bare + self.roster[pto][pfrom].handle_probe(presence) |