diff options
Diffstat (limited to 'sleekxmpp/features/feature_mechanisms')
10 files changed, 124 insertions, 47 deletions
diff --git a/sleekxmpp/features/feature_mechanisms/__init__.py b/sleekxmpp/features/feature_mechanisms/__init__.py index 5379ef4e..9f7611ed 100644 --- a/sleekxmpp/features/feature_mechanisms/__init__.py +++ b/sleekxmpp/features/feature_mechanisms/__init__.py @@ -6,8 +6,17 @@ See the file LICENSE for copying permission. """ -from sleekxmpp.features.feature_mechanisms.mechanisms import feature_mechanisms +from sleekxmpp.plugins.base import register_plugin + +from sleekxmpp.features.feature_mechanisms.mechanisms import FeatureMechanisms from sleekxmpp.features.feature_mechanisms.stanza import Mechanisms from sleekxmpp.features.feature_mechanisms.stanza import Auth from sleekxmpp.features.feature_mechanisms.stanza import Success from sleekxmpp.features.feature_mechanisms.stanza import Failure + + +register_plugin(FeatureMechanisms) + + +# Retain some backwards compatibility +feature_mechanisms = FeatureMechanisms diff --git a/sleekxmpp/features/feature_mechanisms/mechanisms.py b/sleekxmpp/features/feature_mechanisms/mechanisms.py index 2b8321c2..6f01cb14 100644 --- a/sleekxmpp/features/feature_mechanisms/mechanisms.py +++ b/sleekxmpp/features/feature_mechanisms/mechanisms.py @@ -9,36 +9,47 @@ import logging from sleekxmpp.thirdparty import suelta +from sleekxmpp.thirdparty.suelta.exceptions import SASLCancelled, SASLError from sleekxmpp.stanza import StreamFeatures from sleekxmpp.xmlstream import RestartStream, register_stanza_plugin -from sleekxmpp.xmlstream.matcher import * -from sleekxmpp.xmlstream.handler import * -from sleekxmpp.plugins.base import base_plugin +from sleekxmpp.plugins import BasePlugin +from sleekxmpp.xmlstream.matcher import MatchXPath +from sleekxmpp.xmlstream.handler import Callback from sleekxmpp.features.feature_mechanisms import stanza log = logging.getLogger(__name__) -class feature_mechanisms(base_plugin): +class FeatureMechanisms(BasePlugin): - def plugin_init(self): - self.name = 'SASL Mechanisms' - self.rfc = '6120' - self.description = "SASL Stream Feature" - self.stanza = stanza + name = 'feature_mechanisms' + description = 'RFC 6120: Stream Feature: SASL' + dependencies = set() + stanza = stanza + def plugin_init(self): self.use_mech = self.config.get('use_mech', None) + if not self.use_mech and not self.xmpp.boundjid.user: + self.use_mech = 'ANONYMOUS' + def tls_active(): return 'starttls' in self.xmpp.features def basic_callback(mech, values): - if 'username' in values: - values['username'] = self.xmpp.boundjid.user - if 'password' in values: - values['password'] = self.xmpp.password + creds = self.xmpp.credentials + for value in values: + if value == 'username': + values['username'] = self.xmpp.boundjid.user + elif value == 'password': + values['password'] = creds['password'] + elif value == 'email': + jid = self.xmpp.boundjid.bare + values['email'] = creds.get('email', jid) + elif value in creds: + values[value] = creds[value] mech.fulfill(values) sasl_callback = self.config.get('sasl_callback', None) @@ -53,6 +64,9 @@ class feature_mechanisms(base_plugin): tls_active=tls_active, mech=self.use_mech) + self.mech_list = set() + self.attempted_mechs = set() + register_stanza_plugin(StreamFeatures, stanza.Mechanisms) self.xmpp.register_stanza(stanza.Success) @@ -60,19 +74,18 @@ class feature_mechanisms(base_plugin): 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, - once=True)) + instream=True)) self.xmpp.register_handler( Callback('SASL Failure', MatchXPath(stanza.Failure.tag_name()), self._handle_fail, - instream=True, - once=True)) + instream=True)) self.xmpp.register_handler( Callback('SASL Challenge', MatchXPath(stanza.Challenge.tag_name()), @@ -95,14 +108,29 @@ class feature_mechanisms(base_plugin): # server has incorrectly offered it again. return False - mech_list = features['mechanisms'] + if not self.use_mech: + self.mech_list = set(features['mechanisms']) + else: + self.mech_list = set([self.use_mech]) + return self._send_auth() + + def _send_auth(self): + mech_list = self.mech_list - self.attempted_mechs self.mech = self.sasl.choose_mechanism(mech_list) - if self.mech is not None: + if mech_list and self.mech is not None: resp = stanza.Auth(self.xmpp) resp['mechanism'] = self.mech.name - resp['value'] = self.mech.process() - resp.send(now=True) + try: + resp['value'] = self.mech.process() + except SASLCancelled: + self.attempted_mechs.add(self.mech.name) + self._send_auth() + except SASLError: + self.attempted_mechs.add(self.mech.name) + self._send_auth() + else: + resp.send(now=True) else: log.error("No appropriate login method.") self.xmpp.event("no_auth", direct=True) @@ -112,18 +140,26 @@ class feature_mechanisms(base_plugin): def _handle_challenge(self, stanza): """SASL challenge received. Process and send response.""" resp = self.stanza.Response(self.xmpp) - resp['value'] = self.mech.process(stanza['value']) - resp.send(now=True) + try: + resp['value'] = self.mech.process(stanza['value']) + except SASLCancelled: + self.stanza.Abort(self.xmpp).send() + except SASLError: + self.stanza.Abort(self.xmpp).send() + else: + resp.send(now=True) def _handle_success(self, stanza): """SASL authentication succeeded. Restart the stream.""" + self.attempted_mechs = set() self.xmpp.authenticated = True self.xmpp.features.add('mechanisms') 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.xmpp.disconnect() + self._send_auth() return True diff --git a/sleekxmpp/features/feature_mechanisms/stanza/__init__.py b/sleekxmpp/features/feature_mechanisms/stanza/__init__.py index 8b80f358..38991d89 100644 --- a/sleekxmpp/features/feature_mechanisms/stanza/__init__.py +++ b/sleekxmpp/features/feature_mechanisms/stanza/__init__.py @@ -13,3 +13,4 @@ from sleekxmpp.features.feature_mechanisms.stanza.success import Success from sleekxmpp.features.feature_mechanisms.stanza.failure import Failure from sleekxmpp.features.feature_mechanisms.stanza.challenge import Challenge from sleekxmpp.features.feature_mechanisms.stanza.response import Response +from sleekxmpp.features.feature_mechanisms.stanza.abort import Abort diff --git a/sleekxmpp/features/feature_mechanisms/stanza/abort.py b/sleekxmpp/features/feature_mechanisms/stanza/abort.py new file mode 100644 index 00000000..aaca348d --- /dev/null +++ b/sleekxmpp/features/feature_mechanisms/stanza/abort.py @@ -0,0 +1,24 @@ +""" + SleekXMPP: The Sleek XMPP Library + Copyright (C) 2011 Nathanael C. Fritz + This file is part of SleekXMPP. + + See the file LICENSE for copying permission. +""" + +from sleekxmpp.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/sleekxmpp/features/feature_mechanisms/stanza/auth.py b/sleekxmpp/features/feature_mechanisms/stanza/auth.py index e069b57f..69769507 100644 --- a/sleekxmpp/features/feature_mechanisms/stanza/auth.py +++ b/sleekxmpp/features/feature_mechanisms/stanza/auth.py @@ -10,9 +10,7 @@ import base64 from sleekxmpp.thirdparty.suelta.util import bytes -from sleekxmpp.stanza import StreamFeatures -from sleekxmpp.xmlstream import ElementBase, StanzaBase, ET -from sleekxmpp.xmlstream import register_stanza_plugin +from sleekxmpp.xmlstream import StanzaBase class Auth(StanzaBase): @@ -25,15 +23,28 @@ class Auth(StanzaBase): 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): - return base64.b64decode(bytes(self.xml.text)) + 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): - self.xml.text = bytes(base64.b64encode(values)).decode('utf-8') + if not self['mechanism'] in self.plain_mechs: + if values: + self.xml.text = bytes(base64.b64encode(values)).decode('utf-8') + else: + self.xml.text = '=' + else: + self.xml.text = bytes(values).decode('utf-8') def del_value(self): self.xml.text = '' diff --git a/sleekxmpp/features/feature_mechanisms/stanza/challenge.py b/sleekxmpp/features/feature_mechanisms/stanza/challenge.py index 82af869f..85d65403 100644 --- a/sleekxmpp/features/feature_mechanisms/stanza/challenge.py +++ b/sleekxmpp/features/feature_mechanisms/stanza/challenge.py @@ -10,9 +10,7 @@ import base64 from sleekxmpp.thirdparty.suelta.util import bytes -from sleekxmpp.stanza import StreamFeatures -from sleekxmpp.xmlstream import ElementBase, StanzaBase, ET -from sleekxmpp.xmlstream import register_stanza_plugin +from sleekxmpp.xmlstream import StanzaBase class Challenge(StanzaBase): @@ -33,7 +31,10 @@ class Challenge(StanzaBase): return base64.b64decode(bytes(self.xml.text)) def set_value(self, values): - self.xml.text = bytes(base64.b64encode(values)).decode('utf-8') + 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/sleekxmpp/features/feature_mechanisms/stanza/failure.py b/sleekxmpp/features/feature_mechanisms/stanza/failure.py index 027cc5af..5dd0de56 100644 --- a/sleekxmpp/features/feature_mechanisms/stanza/failure.py +++ b/sleekxmpp/features/feature_mechanisms/stanza/failure.py @@ -6,9 +6,7 @@ See the file LICENSE for copying permission. """ -from sleekxmpp.stanza import StreamFeatures -from sleekxmpp.xmlstream import ElementBase, StanzaBase, ET -from sleekxmpp.xmlstream import register_stanza_plugin +from sleekxmpp.xmlstream import StanzaBase, ET class Failure(StanzaBase): diff --git a/sleekxmpp/features/feature_mechanisms/stanza/mechanisms.py b/sleekxmpp/features/feature_mechanisms/stanza/mechanisms.py index c09cafbd..bbd56813 100644 --- a/sleekxmpp/features/feature_mechanisms/stanza/mechanisms.py +++ b/sleekxmpp/features/feature_mechanisms/stanza/mechanisms.py @@ -6,9 +6,7 @@ See the file LICENSE for copying permission. """ -from sleekxmpp.stanza import StreamFeatures -from sleekxmpp.xmlstream import ElementBase, StanzaBase, ET -from sleekxmpp.xmlstream import register_stanza_plugin +from sleekxmpp.xmlstream import ElementBase, ET class Mechanisms(ElementBase): diff --git a/sleekxmpp/features/feature_mechanisms/stanza/response.py b/sleekxmpp/features/feature_mechanisms/stanza/response.py index 45bb8207..78636c9e 100644 --- a/sleekxmpp/features/feature_mechanisms/stanza/response.py +++ b/sleekxmpp/features/feature_mechanisms/stanza/response.py @@ -10,9 +10,7 @@ import base64 from sleekxmpp.thirdparty.suelta.util import bytes -from sleekxmpp.stanza import StreamFeatures -from sleekxmpp.xmlstream import ElementBase, StanzaBase, ET -from sleekxmpp.xmlstream import register_stanza_plugin +from sleekxmpp.xmlstream import StanzaBase class Response(StanzaBase): @@ -33,7 +31,10 @@ class Response(StanzaBase): return base64.b64decode(bytes(self.xml.text)) def set_value(self, values): - self.xml.text = bytes(base64.b64encode(values)).decode('utf-8') + 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/sleekxmpp/features/feature_mechanisms/stanza/success.py b/sleekxmpp/features/feature_mechanisms/stanza/success.py index 028e28a3..7a5a73f2 100644 --- a/sleekxmpp/features/feature_mechanisms/stanza/success.py +++ b/sleekxmpp/features/feature_mechanisms/stanza/success.py @@ -6,9 +6,7 @@ See the file LICENSE for copying permission. """ -from sleekxmpp.stanza import StreamFeatures -from sleekxmpp.xmlstream import ElementBase, StanzaBase, ET -from sleekxmpp.xmlstream import register_stanza_plugin +from sleekxmpp.xmlstream import StanzaBase class Success(StanzaBase): |