diff options
Diffstat (limited to 'sleekxmpp/features')
-rw-r--r-- | sleekxmpp/features/__init__.py | 10 | ||||
-rw-r--r-- | sleekxmpp/features/feature_bind.py | 55 | ||||
-rw-r--r-- | sleekxmpp/features/feature_mechanisms.py | 116 | ||||
-rw-r--r-- | sleekxmpp/features/feature_session.py | 46 | ||||
-rw-r--r-- | sleekxmpp/features/feature_starttls.py | 61 | ||||
-rw-r--r-- | sleekxmpp/features/sasl_anonymous.py | 31 | ||||
-rw-r--r-- | sleekxmpp/features/sasl_plain.py | 41 |
7 files changed, 360 insertions, 0 deletions
diff --git a/sleekxmpp/features/__init__.py b/sleekxmpp/features/__init__.py new file mode 100644 index 00000000..940a37f1 --- /dev/null +++ b/sleekxmpp/features/__init__.py @@ -0,0 +1,10 @@ +""" + SleekXMPP: The Sleek XMPP Library + Copyright (C) 2010 Nathanael C. Fritz + This file is part of SleekXMPP. + + See the file LICENSE for copying permission. +""" + +__all__ = ['feature_starttls', 'feature_mechanisms', + 'sasl_plain', 'sasl_anonymous'] diff --git a/sleekxmpp/features/feature_bind.py b/sleekxmpp/features/feature_bind.py new file mode 100644 index 00000000..caa3844b --- /dev/null +++ b/sleekxmpp/features/feature_bind.py @@ -0,0 +1,55 @@ +""" + SleekXMPP: The Sleek XMPP Library + Copyright (C) 2010 Nathanael C. Fritz + This file is part of SleekXMPP. + + See the file LICENSE for copying permission. +""" + +import logging + +from sleekxmpp.xmlstream.matcher import * +from sleekxmpp.xmlstream.handler import * +from sleekxmpp.plugins.base import base_plugin + + +log = logging.getLogger(__name__) + + +class feature_bind(base_plugin): + + def plugin_init(self): + self.name = 'Bind Resource' + self.rfc = '6120' + self.description = 'Resource Binding Stream Feature' + + self.xmpp.register_feature('bind', + self._handle_bind_resource, + restart=False, + order=10000) + + def _handle_bind_resource(self, features): + """ + Handle requesting a specific resource. + + Arguments: + features -- The stream features stanza. + """ + log.debug("Requesting resource: %s" % self.xmpp.boundjid.resource) + iq = self.xmpp.Iq() + iq['type'] = 'set' + iq.enable('bind') + if self.xmpp.boundjid.resource: + iq['bind']['resource'] = self.xmpp.boundjid.resource + response = iq.send(now=True) + + self.xmpp.set_jid(response['bind']['jid']) + self.xmpp.bound = True + + log.info("Node 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/sleekxmpp/features/feature_mechanisms.py b/sleekxmpp/features/feature_mechanisms.py new file mode 100644 index 00000000..994c9bed --- /dev/null +++ b/sleekxmpp/features/feature_mechanisms.py @@ -0,0 +1,116 @@ +""" + SleekXMPP: The Sleek XMPP Library + Copyright (C) 2010 Nathanael C. Fritz + This file is part of SleekXMPP. + + See the file LICENSE for copying permission. +""" + +import logging + +from sleekxmpp.stanza import stream +from sleekxmpp.xmlstream import RestartStream +from sleekxmpp.xmlstream.matcher import * +from sleekxmpp.xmlstream.handler import * +from sleekxmpp.plugins.base import base_plugin + + +log = logging.getLogger(__name__) + + +class feature_mechanisms(base_plugin): + + def plugin_init(self): + self.name = 'SASL Mechanisms' + self.rfc = '6120' + self.description = "SASL Stream Feature" + + self.xmpp.register_stanza(stream.sasl.Success) + self.xmpp.register_stanza(stream.sasl.Failure) + self.xmpp.register_stanza(stream.sasl.Auth) + + self._mechanism_handlers = {} + self._mechanism_priorities = [] + + self.xmpp.register_handler( + Callback('SASL Success', + MatchXPath(stream.sasl.Success.tag_name()), + self._handle_success, + instream=True, + once=True)) + self.xmpp.register_handler( + Callback('SASL Failure', + MatchXPath(stream.sasl.Failure.tag_name()), + self._handle_fail, + instream=True, + once=True)) + + self.xmpp.register_feature('mechanisms', + self._handle_sasl_auth, + restart=True, + order=self.config.get('order', 100)) + + def register_mechanism(self, name, handler, priority=0): + """ + Register a handler for a SASL authentication mechanism. + + Arguments: + name -- The name of the mechanism (all caps) + handler -- The function that will perform the + authentication. The function must + return True if it is able to carry + out the authentication, False if + a required condition is not met. + priority -- An integer value indicating the + preferred ordering for the mechanism. + High values will be attempted first. + """ + self._mechanism_handlers[name] = handler + self._mechanism_priorities.append((priority, name)) + self._mechanism_priorities.sort(reverse=True) + + def remove_mechanism(self, name): + """ + Remove support for a given SASL authentication mechanism. + + Arguments: + name -- The name of the mechanism to remove (all caps) + """ + if name in self._mechanism_handlers: + del self._mechanism_handlers[name] + + p = self._mechanism_priorities + self._mechanism_priorities = [i for i in p if i[1] != name] + + def _handle_sasl_auth(self, features): + """ + Handle authenticating using SASL. + + Arguments: + features -- The stream features stanza. + """ + for priority, mech in self._mechanism_priorities: + if mech in features['mechanisms']: + log.debug('Attempt to use SASL %s' % mech) + if self._mechanism_handlers[mech](): + break + else: + log.error("No appropriate login method.") + self.xmpp.event("no_auth", direct=True) + self.xmpp.disconnect() + + return True + + def _handle_success(self, stanza): + """SASL authentication succeeded. Restart the stream.""" + self.xmpp.authenticated = True + self.xmpp.features.append('mechanisms') + raise RestartStream() + + def _handle_fail(self, stanza): + """SASL authentication failed. Disconnect and shutdown.""" + log.info("Authentication failed.") + self.xmpp.event("failed_auth", direct=True) + self.xmpp.disconnect() + log.debug("Starting SASL Auth") + return True diff --git a/sleekxmpp/features/feature_session.py b/sleekxmpp/features/feature_session.py new file mode 100644 index 00000000..5bae358c --- /dev/null +++ b/sleekxmpp/features/feature_session.py @@ -0,0 +1,46 @@ +""" + SleekXMPP: The Sleek XMPP Library + Copyright (C) 2010 Nathanael C. Fritz + This file is part of SleekXMPP. + + See the file LICENSE for copying permission. +""" + +import logging + +from sleekxmpp.xmlstream.matcher import * +from sleekxmpp.xmlstream.handler import * +from sleekxmpp.plugins.base import base_plugin + + +log = logging.getLogger(__name__) + + +class feature_session(base_plugin): + + def plugin_init(self): + self.name = 'Start Session' + self.rfc = '3920' + self.description = 'Start Session Stream Feature' + + self.xmpp.register_feature('session', + self._handle_start_session, + restart=False, + order=10001) + + 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') + response = iq.send(now=True) + + log.debug("Established Session") + self.xmpp.sessionstarted = True + self.xmpp.session_started_event.set() + self.xmpp.event("session_start") diff --git a/sleekxmpp/features/feature_starttls.py b/sleekxmpp/features/feature_starttls.py new file mode 100644 index 00000000..5367fa49 --- /dev/null +++ b/sleekxmpp/features/feature_starttls.py @@ -0,0 +1,61 @@ +""" + SleekXMPP: The Sleek XMPP Library + Copyright (C) 2010 Nathanael C. Fritz + This file is part of SleekXMPP. + + See the file LICENSE for copying permission. +""" + +import logging + +from sleekxmpp.stanza.stream import tls +from sleekxmpp.xmlstream import RestartStream +from sleekxmpp.xmlstream.matcher import * +from sleekxmpp.xmlstream.handler import * +from sleekxmpp.plugins.base import base_plugin + + +log = logging.getLogger(__name__) + + +class feature_starttls(base_plugin): + + def plugin_init(self): + self.name = "STARTTLS" + self.rfc = '6120' + self.description = "STARTTLS Stream Feature" + + self.xmpp.register_stanza(tls.Proceed) + self.xmpp.register_handler( + Callback('STARTTLS Proceed', + MatchXPath(tls.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)) + + def _handle_starttls(self, features): + """ + Handle notification that the server supports TLS. + + Arguments: + features -- The stream:features element. + """ + if not self.xmpp.use_tls: + return False + elif self.xmpp.ssl_support: + self.xmpp.send(features['starttls'], now=True) + return True + else: + log.warning("The module tlslite is required to log in" +\ + " to some servers, and has not been found.") + return False + + 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.append('starttls') + raise RestartStream() diff --git a/sleekxmpp/features/sasl_anonymous.py b/sleekxmpp/features/sasl_anonymous.py new file mode 100644 index 00000000..469d9d19 --- /dev/null +++ b/sleekxmpp/features/sasl_anonymous.py @@ -0,0 +1,31 @@ +import base64 +import sys +import logging + +from sleekxmpp.stanza.stream import sasl +from sleekxmpp.plugins.base import base_plugin + + +log = logging.getLogger(__name__) + + +class sasl_anonymous(base_plugin): + + def plugin_init(self): + self.name = 'SASL ANONYMOUS' + self.rfc = '6120' + self.description = 'SASL ANONYMOUS Mechanism' + + self.xmpp.register_sasl_mechanism('ANONYMOUS', + self._handle_anonymous, + priority=self.config.get('priority', 0)) + + def _handle_anonymous(self): + if self.xmpp.boundjid.user: + return False + + resp = sasl.Auth(self.xmpp) + resp['mechanism'] = 'ANONYMOUS' + resp.send(now=True) + + return True diff --git a/sleekxmpp/features/sasl_plain.py b/sleekxmpp/features/sasl_plain.py new file mode 100644 index 00000000..36c7d9df --- /dev/null +++ b/sleekxmpp/features/sasl_plain.py @@ -0,0 +1,41 @@ +import base64 +import sys +import logging + +from sleekxmpp.stanza.stream import sasl +from sleekxmpp.plugins.base import base_plugin + + +log = logging.getLogger(__name__) + + +class sasl_plain(base_plugin): + + def plugin_init(self): + self.name = 'SASL PLAIN' + self.rfc = '6120' + self.description = 'SASL PLAIN Mechanism' + + self.xmpp.register_sasl_mechanism('PLAIN', + self._handle_plain, + priority=self.config.get('priority', 1)) + + def _handle_plain(self): + if not self.xmpp.boundjid.user: + return False + + if sys.version_info < (3, 0): + user = bytes(self.xmpp.boundjid.user) + password = bytes(self.xmpp.password) + else: + user = bytes(self.xmpp.boundjid.user, 'utf-8') + password = bytes(self.xmpp.password, 'utf-8') + + auth = base64.b64encode(b'\x00' + user + \ + b'\x00' + password).decode('utf-8') + + resp = sasl.Auth(self.xmpp) + resp['mechanism'] = 'PLAIN' + resp['value'] = auth + resp.send(now=True) + return True |