diff options
Diffstat (limited to 'slixmpp/plugins/xep_0078')
-rw-r--r-- | slixmpp/plugins/xep_0078/__init__.py | 20 | ||||
-rw-r--r-- | slixmpp/plugins/xep_0078/legacyauth.py | 147 | ||||
-rw-r--r-- | slixmpp/plugins/xep_0078/stanza.py | 41 |
3 files changed, 208 insertions, 0 deletions
diff --git a/slixmpp/plugins/xep_0078/__init__.py b/slixmpp/plugins/xep_0078/__init__.py new file mode 100644 index 00000000..21bdc19e --- /dev/null +++ b/slixmpp/plugins/xep_0078/__init__.py @@ -0,0 +1,20 @@ +""" + Slixmpp: The Slick XMPP Library + Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout + This file is part of Slixmpp. + + See the file LICENSE for copying permission. +""" + +from slixmpp.plugins.base import register_plugin + +from slixmpp.plugins.xep_0078 import stanza +from slixmpp.plugins.xep_0078.stanza import IqAuth, AuthFeature +from slixmpp.plugins.xep_0078.legacyauth import XEP_0078 + + +register_plugin(XEP_0078) + + +# Retain some backwards compatibility +xep_0078 = XEP_0078 diff --git a/slixmpp/plugins/xep_0078/legacyauth.py b/slixmpp/plugins/xep_0078/legacyauth.py new file mode 100644 index 00000000..d3826e59 --- /dev/null +++ b/slixmpp/plugins/xep_0078/legacyauth.py @@ -0,0 +1,147 @@ +""" + 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 uuid +import logging +import hashlib +import random +import sys + +from slixmpp.jid import JID +from slixmpp.exceptions import IqError, IqTimeout +from slixmpp.stanza import Iq, StreamFeatures +from slixmpp.xmlstream import ElementBase, ET, register_stanza_plugin +from slixmpp.plugins import BasePlugin +from slixmpp.plugins.xep_0078 import stanza + + +log = logging.getLogger(__name__) + + +class XEP_0078(BasePlugin): + + """ + XEP-0078 NON-SASL Authentication + + This XEP is OBSOLETE in favor of using SASL, so DO NOT use this plugin + unless you are forced to use an old XMPP server implementation. + """ + + name = 'xep_0078' + description = 'XEP-0078: Non-SASL Authentication' + dependencies = set() + stanza = stanza + default_config = { + 'order': 15 + } + + def plugin_init(self): + self.xmpp.register_feature('auth', + self._handle_auth, + restart=False, + order=self.order) + + self.xmpp.add_event_handler('legacy_protocol', + self._handle_legacy_protocol) + + register_stanza_plugin(Iq, stanza.IqAuth) + register_stanza_plugin(StreamFeatures, stanza.AuthFeature) + + def plugin_end(self): + self.xmpp.del_event_handler('legacy_protocol', + self._handle_legacy_protocol) + self.xmpp.unregister_feature('auth', self.order) + + def _handle_auth(self, features): + # If we can or have already authenticated with SASL, do nothing. + if 'mechanisms' in features['features']: + return False + return self.authenticate() + + def _handle_legacy_protocol(self, event): + self.authenticate() + + def authenticate(self): + if self.xmpp.authenticated: + return False + + log.debug("Starting jabber:iq:auth Authentication") + + # Step 1: Request the auth form + iq = self.xmpp.Iq() + iq['type'] = 'get' + iq['to'] = self.xmpp.requested_jid.host + iq['auth']['username'] = self.xmpp.requested_jid.user + + try: + resp = iq.send(now=True) + except IqError as err: + log.info("Authentication failed: %s", err.iq['error']['condition']) + self.xmpp.event('failed_auth', direct=True) + self.xmpp.disconnect() + return True + except IqTimeout: + log.info("Authentication failed: %s", 'timeout') + self.xmpp.event('failed_auth', direct=True) + self.xmpp.disconnect() + return True + + # Step 2: Fill out auth form for either password or digest auth + iq = self.xmpp.Iq() + iq['type'] = 'set' + iq['auth']['username'] = self.xmpp.requested_jid.user + + # A resource is required, so create a random one if necessary + resource = self.xmpp.requested_jid.resource + if not resource: + resource = str(uuid.uuid4()) + + iq['auth']['resource'] = resource + + if 'digest' in resp['auth']['fields']: + log.debug('Authenticating via jabber:iq:auth Digest') + if sys.version_info < (3, 0): + stream_id = bytes(self.xmpp.stream_id) + password = bytes(self.xmpp.password) + else: + stream_id = bytes(self.xmpp.stream_id, encoding='utf-8') + password = bytes(self.xmpp.password, encoding='utf-8') + + digest = hashlib.sha1(b'%s%s' % (stream_id, password)).hexdigest() + iq['auth']['digest'] = digest + else: + log.warning('Authenticating via jabber:iq:auth Plain.') + iq['auth']['password'] = self.xmpp.password + + # Step 3: Send credentials + try: + result = iq.send(now=True) + except IqError as err: + log.info("Authentication failed") + self.xmpp.event("failed_auth", direct=True) + self.xmpp.disconnect() + except IqTimeout: + log.info("Authentication failed") + self.xmpp.event("failed_auth", direct=True) + self.xmpp.disconnect() + + self.xmpp.features.add('auth') + + self.xmpp.authenticated = True + + self.xmpp.boundjid = JID(self.xmpp.requested_jid, + resource=resource, + cache_lock=True) + self.xmpp.event('session_bind', self.xmpp.boundjid, direct=True) + + log.debug("Established Session") + self.xmpp.sessionstarted = True + self.xmpp.session_started_event.set() + self.xmpp.event('session_start') + + return True diff --git a/slixmpp/plugins/xep_0078/stanza.py b/slixmpp/plugins/xep_0078/stanza.py new file mode 100644 index 00000000..7dc9401d --- /dev/null +++ b/slixmpp/plugins/xep_0078/stanza.py @@ -0,0 +1,41 @@ +""" + 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, register_stanza_plugin + + +class IqAuth(ElementBase): + namespace = 'jabber:iq:auth' + name = 'query' + plugin_attrib = 'auth' + interfaces = set(('fields', 'username', 'password', 'resource', 'digest')) + sub_interfaces = set(('username', 'password', 'resource', 'digest')) + plugin_tag_map = {} + plugin_attrib_map = {} + + def get_fields(self): + fields = set() + for field in self.sub_interfaces: + if self.xml.find('{%s}%s' % (self.namespace, field)) is not None: + fields.add(field) + return fields + + def set_resource(self, value): + self._set_sub_text('resource', value, keep=True) + + def set_password(self, value): + self._set_sub_text('password', value, keep=True) + + +class AuthFeature(ElementBase): + namespace = 'http://jabber.org/features/iq-auth' + name = 'auth' + plugin_attrib = 'auth' + interfaces = set() + plugin_tag_map = {} + plugin_attrib_map = {} |