From 70f69c180cb828f0a76b0d9a441c98036cc96fb0 Mon Sep 17 00:00:00 2001 From: Brian Beggs Date: Tue, 4 May 2010 14:03:38 -0400 Subject: Fixes for disconnection problems detailed in http://github.com/fritzy/SleekXMPP/issues/#issue/20 Fixes to both ClientXMPP & xmlstream. ClientXMPP was not tracking the changes to authenticated and sessionstarted after the client was disconnected. xmlstream had some funkyness with state in the _process method that was cleaned up and hopefully made a little cleaner. Also changed a DNS issue that was occuring that rendered me unable to disconnect. I would recieve the following error upon reconnect. Exception in thread process: Exception in thread process: Traceback (most recent call last): File "/usr/local/lib/python2.6/threading.py", line 532, in __bootstrap_inner self.run() File "/usr/local/lib/python2.6/threading.py", line 484, in run self.__target(*self.__args, **self.__kwargs) File "/home/macdiesel/tmp/workspace/SleekXMPP/sleekxmpp/xmlstream/xmlstream.py", line 202, in _process self.reconnect() File "/home/macdiesel/tmp/workspace/SleekXMPP/sleekxmpp/__init__.py", line 134, in reconnect XMLStream.reconnect(self) File "/home/macdiesel/tmp/workspace/SleekXMPP/sleekxmpp/xmlstream/xmlstream.py", line 289, in reconnect self.connect() File "/home/macdiesel/tmp/workspace/SleekXMPP/sleekxmpp/__init__.py", line 99, in connect answers = dns.resolver.query("_xmpp-client._tcp.%s" % self.server, "SRV") File "/usr/local/lib/python2.6/site-packages/dns/resolver.py", line 732, in query return get_default_resolver().query(qname, rdtype, rdclass, tcp, source) File "/usr/local/lib/python2.6/site-packages/dns/resolver.py", line 617, in query source=source) File "/usr/local/lib/python2.6/site-packages/dns/query.py", line 113, in udp wire = q.to_wire() File "/usr/local/lib/python2.6/site-packages/dns/message.py", line 404, in to_wire r.add_question(rrset.name, rrset.rdtype, rrset.rdclass) File "/usr/local/lib/python2.6/site-packages/dns/renderer.py", line 152, in add_question self.output.write(struct.pack("!HH", rdtype, rdclass)) TypeError: unsupported operand type(s) for &: 'unicode' and 'long' Seems I was getting this error when calling line 99 in ClientXMPP. You can't bit-shift a 1 and a string and this is why this error is coming up. I removed the "SRV" argument and used the default of 1. not sure exactly what this should be so it may need to be fixed back before it's merged back to trunk. The line in question: answers = dns.resolver.query("_xmpp-client._tcp.%s" % self.server, "SRV") --- .gitignore | 1 + sleekxmpp/__init__.py | 6 +++++- sleekxmpp/xmlstream/xmlstream.py | 6 ++++-- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 0fe2c40e..788de4c7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ *.pyc +.project build/ diff --git a/sleekxmpp/__init__.py b/sleekxmpp/__init__.py index 773f7936..e2cfb1b9 100644 --- a/sleekxmpp/__init__.py +++ b/sleekxmpp/__init__.py @@ -96,7 +96,7 @@ class ClientXMPP(basexmpp, XMLStream): else: logging.debug("Since no address is supplied, attempting SRV lookup.") try: - answers = dns.resolver.query("_xmpp-client._tcp.%s" % self.server, "SRV") + answers = dns.resolver.query("_xmpp-client._tcp.%s" % self.server) except dns.resolver.NXDOMAIN: logging.debug("No appropriate SRV record found. Using JID server name.") else: @@ -131,10 +131,14 @@ class ClientXMPP(basexmpp, XMLStream): def reconnect(self): logging.info("Reconnecting") self.event("disconnected") + self.authenticated = False + self.sessionstarted = False XMLStream.reconnect(self) def disconnect(self, init=True, close=False, reconnect=False): self.event("disconnected") + self.authenticated = False + self.sessionstarted = False XMLStream.disconnect(self, reconnect) def registerFeature(self, mask, pointer, breaker = False): diff --git a/sleekxmpp/xmlstream/xmlstream.py b/sleekxmpp/xmlstream/xmlstream.py index 025884b7..13a87a63 100644 --- a/sleekxmpp/xmlstream/xmlstream.py +++ b/sleekxmpp/xmlstream/xmlstream.py @@ -199,9 +199,11 @@ class XMLStream(object): traceback.print_exc() self.disconnect(reconnect=True) if self.state['reconnect']: + self.state.set('connected', False) + self.state.set('processing', False) self.reconnect() - self.state.set('processing', False) - self.eventqueue.put(('quit', None, None)) + else: + self.eventqueue.put(('quit', None, None)) #self.__thread['readXML'] = threading.Thread(name='readXML', target=self.__readXML) #self.__thread['readXML'].start() #self.__thread['spawnEvents'] = threading.Thread(name='spawnEvents', target=self.__spawnEvents) -- cgit v1.2.3 From 223507f36f6dd4c0d4a0733a524fd529231b010b Mon Sep 17 00:00:00 2001 From: Nathan Fritz Date: Wed, 12 May 2010 13:45:36 -0700 Subject: fixed a rather large memory leak --- conn_tests/test_pubsubserver.py | 37 +++++++++++++++++++++++++++++++++++++ sleekxmpp/plugins/xep_0045.py | 4 ++-- sleekxmpp/stanza/error.py | 2 +- sleekxmpp/stanza/htmlim.py | 3 ++- sleekxmpp/stanza/nick.py | 3 ++- sleekxmpp/xmlstream/stanzabase.py | 14 +++++++++----- 6 files changed, 53 insertions(+), 10 deletions(-) diff --git a/conn_tests/test_pubsubserver.py b/conn_tests/test_pubsubserver.py index 7e5b57b0..d1e2208f 100644 --- a/conn_tests/test_pubsubserver.py +++ b/conn_tests/test_pubsubserver.py @@ -43,6 +43,10 @@ class TestPubsubServer(unittest.TestCase): def test001getdefaultconfig(self): """Get the default node config""" + self.xmpp1['xep_0060'].deleteNode(self.pshost, 'testnode2') + self.xmpp1['xep_0060'].deleteNode(self.pshost, 'testnode3') + self.xmpp1['xep_0060'].deleteNode(self.pshost, 'testnode4') + self.xmpp1['xep_0060'].deleteNode(self.pshost, 'testnode5') result = self.xmpp1['xep_0060'].getNodeConfig(self.pshost) self.statev['defaultconfig'] = result self.failUnless(isinstance(result, sleekxmpp.plugins.xep_0004.Form)) @@ -130,6 +134,39 @@ class TestPubsubServer(unittest.TestCase): self.failUnless(msg != False, "Account #1 did not get message event: perhaps node was advertised incorrectly?") self.failUnless(result) +# def test016speedtest(self): +# "Uncached speed test" +# import time +# start = time.time() +# for y in range(0, 50000, 1000): +# start2 = time.time() +# for x in range(y, y+1000): +# self.failUnless(self.xmpp1['xep_0060'].subscribe(self.pshost, "testnode4", subscribee="testuser%s@whatever" % x)) +# print time.time() - start2 +# seconds = time.time() - start +# print "--", seconds +# print "---------" +# time.sleep(15) +# self.failUnless(self.xmpp1['xep_0060'].deleteNode(self.pshost, 'testnode4'), "Could not delete non-cached test node") + +# def test015speedtest(self): +# "cached speed test" +# result = self.xmpp1['xep_0060'].getNodeConfig(self.pshost) +# self.statev['defaultconfig'] = result +# self.statev['defaultconfig'].field['pubsub#node_type'].setValue("leaf") +# self.statev['defaultconfig'].field['sleek#saveonchange'].setValue(True) +# self.failUnless(self.xmpp1['xep_0060'].create_node(self.pshost, 'testnode4', self.statev['defaultconfig'])) +# self.statev['defaultconfig'].field['sleek#saveonchange'].setValue(False) +# self.failUnless(self.xmpp1['xep_0060'].create_node(self.pshost, 'testnode5', self.statev['defaultconfig'])) +# start = time.time() +# for y in range(0, 50000, 1000): +# start2 = time.time() +# for x in range(y, y+1000): +# self.failUnless(self.xmpp1['xep_0060'].subscribe(self.pshost, "testnode5", subscribee="testuser%s@whatever" % x)) +# print time.time() - start2 +# seconds = time.time() - start +# print "--", seconds + def test900cleanup(self): "Cleaning up" self.failUnless(self.xmpp1['xep_0060'].deleteNode(self.pshost, 'testnode2'), "Could not delete test node.") diff --git a/sleekxmpp/plugins/xep_0045.py b/sleekxmpp/plugins/xep_0045.py index 240e6b9a..937c6f96 100644 --- a/sleekxmpp/plugins/xep_0045.py +++ b/sleekxmpp/plugins/xep_0045.py @@ -93,10 +93,10 @@ class MUCPresence(ElementBase): return self def getNick(self): - return self.parent['from'].resource + return self.parent()['from'].resource def getRoom(self): - return self.parent['from'].bare + return self.parent()['from'].bare def setNick(self, value): logging.warning("Cannot set nick through mucpresence plugin.") diff --git a/sleekxmpp/stanza/error.py b/sleekxmpp/stanza/error.py index a1454d1e..15af6624 100644 --- a/sleekxmpp/stanza/error.py +++ b/sleekxmpp/stanza/error.py @@ -22,7 +22,7 @@ class Error(ElementBase): self['type'] = 'cancel' self['condition'] = 'feature-not-implemented' if self.parent is not None: - self.parent['type'] = 'error' + self.parent()['type'] = 'error' def getCondition(self): for child in self.xml.getchildren(): diff --git a/sleekxmpp/stanza/htmlim.py b/sleekxmpp/stanza/htmlim.py index f9ee985f..60686e4a 100644 --- a/sleekxmpp/stanza/htmlim.py +++ b/sleekxmpp/stanza/htmlim.py @@ -31,4 +31,5 @@ class HTMLIM(ElementBase): return html def delHtml(self): - return self.__del__() + if self.parent is not None: + self.parent().xml.remove(self.xml) diff --git a/sleekxmpp/stanza/nick.py b/sleekxmpp/stanza/nick.py index 92a523d6..ac7e3604 100644 --- a/sleekxmpp/stanza/nick.py +++ b/sleekxmpp/stanza/nick.py @@ -22,4 +22,5 @@ class Nick(ElementBase): return self.xml.text def delNick(self): - return self.__del__() + if self.parent is not None: + self.parent().xml.remove(self.xml) diff --git a/sleekxmpp/xmlstream/stanzabase.py b/sleekxmpp/xmlstream/stanzabase.py index 57c727a3..018e81c3 100644 --- a/sleekxmpp/xmlstream/stanzabase.py +++ b/sleekxmpp/xmlstream/stanzabase.py @@ -9,6 +9,7 @@ from xml.etree import cElementTree as ET import logging import traceback import sys +import weakref if sys.version_info < (3,0): from . import tostring26 as tostring @@ -51,7 +52,10 @@ class ElementBase(tostring.ToString): subitem = None def __init__(self, xml=None, parent=None): - self.parent = parent + if parent is None: + self.parent = None + else: + self.parent = weakref.ref(parent) self.xml = xml self.plugins = {} self.iterables = [] @@ -158,7 +162,7 @@ class ElementBase(tostring.ToString): else: self.xml.append(new) if self.parent is not None: - self.parent.xml.append(self.xml) + self.parent().xml.append(self.xml) return True #had to generate XML else: return False @@ -302,9 +306,9 @@ class ElementBase(tostring.ToString): self.xml.append(xml) return self - def __del__(self): - if self.parent is not None: - self.parent.xml.remove(self.xml) + #def __del__(self): #prevents garbage collection of reference cycle + # if self.parent is not None: + # self.parent.xml.remove(self.xml) class StanzaBase(ElementBase): name = 'stanza' -- cgit v1.2.3 From ae41c08fecfe627627f2a4d9b3861d4ae24d673e Mon Sep 17 00:00:00 2001 From: Nathan Fritz Date: Wed, 12 May 2010 18:07:20 -0700 Subject: added test for unsolicided unavailable presence and fixed bug to make it pass --- sleekxmpp/basexmpp.py | 7 ++++--- tests/test_presencestanzas.py | 16 ++++++++++++++++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/sleekxmpp/basexmpp.py b/sleekxmpp/basexmpp.py index fef8538a..13fe2100 100644 --- a/sleekxmpp/basexmpp.py +++ b/sleekxmpp/basexmpp.py @@ -271,12 +271,13 @@ class basexmpp(object): name = self.roster[jid].get('name', '') if show == 'unavailable': logging.debug("%s %s got offline" % (jid, resource)) - if len(self.roster[jid]['presence']): - del self.roster[jid]['presence'][resource] - else: + del self.roster[jid]['presence'][resource] + if len(self.roster[jid]['presence']) == 0 and not self.roster[jid]['in_roster']: del self.roster[jid] if not wasoffline: self.event("got_offline", presence) + else: + return False self.event("changed_status", presence) name = '' if name: diff --git a/tests/test_presencestanzas.py b/tests/test_presencestanzas.py index 430c71ca..23eb911e 100644 --- a/tests/test_presencestanzas.py +++ b/tests/test_presencestanzas.py @@ -11,5 +11,21 @@ class testpresencestanzas(unittest.TestCase): p = self.p.Presence() p['type'] = 'dnd' self.failUnless(str(p) == "dnd") + + def testPresenceUnsolicitedOffline(self): + "Unsolicted offline presence does not spawn changed_status or update roster" + p = self.p.Presence() + p['type'] = 'unavailable' + p['from'] = 'bill@chadmore.com/gmail15af' + import sleekxmpp + c = sleekxmpp.ClientXMPP('crap@wherever', 'password') + happened = [] + def handlechangedpresence(event): + happened.append(True) + c.add_event_handler("changed_status", handlechangedpresence) + c._handlePresence(p) + self.failUnless(happened == [], "changed_status event triggered for superfulous unavailable presence") + self.failUnless(c.roster == {}, "Roster updated for superfulous unavailable presence") + suite = unittest.TestLoader().loadTestsFromTestCase(testpresencestanzas) -- cgit v1.2.3 From c004f042f9976e90cfd42a0698ba83872fe00825 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Thu, 20 May 2010 23:17:22 +0800 Subject: Added del_event_handler to remove handler functions for a given event. All registered handlers for the event which use the given function will be removed. Using this method allows agents to reconfigure their behaviour on the fly without needing to add extra state information to event handling functions. --- sleekxmpp/basexmpp.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/sleekxmpp/basexmpp.py b/sleekxmpp/basexmpp.py index 13fe2100..83431bd7 100644 --- a/sleekxmpp/basexmpp.py +++ b/sleekxmpp/basexmpp.py @@ -185,6 +185,19 @@ class basexmpp(object): self.event_handlers[name] = [] self.event_handlers[name].append((pointer, threaded, disposable)) + def del_event_handler(self, name, pointer): + """Remove a handler for an event.""" + if not name in self.event_handlers: + return + + # Need to keep handlers that do not use + # the given function pointer + def filter_pointers(handler): + return handler[0] != pointer + + self.event_handlers[name] = filter(filter_pointers, + self.event_handlers[name]) + def event(self, name, eventdata = {}): # called on an event for handler in self.event_handlers.get(name, []): if handler[1]: #if threaded -- cgit v1.2.3 From feaa7539affa9a451b568b5b1502630cd3833ca3 Mon Sep 17 00:00:00 2001 From: Nathan Fritz Date: Thu, 20 May 2010 13:09:04 -0700 Subject: added test_events and testing new del_event_handler --- tests/test_events.py | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 tests/test_events.py diff --git a/tests/test_events.py b/tests/test_events.py new file mode 100644 index 00000000..11821dbb --- /dev/null +++ b/tests/test_events.py @@ -0,0 +1,35 @@ +import unittest + +class testevents(unittest.TestCase): + + def setUp(self): + import sleekxmpp.stanza.presence as p + self.p = p + + def testEventHappening(self): + "Test handler working" + import sleekxmpp + c = sleekxmpp.ClientXMPP('crap@wherever', 'password') + happened = [] + def handletestevent(event): + happened.append(True) + c.add_event_handler("test_event", handletestevent) + c.event("test_event", {}) + c.event("test_event", {}) + self.failUnless(happened == [True, True], "event did not get triggered twice") + + def testDelEvent(self): + "Test handler working, then deleted and not triggered" + import sleekxmpp + c = sleekxmpp.ClientXMPP('crap@wherever', 'password') + happened = [] + def handletestevent(event): + happened.append(True) + c.add_event_handler("test_event", handletestevent) + c.event("test_event", {}) + c.del_event_handler("test_event", handletestevent) + c.event("test_event", {}) # should not trigger because it was deleted + self.failUnless(happened == [True], "event did not get triggered the correct number of times") + + +suite = unittest.TestLoader().loadTestsFromTestCase(testevents) -- cgit v1.2.3 From 3920ee394183d95aa6f5b4ee3c8843c2d34ad4cb Mon Sep 17 00:00:00 2001 From: Nathan Fritz Date: Mon, 24 May 2010 14:27:13 -0700 Subject: added plugin indexing to components --- sleekxmpp/componentxmpp.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/sleekxmpp/componentxmpp.py b/sleekxmpp/componentxmpp.py index 9c7a6125..89953687 100755 --- a/sleekxmpp/componentxmpp.py +++ b/sleekxmpp/componentxmpp.py @@ -54,6 +54,16 @@ class ComponentXMPP(basexmpp, XMLStream): self.secret = secret self.registerHandler(Callback('Handshake', MatchXPath('{jabber:component:accept}handshake'), self._handleHandshake)) + def __getitem__(self, key): + if key in self.plugin: + return self.plugin[key] + else: + logging.warning("""Plugin "%s" is not loaded.""" % key) + return False + + def get(self, key, default): + return self.plugin.get(key, default) + def incoming_filter(self, xmlobj): if xmlobj.tag.startswith('{jabber:client}'): xmlobj.tag = xmlobj.tag.replace('jabber:client', self.default_ns) -- cgit v1.2.3 From 828cba875fa5466493e3ea3f804bb349743a0615 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Sat, 22 May 2010 22:39:35 +0800 Subject: Added the error attribute 'code' to the Error object interface. --- sleekxmpp/stanza/error.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sleekxmpp/stanza/error.py b/sleekxmpp/stanza/error.py index 15af6624..f87b6490 100644 --- a/sleekxmpp/stanza/error.py +++ b/sleekxmpp/stanza/error.py @@ -12,7 +12,7 @@ class Error(ElementBase): name = 'error' plugin_attrib = 'error' conditions = set(('bad-request', 'conflict', 'feature-not-implemented', 'forbidden', 'gone', 'item-not-found', 'jid-malformed', 'not-acceptable', 'not-allowed', 'not-authorized', 'payment-required', 'recipient-unavailable', 'redirect', 'registration-required', 'remote-server-not-found', 'remote-server-timeout', 'service-unavailable', 'subscription-required', 'undefined-condition', 'unexpected-request')) - interfaces = set(('condition', 'text', 'type')) + interfaces = set(('code', 'condition', 'text', 'type')) types = set(('cancel', 'continue', 'modify', 'auth', 'wait')) sub_interfaces = set(('text',)) condition_ns = 'urn:ietf:params:xml:ns:xmpp-stanzas' -- cgit v1.2.3 From 35f4ef3452015d7f9ef1e300bc104793de12f1e8 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Sat, 22 May 2010 22:40:30 +0800 Subject: Modified the return values for several methods so that they can be chained. For example: iq.reply().error().setPayload(something.xml).send() --- sleekxmpp/stanza/iq.py | 1 + sleekxmpp/xmlstream/stanzabase.py | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/sleekxmpp/stanza/iq.py b/sleekxmpp/stanza/iq.py index 4969b703..ded7515f 100644 --- a/sleekxmpp/stanza/iq.py +++ b/sleekxmpp/stanza/iq.py @@ -37,6 +37,7 @@ class Iq(RootStanza): def setPayload(self, value): self.clear() StanzaBase.setPayload(self, value) + return self def setQuery(self, value): query = self.xml.find("{%s}query" % value) diff --git a/sleekxmpp/xmlstream/stanzabase.py b/sleekxmpp/xmlstream/stanzabase.py index 018e81c3..3f3f5e08 100644 --- a/sleekxmpp/xmlstream/stanzabase.py +++ b/sleekxmpp/xmlstream/stanzabase.py @@ -332,7 +332,7 @@ class StanzaBase(ElementBase): def setType(self, value): if value in self.types: - self.xml.attrib['type'] = value + self.xml.attrib['type'] = value return self def getPayload(self): @@ -340,15 +340,18 @@ class StanzaBase(ElementBase): def setPayload(self, value): self.xml.append(value) + return self def delPayload(self): self.clear() + return self def clear(self): for child in self.xml.getchildren(): self.xml.remove(child) for plugin in list(self.plugins.keys()): del self.plugins[plugin] + return self def reply(self): self['from'], self['to'] = self['to'], self['from'] @@ -357,6 +360,7 @@ class StanzaBase(ElementBase): def error(self): self['type'] = 'error' + return self def getTo(self): return JID(self._getAttr('to')) -- cgit v1.2.3 From 5ca4ede5ac1ddbc56e97010a2f39ed6ada37817e Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Sun, 23 May 2010 01:30:49 +0800 Subject: Added a flag to registerPlugin to control calling the plugin's post_init method. --- sleekxmpp/basexmpp.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/sleekxmpp/basexmpp.py b/sleekxmpp/basexmpp.py index 83431bd7..292a2088 100644 --- a/sleekxmpp/basexmpp.py +++ b/sleekxmpp/basexmpp.py @@ -86,7 +86,7 @@ class basexmpp(object): self.username = jid.split('@', 1)[0] self.server = jid.split('@',1)[-1].split('/', 1)[0] - def registerPlugin(self, plugin, pconfig = {}): + def registerPlugin(self, plugin, pconfig = {}, run_post=True): """Register a plugin not in plugins.__init__.__all__ but in the plugins directory.""" # discover relative "path" to the plugins module from the main app, and import it. @@ -100,6 +100,8 @@ class basexmpp(object): if hasattr(self.plugin[plugin], 'xep'): xep = "(XEP-%s) " % self.plugin[plugin].xep logging.debug("Loaded Plugin %s%s" % (xep, self.plugin[plugin].description)) + if run_post: + self.plugin[plugin].post_init() def register_plugins(self): """Initiates all plugins in the plugins/__init__.__all__""" @@ -109,7 +111,7 @@ class basexmpp(object): plugin_list = plugins.__all__ for plugin in plugin_list: if plugin in plugins.__all__: - self.registerPlugin(plugin, self.plugin_config.get(plugin, {})) + self.registerPlugin(plugin, self.plugin_config.get(plugin, {}), False) else: raise NameError("No plugin by the name of %s listed in plugins.__all__." % plugin) # run post_init() for cross-plugin interaction -- cgit v1.2.3 From 7ebc0065167189186ddf53e81a47dab887c87a72 Mon Sep 17 00:00:00 2001 From: Nathan Fritz Date: Mon, 24 May 2010 19:33:24 -0700 Subject: updated README, index fix for component --- README | 8 ++++++-- sleekxmpp/componentxmpp.py | 4 ++-- sleekxmpp/plugins/stanza_pubsub.py | 2 +- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/README b/README index a6989090..670ce3cd 100644 --- a/README +++ b/README @@ -1,5 +1,8 @@ -SleekXMPP is an XMPP library written for Python 3.x (with 2.6 compatibility). +SleekXMPP is an XMPP library written for Python 3.1+ (with 2.6 compatibility). +Hosted at http://wiki.github.com/fritzy/SleekXMPP/ + Featured in examples in XMPP: The Definitive Guide by Kevin Smith, Remko Tronçon, and Peter Saint-Andre +If you're coming here from The Definitive Guide, please read http://wiki.github.com/fritzy/SleekXMPP/xmpp-the-definitive-guide SleekXMPP has several design goals/philosophies: - Low number of dependencies. @@ -31,7 +34,8 @@ Since 0.2, here's the Changelog: Credits ---------------- Main Author: Nathan Fritz fritz@netflint.net -XEP-0045 original implementation: Kevin Smith +Contributors: Kevin Smith & Lance Stout Patches: Remko Tronçon Feel free to add fritzy@netflint.net to your roster for direct support and comments. +Join sleekxmpp-discussion@googlegroups.com / http://groups.google.com/group/sleekxmpp-discussion for discussion. diff --git a/sleekxmpp/componentxmpp.py b/sleekxmpp/componentxmpp.py index 89953687..de125814 100755 --- a/sleekxmpp/componentxmpp.py +++ b/sleekxmpp/componentxmpp.py @@ -1,4 +1,4 @@ -#!/usr/bin/python2.5 +#!/usr/bin/python2.6 """ SleekXMPP: The Sleek XMPP Library @@ -54,7 +54,7 @@ class ComponentXMPP(basexmpp, XMLStream): self.secret = secret self.registerHandler(Callback('Handshake', MatchXPath('{jabber:component:accept}handshake'), self._handleHandshake)) - def __getitem__(self, key): + def __getitem__(self, key): if key in self.plugin: return self.plugin[key] else: diff --git a/sleekxmpp/plugins/stanza_pubsub.py b/sleekxmpp/plugins/stanza_pubsub.py index 4187d49c..1dd73d99 100644 --- a/sleekxmpp/plugins/stanza_pubsub.py +++ b/sleekxmpp/plugins/stanza_pubsub.py @@ -281,7 +281,7 @@ class DefaultConfig(ElementBase): def getType(self): t = self._getAttr('type') - if not t: t == 'leaf' + if not t: t = 'leaf' return t stanzaPlugin(PubsubOwner, DefaultConfig) -- cgit v1.2.3 From f18c79082490b9b25c72d8c8870dcbf63770ffb6 Mon Sep 17 00:00:00 2001 From: Hernan E Grecco Date: Wed, 26 May 2010 06:10:34 +0800 Subject: Fixed error registering a plugin. To add a feature to another plugin, it should look into xmpp.plugin dict --- sleekxmpp/plugins/xep_0004.py | 2 +- sleekxmpp/plugins/xep_0009.py | 4 ++-- sleekxmpp/plugins/xep_0092.py | 2 +- sleekxmpp/plugins/xep_0199.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/sleekxmpp/plugins/xep_0004.py b/sleekxmpp/plugins/xep_0004.py index ec859252..24ffa978 100644 --- a/sleekxmpp/plugins/xep_0004.py +++ b/sleekxmpp/plugins/xep_0004.py @@ -31,7 +31,7 @@ class xep_0004(base.base_plugin): self.xmpp.add_handler("", self.handler_message_xform) def post_init(self): - self.xmpp['xep_0030'].add_feature('jabber:x:data') + self.xmpp.plugin['xep_0030'].add_feature('jabber:x:data') def handler_message_xform(self, xml): object = self.handle_form(xml) diff --git a/sleekxmpp/plugins/xep_0009.py b/sleekxmpp/plugins/xep_0009.py index e0da8296..403f94b9 100644 --- a/sleekxmpp/plugins/xep_0009.py +++ b/sleekxmpp/plugins/xep_0009.py @@ -185,8 +185,8 @@ class xep_0009(base.base_plugin): self.activeCalls = [] def post_init(self): - self.xmpp['xep_0030'].add_feature('jabber:iq:rpc') - self.xmpp['xep_0030'].add_identity('automatition','rpc') + self.xmpp.plugin['xep_0030'].add_feature('jabber:iq:rpc') + self.xmpp.plugin['xep_0030'].add_identity('automatition','rpc') def register_call(self, method, name=None): #@returns an string that can be used in acl commands. diff --git a/sleekxmpp/plugins/xep_0092.py b/sleekxmpp/plugins/xep_0092.py index 3d026382..ca0d7e17 100644 --- a/sleekxmpp/plugins/xep_0092.py +++ b/sleekxmpp/plugins/xep_0092.py @@ -33,7 +33,7 @@ class xep_0092(base.base_plugin): self.xmpp.add_handler("" % self.xmpp.default_ns, self.report_version) def post_init(self): - self.xmpp['xep_0030'].add_feature('jabber:iq:version') + self.xmpp.plugin['xep_0030'].add_feature('jabber:iq:version') def report_version(self, xml): iq = self.xmpp.makeIqResult(xml.get('id', 'unknown')) diff --git a/sleekxmpp/plugins/xep_0199.py b/sleekxmpp/plugins/xep_0199.py index 989e6450..9b10b927 100644 --- a/sleekxmpp/plugins/xep_0199.py +++ b/sleekxmpp/plugins/xep_0199.py @@ -35,7 +35,7 @@ class xep_0199(base.base_plugin): #self.xmpp.add_event_handler('session_start', self.handler_pingserver, threaded=True) def post_init(self): - self.xmpp['xep_0030'].add_feature('http://www.xmpp.org/extensions/xep-0199.html#ns') + self.xmpp.plugin['xep_0030'].add_feature('http://www.xmpp.org/extensions/xep-0199.html#ns') def handler_pingserver(self, xml): if not self.running: -- cgit v1.2.3 From 9cfe19c1e14f7045684ed523b65cdbdf3ab48b2e Mon Sep 17 00:00:00 2001 From: Hernan E Grecco Date: Wed, 26 May 2010 06:28:08 +0800 Subject: Changed example.py to register first Xep_0030. This a simple fix to prevent getting a key error as many plugins add features to Xep_0030. A better fix would be to call pos_init after all plugins are loaded. An even better fix would be to define dependencies for each plugin and registering on demand. --- example.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example.py b/example.py index 1ffe724a..c9b6559b 100644 --- a/example.py +++ b/example.py @@ -37,8 +37,8 @@ if __name__ == '__main__': logging.basicConfig(level=opts.loglevel, format='%(levelname)-8s %(message)s') xmpp = Example('user@gmail.com/sleekxmpp', 'password') + xmpp.registerPlugin('xep_0030') xmpp.registerPlugin('xep_0004') - xmpp.registerPlugin('xep_0030') xmpp.registerPlugin('xep_0060') xmpp.registerPlugin('xep_0199') if xmpp.connect(('talk.google.com', 5222)): -- cgit v1.2.3 From f4bc9d9722ea57b5f5d22efeb6bc65cb990e1053 Mon Sep 17 00:00:00 2001 From: Nathan Fritz Date: Wed, 26 May 2010 10:51:51 -0700 Subject: plugins now are checked for post_init having ran when process() is called --- sleekxmpp/basexmpp.py | 9 ++++++--- sleekxmpp/plugins/base.py | 3 ++- sleekxmpp/plugins/xep_0004.py | 1 + sleekxmpp/plugins/xep_0009.py | 1 + sleekxmpp/plugins/xep_0050.py | 1 + sleekxmpp/plugins/xep_0092.py | 1 + sleekxmpp/plugins/xep_0199.py | 1 + 7 files changed, 13 insertions(+), 4 deletions(-) diff --git a/sleekxmpp/basexmpp.py b/sleekxmpp/basexmpp.py index 292a2088..907067fa 100644 --- a/sleekxmpp/basexmpp.py +++ b/sleekxmpp/basexmpp.py @@ -85,8 +85,13 @@ class basexmpp(object): self.jid = self.getjidbare(jid) self.username = jid.split('@', 1)[0] self.server = jid.split('@',1)[-1].split('/', 1)[0] + + def process(self, *args, **kwargs): + for idx in self.plugin: + if not self.plugin[idx].post_inited: self.plugin[idx].post_init() + return super(basexmpp, self).process(*args, **kwargs) - def registerPlugin(self, plugin, pconfig = {}, run_post=True): + def registerPlugin(self, plugin, pconfig = {}): """Register a plugin not in plugins.__init__.__all__ but in the plugins directory.""" # discover relative "path" to the plugins module from the main app, and import it. @@ -100,8 +105,6 @@ class basexmpp(object): if hasattr(self.plugin[plugin], 'xep'): xep = "(XEP-%s) " % self.plugin[plugin].xep logging.debug("Loaded Plugin %s%s" % (xep, self.plugin[plugin].description)) - if run_post: - self.plugin[plugin].post_init() def register_plugins(self): """Initiates all plugins in the plugins/__init__.__all__""" diff --git a/sleekxmpp/plugins/base.py b/sleekxmpp/plugins/base.py index 685833f4..4223646a 100644 --- a/sleekxmpp/plugins/base.py +++ b/sleekxmpp/plugins/base.py @@ -24,6 +24,7 @@ class base_plugin(object): self.description = 'Base Plugin' self.xmpp = xmpp self.config = config + self.post_inited = False self.enable = config.get('enable', True) if self.enable: self.plugin_init() @@ -32,4 +33,4 @@ class base_plugin(object): pass def post_init(self): - pass + self.post_inited = True diff --git a/sleekxmpp/plugins/xep_0004.py b/sleekxmpp/plugins/xep_0004.py index 24ffa978..56d18929 100644 --- a/sleekxmpp/plugins/xep_0004.py +++ b/sleekxmpp/plugins/xep_0004.py @@ -31,6 +31,7 @@ class xep_0004(base.base_plugin): self.xmpp.add_handler("", self.handler_message_xform) def post_init(self): + base.base_plugin.post_init(self) self.xmpp.plugin['xep_0030'].add_feature('jabber:x:data') def handler_message_xform(self, xml): diff --git a/sleekxmpp/plugins/xep_0009.py b/sleekxmpp/plugins/xep_0009.py index 403f94b9..49ffac41 100644 --- a/sleekxmpp/plugins/xep_0009.py +++ b/sleekxmpp/plugins/xep_0009.py @@ -185,6 +185,7 @@ class xep_0009(base.base_plugin): self.activeCalls = [] def post_init(self): + base.base_plugin.post_init(self) self.xmpp.plugin['xep_0030'].add_feature('jabber:iq:rpc') self.xmpp.plugin['xep_0030'].add_identity('automatition','rpc') diff --git a/sleekxmpp/plugins/xep_0050.py b/sleekxmpp/plugins/xep_0050.py index 0ca66ddb..2f356e17 100644 --- a/sleekxmpp/plugins/xep_0050.py +++ b/sleekxmpp/plugins/xep_0050.py @@ -42,6 +42,7 @@ class xep_0050(base.base_plugin): self.sd = self.xmpp.plugin['xep_0030'] def post_init(self): + base.base_plugin.post_init(self) self.sd.add_feature('http://jabber.org/protocol/commands') def addCommand(self, node, name, form, pointer=None, multi=False): diff --git a/sleekxmpp/plugins/xep_0092.py b/sleekxmpp/plugins/xep_0092.py index ca0d7e17..aeebbe0c 100644 --- a/sleekxmpp/plugins/xep_0092.py +++ b/sleekxmpp/plugins/xep_0092.py @@ -33,6 +33,7 @@ class xep_0092(base.base_plugin): self.xmpp.add_handler("" % self.xmpp.default_ns, self.report_version) def post_init(self): + base.base_plugin.post_init(self) self.xmpp.plugin['xep_0030'].add_feature('jabber:iq:version') def report_version(self, xml): diff --git a/sleekxmpp/plugins/xep_0199.py b/sleekxmpp/plugins/xep_0199.py index 9b10b927..ccaf0b3a 100644 --- a/sleekxmpp/plugins/xep_0199.py +++ b/sleekxmpp/plugins/xep_0199.py @@ -35,6 +35,7 @@ class xep_0199(base.base_plugin): #self.xmpp.add_event_handler('session_start', self.handler_pingserver, threaded=True) def post_init(self): + base.base_plugin.post_init(self) self.xmpp.plugin['xep_0030'].add_feature('http://www.xmpp.org/extensions/xep-0199.html#ns') def handler_pingserver(self, xml): -- cgit v1.2.3 From fa92bc866beea0476def1f6bf258b6f3bed4c56f Mon Sep 17 00:00:00 2001 From: Nathan Fritz Date: Wed, 26 May 2010 11:37:01 -0700 Subject: fixed dns unicode problem --- sleekxmpp/__init__.py | 2 +- tests/test_pubsubstanzas.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/sleekxmpp/__init__.py b/sleekxmpp/__init__.py index 773f7936..263f1f99 100644 --- a/sleekxmpp/__init__.py +++ b/sleekxmpp/__init__.py @@ -96,7 +96,7 @@ class ClientXMPP(basexmpp, XMLStream): else: logging.debug("Since no address is supplied, attempting SRV lookup.") try: - answers = dns.resolver.query("_xmpp-client._tcp.%s" % self.server, "SRV") + answers = dns.resolver.query("_xmpp-client._tcp.%s" % self.server, dns.rdatatype.SRV) except dns.resolver.NXDOMAIN: logging.debug("No appropriate SRV record found. Using JID server name.") else: diff --git a/tests/test_pubsubstanzas.py b/tests/test_pubsubstanzas.py index 5353f907..55407c16 100644 --- a/tests/test_pubsubstanzas.py +++ b/tests/test_pubsubstanzas.py @@ -103,10 +103,11 @@ class testpubsubstanzas(unittest.TestCase): iq = self.ps.Iq() iq['pubsub_owner']['default'] iq['pubsub_owner']['default']['node'] = 'mynode' + iq['pubsub_owner']['default']['type'] = 'leaf' form = xep_0004.Form() form.addField('pubsub#title', ftype='text-single', value='This thing is awesome') iq['pubsub_owner']['default']['config'] = form - xmlstring = """This thing is awesome""" + xmlstring = """This thing is awesome""" iq2 = self.ps.Iq(None, self.ps.ET.fromstring(xmlstring)) iq3 = self.ps.Iq() values = iq2.getValues() -- cgit v1.2.3 From 1e3a6e1b5f06d307295be6b4ccaf75bf2a48399d Mon Sep 17 00:00:00 2001 From: Nathan Fritz Date: Wed, 26 May 2010 11:46:56 -0700 Subject: added muc room to readme --- README | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README b/README index 670ce3cd..abc2d090 100644 --- a/README +++ b/README @@ -38,4 +38,5 @@ Contributors: Kevin Smith & Lance Stout Patches: Remko Tronçon Feel free to add fritzy@netflint.net to your roster for direct support and comments. -Join sleekxmpp-discussion@googlegroups.com / http://groups.google.com/group/sleekxmpp-discussion for discussion. +Join sleekxmpp-discussion@googlegroups.com / http://groups.google.com/group/sleekxmpp-discussion for email discussion. +Join sleek@conference.jabber.org for groupchat discussion. -- cgit v1.2.3 From 59b8406573c12f0218d27daa2f5d373aef89e116 Mon Sep 17 00:00:00 2001 From: Brian Beggs Date: Wed, 2 Jun 2010 07:34:43 -0400 Subject: Added .pydevproject to the .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 15997ce4..8b41dac4 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ .project build/ *.swp +.pydevproject -- cgit v1.2.3 From c0457cf5d002212a3d9946c8df9879618baf830c Mon Sep 17 00:00:00 2001 From: Brian Beggs Date: Wed, 2 Jun 2010 08:28:49 -0400 Subject: moddified plugin loading so plugins located outside of the plugins directory in sleek may be loaded. Added optional argument pluginModule that is a string that represents the module the desired plugin should be loaded from. An exception on plugin loading now also will not cause the program to exit. The exception is caught and loading of other plugins contains. --- .gitignore | 1 + sleekxmpp/basexmpp.py | 17 ++++++++++------- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/.gitignore b/.gitignore index 8b41dac4..6257bbf6 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ build/ *.swp .pydevproject +.settings diff --git a/sleekxmpp/basexmpp.py b/sleekxmpp/basexmpp.py index a916fe86..5f330176 100644 --- a/sleekxmpp/basexmpp.py +++ b/sleekxmpp/basexmpp.py @@ -1,9 +1,9 @@ """ - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2010 Nathanael C. Fritz - This file is part of SleekXMPP. + SleekXMPP: The Sleek XMPP Library + Copyright (C) 2010 Nathanael C. Fritz + This file is part of SleekXMPP. - See the file license.txt for copying permission. + See the file license.txt for copying permission. """ from __future__ import with_statement, unicode_literals @@ -91,15 +91,18 @@ class basexmpp(object): if not self.plugin[idx].post_inited: self.plugin[idx].post_init() return super(basexmpp, self).process(*args, **kwargs) - def registerPlugin(self, plugin, pconfig = {}): + def registerPlugin(self, plugin, pconfig = {}, pluginModule = None): """Register a plugin not in plugins.__init__.__all__ but in the plugins directory.""" # discover relative "path" to the plugins module from the main app, and import it. # TODO: # gross, this probably isn't necessary anymore, especially for an installed module - __import__("%s.%s" % (globals()['plugins'].__name__, plugin)) + if pluginModule: + module = __import__("%s.%s" % (pluginModule, plugin), globals(), locals(), [plugin]) + else: + module = __import__("%s.%s" % (globals()['plugins'].__name__, plugin), globals(), locals(), [plugin]) # init the plugin class - self.plugin[plugin] = getattr(getattr(plugins, plugin), plugin)(self, pconfig) # eek + self.plugin[plugin] = getattr(module, plugin)(self, pconfig) # eek # all of this for a nice debug? sure. xep = '' if hasattr(self.plugin[plugin], 'xep'): -- cgit v1.2.3 From b0066f3ef4644c14f645f589fb36846f23f664ab Mon Sep 17 00:00:00 2001 From: Brian Beggs Date: Wed, 2 Jun 2010 08:45:42 -0400 Subject: added try/catch block to plugin loading --- sleekxmpp/basexmpp.py | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/sleekxmpp/basexmpp.py b/sleekxmpp/basexmpp.py index 5f330176..b78c255c 100644 --- a/sleekxmpp/basexmpp.py +++ b/sleekxmpp/basexmpp.py @@ -97,18 +97,21 @@ class basexmpp(object): # discover relative "path" to the plugins module from the main app, and import it. # TODO: # gross, this probably isn't necessary anymore, especially for an installed module - if pluginModule: - module = __import__("%s.%s" % (pluginModule, plugin), globals(), locals(), [plugin]) - else: - module = __import__("%s.%s" % (globals()['plugins'].__name__, plugin), globals(), locals(), [plugin]) - # init the plugin class - self.plugin[plugin] = getattr(module, plugin)(self, pconfig) # eek - # all of this for a nice debug? sure. - xep = '' - if hasattr(self.plugin[plugin], 'xep'): - xep = "(XEP-%s) " % self.plugin[plugin].xep - logging.debug("Loaded Plugin %s%s" % (xep, self.plugin[plugin].description)) - + try: + if pluginModule: + module = __import__(pluginModule, globals(), locals(), [plugin]) + else: + module = __import__("%s.%s" % (globals()['plugins'].__name__, plugin), globals(), locals(), [plugin]) + # init the plugin class + self.plugin[plugin] = getattr(module, plugin)(self, pconfig) # eek + # all of this for a nice debug? sure. + xep = '' + if hasattr(self.plugin[plugin], 'xep'): + xep = "(XEP-%s) " % self.plugin[plugin].xep + logging.debug("Loaded Plugin %s%s" % (xep, self.plugin[plugin].description)) + except: + logging.error("Unable to load plugin: %s" %(plugin) ) + def register_plugins(self): """Initiates all plugins in the plugins/__init__.__all__""" if self.plugin_whitelist: -- cgit v1.2.3 From 3f96226e29ee94eb718d01fa9cd052cfeb4fcfb5 Mon Sep 17 00:00:00 2001 From: Brian Beggs Date: Thu, 3 Jun 2010 10:02:55 -0400 Subject: Added additional logging when a plugin fails to import correctly. --- sleekxmpp/basexmpp.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sleekxmpp/basexmpp.py b/sleekxmpp/basexmpp.py index b78c255c..936b5d18 100644 --- a/sleekxmpp/basexmpp.py +++ b/sleekxmpp/basexmpp.py @@ -109,8 +109,9 @@ class basexmpp(object): if hasattr(self.plugin[plugin], 'xep'): xep = "(XEP-%s) " % self.plugin[plugin].xep logging.debug("Loaded Plugin %s%s" % (xep, self.plugin[plugin].description)) - except: + except Exception, e: logging.error("Unable to load plugin: %s" %(plugin) ) + logging.exception(e) def register_plugins(self): """Initiates all plugins in the plugins/__init__.__all__""" -- cgit v1.2.3 From f5d04664627b28f9439d9ed5214a0060a77a2bf2 Mon Sep 17 00:00:00 2001 From: Brian Beggs Date: Fri, 18 Jun 2010 09:51:29 -0400 Subject: working on digest-md5 authentication --- sleekxmpp/__init__.py | 101 ++++++++++++++++++++++++++++++++++++--- sleekxmpp/xmlstream/xmlstream.py | 14 ++++-- 2 files changed, 104 insertions(+), 11 deletions(-) diff --git a/sleekxmpp/__init__.py b/sleekxmpp/__init__.py index 74eb290a..1cf6eac3 100644 --- a/sleekxmpp/__init__.py +++ b/sleekxmpp/__init__.py @@ -27,6 +27,12 @@ import sys import random import copy from . import plugins +from xml.etree.cElementTree import tostring +from xml.etree.cElementTree import Element +from cStringIO import StringIO +import hashlib +from binascii import hexlify + #from . import stanza srvsupport = True try: @@ -71,8 +77,9 @@ class ClientXMPP(basexmpp, XMLStream): self.sessionstarted = False self.bound = False self.bindfail = False - self.registerHandler(Callback('Stream Features', MatchXPath('{http://etherx.jabber.org/streams}features'), self._handleStreamFeatures, thread=True)) - self.registerHandler(Callback('Roster Update', MatchXPath('{%s}iq/{jabber:iq:roster}query' % self.default_ns), self._handleRoster, thread=True)) + self.digest_auth_started = False + XMLStream.registerHandler(self, Callback('Stream Features', MatchXPath('{http://etherx.jabber.org/streams}features'), self._handleStreamFeatures, thread=True)) + XMLStream.registerHandler(self, Callback('Roster Update', MatchXPath('{%s}iq/{jabber:iq:roster}query' % self.default_ns), self._handleRoster, thread=True)) #self.registerHandler(Callback('Roster Update', MatchXMLMask("" % self.default_ns), self._handlePresenceSubscribe, thread=True)) self.registerFeature("", self.handler_starttls, True) self.registerFeature("", self.handler_sasl_auth, True) @@ -192,7 +199,7 @@ class ClientXMPP(basexmpp, XMLStream): _stanza = "" if not self.event_handlers.get(_stanza,None): # don't add handler > once self.add_handler( _stanza, self.handler_tls_start, instream=True ) - self.sendXML(xml) + self.sendPriorityRaw(self.tostring(xml)) return True else: logging.warning("The module tlslite is required in to some servers, and has not been found.") @@ -213,11 +220,15 @@ class ClientXMPP(basexmpp, XMLStream): if len(sasl_mechs): for sasl_mech in sasl_mechs: self.features.append("sasl:%s" % sasl_mech.text) - if 'sasl:PLAIN' in self.features: + if 'sasl:DIGEST-MD5' in self.features: + self.add_handler("", self.handler_sasl_digest_md5_auth, instream=True) + self.add_handler("", self.handler_sasl_digest_md5_auth_fail, instream=True) + self.sendPriorityRaw("""""") + elif 'sasl:PLAIN' in self.features: if sys.version_info < (3,0): - self.send("""%s""" % base64.b64encode(b'\x00' + bytes(self.username) + b'\x00' + bytes(self.password)).decode('utf-8')) + self.sendPriorityRaw("""%s""" % base64.b64encode(b'\x00' + bytes(self.username) + b'\x00' + bytes(self.password)).decode('utf-8')) else: - self.send("""%s""" % base64.b64encode(b'\x00' + bytes(self.username, 'utf-8') + b'\x00' + bytes(self.password, 'utf-8')).decode('utf-8')) + self.sendPriorityRaw("""%s""" % base64.b64encode(b'\x00' + bytes(self.username, 'utf-8') + b'\x00' + bytes(self.password, 'utf-8')).decode('utf-8')) else: logging.error("No appropriate login method.") self.disconnect() @@ -225,6 +236,66 @@ class ClientXMPP(basexmpp, XMLStream): # self._auth_digestmd5() return True + def handler_sasl_digest_md5_auth(self, xml): + logging.debug(tostring(xml)) + logging.debug(xml) + logging.debug(type(xml).__name__) + + if self.digest_auth_started == False: + logging.debug(base64.b64decode(xml.text).split(',', 6)) + + challenge = [item.split('=', 1) for item in base64.b64decode(xml.text).replace("\"", "").split(',', 6) ] + challenge = dict(challenge) + logging.debug(challenge) + + #TODO: check for abort states + #Realm, nonce, qop should all be present + #charset can be either UTF-8 or if not present use ISO 8859-1 + + #x = bytes(self.username) + b":" + bytes(self.domain) + b":" + bytes(self.password) + #ha1_1 = hashlib.md5(x).hexdigest() + #ha1_2 = b":" + bytes(challenge["nonce"]) + b":" + b"C6gVvo6BQKn7Hwvah99SqNQFgmLxtsHYeOs8etcU" #+ b":" + bytes(self.fulljid) + #ha1 = hashlib.md5(ha1_1 + ha1_2).hexdigest() + # + #ha2 = hashlib.md5(b"AUTHENTICATE:" + b"xmpp/" + bytes(self.server)).hexdigest() + #b = base64.b16encode(ha1) + b":" + bytes(challenge["nonce"]) + b":" + b"""00000001""" + b":" + b"C6gVvo6BQKn7Hwvah99SqNQFgmLxtsHYeOs8etcU" + b":" + bytes(challenge["qop"]) + b":" + base64.b16encode(ha2) + #hash = base64.b16encode(hashlib.md5(b).hexdigest()) + + + #a1 = y + b":" + bytes(challenge["nonce"]) + b":" + b"C6gVvo6BQKn7Hwvah99SqNQFgmLxtsHYeOs8etcU" + b":" + bytes(self.jid) + #a2 = b"AUTHENTICATE:" + b"xmpp/" + bytes(self.server) + #ha1 = hashlib.md5(a1).hexdigest() + #ha2 = hashlib.md5(a2).hexdigest() + #kd = ha1 + b":" + bytes(challenge["nonce"]) + b":" + b"""00000001""" + b":" + b"C6gVvo6BQKn7Hwvah99SqNQFgmLxtsHYeOs8etcU" + b":" + bytes(challenge["qop"]) + b":" + ha2 + #z = hashlib.md5(kd).hexdigest() + + #take 3 + cnonce = "dfajoiqewoivnoeiw" + for i in range(12): + cnonce = cnonce + chr(random.randint(0,0xff)).decode("utf-8", "replace") + cnonce = base64.encodestring(cnonce)[0:-1] + urp = hashlib.md5("%s:%s:%s" % (self.username, challenge["realm"], self.password) ).digest() + a1 = "%s:%s:%s" % (urp.decode("utf-8", "replace"), challenge["nonce"], cnonce) + a2 = "AUTHENTICATE:xmpp/%s" % self.domain + responseHash = hexlify(md5("%s:%s:00000001:%s:%s:%s" + % (hexlify(md5(a1)), challenge["nonce"], cnonce, challenge["qop"], hexlify(md5(a2))) )) + print responseHash + responseHash1 = resp(self.username, challenge["realm"], self.password, challenge["nonce"], cnonce, "AUTHENTICATE:xmpp/%s" % self.domain) + responseHash2 = resp(self.username, challenge["realm"], self.password, challenge["nonce"], cnonce, "AUTHENTICATE:xmpp/%s" % self.domain) + print responseHash1 + print responseHash2 + response1 = 'username="%s"%s,nonce="%s",cnonce="%s",nc=00000001,qop=auth,digest-uri="%s",response=%s' %(self.username, ',realm="%s"' % challenge['realm'], challenge["nonce"], cnonce, 'AUTHENTICATE:xmpp/%s' % self.domain, responseHash1) + response = '''username="%s",realm="%s",nonce="%s",cnonce="%s",nc=00000001,qop=%s,digest-uri="%s",response=%s''' %(self.username, challenge["realm"], challenge["nonce"], cnonce, challenge["qop"], "AUTHENTICATE:xmpp/%s" % self.domain, responseHash1) + print response + print response1 + self.sendPriorityRaw("""%s""" %base64.encodestring(response1)[:-1]) + else: + pass + + def handler_sasl_digest_md5_auth_fail(self, xml): + self.digest_auth_started = False + self.handler_auth_fail(xml) + def handler_auth_success(self, xml): logging.debug("Authentication successful.") self.authenticated = True @@ -233,6 +304,7 @@ class ClientXMPP(basexmpp, XMLStream): def handler_auth_fail(self, xml): logging.warning("Authentication failed.") + logging.debug(tostring(xml, 'utf-8')) self.disconnect() self.event("failed_auth") @@ -273,3 +345,20 @@ class ClientXMPP(basexmpp, XMLStream): if iq['type'] == 'set': self.send(self.Iq().setValues({'type': 'result', 'id': iq['id']}).enable('roster')) self.event("roster_update", iq) + +def md5(indata): + try: + import hashlib + md5 = hashlib.md5(indata) + except ImportError: + import md5 + md5 = md5.new(indata) + return md5.digest() + +def resp(username, realm, password, nonce, cnonce, digest_uri): + "constructs a response string as defined in 2.1.2.1" + urp = md5("%s:%s:%s" % (username,realm,password)) + a1 = "%s:%s:%s" % (urp.decode("utf-8", "replace"), nonce, cnonce) + a2 = "AUTHENTICATE:%s" % digest_uri + return hexlify(md5("%s:%s:00000001:%s:auth:%s" + % (hexlify(md5(a1)), nonce, cnonce, hexlify(md5(a2))))) \ No newline at end of file diff --git a/sleekxmpp/xmlstream/xmlstream.py b/sleekxmpp/xmlstream/xmlstream.py index 6dbe7b30..a8bcac00 100644 --- a/sleekxmpp/xmlstream/xmlstream.py +++ b/sleekxmpp/xmlstream/xmlstream.py @@ -82,7 +82,7 @@ class XMLStream(object): self.stream_footer = "" self.eventqueue = queue.Queue() - self.sendqueue = queue.Queue() + self.sendqueue = queue.PriorityQueue() self.scheduler = scheduler.Scheduler(self.eventqueue) self.namespace_map = {} @@ -219,7 +219,7 @@ class XMLStream(object): while self.run: if not self.state.ensure('connected',wait=2): continue try: - self.sendRaw(self.stream_header) + self.sendPriorityRaw(self.stream_header) while self.run and self.__readXML(): pass except socket.timeout: logging.debug('socket rcv timeout') @@ -277,7 +277,7 @@ class XMLStream(object): data = None try: - data = self.sendqueue.get(True,5) + data = self.sendqueue.get(True,5)[1] logging.debug("SEND: %s" % data) self.socket.sendall(data.encode('utf-8')) except queue.Empty: @@ -298,7 +298,11 @@ class XMLStream(object): self.disconnect(reconnect=True) def sendRaw(self, data): - self.sendqueue.put(data) + self.sendqueue.put((1, data)) + return True + + def sendPriorityRaw(self, data): + self.sendqueue.put((0, data)) return True def disconnect(self, reconnect=False): @@ -306,7 +310,7 @@ class XMLStream(object): logging.warning("Already disconnected.") return logging.debug("Disconnecting...") - self.sendRaw(self.stream_footer) + self.sendPriorityRaw(self.stream_footer) time.sleep(5) #send end of stream #wait for end of stream back -- cgit v1.2.3 From c0a6291feae148916064ab71e13815a84008fada Mon Sep 17 00:00:00 2001 From: Brian Beggs Date: Mon, 21 Jun 2010 09:23:56 -0400 Subject: More digest-md5 changes --- sleekxmpp/__init__.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/sleekxmpp/__init__.py b/sleekxmpp/__init__.py index 1cf6eac3..848fae26 100644 --- a/sleekxmpp/__init__.py +++ b/sleekxmpp/__init__.py @@ -270,22 +270,22 @@ class ClientXMPP(basexmpp, XMLStream): #z = hashlib.md5(kd).hexdigest() #take 3 - cnonce = "dfajoiqewoivnoeiw" + cnonce = "" for i in range(12): cnonce = cnonce + chr(random.randint(0,0xff)).decode("utf-8", "replace") cnonce = base64.encodestring(cnonce)[0:-1] - urp = hashlib.md5("%s:%s:%s" % (self.username, challenge["realm"], self.password) ).digest() - a1 = "%s:%s:%s" % (urp.decode("utf-8", "replace"), challenge["nonce"], cnonce) + urp = md5("%s:%s:%s" % (self.username, self.domain, self.password) ) + a1 = "%s:%s:%s" % (urp.encode, challenge["nonce"].encode(), cnonce.encode()) a2 = "AUTHENTICATE:xmpp/%s" % self.domain responseHash = hexlify(md5("%s:%s:00000001:%s:%s:%s" % (hexlify(md5(a1)), challenge["nonce"], cnonce, challenge["qop"], hexlify(md5(a2))) )) print responseHash - responseHash1 = resp(self.username, challenge["realm"], self.password, challenge["nonce"], cnonce, "AUTHENTICATE:xmpp/%s" % self.domain) - responseHash2 = resp(self.username, challenge["realm"], self.password, challenge["nonce"], cnonce, "AUTHENTICATE:xmpp/%s" % self.domain) + responseHash1 = resp(self.username, self.domain, self.password, challenge["nonce"], cnonce, "AUTHENTICATE:xmpp/%s" % self.domain) + responseHash2 = resp(self.username, self.domain, self.password, challenge["nonce"], cnonce, "AUTHENTICATE:xmpp/%s" % self.domain) print responseHash1 print responseHash2 - response1 = 'username="%s"%s,nonce="%s",cnonce="%s",nc=00000001,qop=auth,digest-uri="%s",response=%s' %(self.username, ',realm="%s"' % challenge['realm'], challenge["nonce"], cnonce, 'AUTHENTICATE:xmpp/%s' % self.domain, responseHash1) - response = '''username="%s",realm="%s",nonce="%s",cnonce="%s",nc=00000001,qop=%s,digest-uri="%s",response=%s''' %(self.username, challenge["realm"], challenge["nonce"], cnonce, challenge["qop"], "AUTHENTICATE:xmpp/%s" % self.domain, responseHash1) + response1 = 'username="%s"%s,nonce="%s",cnonce="%s",nc=00000001,qop=auth,digest-uri="%s",response=%s' %(self.username, ',realm="%s"' % self.domain, challenge["nonce"], cnonce, 'AUTHENTICATE:xmpp/%s' % self.domain, responseHash1) + response = '''username="%s",realm="%s",nonce="%s",cnonce="%s",nc=00000001,qop=%s,digest-uri="%s",response=%s''' %(self.username, self.domain, challenge["nonce"], cnonce, challenge["qop"], "AUTHENTICATE:xmpp/%s" % self.domain, responseHash1) print response print response1 self.sendPriorityRaw("""%s""" %base64.encodestring(response1)[:-1]) -- cgit v1.2.3 From c538ffae79728f7cb2675e14af64915682c19fe1 Mon Sep 17 00:00:00 2001 From: Brian Beggs Date: Wed, 30 Jun 2010 13:54:53 -0400 Subject: digest-md5 auth now works, had to remove from __future__ import unicode_literals to get it working correctly. Also some improvments for the prioroity message sending. --- sleekxmpp/__init__.py | 70 ++++++++++++--------------------------- sleekxmpp/stanza/iq.py | 14 ++++---- sleekxmpp/xmlstream/stanzabase.py | 15 +++++---- 3 files changed, 36 insertions(+), 63 deletions(-) diff --git a/sleekxmpp/__init__.py b/sleekxmpp/__init__.py index 848fae26..894fdc74 100644 --- a/sleekxmpp/__init__.py +++ b/sleekxmpp/__init__.py @@ -7,7 +7,6 @@ See the file license.txt for copying permission. """ -from __future__ import absolute_import, unicode_literals from . basexmpp import basexmpp from xml.etree import cElementTree as ET from . xmlstream.xmlstream import XMLStream @@ -30,8 +29,6 @@ from . import plugins from xml.etree.cElementTree import tostring from xml.etree.cElementTree import Element from cStringIO import StringIO -import hashlib -from binascii import hexlify #from . import stanza srvsupport = True @@ -115,7 +112,7 @@ class ClientXMPP(basexmpp, XMLStream): logging.debug("Since no address is supplied, attempting SRV lookup.") try: answers = dns.resolver.query("_xmpp-client._tcp.%s" % self.domain, - dns.rdatatype.SRV ) + dns.rdatatype.SRV ) except dns.resolver.NXDOMAIN: logging.debug("No appropriate SRV record found. Using JID server name.") else: @@ -252,43 +249,17 @@ class ClientXMPP(basexmpp, XMLStream): #Realm, nonce, qop should all be present #charset can be either UTF-8 or if not present use ISO 8859-1 - #x = bytes(self.username) + b":" + bytes(self.domain) + b":" + bytes(self.password) - #ha1_1 = hashlib.md5(x).hexdigest() - #ha1_2 = b":" + bytes(challenge["nonce"]) + b":" + b"C6gVvo6BQKn7Hwvah99SqNQFgmLxtsHYeOs8etcU" #+ b":" + bytes(self.fulljid) - #ha1 = hashlib.md5(ha1_1 + ha1_2).hexdigest() - # - #ha2 = hashlib.md5(b"AUTHENTICATE:" + b"xmpp/" + bytes(self.server)).hexdigest() - #b = base64.b16encode(ha1) + b":" + bytes(challenge["nonce"]) + b":" + b"""00000001""" + b":" + b"C6gVvo6BQKn7Hwvah99SqNQFgmLxtsHYeOs8etcU" + b":" + bytes(challenge["qop"]) + b":" + base64.b16encode(ha2) - #hash = base64.b16encode(hashlib.md5(b).hexdigest()) - - - #a1 = y + b":" + bytes(challenge["nonce"]) + b":" + b"C6gVvo6BQKn7Hwvah99SqNQFgmLxtsHYeOs8etcU" + b":" + bytes(self.jid) - #a2 = b"AUTHENTICATE:" + b"xmpp/" + bytes(self.server) - #ha1 = hashlib.md5(a1).hexdigest() - #ha2 = hashlib.md5(a2).hexdigest() - #kd = ha1 + b":" + bytes(challenge["nonce"]) + b":" + b"""00000001""" + b":" + b"C6gVvo6BQKn7Hwvah99SqNQFgmLxtsHYeOs8etcU" + b":" + bytes(challenge["qop"]) + b":" + ha2 - #z = hashlib.md5(kd).hexdigest() - - #take 3 + #Compute the cnonce - a unique hex string only used in this request cnonce = "" - for i in range(12): - cnonce = cnonce + chr(random.randint(0,0xff)).decode("utf-8", "replace") + for i in range(7): + cnonce+=hex(int(random.random()*65536*4096))[2:] cnonce = base64.encodestring(cnonce)[0:-1] - urp = md5("%s:%s:%s" % (self.username, self.domain, self.password) ) - a1 = "%s:%s:%s" % (urp.encode, challenge["nonce"].encode(), cnonce.encode()) - a2 = "AUTHENTICATE:xmpp/%s" % self.domain - responseHash = hexlify(md5("%s:%s:00000001:%s:%s:%s" - % (hexlify(md5(a1)), challenge["nonce"], cnonce, challenge["qop"], hexlify(md5(a2))) )) - print responseHash - responseHash1 = resp(self.username, self.domain, self.password, challenge["nonce"], cnonce, "AUTHENTICATE:xmpp/%s" % self.domain) - responseHash2 = resp(self.username, self.domain, self.password, challenge["nonce"], cnonce, "AUTHENTICATE:xmpp/%s" % self.domain) - print responseHash1 - print responseHash2 - response1 = 'username="%s"%s,nonce="%s",cnonce="%s",nc=00000001,qop=auth,digest-uri="%s",response=%s' %(self.username, ',realm="%s"' % self.domain, challenge["nonce"], cnonce, 'AUTHENTICATE:xmpp/%s' % self.domain, responseHash1) - response = '''username="%s",realm="%s",nonce="%s",cnonce="%s",nc=00000001,qop=%s,digest-uri="%s",response=%s''' %(self.username, self.domain, challenge["nonce"], cnonce, challenge["qop"], "AUTHENTICATE:xmpp/%s" % self.domain, responseHash1) - print response - print response1 - self.sendPriorityRaw("""%s""" %base64.encodestring(response1)[:-1]) + + a1 = "%s:%s:%s" %(md5("%s:%s:%s" % (self.username, self.domain, self.password)), challenge["nonce"], cnonce ) + a2 = "AUTHENTICATE:xmpp/%s" %self.domain + responseHash = md5digest("%s:%s:00000001:%s:auth:%s" %(md5digest(a1), challenge["nonce"], cnonce, md5digest(a2) ) ) + response = '''charset=utf-8,username="%s",realm="%s",nonce="%s",nc=00000001,cnonce="%s",digest-uri="%s",response=%s,qop=%s,''' %(self.username, self.domain, challenge["nonce"], cnonce, "xmpp/%s" % self.domain, responseHash, challenge["qop"]) + self.sendPriorityRaw("""%s""" %base64.encodestring(response)[:-1]) else: pass @@ -346,19 +317,20 @@ class ClientXMPP(basexmpp, XMLStream): self.send(self.Iq().setValues({'type': 'result', 'id': iq['id']}).enable('roster')) self.event("roster_update", iq) -def md5(indata): +def md5(data): try: import hashlib - md5 = hashlib.md5(indata) + md5 = hashlib.md5(data) except ImportError: import md5 - md5 = md5.new(indata) + md5 = md5.new(data) return md5.digest() -def resp(username, realm, password, nonce, cnonce, digest_uri): - "constructs a response string as defined in 2.1.2.1" - urp = md5("%s:%s:%s" % (username,realm,password)) - a1 = "%s:%s:%s" % (urp.decode("utf-8", "replace"), nonce, cnonce) - a2 = "AUTHENTICATE:%s" % digest_uri - return hexlify(md5("%s:%s:00000001:%s:auth:%s" - % (hexlify(md5(a1)), nonce, cnonce, hexlify(md5(a2))))) \ No newline at end of file +def md5digest(data): + try: + import hashlib + md5 = hashlib.md5(data) + except ImportError: + import md5 + md5 = md5.new(data) + return md5.hexdigest() \ No newline at end of file diff --git a/sleekxmpp/stanza/iq.py b/sleekxmpp/stanza/iq.py index ded7515f..26f09268 100644 --- a/sleekxmpp/stanza/iq.py +++ b/sleekxmpp/stanza/iq.py @@ -1,9 +1,9 @@ """ - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2010 Nathanael C. Fritz - This file is part of SleekXMPP. + SleekXMPP: The Sleek XMPP Library + Copyright (C) 2010 Nathanael C. Fritz + This file is part of SleekXMPP. - See the file license.txt for copying permission. + See the file license.txt for copying permission. """ from .. xmlstream.stanzabase import StanzaBase from xml.etree import cElementTree as ET @@ -67,11 +67,11 @@ class Iq(RootStanza): self.xml.remove(child) return self - def send(self, block=True, timeout=10): + def send(self, block=True, timeout=10, priority=False): if block and self['type'] in ('get', 'set'): waitfor = Waiter('IqWait_%s' % self['id'], MatcherId(self['id'])) self.stream.registerHandler(waitfor) - StanzaBase.send(self) + StanzaBase.send(self, priority) return waitfor.wait(timeout) else: - return StanzaBase.send(self) + return StanzaBase.send(self, priority) diff --git a/sleekxmpp/xmlstream/stanzabase.py b/sleekxmpp/xmlstream/stanzabase.py index 64020c8f..34513807 100644 --- a/sleekxmpp/xmlstream/stanzabase.py +++ b/sleekxmpp/xmlstream/stanzabase.py @@ -1,9 +1,9 @@ """ - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2010 Nathanael C. Fritz - This file is part of SleekXMPP. + SleekXMPP: The Sleek XMPP Library + Copyright (C) 2010 Nathanael C. Fritz + This file is part of SleekXMPP. - See the file license.txt for copying permission. + See the file license.txt for copying permission. """ from xml.etree import cElementTree as ET import logging @@ -383,6 +383,7 @@ class StanzaBase(ElementBase): def exception(self, e): logging.error(traceback.format_tb(e)) - def send(self): - self.stream.sendRaw(self.__str__()) - + def send(self, priority=False): + if priority: self.stream.sendPriorityRaw(self.__str__()) + else: self.stream.sendRaw(self.__str__()) + -- cgit v1.2.3 From fa7f72d0af3f404abb7426a29ed0aad1d792b90c Mon Sep 17 00:00:00 2001 From: Brian Beggs Date: Wed, 30 Jun 2010 14:30:18 -0400 Subject: Fixed a defect where handlers for SASL authentication were being added multiple times. This caused issues when trying to reconnect. A handler for the auth mech would get added each reconnection attempt, causing digest-md5, success and failure to be called x times for each x number of retries. Handlers for sasl authentication as well as success and failure are now added during the __init__ method. --- sleekxmpp/__init__.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/sleekxmpp/__init__.py b/sleekxmpp/__init__.py index 894fdc74..f05e8a5f 100644 --- a/sleekxmpp/__init__.py +++ b/sleekxmpp/__init__.py @@ -7,6 +7,7 @@ See the file license.txt for copying permission. """ +from __future__ import absolute_import from . basexmpp import basexmpp from xml.etree import cElementTree as ET from . xmlstream.xmlstream import XMLStream @@ -77,6 +78,11 @@ class ClientXMPP(basexmpp, XMLStream): self.digest_auth_started = False XMLStream.registerHandler(self, Callback('Stream Features', MatchXPath('{http://etherx.jabber.org/streams}features'), self._handleStreamFeatures, thread=True)) XMLStream.registerHandler(self, Callback('Roster Update', MatchXPath('{%s}iq/{jabber:iq:roster}query' % self.default_ns), self._handleRoster, thread=True)) + #SASL Auth handlers + basexmpp.add_handler(self, "", self.handler_sasl_digest_md5_auth, instream=True) + basexmpp.add_handler(self, "", self.handler_sasl_digest_md5_auth_fail, instream=True) + basexmpp.add_handler(self, "", self.handler_auth_success, instream=True) + basexmpp.add_handler(self, "", self.handler_auth_fail, instream=True) #self.registerHandler(Callback('Roster Update', MatchXMLMask("" % self.default_ns), self._handlePresenceSubscribe, thread=True)) self.registerFeature("", self.handler_starttls, True) self.registerFeature("", self.handler_sasl_auth, True) @@ -211,15 +217,11 @@ class ClientXMPP(basexmpp, XMLStream): if '{urn:ietf:params:xml:ns:xmpp-tls}starttls' in self.features: return False logging.debug("Starting SASL Auth") - self.add_handler("", self.handler_auth_success, instream=True) - self.add_handler("", self.handler_auth_fail, instream=True) sasl_mechs = xml.findall('{urn:ietf:params:xml:ns:xmpp-sasl}mechanism') if len(sasl_mechs): for sasl_mech in sasl_mechs: self.features.append("sasl:%s" % sasl_mech.text) if 'sasl:DIGEST-MD5' in self.features: - self.add_handler("", self.handler_sasl_digest_md5_auth, instream=True) - self.add_handler("", self.handler_sasl_digest_md5_auth_fail, instream=True) self.sendPriorityRaw("""""") elif 'sasl:PLAIN' in self.features: if sys.version_info < (3,0): -- cgit v1.2.3 From 9bdb297fe27c8f00d81fb6e924abc71ec0282b95 Mon Sep 17 00:00:00 2001 From: Brian Beggs Date: Wed, 30 Jun 2010 14:44:57 -0400 Subject: basic checking for digest-md5 to make sure the necessary components are there to complete auth. If not a failed_auth event is dispatched and the socket disconnected. --- sleekxmpp/__init__.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/sleekxmpp/__init__.py b/sleekxmpp/__init__.py index f05e8a5f..9185c175 100644 --- a/sleekxmpp/__init__.py +++ b/sleekxmpp/__init__.py @@ -241,16 +241,17 @@ class ClientXMPP(basexmpp, XMLStream): logging.debug(type(xml).__name__) if self.digest_auth_started == False: - logging.debug(base64.b64decode(xml.text).split(',', 6)) - challenge = [item.split('=', 1) for item in base64.b64decode(xml.text).replace("\"", "").split(',', 6) ] challenge = dict(challenge) logging.debug(challenge) - #TODO: check for abort states #Realm, nonce, qop should all be present - #charset can be either UTF-8 or if not present use ISO 8859-1 - + if not challenge['realm'] or not challenge['qop'] or not challenge['nonce']: + logging.error("Error during digest-md5 authentication. Challenge missing critical information. Challenge: %s" %base64.b64decode(xml.text)) + self.disconnect() + self.event("failed_auth") + return + #TODO: charset can be either UTF-8 or if not present use ISO 8859-1 defaulting for UTF-8 for now #Compute the cnonce - a unique hex string only used in this request cnonce = "" for i in range(7): @@ -263,7 +264,7 @@ class ClientXMPP(basexmpp, XMLStream): response = '''charset=utf-8,username="%s",realm="%s",nonce="%s",nc=00000001,cnonce="%s",digest-uri="%s",response=%s,qop=%s,''' %(self.username, self.domain, challenge["nonce"], cnonce, "xmpp/%s" % self.domain, responseHash, challenge["qop"]) self.sendPriorityRaw("""%s""" %base64.encodestring(response)[:-1]) else: - pass + logging.warn("handler_sasl_digest_md5_auth called while digest_auth_started is false") def handler_sasl_digest_md5_auth_fail(self, xml): self.digest_auth_started = False -- cgit v1.2.3 From 488d5b29d44fa0bc1597f75d3a5b000fe1970db9 Mon Sep 17 00:00:00 2001 From: Brian Beggs Date: Wed, 30 Jun 2010 14:48:45 -0400 Subject: fixed typo --- sleekxmpp/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sleekxmpp/__init__.py b/sleekxmpp/__init__.py index 9185c175..2bc59444 100644 --- a/sleekxmpp/__init__.py +++ b/sleekxmpp/__init__.py @@ -264,7 +264,7 @@ class ClientXMPP(basexmpp, XMLStream): response = '''charset=utf-8,username="%s",realm="%s",nonce="%s",nc=00000001,cnonce="%s",digest-uri="%s",response=%s,qop=%s,''' %(self.username, self.domain, challenge["nonce"], cnonce, "xmpp/%s" % self.domain, responseHash, challenge["qop"]) self.sendPriorityRaw("""%s""" %base64.encodestring(response)[:-1]) else: - logging.warn("handler_sasl_digest_md5_auth called while digest_auth_started is false") + logging.warn("handler_sasl_digest_md5_auth called while digest_auth_started is True (has already begun)") def handler_sasl_digest_md5_auth_fail(self, xml): self.digest_auth_started = False -- cgit v1.2.3 From fff54eaf2f069db22c2a0a3cf0e522d628e188b3 Mon Sep 17 00:00:00 2001 From: Brian Beggs Date: Thu, 1 Jul 2010 08:44:39 -0400 Subject: temporary removed future support for sleek to support digest-md5 auth --- sleekxmpp/__init__.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/sleekxmpp/__init__.py b/sleekxmpp/__init__.py index 2bc59444..cf43ede7 100644 --- a/sleekxmpp/__init__.py +++ b/sleekxmpp/__init__.py @@ -7,7 +7,6 @@ See the file license.txt for copying permission. """ -from __future__ import absolute_import from . basexmpp import basexmpp from xml.etree import cElementTree as ET from . xmlstream.xmlstream import XMLStream @@ -257,8 +256,8 @@ class ClientXMPP(basexmpp, XMLStream): for i in range(7): cnonce+=hex(int(random.random()*65536*4096))[2:] cnonce = base64.encodestring(cnonce)[0:-1] - - a1 = "%s:%s:%s" %(md5("%s:%s:%s" % (self.username, self.domain, self.password)), challenge["nonce"], cnonce ) + a1 = md5("%s:%s:%s" % (self.username, self.domain, self.password)) + a1 = "%s:%s:%s" %(a1, challenge["nonce"], cnonce ) a2 = "AUTHENTICATE:xmpp/%s" %self.domain responseHash = md5digest("%s:%s:00000001:%s:auth:%s" %(md5digest(a1), challenge["nonce"], cnonce, md5digest(a2) ) ) response = '''charset=utf-8,username="%s",realm="%s",nonce="%s",nc=00000001,cnonce="%s",digest-uri="%s",response=%s,qop=%s,''' %(self.username, self.domain, challenge["nonce"], cnonce, "xmpp/%s" % self.domain, responseHash, challenge["qop"]) -- cgit v1.2.3 From d62a30b0f8353640928d11cdfd76ea3c7a9b5cc3 Mon Sep 17 00:00:00 2001 From: Brian Beggs Date: Thu, 1 Jul 2010 09:46:12 -0400 Subject: digest-md5 authentication now works with unicode-literals import. Re-added the __future__ imports that were removed. --- sleekxmpp/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sleekxmpp/__init__.py b/sleekxmpp/__init__.py index b62e35db..3379e45d 100644 --- a/sleekxmpp/__init__.py +++ b/sleekxmpp/__init__.py @@ -7,6 +7,7 @@ See the file license.txt for copying permission. """ +from __future__ import absolute_import, unicode_literals from . basexmpp import basexmpp from xml.etree import cElementTree as ET from . xmlstream.xmlstream import XMLStream @@ -255,8 +256,7 @@ class ClientXMPP(basexmpp, XMLStream): for i in range(7): cnonce+=hex(int(random.random()*65536*4096))[2:] cnonce = base64.encodestring(cnonce)[0:-1] - a1 = md5("%s:%s:%s" % (self.username, self.domain, self.password)) - a1 = "%s:%s:%s" %(a1, challenge["nonce"], cnonce ) + a1 = b"%s:%s:%s" %(md5("%s:%s:%s" % (self.username, self.domain, self.password)), challenge["nonce"].encode("UTF-8"), cnonce.encode("UTF-8") ) a2 = "AUTHENTICATE:xmpp/%s" %self.domain responseHash = md5digest("%s:%s:00000001:%s:auth:%s" %(md5digest(a1), challenge["nonce"], cnonce, md5digest(a2) ) ) response = '''charset=utf-8,username="%s",realm="%s",nonce="%s",nc=00000001,cnonce="%s",digest-uri="%s",response=%s,qop=%s,''' %(self.username, self.domain, challenge["nonce"], cnonce, "xmpp/%s" % self.domain, responseHash, challenge["qop"]) -- cgit v1.2.3