diff --git a/sleekxmpp/basexmpp.py b/sleekxmpp/basexmpp.py index edb20d0..2548934 100644 --- a/sleekxmpp/basexmpp.py +++ b/sleekxmpp/basexmpp.py @@ -26,6 +26,14 @@ from sleekxmpp.xmlstream.matcher import * from sleekxmpp.xmlstream.handler import * +# Flag indicating if DNS SRV records are available for use. +SRV_SUPPORT = True +try: + import dns.resolver +except: + SRV_SUPPORT = False + + # In order to make sure that Unicode is handled properly # in Python 2.x, reset the default encoding. if sys.version_info < (3, 0): diff --git a/sleekxmpp/componentxmpp.py b/sleekxmpp/componentxmpp.py old mode 100755 new mode 100644 index 00e5252..abb20d9 --- a/sleekxmpp/componentxmpp.py +++ b/sleekxmpp/componentxmpp.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python - """ SleekXMPP: The Sleek XMPP Library Copyright (C) 2010 Nathanael C. Fritz @@ -7,72 +5,132 @@ See the file LICENSE for copying permission. """ -from __future__ import absolute_import -from . basexmpp import BaseXMPP -from xml.etree import cElementTree as ET -from . xmlstream.xmlstream import XMLStream -from . xmlstream.xmlstream import RestartStream -from . xmlstream.matcher.xmlmask import MatchXMLMask -from . xmlstream.matcher.xpath import MatchXPath -from . xmlstream.matcher.many import MatchMany -from . xmlstream.handler.callback import Callback -from . xmlstream.stanzabase import StanzaBase -from . xmlstream import xmlstream as xmlstreammod -import time +from __future__ import absolute_import + import logging import base64 import sys -import random -import copy -from . import plugins -from . import stanza import hashlib -srvsupport = True -try: - import dns.resolver -except ImportError: - srvsupport = False + +from sleekxmpp import plugins +from sleekxmpp import stanza +from sleekxmpp.basexmpp import BaseXMPP, SRV_SUPPORT +from sleekxmpp.xmlstream import XMLStream, RestartStream +from sleekxmpp.xmlstream.matcher import * +from sleekxmpp.xmlstream.handler import * +from sleekxmpp.xmlstream.stanzabase import StanzaBase, ET -class ComponentXMPP(basexmpp, XMLStream): - """SleekXMPP's client class. Use only for good, not evil.""" +class ComponentXMPP(BaseXMPP): - def __init__(self, jid, secret, host, port, 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, default_ns) - self.auto_authorize = None - self.stream_header = "" % jid - self.stream_footer = "" - self.server_host = host - self.server_port = port - self.set_jid(jid) - self.secret = secret - self.is_component = True - self.registerHandler(Callback('Handshake', MatchXPath('{jabber:component:accept}handshake'), self._handleHandshake)) + """ + SleekXMPP's basic XMPP server component. - def incoming_filter(self, xmlobj): - if xmlobj.tag.startswith('{jabber:client}'): - xmlobj.tag = xmlobj.tag.replace('jabber:client', self.default_ns) - for sub in xmlobj: - self.incoming_filter(sub) - return xmlobj + Use only for good, not for evil. - def start_stream_handler(self, xml): - sid = xml.get('id', '') - handshake = ET.Element('{jabber:component:accept}handshake') - if sys.version_info < (3,0): - handshake.text = hashlib.sha1("%s%s" % (sid, self.secret)).hexdigest().lower() - else: - handshake.text = hashlib.sha1(bytes("%s%s" % (sid, self.secret), 'utf-8')).hexdigest().lower() - self.sendXML(handshake) + Methods: + connect -- Overrides XMLStream.connect. + incoming_filter -- Overrides XMLStream.incoming_filter. + start_stream_handler -- Overrides XMLStream.start_stream_handler. + """ - def _handleHandshake(self, xml): - self.event("session_start") + def __init__(self, jid, secret, host, port, + plugin_config={}, plugin_whitelist=[], use_jc_ns=False): + """ + Arguments: + jid -- The JID of the component. + secret -- The secret or password for the component. + host -- The server accepting the component. + port -- The port used to connect to the server. + plugin_config -- A dictionary of plugin configurations. + plugin_whitelist -- A list of desired plugins to load + when using register_plugins. + use_js_ns -- Indicates if the 'jabber:client' namespace + should be used instead of the standard + 'jabber:component:accept' namespace. + Defaults to False. + """ + if use_jc_ns: + default_ns = 'jabber:client' + else: + default_ns = 'jabber:component:accept' + BaseXMPP.__init__(self, default_ns) - def connect(self): - logging.debug("Connecting to %s:%s" % (self.server_host, self.server_port)) - return xmlstreammod.XMLStream.connect(self, self.server_host, self.server_port) + self.auto_authorize = None + self.stream_header = "" % ( + 'xmlns="jabber:component:accept"', + 'xmlns:stream="http://etherx.jabber.org/streams"', + jid) + self.stream_footer = "" + self.server_host = host + self.server_port = port + self.set_jid(jid) + self.secret = secret + self.is_component = True + + self.register_handler( + Callback('Handshake', + MatchXPath('{jabber:component:accept}handshake'), + self._handle_handshake)) + + def connect(self): + """ + Connect to the server. + + Overrides XMLStream.connect. + """ + logging.debug("Connecting to %s:%s" % (self.server_host, + self.server_port)) + return XMLStream.connect(self, self.server_host, + self.server_port) + + def incoming_filter(self, xml): + """ + Pre-process incoming XML stanzas by converting any 'jabber:client' + namespaced elements to the component's default namespace. + + Overrides XMLStream.incoming_filter. + + Arguments: + 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. + + Overrides XMLStream.start_stream_handler. + + Arguments: + xml -- The incoming stream's root element. + """ + # 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) + + def _handle_handshake(self, xml): + """ + The handshake has been accepted. + + Arguments: + xml -- The reply handshake stanza. + """ + self.event("session_start")