From 5ab77c745270d7d5c016c1dc7ef2a82533a4b16e Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Thu, 17 Jul 2014 14:19:04 +0200 Subject: Rename to slixmpp --- slixmpp/features/__init__.py | 16 ++ slixmpp/features/feature_bind/__init__.py | 19 ++ slixmpp/features/feature_bind/bind.py | 65 ++++++ slixmpp/features/feature_bind/stanza.py | 21 ++ slixmpp/features/feature_mechanisms/__init__.py | 22 ++ slixmpp/features/feature_mechanisms/mechanisms.py | 244 +++++++++++++++++++++ .../features/feature_mechanisms/stanza/__init__.py | 16 ++ .../features/feature_mechanisms/stanza/abort.py | 24 ++ slixmpp/features/feature_mechanisms/stanza/auth.py | 49 +++++ .../feature_mechanisms/stanza/challenge.py | 39 ++++ .../features/feature_mechanisms/stanza/failure.py | 76 +++++++ .../feature_mechanisms/stanza/mechanisms.py | 53 +++++ .../features/feature_mechanisms/stanza/response.py | 39 ++++ .../features/feature_mechanisms/stanza/success.py | 38 ++++ slixmpp/features/feature_preapproval/__init__.py | 15 ++ .../features/feature_preapproval/preapproval.py | 42 ++++ slixmpp/features/feature_preapproval/stanza.py | 17 ++ slixmpp/features/feature_rosterver/__init__.py | 19 ++ slixmpp/features/feature_rosterver/rosterver.py | 42 ++++ slixmpp/features/feature_rosterver/stanza.py | 17 ++ slixmpp/features/feature_session/__init__.py | 19 ++ slixmpp/features/feature_session/session.py | 54 +++++ slixmpp/features/feature_session/stanza.py | 20 ++ slixmpp/features/feature_starttls/__init__.py | 19 ++ slixmpp/features/feature_starttls/stanza.py | 45 ++++ slixmpp/features/feature_starttls/starttls.py | 66 ++++++ 26 files changed, 1096 insertions(+) create mode 100644 slixmpp/features/__init__.py create mode 100644 slixmpp/features/feature_bind/__init__.py create mode 100644 slixmpp/features/feature_bind/bind.py create mode 100644 slixmpp/features/feature_bind/stanza.py create mode 100644 slixmpp/features/feature_mechanisms/__init__.py create mode 100644 slixmpp/features/feature_mechanisms/mechanisms.py create mode 100644 slixmpp/features/feature_mechanisms/stanza/__init__.py create mode 100644 slixmpp/features/feature_mechanisms/stanza/abort.py create mode 100644 slixmpp/features/feature_mechanisms/stanza/auth.py create mode 100644 slixmpp/features/feature_mechanisms/stanza/challenge.py create mode 100644 slixmpp/features/feature_mechanisms/stanza/failure.py create mode 100644 slixmpp/features/feature_mechanisms/stanza/mechanisms.py create mode 100644 slixmpp/features/feature_mechanisms/stanza/response.py create mode 100644 slixmpp/features/feature_mechanisms/stanza/success.py create mode 100644 slixmpp/features/feature_preapproval/__init__.py create mode 100644 slixmpp/features/feature_preapproval/preapproval.py create mode 100644 slixmpp/features/feature_preapproval/stanza.py create mode 100644 slixmpp/features/feature_rosterver/__init__.py create mode 100644 slixmpp/features/feature_rosterver/rosterver.py create mode 100644 slixmpp/features/feature_rosterver/stanza.py create mode 100644 slixmpp/features/feature_session/__init__.py create mode 100644 slixmpp/features/feature_session/session.py create mode 100644 slixmpp/features/feature_session/stanza.py create mode 100644 slixmpp/features/feature_starttls/__init__.py create mode 100644 slixmpp/features/feature_starttls/stanza.py create mode 100644 slixmpp/features/feature_starttls/starttls.py (limited to 'slixmpp/features') diff --git a/slixmpp/features/__init__.py b/slixmpp/features/__init__.py new file mode 100644 index 00000000..5b728ee8 --- /dev/null +++ b/slixmpp/features/__init__.py @@ -0,0 +1,16 @@ +""" + Slixmpp: The Slick XMPP Library + Copyright (C) 2011 Nathanael C. Fritz + This file is part of Slixmpp. + + See the file LICENSE for copying permission. +""" + +__all__ = [ + 'feature_starttls', + 'feature_mechanisms', + 'feature_bind', + 'feature_session', + 'feature_rosterver', + 'feature_preapproval' +] diff --git a/slixmpp/features/feature_bind/__init__.py b/slixmpp/features/feature_bind/__init__.py new file mode 100644 index 00000000..65f5b626 --- /dev/null +++ b/slixmpp/features/feature_bind/__init__.py @@ -0,0 +1,19 @@ +""" + Slixmpp: The Slick XMPP Library + Copyright (C) 2011 Nathanael C. Fritz + This file is part of Slixmpp. + + See the file LICENSE for copying permission. +""" + +from slixmpp.plugins.base import register_plugin + +from slixmpp.features.feature_bind.bind import FeatureBind +from slixmpp.features.feature_bind.stanza import Bind + + +register_plugin(FeatureBind) + + +# Retain some backwards compatibility +feature_bind = FeatureBind diff --git a/slixmpp/features/feature_bind/bind.py b/slixmpp/features/feature_bind/bind.py new file mode 100644 index 00000000..ac69ee77 --- /dev/null +++ b/slixmpp/features/feature_bind/bind.py @@ -0,0 +1,65 @@ +""" + Slixmpp: The Slick XMPP Library + Copyright (C) 2011 Nathanael C. Fritz + This file is part of Slixmpp. + + See the file LICENSE for copying permission. +""" + +import logging + +from slixmpp.jid import JID +from slixmpp.stanza import Iq, StreamFeatures +from slixmpp.features.feature_bind import stanza +from slixmpp.xmlstream import register_stanza_plugin +from slixmpp.plugins import BasePlugin + + +log = logging.getLogger(__name__) + + +class FeatureBind(BasePlugin): + + name = 'feature_bind' + description = 'RFC 6120: Stream Feature: Resource Binding' + dependencies = set() + stanza = stanza + + def plugin_init(self): + self.xmpp.register_feature('bind', + self._handle_bind_resource, + restart=False, + order=10000) + + register_stanza_plugin(Iq, stanza.Bind) + register_stanza_plugin(StreamFeatures, stanza.Bind) + + def _handle_bind_resource(self, features): + """ + Handle requesting a specific resource. + + Arguments: + features -- The stream features stanza. + """ + log.debug("Requesting resource: %s", self.xmpp.requested_jid.resource) + iq = self.xmpp.Iq() + iq['type'] = 'set' + iq.enable('bind') + if self.xmpp.requested_jid.resource: + iq['bind']['resource'] = self.xmpp.requested_jid.resource + response = iq.send(now=True) + + self.xmpp.boundjid = JID(response['bind']['jid'], cache_lock=True) + self.xmpp.bound = True + self.xmpp.event('session_bind', self.xmpp.boundjid, direct=True) + self.xmpp.session_bind_event.set() + + self.xmpp.features.add('bind') + + log.info("JID set to: %s", self.xmpp.boundjid.full) + + if 'session' not in features['features']: + log.debug("Established Session") + self.xmpp.sessionstarted = True + self.xmpp.session_started_event.set() + self.xmpp.event('session_start') diff --git a/slixmpp/features/feature_bind/stanza.py b/slixmpp/features/feature_bind/stanza.py new file mode 100644 index 00000000..b9ecd97c --- /dev/null +++ b/slixmpp/features/feature_bind/stanza.py @@ -0,0 +1,21 @@ +""" + Slixmpp: The Slick XMPP Library + Copyright (C) 2011 Nathanael C. Fritz + This file is part of Slixmpp. + + See the file LICENSE for copying permission. +""" + +from slixmpp.xmlstream import ElementBase + + +class Bind(ElementBase): + + """ + """ + + name = 'bind' + namespace = 'urn:ietf:params:xml:ns:xmpp-bind' + interfaces = set(('resource', 'jid')) + sub_interfaces = interfaces + plugin_attrib = 'bind' diff --git a/slixmpp/features/feature_mechanisms/__init__.py b/slixmpp/features/feature_mechanisms/__init__.py new file mode 100644 index 00000000..7532eaa2 --- /dev/null +++ b/slixmpp/features/feature_mechanisms/__init__.py @@ -0,0 +1,22 @@ +""" + Slixmpp: The Slick XMPP Library + Copyright (C) 2011 Nathanael C. Fritz + This file is part of Slixmpp. + + See the file LICENSE for copying permission. +""" + +from slixmpp.plugins.base import register_plugin + +from slixmpp.features.feature_mechanisms.mechanisms import FeatureMechanisms +from slixmpp.features.feature_mechanisms.stanza import Mechanisms +from slixmpp.features.feature_mechanisms.stanza import Auth +from slixmpp.features.feature_mechanisms.stanza import Success +from slixmpp.features.feature_mechanisms.stanza import Failure + + +register_plugin(FeatureMechanisms) + + +# Retain some backwards compatibility +feature_mechanisms = FeatureMechanisms diff --git a/slixmpp/features/feature_mechanisms/mechanisms.py b/slixmpp/features/feature_mechanisms/mechanisms.py new file mode 100644 index 00000000..663bfe57 --- /dev/null +++ b/slixmpp/features/feature_mechanisms/mechanisms.py @@ -0,0 +1,244 @@ +""" + Slixmpp: The Slick XMPP Library + Copyright (C) 2011 Nathanael C. Fritz + This file is part of Slixmpp. + + See the file LICENSE for copying permission. +""" + +import ssl +import logging + +from slixmpp.util import sasl +from slixmpp.util.stringprep_profiles import StringPrepError +from slixmpp.stanza import StreamFeatures +from slixmpp.xmlstream import RestartStream, register_stanza_plugin +from slixmpp.plugins import BasePlugin +from slixmpp.xmlstream.matcher import MatchXPath +from slixmpp.xmlstream.handler import Callback +from slixmpp.features.feature_mechanisms import stanza + + +log = logging.getLogger(__name__) + + +class FeatureMechanisms(BasePlugin): + + name = 'feature_mechanisms' + description = 'RFC 6120: Stream Feature: SASL' + dependencies = set() + stanza = stanza + default_config = { + 'use_mech': None, + 'use_mechs': None, + 'min_mech': None, + 'sasl_callback': None, + 'security_callback': None, + 'encrypted_plain': True, + 'unencrypted_plain': False, + 'unencrypted_digest': False, + 'unencrypted_cram': False, + 'unencrypted_scram': True, + 'order': 100 + } + + def plugin_init(self): + if self.sasl_callback is None: + self.sasl_callback = self._default_credentials + + if self.security_callback is None: + self.security_callback = self._default_security + + creds = self.sasl_callback(set(['username']), set()) + if not self.use_mech and not creds['username']: + self.use_mech = 'ANONYMOUS' + + self.mech = None + self.mech_list = set() + self.attempted_mechs = set() + + register_stanza_plugin(StreamFeatures, stanza.Mechanisms) + + self.xmpp.register_stanza(stanza.Success) + self.xmpp.register_stanza(stanza.Failure) + self.xmpp.register_stanza(stanza.Auth) + self.xmpp.register_stanza(stanza.Challenge) + self.xmpp.register_stanza(stanza.Response) + self.xmpp.register_stanza(stanza.Abort) + + self.xmpp.register_handler( + Callback('SASL Success', + MatchXPath(stanza.Success.tag_name()), + self._handle_success, + instream=True)) + self.xmpp.register_handler( + Callback('SASL Failure', + MatchXPath(stanza.Failure.tag_name()), + self._handle_fail, + instream=True)) + self.xmpp.register_handler( + Callback('SASL Challenge', + MatchXPath(stanza.Challenge.tag_name()), + self._handle_challenge)) + + self.xmpp.register_feature('mechanisms', + self._handle_sasl_auth, + restart=True, + order=self.order) + + def _default_credentials(self, required_values, optional_values): + creds = self.xmpp.credentials + result = {} + values = required_values.union(optional_values) + for value in values: + if value == 'username': + result[value] = creds.get('username', self.xmpp.requested_jid.user) + elif value == 'email': + jid = self.xmpp.requested_jid.bare + result[value] = creds.get('email', jid) + elif value == 'channel_binding': + if hasattr(self.xmpp.socket, 'get_channel_binding'): + result[value] = self.xmpp.socket.get_channel_binding() + else: + log.debug("Channel binding not supported.") + log.debug("Use Python 3.3+ for channel binding and " + \ + "SCRAM-SHA-1-PLUS support") + result[value] = None + elif value == 'host': + result[value] = creds.get('host', self.xmpp.requested_jid.domain) + elif value == 'realm': + result[value] = creds.get('realm', self.xmpp.requested_jid.domain) + elif value == 'service-name': + result[value] = creds.get('service-name', self.xmpp._service_name) + elif value == 'service': + result[value] = creds.get('service', 'xmpp') + elif value in creds: + result[value] = creds[value] + return result + + def _default_security(self, values): + result = {} + for value in values: + if value == 'encrypted': + if 'starttls' in self.xmpp.features: + result[value] = True + elif isinstance(self.xmpp.socket, ssl.SSLSocket): + result[value] = True + else: + result[value] = False + else: + result[value] = self.config.get(value, False) + return result + + def _handle_sasl_auth(self, features): + """ + Handle authenticating using SASL. + + Arguments: + features -- The stream features stanza. + """ + if 'mechanisms' in self.xmpp.features: + # SASL authentication has already succeeded, but the + # server has incorrectly offered it again. + return False + + enforce_limit = False + limited_mechs = self.use_mechs + + if limited_mechs is None: + limited_mechs = set() + elif limited_mechs and not isinstance(limited_mechs, set): + limited_mechs = set(limited_mechs) + enforce_limit = True + + if self.use_mech: + limited_mechs.add(self.use_mech) + enforce_limit = True + + if enforce_limit: + self.use_mechs = limited_mechs + + self.mech_list = set(features['mechanisms']) + + return self._send_auth() + + def _send_auth(self): + mech_list = self.mech_list - self.attempted_mechs + try: + self.mech = sasl.choose(mech_list, + self.sasl_callback, + self.security_callback, + limit=self.use_mechs, + min_mech=self.min_mech) + except sasl.SASLNoAppropriateMechanism: + log.error("No appropriate login method.") + self.xmpp.event("no_auth", direct=True) + self.xmpp.event("failed_auth", direct=True) + self.attempted_mechs = set() + return self.xmpp.disconnect() + except StringPrepError: + log.exception("A credential value did not pass SASLprep.") + self.xmpp.disconnect() + + resp = stanza.Auth(self.xmpp) + resp['mechanism'] = self.mech.name + try: + resp['value'] = self.mech.process() + except sasl.SASLCancelled: + self.attempted_mechs.add(self.mech.name) + self._send_auth() + except sasl.SASLFailed: + self.attempted_mechs.add(self.mech.name) + self._send_auth() + except sasl.SASLMutualAuthFailed: + log.error("Mutual authentication failed! " + \ + "A security breach is possible.") + self.attempted_mechs.add(self.mech.name) + self.xmpp.disconnect() + else: + resp.send(now=True) + + return True + + def _handle_challenge(self, stanza): + """SASL challenge received. Process and send response.""" + resp = self.stanza.Response(self.xmpp) + try: + resp['value'] = self.mech.process(stanza['value']) + except sasl.SASLCancelled: + self.stanza.Abort(self.xmpp).send() + except sasl.SASLFailed: + self.stanza.Abort(self.xmpp).send() + except sasl.SASLMutualAuthFailed: + log.error("Mutual authentication failed! " + \ + "A security breach is possible.") + self.attempted_mechs.add(self.mech.name) + self.xmpp.disconnect() + else: + if resp.get_value() == '': + resp.del_value() + resp.send(now=True) + + def _handle_success(self, stanza): + """SASL authentication succeeded. Restart the stream.""" + try: + final = self.mech.process(stanza['value']) + except sasl.SASLMutualAuthFailed: + log.error("Mutual authentication failed! " + \ + "A security breach is possible.") + self.attempted_mechs.add(self.mech.name) + self.xmpp.disconnect() + else: + self.attempted_mechs = set() + self.xmpp.authenticated = True + self.xmpp.features.add('mechanisms') + self.xmpp.event('auth_success', stanza, direct=True) + raise RestartStream() + + def _handle_fail(self, stanza): + """SASL authentication failed. Disconnect and shutdown.""" + self.attempted_mechs.add(self.mech.name) + log.info("Authentication failed: %s", stanza['condition']) + self.xmpp.event("failed_auth", stanza, direct=True) + self._send_auth() + return True diff --git a/slixmpp/features/feature_mechanisms/stanza/__init__.py b/slixmpp/features/feature_mechanisms/stanza/__init__.py new file mode 100644 index 00000000..4d515bf2 --- /dev/null +++ b/slixmpp/features/feature_mechanisms/stanza/__init__.py @@ -0,0 +1,16 @@ +""" + Slixmpp: The Slick XMPP Library + Copyright (C) 2011 Nathanael C. Fritz + This file is part of Slixmpp. + + See the file LICENSE for copying permission. +""" + + +from slixmpp.features.feature_mechanisms.stanza.mechanisms import Mechanisms +from slixmpp.features.feature_mechanisms.stanza.auth import Auth +from slixmpp.features.feature_mechanisms.stanza.success import Success +from slixmpp.features.feature_mechanisms.stanza.failure import Failure +from slixmpp.features.feature_mechanisms.stanza.challenge import Challenge +from slixmpp.features.feature_mechanisms.stanza.response import Response +from slixmpp.features.feature_mechanisms.stanza.abort import Abort diff --git a/slixmpp/features/feature_mechanisms/stanza/abort.py b/slixmpp/features/feature_mechanisms/stanza/abort.py new file mode 100644 index 00000000..fca29aee --- /dev/null +++ b/slixmpp/features/feature_mechanisms/stanza/abort.py @@ -0,0 +1,24 @@ +""" + Slixmpp: The Slick XMPP Library + Copyright (C) 2011 Nathanael C. Fritz + This file is part of Slixmpp. + + See the file LICENSE for copying permission. +""" + +from slixmpp.xmlstream import StanzaBase + + +class Abort(StanzaBase): + + """ + """ + + name = 'abort' + namespace = 'urn:ietf:params:xml:ns:xmpp-sasl' + interfaces = set() + plugin_attrib = name + + def setup(self, xml): + StanzaBase.setup(self, xml) + self.xml.tag = self.tag_name() diff --git a/slixmpp/features/feature_mechanisms/stanza/auth.py b/slixmpp/features/feature_mechanisms/stanza/auth.py new file mode 100644 index 00000000..c32069ec --- /dev/null +++ b/slixmpp/features/feature_mechanisms/stanza/auth.py @@ -0,0 +1,49 @@ +""" + Slixmpp: The Slick XMPP Library + Copyright (C) 2011 Nathanael C. Fritz + This file is part of Slixmpp. + + See the file LICENSE for copying permission. +""" + +import base64 + +from slixmpp.util import bytes +from slixmpp.xmlstream import StanzaBase + + +class Auth(StanzaBase): + + """ + """ + + name = 'auth' + namespace = 'urn:ietf:params:xml:ns:xmpp-sasl' + interfaces = set(('mechanism', 'value')) + plugin_attrib = name + + #: Some SASL mechs require sending values as is, + #: without converting base64. + plain_mechs = set(['X-MESSENGER-OAUTH2']) + + def setup(self, xml): + StanzaBase.setup(self, xml) + self.xml.tag = self.tag_name() + + def get_value(self): + if not self['mechanism'] in self.plain_mechs: + return base64.b64decode(bytes(self.xml.text)) + else: + return self.xml.text + + def set_value(self, values): + if not self['mechanism'] in self.plain_mechs: + if values: + self.xml.text = bytes(base64.b64encode(values)).decode('utf-8') + elif values == b'': + self.xml.text = '=' + else: + self.xml.text = bytes(values).decode('utf-8') + + def del_value(self): + self.xml.text = '' diff --git a/slixmpp/features/feature_mechanisms/stanza/challenge.py b/slixmpp/features/feature_mechanisms/stanza/challenge.py new file mode 100644 index 00000000..21a061ee --- /dev/null +++ b/slixmpp/features/feature_mechanisms/stanza/challenge.py @@ -0,0 +1,39 @@ +""" + Slixmpp: The Slick XMPP Library + Copyright (C) 2011 Nathanael C. Fritz + This file is part of Slixmpp. + + See the file LICENSE for copying permission. +""" + +import base64 + +from slixmpp.util import bytes +from slixmpp.xmlstream import StanzaBase + + +class Challenge(StanzaBase): + + """ + """ + + name = 'challenge' + namespace = 'urn:ietf:params:xml:ns:xmpp-sasl' + interfaces = set(('value',)) + plugin_attrib = name + + def setup(self, xml): + StanzaBase.setup(self, xml) + self.xml.tag = self.tag_name() + + def get_value(self): + return base64.b64decode(bytes(self.xml.text)) + + def set_value(self, values): + if values: + self.xml.text = bytes(base64.b64encode(values)).decode('utf-8') + else: + self.xml.text = '=' + + def del_value(self): + self.xml.text = '' diff --git a/slixmpp/features/feature_mechanisms/stanza/failure.py b/slixmpp/features/feature_mechanisms/stanza/failure.py new file mode 100644 index 00000000..cc0ac877 --- /dev/null +++ b/slixmpp/features/feature_mechanisms/stanza/failure.py @@ -0,0 +1,76 @@ +""" + Slixmpp: The Slick XMPP Library + Copyright (C) 2011 Nathanael C. Fritz + This file is part of Slixmpp. + + See the file LICENSE for copying permission. +""" + +from slixmpp.xmlstream import StanzaBase, ET + + +class Failure(StanzaBase): + + """ + """ + + name = 'failure' + namespace = 'urn:ietf:params:xml:ns:xmpp-sasl' + interfaces = set(('condition', 'text')) + plugin_attrib = name + sub_interfaces = set(('text',)) + conditions = set(('aborted', 'account-disabled', 'credentials-expired', + 'encryption-required', 'incorrect-encoding', 'invalid-authzid', + 'invalid-mechanism', 'malformed-request', 'mechansism-too-weak', + 'not-authorized', 'temporary-auth-failure')) + + def setup(self, xml=None): + """ + Populate the stanza object using an optional XML object. + + Overrides ElementBase.setup. + + Sets a default error type and condition, and changes the + parent stanza's type to 'error'. + + Arguments: + xml -- Use an existing XML object for the stanza's values. + """ + # StanzaBase overrides self.namespace + self.namespace = Failure.namespace + + if StanzaBase.setup(self, xml): + #If we had to generate XML then set default values. + self['condition'] = 'not-authorized' + + self.xml.tag = self.tag_name() + + def get_condition(self): + """Return the condition element's name.""" + for child in self.xml: + if "{%s}" % self.namespace in child.tag: + cond = child.tag.split('}', 1)[-1] + if cond in self.conditions: + return cond + return 'not-authorized' + + def set_condition(self, value): + """ + Set the tag name of the condition element. + + Arguments: + value -- The tag name of the condition element. + """ + if value in self.conditions: + del self['condition'] + self.xml.append(ET.Element("{%s}%s" % (self.namespace, value))) + return self + + def del_condition(self): + """Remove the condition element.""" + for child in self.xml: + if "{%s}" % self.condition_ns in child.tag: + tag = child.tag.split('}', 1)[-1] + if tag in self.conditions: + self.xml.remove(child) + return self diff --git a/slixmpp/features/feature_mechanisms/stanza/mechanisms.py b/slixmpp/features/feature_mechanisms/stanza/mechanisms.py new file mode 100644 index 00000000..4437e155 --- /dev/null +++ b/slixmpp/features/feature_mechanisms/stanza/mechanisms.py @@ -0,0 +1,53 @@ +""" + Slixmpp: The Slick XMPP Library + Copyright (C) 2011 Nathanael C. Fritz + This file is part of Slixmpp. + + See the file LICENSE for copying permission. +""" + +from slixmpp.xmlstream import ElementBase, ET + + +class Mechanisms(ElementBase): + + """ + """ + + name = 'mechanisms' + namespace = 'urn:ietf:params:xml:ns:xmpp-sasl' + interfaces = set(('mechanisms', 'required')) + plugin_attrib = name + is_extension = True + + def get_required(self): + """ + """ + return True + + def get_mechanisms(self): + """ + """ + results = [] + mechs = self.findall('{%s}mechanism' % self.namespace) + if mechs: + for mech in mechs: + results.append(mech.text) + return results + + def set_mechanisms(self, values): + """ + """ + self.del_mechanisms() + for val in values: + mech = ET.Element('{%s}mechanism' % self.namespace) + mech.text = val + self.append(mech) + + def del_mechanisms(self): + """ + """ + mechs = self.findall('{%s}mechanism' % self.namespace) + if mechs: + for mech in mechs: + self.xml.remove(mech) diff --git a/slixmpp/features/feature_mechanisms/stanza/response.py b/slixmpp/features/feature_mechanisms/stanza/response.py new file mode 100644 index 00000000..8da236ba --- /dev/null +++ b/slixmpp/features/feature_mechanisms/stanza/response.py @@ -0,0 +1,39 @@ +""" + Slixmpp: The Slick XMPP Library + Copyright (C) 2011 Nathanael C. Fritz + This file is part of Slixmpp. + + See the file LICENSE for copying permission. +""" + +import base64 + +from slixmpp.util import bytes +from slixmpp.xmlstream import StanzaBase + + +class Response(StanzaBase): + + """ + """ + + name = 'response' + namespace = 'urn:ietf:params:xml:ns:xmpp-sasl' + interfaces = set(('value',)) + plugin_attrib = name + + def setup(self, xml): + StanzaBase.setup(self, xml) + self.xml.tag = self.tag_name() + + def get_value(self): + return base64.b64decode(bytes(self.xml.text)) + + def set_value(self, values): + if values: + self.xml.text = bytes(base64.b64encode(values)).decode('utf-8') + else: + self.xml.text = '=' + + def del_value(self): + self.xml.text = '' diff --git a/slixmpp/features/feature_mechanisms/stanza/success.py b/slixmpp/features/feature_mechanisms/stanza/success.py new file mode 100644 index 00000000..f7cde0f8 --- /dev/null +++ b/slixmpp/features/feature_mechanisms/stanza/success.py @@ -0,0 +1,38 @@ +""" + Slixmpp: The Slick XMPP Library + Copyright (C) 2011 Nathanael C. Fritz + This file is part of Slixmpp. + + See the file LICENSE for copying permission. +""" + +import base64 + +from slixmpp.util import bytes +from slixmpp.xmlstream import StanzaBase + +class Success(StanzaBase): + + """ + """ + + name = 'success' + namespace = 'urn:ietf:params:xml:ns:xmpp-sasl' + interfaces = set(['value']) + plugin_attrib = name + + def setup(self, xml): + StanzaBase.setup(self, xml) + self.xml.tag = self.tag_name() + + def get_value(self): + return base64.b64decode(bytes(self.xml.text)) + + def set_value(self, values): + if values: + self.xml.text = bytes(base64.b64encode(values)).decode('utf-8') + else: + self.xml.text = '=' + + def del_value(self): + self.xml.text = '' diff --git a/slixmpp/features/feature_preapproval/__init__.py b/slixmpp/features/feature_preapproval/__init__.py new file mode 100644 index 00000000..f22be050 --- /dev/null +++ b/slixmpp/features/feature_preapproval/__init__.py @@ -0,0 +1,15 @@ +""" + Slixmpp: The Slick XMPP Library + Copyright (C) 2012 Nathanael C. Fritz + This file is part of Slixmpp. + + See the file LICENSE for copying permission. +""" + +from slixmpp.plugins.base import register_plugin + +from slixmpp.features.feature_preapproval.preapproval import FeaturePreApproval +from slixmpp.features.feature_preapproval.stanza import PreApproval + + +register_plugin(FeaturePreApproval) diff --git a/slixmpp/features/feature_preapproval/preapproval.py b/slixmpp/features/feature_preapproval/preapproval.py new file mode 100644 index 00000000..1d60d7e7 --- /dev/null +++ b/slixmpp/features/feature_preapproval/preapproval.py @@ -0,0 +1,42 @@ +""" + Slixmpp: The Slick XMPP Library + Copyright (C) 2012 Nathanael C. Fritz + This file is part of Slixmpp. + + See the file LICENSE for copying permission. +""" + +import logging + +from slixmpp.stanza import StreamFeatures +from slixmpp.features.feature_preapproval import stanza +from slixmpp.xmlstream import register_stanza_plugin +from slixmpp.plugins.base import BasePlugin + + +log = logging.getLogger(__name__) + + +class FeaturePreApproval(BasePlugin): + + name = 'feature_preapproval' + description = 'RFC 6121: Stream Feature: Subscription Pre-Approval' + dependences = set() + stanza = stanza + + def plugin_init(self): + self.xmpp.register_feature('preapproval', + self._handle_preapproval, + restart=False, + order=9001) + + register_stanza_plugin(StreamFeatures, stanza.PreApproval) + + def _handle_preapproval(self, features): + """Save notice that the server support subscription pre-approvals. + + Arguments: + features -- The stream features stanza. + """ + log.debug("Server supports subscription pre-approvals.") + self.xmpp.features.add('preapproval') diff --git a/slixmpp/features/feature_preapproval/stanza.py b/slixmpp/features/feature_preapproval/stanza.py new file mode 100644 index 00000000..03d721ef --- /dev/null +++ b/slixmpp/features/feature_preapproval/stanza.py @@ -0,0 +1,17 @@ +""" + Slixmpp: The Slick XMPP Library + Copyright (C) 2012 Nathanael C. Fritz + This file is part of Slixmpp. + + See the file LICENSE for copying permission. +""" + +from slixmpp.xmlstream import ElementBase + + +class PreApproval(ElementBase): + + name = 'sub' + namespace = 'urn:xmpp:features:pre-approval' + interfaces = set() + plugin_attrib = 'preapproval' diff --git a/slixmpp/features/feature_rosterver/__init__.py b/slixmpp/features/feature_rosterver/__init__.py new file mode 100644 index 00000000..d338b584 --- /dev/null +++ b/slixmpp/features/feature_rosterver/__init__.py @@ -0,0 +1,19 @@ +""" + Slixmpp: The Slick XMPP Library + Copyright (C) 2012 Nathanael C. Fritz + This file is part of Slixmpp. + + See the file LICENSE for copying permission. +""" + +from slixmpp.plugins.base import register_plugin + +from slixmpp.features.feature_rosterver.rosterver import FeatureRosterVer +from slixmpp.features.feature_rosterver.stanza import RosterVer + + +register_plugin(FeatureRosterVer) + + +# Retain some backwards compatibility +feature_rosterver = FeatureRosterVer diff --git a/slixmpp/features/feature_rosterver/rosterver.py b/slixmpp/features/feature_rosterver/rosterver.py new file mode 100644 index 00000000..2c2c8c84 --- /dev/null +++ b/slixmpp/features/feature_rosterver/rosterver.py @@ -0,0 +1,42 @@ +""" + Slixmpp: The Slick XMPP Library + Copyright (C) 2012 Nathanael C. Fritz + This file is part of Slixmpp. + + See the file LICENSE for copying permission. +""" + +import logging + +from slixmpp.stanza import StreamFeatures +from slixmpp.features.feature_rosterver import stanza +from slixmpp.xmlstream import register_stanza_plugin +from slixmpp.plugins.base import BasePlugin + + +log = logging.getLogger(__name__) + + +class FeatureRosterVer(BasePlugin): + + name = 'feature_rosterver' + description = 'RFC 6121: Stream Feature: Roster Versioning' + dependences = set() + stanza = stanza + + def plugin_init(self): + self.xmpp.register_feature('rosterver', + self._handle_rosterver, + restart=False, + order=9000) + + register_stanza_plugin(StreamFeatures, stanza.RosterVer) + + def _handle_rosterver(self, features): + """Enable using roster versioning. + + Arguments: + features -- The stream features stanza. + """ + log.debug("Enabling roster versioning.") + self.xmpp.features.add('rosterver') diff --git a/slixmpp/features/feature_rosterver/stanza.py b/slixmpp/features/feature_rosterver/stanza.py new file mode 100644 index 00000000..c9a4a2da --- /dev/null +++ b/slixmpp/features/feature_rosterver/stanza.py @@ -0,0 +1,17 @@ +""" + Slixmpp: The Slick XMPP Library + Copyright (C) 2012 Nathanael C. Fritz + This file is part of Slixmpp. + + See the file LICENSE for copying permission. +""" + +from slixmpp.xmlstream import ElementBase + + +class RosterVer(ElementBase): + + name = 'ver' + namespace = 'urn:xmpp:features:rosterver' + interfaces = set() + plugin_attrib = 'rosterver' diff --git a/slixmpp/features/feature_session/__init__.py b/slixmpp/features/feature_session/__init__.py new file mode 100644 index 00000000..0ac950c6 --- /dev/null +++ b/slixmpp/features/feature_session/__init__.py @@ -0,0 +1,19 @@ +""" + Slixmpp: The Slick XMPP Library + Copyright (C) 2011 Nathanael C. Fritz + This file is part of Slixmpp. + + See the file LICENSE for copying permission. +""" + +from slixmpp.plugins.base import register_plugin + +from slixmpp.features.feature_session.session import FeatureSession +from slixmpp.features.feature_session.stanza import Session + + +register_plugin(FeatureSession) + + +# Retain some backwards compatibility +feature_session = FeatureSession diff --git a/slixmpp/features/feature_session/session.py b/slixmpp/features/feature_session/session.py new file mode 100644 index 00000000..c2694a9f --- /dev/null +++ b/slixmpp/features/feature_session/session.py @@ -0,0 +1,54 @@ +""" + Slixmpp: The Slick XMPP Library + Copyright (C) 2011 Nathanael C. Fritz + This file is part of Slixmpp. + + See the file LICENSE for copying permission. +""" + +import logging + +from slixmpp.stanza import Iq, StreamFeatures +from slixmpp.xmlstream import register_stanza_plugin +from slixmpp.plugins import BasePlugin + +from slixmpp.features.feature_session import stanza + + +log = logging.getLogger(__name__) + + +class FeatureSession(BasePlugin): + + name = 'feature_session' + description = 'RFC 3920: Stream Feature: Start Session' + dependencies = set() + stanza = stanza + + def plugin_init(self): + self.xmpp.register_feature('session', + self._handle_start_session, + restart=False, + order=10001) + + register_stanza_plugin(Iq, stanza.Session) + register_stanza_plugin(StreamFeatures, stanza.Session) + + def _handle_start_session(self, features): + """ + Handle the start of the session. + + Arguments: + feature -- The stream features element. + """ + iq = self.xmpp.Iq() + iq['type'] = 'set' + iq.enable('session') + iq.send(now=True) + + self.xmpp.features.add('session') + + log.debug("Established Session") + self.xmpp.sessionstarted = True + self.xmpp.session_started_event.set() + self.xmpp.event('session_start') diff --git a/slixmpp/features/feature_session/stanza.py b/slixmpp/features/feature_session/stanza.py new file mode 100644 index 00000000..f68483d6 --- /dev/null +++ b/slixmpp/features/feature_session/stanza.py @@ -0,0 +1,20 @@ +""" + Slixmpp: The Slick XMPP Library + Copyright (C) 2011 Nathanael C. Fritz + This file is part of Slixmpp. + + See the file LICENSE for copying permission. +""" + +from slixmpp.xmlstream import ElementBase + + +class Session(ElementBase): + + """ + """ + + name = 'session' + namespace = 'urn:ietf:params:xml:ns:xmpp-session' + interfaces = set() + plugin_attrib = 'session' diff --git a/slixmpp/features/feature_starttls/__init__.py b/slixmpp/features/feature_starttls/__init__.py new file mode 100644 index 00000000..81a88650 --- /dev/null +++ b/slixmpp/features/feature_starttls/__init__.py @@ -0,0 +1,19 @@ +""" + Slixmpp: The Slick XMPP Library + Copyright (C) 2011 Nathanael C. Fritz + This file is part of Slixmpp. + + See the file LICENSE for copying permission. +""" + +from slixmpp.plugins.base import register_plugin + +from slixmpp.features.feature_starttls.starttls import FeatureSTARTTLS +from slixmpp.features.feature_starttls.stanza import * + + +register_plugin(FeatureSTARTTLS) + + +# Retain some backwards compatibility +feature_starttls = FeatureSTARTTLS diff --git a/slixmpp/features/feature_starttls/stanza.py b/slixmpp/features/feature_starttls/stanza.py new file mode 100644 index 00000000..df50897e --- /dev/null +++ b/slixmpp/features/feature_starttls/stanza.py @@ -0,0 +1,45 @@ +""" + Slixmpp: The Slick XMPP Library + Copyright (C) 2011 Nathanael C. Fritz + This file is part of Slixmpp. + + See the file LICENSE for copying permission. +""" + +from slixmpp.xmlstream import StanzaBase, ElementBase + + +class STARTTLS(ElementBase): + + """ + """ + + name = 'starttls' + namespace = 'urn:ietf:params:xml:ns:xmpp-tls' + interfaces = set(('required',)) + plugin_attrib = name + + def get_required(self): + """ + """ + return True + + +class Proceed(StanzaBase): + + """ + """ + + name = 'proceed' + namespace = 'urn:ietf:params:xml:ns:xmpp-tls' + interfaces = set() + + +class Failure(StanzaBase): + + """ + """ + + name = 'failure' + namespace = 'urn:ietf:params:xml:ns:xmpp-tls' + interfaces = set() diff --git a/slixmpp/features/feature_starttls/starttls.py b/slixmpp/features/feature_starttls/starttls.py new file mode 100644 index 00000000..4b9dd60b --- /dev/null +++ b/slixmpp/features/feature_starttls/starttls.py @@ -0,0 +1,66 @@ +""" + Slixmpp: The Slick XMPP Library + Copyright (C) 2011 Nathanael C. Fritz + This file is part of Slixmpp. + + See the file LICENSE for copying permission. +""" + +import logging + +from slixmpp.stanza import StreamFeatures +from slixmpp.xmlstream import RestartStream, register_stanza_plugin +from slixmpp.plugins import BasePlugin +from slixmpp.xmlstream.matcher import MatchXPath +from slixmpp.xmlstream.handler import Callback +from slixmpp.features.feature_starttls import stanza + + +log = logging.getLogger(__name__) + + +class FeatureSTARTTLS(BasePlugin): + + name = 'feature_starttls' + description = 'RFC 6120: Stream Feature: STARTTLS' + dependencies = set() + stanza = stanza + + def plugin_init(self): + self.xmpp.register_handler( + Callback('STARTTLS Proceed', + MatchXPath(stanza.Proceed.tag_name()), + self._handle_starttls_proceed, + instream=True)) + self.xmpp.register_feature('starttls', + self._handle_starttls, + restart=True, + order=self.config.get('order', 0)) + + self.xmpp.register_stanza(stanza.Proceed) + self.xmpp.register_stanza(stanza.Failure) + register_stanza_plugin(StreamFeatures, stanza.STARTTLS) + + def _handle_starttls(self, features): + """ + Handle notification that the server supports TLS. + + Arguments: + features -- The stream:features element. + """ + if 'starttls' in self.xmpp.features: + # We have already negotiated TLS, but the server is + # offering it again, against spec. + return False + elif not self.xmpp.use_tls: + return False + else: + self.xmpp.send(features['starttls'], now=True) + return True + + def _handle_starttls_proceed(self, proceed): + """Restart the XML stream when TLS is accepted.""" + log.debug("Starting TLS") + if self.xmpp.start_tls(): + self.xmpp.features.add('starttls') + raise RestartStream() -- cgit v1.2.3