From 1a270dc05cc368000f3545975befa0589031b684 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Fri, 28 Jan 2011 00:49:37 -0500 Subject: First pass at re-worked stream features. Stream features now use stanza objects! Features are given a ranking that expresses the dependency relationships (since only one feature is negotiated at a time, the dependency graph can be replaced by a line). >>> xmpp.register_feature('my_feature', _my_handler, >>> restart=True, # Requires stream restart >>> order=600) # Ranking (out of ~ 10,000, >>> # lower #'s executed first) SASL mechanisms may now be added or disabled as needed. Each mechanism is given a priority value indicating the order in which the client wishes for mechanisms to be tried. Higher priority numbers are executed first. >>> xmpp.register_sasl_mechanism('SASL-MECH', _mech_handler, >>> priority=0) Disabling a SASL mechanism: >>> xmpp.remove_sasl_mechanism('ANONYMOUS') --- sleekxmpp/clientxmpp.py | 361 ++++++++++++++++++++++++++++-------------------- 1 file changed, 213 insertions(+), 148 deletions(-) (limited to 'sleekxmpp/clientxmpp.py') diff --git a/sleekxmpp/clientxmpp.py b/sleekxmpp/clientxmpp.py index a1813985..e05f8e7b 100644 --- a/sleekxmpp/clientxmpp.py +++ b/sleekxmpp/clientxmpp.py @@ -18,9 +18,11 @@ import threading from sleekxmpp import plugins from sleekxmpp import stanza from sleekxmpp.basexmpp import BaseXMPP -from sleekxmpp.stanza import Message, Presence, Iq +from sleekxmpp.stanza import * +from sleekxmpp.stanza import tls +from sleekxmpp.stanza import sasl from sleekxmpp.xmlstream import XMLStream, RestartStream -from sleekxmpp.xmlstream import StanzaBase, ET +from sleekxmpp.xmlstream import StanzaBase, ET, register_stanza_plugin from sleekxmpp.xmlstream.matcher import * from sleekxmpp.xmlstream.handler import * @@ -92,14 +94,24 @@ class ClientXMPP(BaseXMPP): self.stream_footer = "" self.features = [] - self.registered_features = [] + self._stream_feature_handlers = {} + self._stream_feature_order = [] + self._sasl_mechanism_handlers = {} + self._sasl_mechanism_priorities = [] #TODO: Use stream state here self.authenticated = False self.sessionstarted = False self.bound = False self.bindfail = False - self.add_event_handler('connected', self.handle_connected) + + self.add_event_handler('connected', self._handle_connected) + + self.register_stanza(StreamFeatures) + self.register_stanza(tls.Proceed) + self.register_stanza(sasl.Success) + self.register_stanza(sasl.Failure) + self.register_stanza(sasl.Auth) self.register_handler( Callback('Stream Features', @@ -112,32 +124,25 @@ class ClientXMPP(BaseXMPP): 'jabber:iq:roster')), self._handle_roster)) - self.register_feature( - "", - self._handle_starttls, True) - self.register_feature( - "", - self._handle_sasl_auth, True) - self.register_feature( - "", - self._handle_bind_resource) - self.register_feature( - "", - self._handle_start_session) - - def handle_connected(self, event=None): - #TODO: Use stream state here - self.authenticated = False - self.sessionstarted = False - self.bound = False - self.bindfail = False - self.schedule("session timeout checker", 15, - self._session_timeout_check) - - def _session_timeout_check(self): - if not self.session_started_event.isSet(): - log.debug("Session start has taken more than 15 seconds") - self.disconnect(reconnect=self.auto_reconnect) + self.register_feature('starttls', self._handle_starttls, + restart=True, + order=0) + self.register_feature('mechanisms', self._handle_sasl_auth, + restart=True, + order=100) + self.register_feature('bind', self._handle_bind_resource, + restart=False, + order=10000) + self.register_feature('session', self._handle_start_session, + restart=False, + order=10001) + + self.register_sasl_mechanism('PLAIN', + self._handle_sasl_plain, + priority=1) + self.register_sasl_mechanism('ANONYMOUS', + self._handle_sasl_plain, + priority=0) def connect(self, address=tuple(), reattempt=True): """ @@ -197,19 +202,54 @@ class ClientXMPP(BaseXMPP): return XMLStream.connect(self, address[0], address[1], use_tls=True, reattempt=reattempt) - def register_feature(self, mask, pointer, breaker=False): + def register_feature(self, name, handler, restart=False, order=5000): """ Register a stream feature. Arguments: - mask -- An XML string matching the feature's element. - pointer -- The function to execute if the feature is received. - breaker -- Indicates if feature processing should halt with + name -- The name of the stream feature. + handler -- The function to execute if the feature is received. + restart -- Indicates if feature processing should halt with this feature. Defaults to False. + order -- The relative ordering in which the feature should + be negotiated. Lower values will be attempted + earlier when available. + """ + self._stream_feature_handlers[name] = (handler, restart) + self._stream_feature_order.append((order, name)) + self._stream_feature_order.sort() + + def register_sasl_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._sasl_mechanism_handlers[name] = handler + self._sasl_mechanism_priorities.append((priority, name)) + self._sasl_mechanism_priorities.sort(reverse=True) + + def remove_sasl_mechanism(self, name): + """ + Remove support for a given SASL authentication mechanism. + + Arguments: + name -- The name of the mechanism to remove (all caps) """ - self.registered_features.append((MatchXMLMask(mask), - pointer, - breaker)) + if name in self._sasl_mechanism_handlers: + del self._sasl_mechanism_handlers[name] + + p = self._sasl_mechanism_priorities + self._sasl_mechanism_priorities = [i for i in p if i[1] != name] def update_roster(self, jid, name=None, subscription=None, groups=[]): """ @@ -223,7 +263,8 @@ class ClientXMPP(BaseXMPP): to 'remove', the entry will be deleted. groups -- The roster groups that contain this item. """ - iq = self.Iq()._set_stanza_values({'type': 'set'}) + iq = self.Iq() + iq['type'] = 'set' iq['roster']['items'] = {jid: {'name': name, 'subscription': subscription, 'groups': groups}} @@ -242,10 +283,27 @@ class ClientXMPP(BaseXMPP): def get_roster(self): """Request the roster from the server.""" - iq = self.Iq()._set_stanza_values({'type': 'get'}).enable('roster') + iq = self.Iq() + iq['type'] = 'get' + iq.enable('roster') response = iq.send() self._handle_roster(response, request=True) + def _handle_connected(self, event=None): + #TODO: Use stream state here + self.authenticated = False + self.sessionstarted = False + self.bound = False + self.bindfail = False + self.features = [] + + def session_timeout(): + if not self.session_started_event.isSet(): + log.debug("Session start has taken more than 15 seconds") + self.disconnect(reconnect=self.auto_reconnect) + + self.schedule("session timeout checker", 15, session_timeout) + def _handle_stream_features(self, features): """ Process the received stream features. @@ -253,167 +311,174 @@ class ClientXMPP(BaseXMPP): Arguments: features -- The features stanza. """ - # Record all of the features. - self.features = [] - for sub in features.xml: - self.features.append(sub.tag) - - # Process the features. - for sub in features.xml: - for feature in self.registered_features: - mask, handler, halt = feature - if mask.match(sub): - if handler(sub) and halt: - # Don't continue if the feature was - # marked as a breaker. - return True - - def _handle_starttls(self, xml): + for order, name in self._stream_feature_order: + if name in features['features']: + handler, restart = self._stream_feature_handlers[name] + if handler(features) and restart: + # Don't continue if the feature requires + # restarting the XML stream. + return True + + def _handle_starttls(self, features): """ Handle notification that the server supports TLS. Arguments: - xml -- The STARTLS proceed element. + features -- The stream:features element. """ - if not self.authenticated and self.ssl_support: - tls_ns = 'urn:ietf:params:xml:ns:xmpp-tls' - self.add_handler("" % tls_ns, - self._handle_tls_start, - name='TLS Proceed', - instream=True) - self.send_xml(xml) + + def tls_proceed(proceed): + """Restart the XML stream when TLS is accepted.""" + log.debug("Starting TLS") + if self.start_tls(): + self.features.append('starttls') + raise RestartStream() + + if self.ssl_support: + self.register_handler( + Callback('STARTTLS Proceed', + MatchXPath(tls.Proceed.tag_name()), + tls_proceed, + instream=True)) + self.send(features['starttls']) 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_tls_start(self, xml): + def _handle_sasl_auth(self, features): """ - Handle encrypting the stream using TLS. + Handle authenticating using SASL. - Restarts the stream. + Arguments: + features -- The stream features stanza. """ - log.debug("Starting TLS") - if self.start_tls(): + + def sasl_success(stanza): + """SASL authentication succeeded. Restart the stream.""" + self.authenticated = True + self.features.append('mechanisms') raise RestartStream() - def _handle_sasl_auth(self, xml): + def sasl_fail(stanza): + """SASL authentication failed. Disconnect and shutdown.""" + log.info("Authentication failed.") + self.event("failed_auth", direct=True) + self.disconnect() + log.debug("Starting SASL Auth") + return True + + self.register_handler( + Callback('SASL Success', + MatchXPath(sasl.Success.tag_name()), + sasl_success, + instream=True, + once=True)) + + self.register_handler( + Callback('SASL Failure', + MatchXPath(sasl.Failure.tag_name()), + sasl_fail, + instream=True, + once=True)) + + for priority, mech in self._sasl_mechanism_priorities: + if mech in self._sasl_mechanism_handlers: + handler = self._sasl_mechanism_handlers[mech] + if handler(self): + break + else: + log.error("No appropriate login method.") + self.disconnect() + + return True + + def _handle_sasl_plain(self, xmpp): """ - Handle authenticating using SASL. + Attempt to authenticate using SASL PLAIN. Arguments: - xml -- The SASL mechanisms stanza. + xmpp -- The SleekXMPP connection instance. """ - if '{urn:ietf:params:xml:ns:xmpp-tls}starttls' in self.features: + if not xmpp.boundjid.user: return False - log.debug("Starting SASL Auth") - sasl_ns = 'urn:ietf:params:xml:ns:xmpp-sasl' - self.add_handler("" % sasl_ns, - self._handle_auth_success, - name='SASL Sucess', - instream=True) - self.add_handler("" % sasl_ns, - self._handle_auth_fail, - name='SASL Failure', - instream=True) - - sasl_mechs = xml.findall('{%s}mechanism' % sasl_ns) - if sasl_mechs: - for sasl_mech in sasl_mechs: - self.features.append("sasl:%s" % sasl_mech.text) - if 'sasl:PLAIN' in self.features and self.boundjid.user: - if sys.version_info < (3, 0): - user = bytes(self.boundjid.user) - password = bytes(self.password) - else: - user = bytes(self.boundjid.user, 'utf-8') - password = bytes(self.password, 'utf-8') - - auth = base64.b64encode(b'\x00' + user + \ - b'\x00' + password).decode('utf-8') - - self.send("%s" % ( - sasl_ns, - auth)) - elif 'sasl:ANONYMOUS' in self.features and not self.boundjid.user: - self.send("" % ( - sasl_ns, - 'ANONYMOUS')) - else: - log.error("No appropriate login method.") - self.disconnect() + if sys.version_info < (3, 0): + user = bytes(self.boundjid.user) + password = bytes(self.password) + else: + user = bytes(self.boundjid.user, 'utf-8') + password = bytes(self.password, 'utf-8') + + auth = base64.b64encode(b'\x00' + user + \ + b'\x00' + password).decode('utf-8') + + resp = sasl.Auth(xmpp) + resp['mechanism'] = 'PLAIN' + resp['value'] = auth + resp.send() + return True - def _handle_auth_success(self, xml): + def _handle_sasl_anonymous(self, xmpp): """ - SASL authentication succeeded. Restart the stream. + Attempt to authenticate using SASL ANONYMOUS. Arguments: - xml -- The SASL authentication success element. + xmpp -- The SleekXMPP connection instance. """ - self.authenticated = True - self.features = [] - raise RestartStream() + if xmpp.boundjid.user: + return False - def _handle_auth_fail(self, xml): - """ - SASL authentication failed. Disconnect and shutdown. + resp = sasl.Auth(xmpp) + resp['mechanism'] = 'ANONYMOUS' + resp.send() - Arguments: - xml -- The SASL authentication failure element. - """ - log.info("Authentication failed.") - self.event("failed_auth", direct=True) - self.disconnect() + return True - def _handle_bind_resource(self, xml): + def _handle_bind_resource(self, features): """ Handle requesting a specific resource. Arguments: - xml -- The bind feature element. + features -- The stream features stanza. """ log.debug("Requesting resource: %s" % self.boundjid.resource) - xml.clear() - iq = self.Iq(stype='set') + iq = self.Iq() + iq['type'] = 'set' + iq.enable('bind') if self.boundjid.resource: - res = ET.Element('resource') - res.text = self.boundjid.resource - xml.append(res) - iq.append(xml) + iq['bind']['resource'] = self.boundjid.resource response = iq.send() - bind_ns = 'urn:ietf:params:xml:ns:xmpp-bind' - self.set_jid(response.xml.find('{%s}bind/{%s}jid' % (bind_ns, - bind_ns)).text) + self.set_jid(response['bind']['jid']) self.bound = True + log.info("Node set to: %s" % self.boundjid.full) - session_ns = 'urn:ietf:params:xml:ns:xmpp-session' - if "{%s}session" % session_ns not in self.features or self.bindfail: + + if 'session' not in features['features']: log.debug("Established Session") self.sessionstarted = True self.session_started_event.set() self.event("session_start") - def _handle_start_session(self, xml): + def _handle_start_session(self, features): """ Handle the start of the session. Arguments: - xml -- The session feature element. + feature -- The stream features element. """ - if self.authenticated and self.bound: - iq = self.makeIqSet(xml) - response = iq.send() - log.debug("Established Session") - self.sessionstarted = True - self.session_started_event.set() - self.event("session_start") - else: - # Bind probably hasn't happened yet. - self.bindfail = True + iq = self.Iq() + iq['type'] = 'set' + iq.enable('session') + response = iq.send() + + log.debug("Established Session") + self.sessionstarted = True + self.session_started_event.set() + self.event("session_start") def _handle_roster(self, iq, request=False): """ -- cgit v1.2.3 From 3b1f3fddf093f9bad80522287b8425a713ea8c5e Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Tue, 28 Jun 2011 11:06:44 -0700 Subject: Reorganized stream level stanzas. --- sleekxmpp/clientxmpp.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'sleekxmpp/clientxmpp.py') diff --git a/sleekxmpp/clientxmpp.py b/sleekxmpp/clientxmpp.py index 3a5f41bd..ea9654a6 100644 --- a/sleekxmpp/clientxmpp.py +++ b/sleekxmpp/clientxmpp.py @@ -19,8 +19,7 @@ from sleekxmpp import plugins from sleekxmpp import stanza from sleekxmpp.basexmpp import BaseXMPP from sleekxmpp.stanza import * -from sleekxmpp.stanza import tls -from sleekxmpp.stanza import sasl +from sleekxmpp.stanza.stream import tls, sasl from sleekxmpp.xmlstream import XMLStream, RestartStream from sleekxmpp.xmlstream import StanzaBase, ET, register_stanza_plugin from sleekxmpp.xmlstream.matcher import * -- cgit v1.2.3 From 9ed972ffeba8f5071d5cae8497322764207fec04 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Wed, 29 Jun 2011 14:05:27 -0700 Subject: Fix SASL mechanism selection bug. ANONYMOUS was being treated as PLAIN, mechanism was being chosen purely from supported mechanisms, not those provided by the server. Broke nested handler methods into top-level methods. --- sleekxmpp/clientxmpp.py | 85 ++++++++++++++++++++++++------------------------- 1 file changed, 41 insertions(+), 44 deletions(-) (limited to 'sleekxmpp/clientxmpp.py') diff --git a/sleekxmpp/clientxmpp.py b/sleekxmpp/clientxmpp.py index ea9654a6..5d7ca125 100644 --- a/sleekxmpp/clientxmpp.py +++ b/sleekxmpp/clientxmpp.py @@ -112,6 +112,23 @@ class ClientXMPP(BaseXMPP): self.default_ns, 'jabber:iq:roster')), self._handle_roster)) + self.register_handler( + Callback('SASL Success', + MatchXPath(sasl.Success.tag_name()), + self._handle_sasl_success, + instream=True, + once=True)) + self.register_handler( + Callback('SASL Failure', + MatchXPath(sasl.Failure.tag_name()), + self._handle_sasl_fail, + instream=True, + once=True)) + self.register_handler( + Callback('STARTTLS Proceed', + MatchXPath(tls.Proceed.tag_name()), + self._handle_starttls_proceed, + instream=True)) self.register_feature('starttls', self._handle_starttls, restart=True, @@ -130,7 +147,7 @@ class ClientXMPP(BaseXMPP): self._handle_sasl_plain, priority=1) self.register_sasl_mechanism('ANONYMOUS', - self._handle_sasl_plain, + self._handle_sasl_anonymous, priority=0) def connect(self, address=tuple(), reattempt=True, use_tls=True): @@ -349,22 +366,9 @@ class ClientXMPP(BaseXMPP): Arguments: features -- The stream:features element. """ - - def tls_proceed(proceed): - """Restart the XML stream when TLS is accepted.""" - log.debug("Starting TLS") - if self.start_tls(): - self.features.append('starttls') - raise RestartStream() - if not self.use_tls: return False elif self.ssl_support: - self.register_handler( - Callback('STARTTLS Proceed', - MatchXPath(tls.Proceed.tag_name()), - tls_proceed, - instream=True)) self.send(features['starttls'], now=True) return True else: @@ -372,6 +376,13 @@ class ClientXMPP(BaseXMPP): " 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.start_tls(): + self.features.append('starttls') + raise RestartStream() + def _handle_sasl_auth(self, features): """ Handle authenticating using SASL. @@ -379,46 +390,32 @@ class ClientXMPP(BaseXMPP): Arguments: features -- The stream features stanza. """ - - def sasl_success(stanza): - """SASL authentication succeeded. Restart the stream.""" - self.authenticated = True - self.features.append('mechanisms') - raise RestartStream() - - def sasl_fail(stanza): - """SASL authentication failed. Disconnect and shutdown.""" - log.info("Authentication failed.") - self.event("failed_auth", direct=True) - self.disconnect() - log.debug("Starting SASL Auth") - return True - - self.register_handler( - Callback('SASL Success', - MatchXPath(sasl.Success.tag_name()), - sasl_success, - instream=True, - once=True)) - - self.register_handler( - Callback('SASL Failure', - MatchXPath(sasl.Failure.tag_name()), - sasl_fail, - instream=True, - once=True)) - for priority, mech in self._sasl_mechanism_priorities: - if mech in self._sasl_mechanism_handlers: + if mech in features['mechanisms']: handler = self._sasl_mechanism_handlers[mech] if handler(self): break else: log.error("No appropriate login method.") + self.event("no_auth", direct=True) self.disconnect() return True + def _handle_sasl_success(self, stanza): + """SASL authentication succeeded. Restart the stream.""" + self.authenticated = True + self.features.append('mechanisms') + raise RestartStream() + + def _handle_sasl_fail(self, stanza): + """SASL authentication failed. Disconnect and shutdown.""" + log.info("Authentication failed.") + self.event("failed_auth", direct=True) + self.disconnect() + log.debug("Starting SASL Auth") + return True + def _handle_sasl_plain(self, xmpp): """ Attempt to authenticate using SASL PLAIN. -- cgit v1.2.3 From 754ac5092a3a37819a71f6565a1e54b3f2547940 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Thu, 30 Jun 2011 15:40:22 -0700 Subject: Reorganize features into plugins. --- sleekxmpp/clientxmpp.py | 207 ++++-------------------------------------------- 1 file changed, 16 insertions(+), 191 deletions(-) (limited to 'sleekxmpp/clientxmpp.py') diff --git a/sleekxmpp/clientxmpp.py b/sleekxmpp/clientxmpp.py index 5d7ca125..9c2696da 100644 --- a/sleekxmpp/clientxmpp.py +++ b/sleekxmpp/clientxmpp.py @@ -15,8 +15,10 @@ import hashlib import random import threading +import sleekxmpp from sleekxmpp import plugins from sleekxmpp import stanza +from sleekxmpp import features from sleekxmpp.basexmpp import BaseXMPP from sleekxmpp.stanza import * from sleekxmpp.stanza.stream import tls, sasl @@ -97,10 +99,6 @@ class ClientXMPP(BaseXMPP): self.add_event_handler('connected', self._handle_connected) self.register_stanza(StreamFeatures) - self.register_stanza(tls.Proceed) - self.register_stanza(sasl.Success) - self.register_stanza(sasl.Failure) - self.register_stanza(sasl.Auth) self.register_handler( Callback('Stream Features', @@ -112,43 +110,18 @@ class ClientXMPP(BaseXMPP): self.default_ns, 'jabber:iq:roster')), self._handle_roster)) - self.register_handler( - Callback('SASL Success', - MatchXPath(sasl.Success.tag_name()), - self._handle_sasl_success, - instream=True, - once=True)) - self.register_handler( - Callback('SASL Failure', - MatchXPath(sasl.Failure.tag_name()), - self._handle_sasl_fail, - instream=True, - once=True)) - self.register_handler( - Callback('STARTTLS Proceed', - MatchXPath(tls.Proceed.tag_name()), - self._handle_starttls_proceed, - instream=True)) - - self.register_feature('starttls', self._handle_starttls, - restart=True, - order=0) - self.register_feature('mechanisms', self._handle_sasl_auth, - restart=True, - order=100) - self.register_feature('bind', self._handle_bind_resource, - restart=False, - order=10000) - self.register_feature('session', self._handle_start_session, - restart=False, - order=10001) - - self.register_sasl_mechanism('PLAIN', - self._handle_sasl_plain, - priority=1) - self.register_sasl_mechanism('ANONYMOUS', - self._handle_sasl_anonymous, - priority=0) + + # Setup default stream features + self.register_plugin('feature_starttls') + self.register_plugin('feature_mechanisms') + self.register_plugin('feature_bind') + self.register_plugin('feature_session') + + # Setup default SASL mechanisms + self.register_plugin('sasl_plain', + {'priority': 1}) + self.register_plugin('sasl_anonymous', + {'priority': 0}) def connect(self, address=tuple(), reattempt=True, use_tls=True): """ @@ -242,9 +215,7 @@ class ClientXMPP(BaseXMPP): preferred ordering for the mechanism. High values will be attempted first. """ - self._sasl_mechanism_handlers[name] = handler - self._sasl_mechanism_priorities.append((priority, name)) - self._sasl_mechanism_priorities.sort(reverse=True) + self['feature_mechanisms'].register_mechanism(name, handler, priority) def remove_sasl_mechanism(self, name): """ @@ -253,11 +224,7 @@ class ClientXMPP(BaseXMPP): Arguments: name -- The name of the mechanism to remove (all caps) """ - if name in self._sasl_mechanism_handlers: - del self._sasl_mechanism_handlers[name] - - p = self._sasl_mechanism_priorities - self._sasl_mechanism_priorities = [i for i in p if i[1] != name] + self['feature_mechanisms'].remove_mechanism(name) def update_roster(self, jid, name=None, subscription=None, groups=[], block=True, timeout=None, callback=None): @@ -359,148 +326,6 @@ class ClientXMPP(BaseXMPP): # restarting the XML stream. return True - def _handle_starttls(self, features): - """ - Handle notification that the server supports TLS. - - Arguments: - features -- The stream:features element. - """ - if not self.use_tls: - return False - elif self.ssl_support: - self.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.start_tls(): - self.features.append('starttls') - raise RestartStream() - - def _handle_sasl_auth(self, features): - """ - Handle authenticating using SASL. - - Arguments: - features -- The stream features stanza. - """ - for priority, mech in self._sasl_mechanism_priorities: - if mech in features['mechanisms']: - handler = self._sasl_mechanism_handlers[mech] - if handler(self): - break - else: - log.error("No appropriate login method.") - self.event("no_auth", direct=True) - self.disconnect() - - return True - - def _handle_sasl_success(self, stanza): - """SASL authentication succeeded. Restart the stream.""" - self.authenticated = True - self.features.append('mechanisms') - raise RestartStream() - - def _handle_sasl_fail(self, stanza): - """SASL authentication failed. Disconnect and shutdown.""" - log.info("Authentication failed.") - self.event("failed_auth", direct=True) - self.disconnect() - log.debug("Starting SASL Auth") - return True - - def _handle_sasl_plain(self, xmpp): - """ - Attempt to authenticate using SASL PLAIN. - - Arguments: - xmpp -- The SleekXMPP connection instance. - """ - if not xmpp.boundjid.user: - return False - - if sys.version_info < (3, 0): - user = bytes(self.boundjid.user) - password = bytes(self.password) - else: - user = bytes(self.boundjid.user, 'utf-8') - password = bytes(self.password, 'utf-8') - - auth = base64.b64encode(b'\x00' + user + \ - b'\x00' + password).decode('utf-8') - - resp = sasl.Auth(xmpp) - resp['mechanism'] = 'PLAIN' - resp['value'] = auth - resp.send(now=True) - return True - - def _handle_sasl_anonymous(self, xmpp): - """ - Attempt to authenticate using SASL ANONYMOUS. - - Arguments: - xmpp -- The SleekXMPP connection instance. - """ - if xmpp.boundjid.user: - return False - - resp = sasl.Auth(xmpp) - resp['mechanism'] = 'ANONYMOUS' - resp.send() - - return True - - def _handle_bind_resource(self, features): - """ - Handle requesting a specific resource. - - Arguments: - features -- The stream features stanza. - """ - log.debug("Requesting resource: %s" % self.boundjid.resource) - iq = self.Iq() - iq['type'] = 'set' - iq.enable('bind') - if self.boundjid.resource: - iq['bind']['resource'] = self.boundjid.resource - response = iq.send(now=True) - - self.set_jid(response['bind']['jid']) - self.bound = True - - log.info("Node set to: %s" % self.boundjid.full) - - if 'session' not in features['features']: - log.debug("Established Session") - self.sessionstarted = True - self.session_started_event.set() - self.event("session_start") - - def _handle_start_session(self, features): - """ - Handle the start of the session. - - Arguments: - feature -- The stream features element. - """ - iq = self.Iq() - iq['type'] = 'set' - iq.enable('session') - response = iq.send(now=True) - - log.debug("Established Session") - self.sessionstarted = True - self.session_started_event.set() - self.event("session_start") - def _handle_roster(self, iq, request=False): """ Update the roster after receiving a roster stanza. -- cgit v1.2.3 From 634f5d691bab9855deddc4c201389bb60470d76e Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Fri, 1 Jul 2011 14:45:55 -0700 Subject: Continued reorganization and streamlining. --- sleekxmpp/clientxmpp.py | 2 -- 1 file changed, 2 deletions(-) (limited to 'sleekxmpp/clientxmpp.py') diff --git a/sleekxmpp/clientxmpp.py b/sleekxmpp/clientxmpp.py index 9c2696da..7245053f 100644 --- a/sleekxmpp/clientxmpp.py +++ b/sleekxmpp/clientxmpp.py @@ -87,8 +87,6 @@ class ClientXMPP(BaseXMPP): self.features = [] self._stream_feature_handlers = {} self._stream_feature_order = [] - self._sasl_mechanism_handlers = {} - self._sasl_mechanism_priorities = [] #TODO: Use stream state here self.authenticated = False -- cgit v1.2.3 From b0297af38d6dcd9ebfdaa0131ea798c9fe2b8c63 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Sat, 2 Jul 2011 21:43:02 -0700 Subject: Finish cleaning up stream feature organization. Fixed missing references that weren't caught due to leftover pyc file allowing tests to keep working when they shouldn't have. --- sleekxmpp/clientxmpp.py | 1 - 1 file changed, 1 deletion(-) (limited to 'sleekxmpp/clientxmpp.py') diff --git a/sleekxmpp/clientxmpp.py b/sleekxmpp/clientxmpp.py index 7245053f..17a7582f 100644 --- a/sleekxmpp/clientxmpp.py +++ b/sleekxmpp/clientxmpp.py @@ -21,7 +21,6 @@ from sleekxmpp import stanza from sleekxmpp import features from sleekxmpp.basexmpp import BaseXMPP from sleekxmpp.stanza import * -from sleekxmpp.stanza.stream import tls, sasl from sleekxmpp.xmlstream import XMLStream, RestartStream from sleekxmpp.xmlstream import StanzaBase, ET, register_stanza_plugin from sleekxmpp.xmlstream.matcher import * -- cgit v1.2.3 From fba235a801a3a1c06d1769cdc944b72dce33f88a Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Sat, 2 Jul 2011 21:57:50 -0700 Subject: Simplify SASL mech registration. Moved SASL registration completely to the feature plugin, instead of keeping a portion of it in ClientXMPP. --- sleekxmpp/clientxmpp.py | 26 -------------------------- 1 file changed, 26 deletions(-) (limited to 'sleekxmpp/clientxmpp.py') diff --git a/sleekxmpp/clientxmpp.py b/sleekxmpp/clientxmpp.py index 17a7582f..5b36e845 100644 --- a/sleekxmpp/clientxmpp.py +++ b/sleekxmpp/clientxmpp.py @@ -197,32 +197,6 @@ class ClientXMPP(BaseXMPP): self._stream_feature_order.append((order, name)) self._stream_feature_order.sort() - def register_sasl_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['feature_mechanisms'].register_mechanism(name, handler, priority) - - def remove_sasl_mechanism(self, name): - """ - Remove support for a given SASL authentication mechanism. - - Arguments: - name -- The name of the mechanism to remove (all caps) - """ - self['feature_mechanisms'].remove_mechanism(name) - def update_roster(self, jid, name=None, subscription=None, groups=[], block=True, timeout=None, callback=None): """ -- cgit v1.2.3 From b898b14b77d739cb1c118c9e3648aa268348d293 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Sat, 2 Jul 2011 22:30:34 -0700 Subject: Use a set to track negotiated features. Added guards to prevent renegotiating STARTTLS or SASL in cases where servers don't behave properly. --- sleekxmpp/clientxmpp.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'sleekxmpp/clientxmpp.py') diff --git a/sleekxmpp/clientxmpp.py b/sleekxmpp/clientxmpp.py index 5b36e845..5eb9c90a 100644 --- a/sleekxmpp/clientxmpp.py +++ b/sleekxmpp/clientxmpp.py @@ -83,7 +83,7 @@ class ClientXMPP(BaseXMPP): "xmlns='%s'" % self.default_ns) self.stream_footer = "" - self.features = [] + self.features = set() self._stream_feature_handlers = {} self._stream_feature_order = [] @@ -273,7 +273,7 @@ class ClientXMPP(BaseXMPP): self.sessionstarted = False self.bound = False self.bindfail = False - self.features = [] + self.features = set() def session_timeout(): if not self.session_started_event.isSet(): -- cgit v1.2.3 From d4091dbde641dc9796b51e032ea23a0ba5c1fcbb Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Wed, 3 Aug 2011 17:00:51 -0700 Subject: Integrate a modified version of Dave Cridland's Suelta SASL library. --- sleekxmpp/clientxmpp.py | 6 ------ 1 file changed, 6 deletions(-) (limited to 'sleekxmpp/clientxmpp.py') diff --git a/sleekxmpp/clientxmpp.py b/sleekxmpp/clientxmpp.py index 5eb9c90a..04c19d3f 100644 --- a/sleekxmpp/clientxmpp.py +++ b/sleekxmpp/clientxmpp.py @@ -114,12 +114,6 @@ class ClientXMPP(BaseXMPP): self.register_plugin('feature_bind') self.register_plugin('feature_session') - # Setup default SASL mechanisms - self.register_plugin('sasl_plain', - {'priority': 1}) - self.register_plugin('sasl_anonymous', - {'priority': 0}) - def connect(self, address=tuple(), reattempt=True, use_tls=True): """ Connect to the XMPP server. -- cgit v1.2.3