16ded16be15dd865a9b21ea304d5273c8be299c87Steve Block#!/usr/bin/python2.4 26ded16be15dd865a9b21ea304d5273c8be299c87Steve Block# Copyright (c) 2010 The Chromium Authors. All rights reserved. 36ded16be15dd865a9b21ea304d5273c8be299c87Steve Block# Use of this source code is governed by a BSD-style license that can be 46ded16be15dd865a9b21ea304d5273c8be299c87Steve Block# found in the LICENSE file. 56ded16be15dd865a9b21ea304d5273c8be299c87Steve Block 66ded16be15dd865a9b21ea304d5273c8be299c87Steve Block"""A bare-bones and non-compliant XMPP server. 76ded16be15dd865a9b21ea304d5273c8be299c87Steve Block 86ded16be15dd865a9b21ea304d5273c8be299c87Steve BlockJust enough of the protocol is implemented to get it to work with 96ded16be15dd865a9b21ea304d5273c8be299c87Steve BlockChrome's sync notification system. 106ded16be15dd865a9b21ea304d5273c8be299c87Steve Block""" 116ded16be15dd865a9b21ea304d5273c8be299c87Steve Block 120d5e116f6aee03185f237311a943491bb079a768Kristian Monsenimport asynchat 136ded16be15dd865a9b21ea304d5273c8be299c87Steve Blockimport asyncore 146ded16be15dd865a9b21ea304d5273c8be299c87Steve Blockimport base64 156ded16be15dd865a9b21ea304d5273c8be299c87Steve Blockimport re 166ded16be15dd865a9b21ea304d5273c8be299c87Steve Blockimport socket 176ded16be15dd865a9b21ea304d5273c8be299c87Steve Blockfrom xml.dom import minidom 186ded16be15dd865a9b21ea304d5273c8be299c87Steve Block 190d5e116f6aee03185f237311a943491bb079a768Kristian Monsen# pychecker complains about the use of fileno(), which is implemented 200d5e116f6aee03185f237311a943491bb079a768Kristian Monsen# by asyncore by forwarding to an internal object via __getattr__. 210d5e116f6aee03185f237311a943491bb079a768Kristian Monsen__pychecker__ = 'no-classattr' 221e0659c275bb392c045087af4f6b0d7565cb3d77Steve Block 230d5e116f6aee03185f237311a943491bb079a768Kristian Monsen 240d5e116f6aee03185f237311a943491bb079a768Kristian Monsenclass Error(Exception): 250d5e116f6aee03185f237311a943491bb079a768Kristian Monsen """Error class for this module.""" 260d5e116f6aee03185f237311a943491bb079a768Kristian Monsen pass 270d5e116f6aee03185f237311a943491bb079a768Kristian Monsen 280d5e116f6aee03185f237311a943491bb079a768Kristian Monsen 290d5e116f6aee03185f237311a943491bb079a768Kristian Monsenclass UnexpectedXml(Error): 300d5e116f6aee03185f237311a943491bb079a768Kristian Monsen """Raised when an unexpected XML element has been encountered.""" 310d5e116f6aee03185f237311a943491bb079a768Kristian Monsen 326ded16be15dd865a9b21ea304d5273c8be299c87Steve Block def __init__(self, xml_element): 336ded16be15dd865a9b21ea304d5273c8be299c87Steve Block xml_text = xml_element.toxml() 346ded16be15dd865a9b21ea304d5273c8be299c87Steve Block Error.__init__(self, 'Unexpected XML element', xml_text) 356ded16be15dd865a9b21ea304d5273c8be299c87Steve Block 366ded16be15dd865a9b21ea304d5273c8be299c87Steve Block 376ded16be15dd865a9b21ea304d5273c8be299c87Steve Blockdef ParseXml(xml_string): 386ded16be15dd865a9b21ea304d5273c8be299c87Steve Block """Parses the given string as XML and returns a minidom element 390d5e116f6aee03185f237311a943491bb079a768Kristian Monsen object. 400d5e116f6aee03185f237311a943491bb079a768Kristian Monsen """ 416ded16be15dd865a9b21ea304d5273c8be299c87Steve Block dom = minidom.parseString(xml_string) 426ded16be15dd865a9b21ea304d5273c8be299c87Steve Block 436ded16be15dd865a9b21ea304d5273c8be299c87Steve Block # minidom handles xmlns specially, but there's a bug where it sets 446ded16be15dd865a9b21ea304d5273c8be299c87Steve Block # the attribute value to None, which causes toxml() or toprettyxml() 456ded16be15dd865a9b21ea304d5273c8be299c87Steve Block # to break. 460d5e116f6aee03185f237311a943491bb079a768Kristian Monsen def FixMinidomXmlnsBug(xml_element): 470d5e116f6aee03185f237311a943491bb079a768Kristian Monsen if xml_element.getAttribute('xmlns') is None: 486ded16be15dd865a9b21ea304d5273c8be299c87Steve Block xml_element.setAttribute('xmlns', '') 496ded16be15dd865a9b21ea304d5273c8be299c87Steve Block 506ded16be15dd865a9b21ea304d5273c8be299c87Steve Block def ApplyToAllDescendantElements(xml_element, fn): 516ded16be15dd865a9b21ea304d5273c8be299c87Steve Block fn(xml_element) 520d5e116f6aee03185f237311a943491bb079a768Kristian Monsen for node in xml_element.childNodes: 530d5e116f6aee03185f237311a943491bb079a768Kristian Monsen if node.nodeType == node.ELEMENT_NODE: 546ded16be15dd865a9b21ea304d5273c8be299c87Steve Block ApplyToAllDescendantElements(node, fn) 556ded16be15dd865a9b21ea304d5273c8be299c87Steve Block 566ded16be15dd865a9b21ea304d5273c8be299c87Steve Block root = dom.documentElement 576ded16be15dd865a9b21ea304d5273c8be299c87Steve Block ApplyToAllDescendantElements(root, FixMinidomXmlnsBug) 580d5e116f6aee03185f237311a943491bb079a768Kristian Monsen return root 590d5e116f6aee03185f237311a943491bb079a768Kristian Monsen 606ded16be15dd865a9b21ea304d5273c8be299c87Steve Block 616ded16be15dd865a9b21ea304d5273c8be299c87Steve Blockdef CloneXml(xml): 626ded16be15dd865a9b21ea304d5273c8be299c87Steve Block """Returns a deep copy of the given XML element. 636ded16be15dd865a9b21ea304d5273c8be299c87Steve Block 640d5e116f6aee03185f237311a943491bb079a768Kristian Monsen Args: 650d5e116f6aee03185f237311a943491bb079a768Kristian Monsen xml: The XML element, which should be something returned from 666ded16be15dd865a9b21ea304d5273c8be299c87Steve Block ParseXml() (i.e., a root element). 676ded16be15dd865a9b21ea304d5273c8be299c87Steve Block """ 686ded16be15dd865a9b21ea304d5273c8be299c87Steve Block return xml.ownerDocument.cloneNode(True).documentElement 696ded16be15dd865a9b21ea304d5273c8be299c87Steve Block 700d5e116f6aee03185f237311a943491bb079a768Kristian Monsen 710d5e116f6aee03185f237311a943491bb079a768Kristian Monsenclass StanzaParser(object): 726ded16be15dd865a9b21ea304d5273c8be299c87Steve Block """A hacky incremental XML parser. 736ded16be15dd865a9b21ea304d5273c8be299c87Steve Block 746ded16be15dd865a9b21ea304d5273c8be299c87Steve Block StanzaParser consumes data incrementally via FeedString() and feeds 756ded16be15dd865a9b21ea304d5273c8be299c87Steve Block its delegate complete parsed stanzas (i.e., XML documents) via 760d5e116f6aee03185f237311a943491bb079a768Kristian Monsen FeedStanza(). Any stanzas passed to FeedStanza() are unlinked after 770d5e116f6aee03185f237311a943491bb079a768Kristian Monsen the callback is done. 786ded16be15dd865a9b21ea304d5273c8be299c87Steve Block 796ded16be15dd865a9b21ea304d5273c8be299c87Steve Block Use like so: 806ded16be15dd865a9b21ea304d5273c8be299c87Steve Block 816ded16be15dd865a9b21ea304d5273c8be299c87Steve Block class MyClass(object): 826ded16be15dd865a9b21ea304d5273c8be299c87Steve Block ... 836ded16be15dd865a9b21ea304d5273c8be299c87Steve Block def __init__(self, ...): 846ded16be15dd865a9b21ea304d5273c8be299c87Steve Block ... 850d5e116f6aee03185f237311a943491bb079a768Kristian Monsen self._parser = StanzaParser(self) 866ded16be15dd865a9b21ea304d5273c8be299c87Steve Block ... 876ded16be15dd865a9b21ea304d5273c8be299c87Steve Block 886ded16be15dd865a9b21ea304d5273c8be299c87Steve Block def SomeFunction(self, ...): 896ded16be15dd865a9b21ea304d5273c8be299c87Steve Block ... 906ded16be15dd865a9b21ea304d5273c8be299c87Steve Block self._parser.FeedString(some_data) 916ded16be15dd865a9b21ea304d5273c8be299c87Steve Block ... 926ded16be15dd865a9b21ea304d5273c8be299c87Steve Block 930d5e116f6aee03185f237311a943491bb079a768Kristian Monsen def FeedStanza(self, stanza): 946ded16be15dd865a9b21ea304d5273c8be299c87Steve Block ... 956ded16be15dd865a9b21ea304d5273c8be299c87Steve Block print stanza.toprettyxml() 966ded16be15dd865a9b21ea304d5273c8be299c87Steve Block ... 976ded16be15dd865a9b21ea304d5273c8be299c87Steve Block """ 986ded16be15dd865a9b21ea304d5273c8be299c87Steve Block 996ded16be15dd865a9b21ea304d5273c8be299c87Steve Block # NOTE(akalin): The following regexps are naive, but necessary since 1006ded16be15dd865a9b21ea304d5273c8be299c87Steve Block # none of the existing Python 2.4/2.5 XML libraries support 1010d5e116f6aee03185f237311a943491bb079a768Kristian Monsen # incremental parsing. This works well enough for our purposes. 1020d5e116f6aee03185f237311a943491bb079a768Kristian Monsen # 1030d5e116f6aee03185f237311a943491bb079a768Kristian Monsen # The regexps below assume that any present XML element starts at 1040d5e116f6aee03185f237311a943491bb079a768Kristian Monsen # the beginning of the string, but there may be trailing whitespace. 1050d5e116f6aee03185f237311a943491bb079a768Kristian Monsen 1060d5e116f6aee03185f237311a943491bb079a768Kristian Monsen # Matches an opening stream tag (e.g., '<stream:stream foo="bar">') 1070d5e116f6aee03185f237311a943491bb079a768Kristian Monsen # (assumes that the stream XML namespace is defined in the tag). 1080d5e116f6aee03185f237311a943491bb079a768Kristian Monsen _stream_re = re.compile(r'^(<stream:stream [^>]*>)\s*') 1090d5e116f6aee03185f237311a943491bb079a768Kristian Monsen 1100d5e116f6aee03185f237311a943491bb079a768Kristian Monsen # Matches an empty element tag (e.g., '<foo bar="baz"/>'). 1110d5e116f6aee03185f237311a943491bb079a768Kristian Monsen _empty_element_re = re.compile(r'^(<[^>]*/>)\s*') 1120d5e116f6aee03185f237311a943491bb079a768Kristian Monsen 1130d5e116f6aee03185f237311a943491bb079a768Kristian Monsen # Matches a non-empty element (e.g., '<foo bar="baz">quux</foo>'). 1140d5e116f6aee03185f237311a943491bb079a768Kristian Monsen # Does *not* handle nested elements. 1150d5e116f6aee03185f237311a943491bb079a768Kristian Monsen _non_empty_element_re = re.compile(r'^(<([^ >]*)[^>]*>.*?</\2>)\s*') 1160d5e116f6aee03185f237311a943491bb079a768Kristian Monsen 1170d5e116f6aee03185f237311a943491bb079a768Kristian Monsen # The closing tag for a stream tag. We have to insert this 1180d5e116f6aee03185f237311a943491bb079a768Kristian Monsen # ourselves since all XML stanzas are children of the stream tag, 1190d5e116f6aee03185f237311a943491bb079a768Kristian Monsen # which is never closed until the connection is closed. 1200d5e116f6aee03185f237311a943491bb079a768Kristian Monsen _stream_suffix = '</stream:stream>' 1210d5e116f6aee03185f237311a943491bb079a768Kristian Monsen 1220d5e116f6aee03185f237311a943491bb079a768Kristian Monsen def __init__(self, delegate): 1230d5e116f6aee03185f237311a943491bb079a768Kristian Monsen self._buffer = '' 1240d5e116f6aee03185f237311a943491bb079a768Kristian Monsen self._delegate = delegate 1250d5e116f6aee03185f237311a943491bb079a768Kristian Monsen 1260d5e116f6aee03185f237311a943491bb079a768Kristian Monsen def FeedString(self, data): 1270d5e116f6aee03185f237311a943491bb079a768Kristian Monsen """Consumes the given string data, possibly feeding one or more 1280d5e116f6aee03185f237311a943491bb079a768Kristian Monsen stanzas to the delegate. 1290d5e116f6aee03185f237311a943491bb079a768Kristian Monsen """ 1300d5e116f6aee03185f237311a943491bb079a768Kristian Monsen self._buffer += data 1310d5e116f6aee03185f237311a943491bb079a768Kristian Monsen while (self._ProcessBuffer(self._stream_re, self._stream_suffix) or 1320d5e116f6aee03185f237311a943491bb079a768Kristian Monsen self._ProcessBuffer(self._empty_element_re) or 1330d5e116f6aee03185f237311a943491bb079a768Kristian Monsen self._ProcessBuffer(self._non_empty_element_re)): 1340d5e116f6aee03185f237311a943491bb079a768Kristian Monsen pass 1350d5e116f6aee03185f237311a943491bb079a768Kristian Monsen 1360d5e116f6aee03185f237311a943491bb079a768Kristian Monsen def _ProcessBuffer(self, regexp, xml_suffix=''): 1370d5e116f6aee03185f237311a943491bb079a768Kristian Monsen """If the buffer matches the given regexp, removes the match from 1380d5e116f6aee03185f237311a943491bb079a768Kristian Monsen the buffer, appends the given suffix, parses it, and feeds it to 1390d5e116f6aee03185f237311a943491bb079a768Kristian Monsen the delegate. 1400d5e116f6aee03185f237311a943491bb079a768Kristian Monsen 1410d5e116f6aee03185f237311a943491bb079a768Kristian Monsen Returns: 1420d5e116f6aee03185f237311a943491bb079a768Kristian Monsen Whether or not the buffer matched the given regexp. 1430d5e116f6aee03185f237311a943491bb079a768Kristian Monsen """ 1440d5e116f6aee03185f237311a943491bb079a768Kristian Monsen results = regexp.match(self._buffer) 1450d5e116f6aee03185f237311a943491bb079a768Kristian Monsen if not results: 1460d5e116f6aee03185f237311a943491bb079a768Kristian Monsen return False 1470d5e116f6aee03185f237311a943491bb079a768Kristian Monsen xml_text = self._buffer[:results.end()] + xml_suffix 1480d5e116f6aee03185f237311a943491bb079a768Kristian Monsen self._buffer = self._buffer[results.end():] 1490d5e116f6aee03185f237311a943491bb079a768Kristian Monsen stanza = ParseXml(xml_text) 1500d5e116f6aee03185f237311a943491bb079a768Kristian Monsen self._delegate.FeedStanza(stanza) 1510d5e116f6aee03185f237311a943491bb079a768Kristian Monsen # Needed because stanza may have cycles. 1520d5e116f6aee03185f237311a943491bb079a768Kristian Monsen stanza.unlink() 1530d5e116f6aee03185f237311a943491bb079a768Kristian Monsen return True 1540d5e116f6aee03185f237311a943491bb079a768Kristian Monsen 1550d5e116f6aee03185f237311a943491bb079a768Kristian Monsen 1560d5e116f6aee03185f237311a943491bb079a768Kristian Monsenclass Jid(object): 1570d5e116f6aee03185f237311a943491bb079a768Kristian Monsen """Simple struct for an XMPP jid (essentially an e-mail address with 1580d5e116f6aee03185f237311a943491bb079a768Kristian Monsen an optional resource string). 1590d5e116f6aee03185f237311a943491bb079a768Kristian Monsen """ 1600d5e116f6aee03185f237311a943491bb079a768Kristian Monsen 1610d5e116f6aee03185f237311a943491bb079a768Kristian Monsen def __init__(self, username, domain, resource=''): 1620d5e116f6aee03185f237311a943491bb079a768Kristian Monsen self.username = username 1630d5e116f6aee03185f237311a943491bb079a768Kristian Monsen self.domain = domain 1640d5e116f6aee03185f237311a943491bb079a768Kristian Monsen self.resource = resource 1650d5e116f6aee03185f237311a943491bb079a768Kristian Monsen 1660d5e116f6aee03185f237311a943491bb079a768Kristian Monsen def __str__(self): 1670d5e116f6aee03185f237311a943491bb079a768Kristian Monsen jid_str = "%s@%s" % (self.username, self.domain) 1680d5e116f6aee03185f237311a943491bb079a768Kristian Monsen if self.resource: 1690d5e116f6aee03185f237311a943491bb079a768Kristian Monsen jid_str += '/' + self.resource 1700d5e116f6aee03185f237311a943491bb079a768Kristian Monsen return jid_str 1710d5e116f6aee03185f237311a943491bb079a768Kristian Monsen 1720d5e116f6aee03185f237311a943491bb079a768Kristian Monsen def GetBareJid(self): 1730d5e116f6aee03185f237311a943491bb079a768Kristian Monsen return Jid(self.username, self.domain) 1740d5e116f6aee03185f237311a943491bb079a768Kristian Monsen 1750d5e116f6aee03185f237311a943491bb079a768Kristian Monsen 1760d5e116f6aee03185f237311a943491bb079a768Kristian Monsenclass IdGenerator(object): 1770d5e116f6aee03185f237311a943491bb079a768Kristian Monsen """Simple class to generate unique IDs for XMPP messages.""" 1780d5e116f6aee03185f237311a943491bb079a768Kristian Monsen 1790d5e116f6aee03185f237311a943491bb079a768Kristian Monsen def __init__(self, prefix): 1800d5e116f6aee03185f237311a943491bb079a768Kristian Monsen self._prefix = prefix 1810d5e116f6aee03185f237311a943491bb079a768Kristian Monsen self._id = 0 1820d5e116f6aee03185f237311a943491bb079a768Kristian Monsen 1830d5e116f6aee03185f237311a943491bb079a768Kristian Monsen def GetNextId(self): 1840d5e116f6aee03185f237311a943491bb079a768Kristian Monsen next_id = "%s.%s" % (self._prefix, self._id) 1850d5e116f6aee03185f237311a943491bb079a768Kristian Monsen self._id += 1 1860d5e116f6aee03185f237311a943491bb079a768Kristian Monsen return next_id 1870d5e116f6aee03185f237311a943491bb079a768Kristian Monsen 1880d5e116f6aee03185f237311a943491bb079a768Kristian Monsen 1890d5e116f6aee03185f237311a943491bb079a768Kristian Monsenclass HandshakeTask(object): 1900d5e116f6aee03185f237311a943491bb079a768Kristian Monsen """Class to handle the initial handshake with a connected XMPP 1910d5e116f6aee03185f237311a943491bb079a768Kristian Monsen client. 1920d5e116f6aee03185f237311a943491bb079a768Kristian Monsen """ 1930d5e116f6aee03185f237311a943491bb079a768Kristian Monsen 1940d5e116f6aee03185f237311a943491bb079a768Kristian Monsen # The handshake states in order. 1950d5e116f6aee03185f237311a943491bb079a768Kristian Monsen (_INITIAL_STREAM_NEEDED, 1960d5e116f6aee03185f237311a943491bb079a768Kristian Monsen _AUTH_NEEDED, 1970d5e116f6aee03185f237311a943491bb079a768Kristian Monsen _AUTH_STREAM_NEEDED, 1980d5e116f6aee03185f237311a943491bb079a768Kristian Monsen _BIND_NEEDED, 1990d5e116f6aee03185f237311a943491bb079a768Kristian Monsen _SESSION_NEEDED, 2000d5e116f6aee03185f237311a943491bb079a768Kristian Monsen _FINISHED) = range(6) 2010d5e116f6aee03185f237311a943491bb079a768Kristian Monsen 2026ded16be15dd865a9b21ea304d5273c8be299c87Steve Block # Used when in the _INITIAL_STREAM_NEEDED and _AUTH_STREAM_NEEDED 2036ded16be15dd865a9b21ea304d5273c8be299c87Steve Block # states. Not an XML object as it's only the opening tag. 2046ded16be15dd865a9b21ea304d5273c8be299c87Steve Block # 2056ded16be15dd865a9b21ea304d5273c8be299c87Steve Block # The from and id attributes are filled in later. 2066ded16be15dd865a9b21ea304d5273c8be299c87Steve Block _STREAM_DATA = ( 2076ded16be15dd865a9b21ea304d5273c8be299c87Steve Block '<stream:stream from="%s" id="%s" ' 2086ded16be15dd865a9b21ea304d5273c8be299c87Steve Block 'version="1.0" xmlns:stream="http://etherx.jabber.org/streams" ' 2096ded16be15dd865a9b21ea304d5273c8be299c87Steve Block 'xmlns="jabber:client">') 2106ded16be15dd865a9b21ea304d5273c8be299c87Steve Block 2116ded16be15dd865a9b21ea304d5273c8be299c87Steve Block # Used when in the _INITIAL_STREAM_NEEDED state. 21225f6136652d8341ed047e7fc1a450af5bd218ea9Kristian Monsen _AUTH_STANZA = ParseXml( 21325f6136652d8341ed047e7fc1a450af5bd218ea9Kristian Monsen '<stream:features xmlns:stream="http://etherx.jabber.org/streams">' 2146ded16be15dd865a9b21ea304d5273c8be299c87Steve Block ' <mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl">' 21525f6136652d8341ed047e7fc1a450af5bd218ea9Kristian Monsen ' <mechanism>PLAIN</mechanism>' 2166ded16be15dd865a9b21ea304d5273c8be299c87Steve Block ' <mechanism>X-GOOGLE-TOKEN</mechanism>' 2176ded16be15dd865a9b21ea304d5273c8be299c87Steve Block ' </mechanisms>' 2180d5e116f6aee03185f237311a943491bb079a768Kristian Monsen '</stream:features>') 2196ded16be15dd865a9b21ea304d5273c8be299c87Steve Block 2206ded16be15dd865a9b21ea304d5273c8be299c87Steve Block # Used when in the _AUTH_NEEDED state. 2216ded16be15dd865a9b21ea304d5273c8be299c87Steve Block _AUTH_SUCCESS_STANZA = ParseXml( 2226ded16be15dd865a9b21ea304d5273c8be299c87Steve Block '<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl"/>') 2236ded16be15dd865a9b21ea304d5273c8be299c87Steve Block 2246ded16be15dd865a9b21ea304d5273c8be299c87Steve Block # Used when in the _AUTH_STREAM_NEEDED state. 2256ded16be15dd865a9b21ea304d5273c8be299c87Steve Block _BIND_STANZA = ParseXml( 2266ded16be15dd865a9b21ea304d5273c8be299c87Steve Block '<stream:features xmlns:stream="http://etherx.jabber.org/streams">' 2276ded16be15dd865a9b21ea304d5273c8be299c87Steve Block ' <bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"/>' 2286ded16be15dd865a9b21ea304d5273c8be299c87Steve Block ' <session xmlns="urn:ietf:params:xml:ns:xmpp-session"/>' 2290d5e116f6aee03185f237311a943491bb079a768Kristian Monsen '</stream:features>') 2300d5e116f6aee03185f237311a943491bb079a768Kristian Monsen 2310d5e116f6aee03185f237311a943491bb079a768Kristian Monsen # Used when in the _BIND_NEEDED state. 2320d5e116f6aee03185f237311a943491bb079a768Kristian Monsen # 2330d5e116f6aee03185f237311a943491bb079a768Kristian Monsen # The id and jid attributes are filled in later. 2340d5e116f6aee03185f237311a943491bb079a768Kristian Monsen _BIND_RESULT_STANZA = ParseXml( 2350d5e116f6aee03185f237311a943491bb079a768Kristian Monsen '<iq id="" type="result">' 2360d5e116f6aee03185f237311a943491bb079a768Kristian Monsen ' <bind xmlns="urn:ietf:params:xml:ns:xmpp-bind">' 2370d5e116f6aee03185f237311a943491bb079a768Kristian Monsen ' <jid/>' 2380d5e116f6aee03185f237311a943491bb079a768Kristian Monsen ' </bind>' 2390d5e116f6aee03185f237311a943491bb079a768Kristian Monsen '</iq>') 2400d5e116f6aee03185f237311a943491bb079a768Kristian Monsen 2410d5e116f6aee03185f237311a943491bb079a768Kristian Monsen # Used when in the _SESSION_NEEDED state. 2420d5e116f6aee03185f237311a943491bb079a768Kristian Monsen # 2430d5e116f6aee03185f237311a943491bb079a768Kristian Monsen # The id attribute is filled in later. 2440d5e116f6aee03185f237311a943491bb079a768Kristian Monsen _IQ_RESPONSE_STANZA = ParseXml('<iq id="" type="result"/>') 2450d5e116f6aee03185f237311a943491bb079a768Kristian Monsen 2460d5e116f6aee03185f237311a943491bb079a768Kristian Monsen def __init__(self, connection, resource_prefix): 2470d5e116f6aee03185f237311a943491bb079a768Kristian Monsen self._connection = connection 2480d5e116f6aee03185f237311a943491bb079a768Kristian Monsen self._id_generator = IdGenerator(resource_prefix) 2490d5e116f6aee03185f237311a943491bb079a768Kristian Monsen self._username = '' 2500d5e116f6aee03185f237311a943491bb079a768Kristian Monsen self._domain = '' 2510d5e116f6aee03185f237311a943491bb079a768Kristian Monsen self._jid = None 2520d5e116f6aee03185f237311a943491bb079a768Kristian Monsen self._resource_prefix = resource_prefix 2530d5e116f6aee03185f237311a943491bb079a768Kristian Monsen self._state = self._INITIAL_STREAM_NEEDED 2540d5e116f6aee03185f237311a943491bb079a768Kristian Monsen 2550d5e116f6aee03185f237311a943491bb079a768Kristian Monsen def FeedStanza(self, stanza): 2560d5e116f6aee03185f237311a943491bb079a768Kristian Monsen """Inspects the given stanza and changes the handshake state if needed. 2570d5e116f6aee03185f237311a943491bb079a768Kristian Monsen 2580d5e116f6aee03185f237311a943491bb079a768Kristian Monsen Called when a stanza is received from the client. Inspects the 2590d5e116f6aee03185f237311a943491bb079a768Kristian Monsen stanza to make sure it has the expected attributes given the 2600d5e116f6aee03185f237311a943491bb079a768Kristian Monsen current state, advances the state if needed, and sends a reply to 2610d5e116f6aee03185f237311a943491bb079a768Kristian Monsen the client if needed. 2620d5e116f6aee03185f237311a943491bb079a768Kristian Monsen """ 2630d5e116f6aee03185f237311a943491bb079a768Kristian Monsen def ExpectStanza(stanza, name): 2640d5e116f6aee03185f237311a943491bb079a768Kristian Monsen if stanza.tagName != name: 2650d5e116f6aee03185f237311a943491bb079a768Kristian Monsen raise UnexpectedXml(stanza) 2660d5e116f6aee03185f237311a943491bb079a768Kristian Monsen 2670d5e116f6aee03185f237311a943491bb079a768Kristian Monsen def ExpectIq(stanza, type, name): 2680d5e116f6aee03185f237311a943491bb079a768Kristian Monsen ExpectStanza(stanza, 'iq') 269 if (stanza.getAttribute('type') != type or 270 stanza.firstChild.tagName != name): 271 raise UnexpectedXml(stanza) 272 273 def GetStanzaId(stanza): 274 return stanza.getAttribute('id') 275 276 def HandleStream(stanza): 277 ExpectStanza(stanza, 'stream:stream') 278 domain = stanza.getAttribute('to') 279 if domain: 280 self._domain = domain 281 SendStreamData() 282 283 def SendStreamData(): 284 next_id = self._id_generator.GetNextId() 285 stream_data = self._STREAM_DATA % (self._domain, next_id) 286 self._connection.SendData(stream_data) 287 288 def GetUserDomain(stanza): 289 encoded_username_password = stanza.firstChild.data 290 username_password = base64.b64decode(encoded_username_password) 291 (_, username_domain, _) = username_password.split('\0') 292 # The domain may be omitted. 293 # 294 # If we were using python 2.5, we'd be able to do: 295 # 296 # username, _, domain = username_domain.partition('@') 297 # if not domain: 298 # domain = self._domain 299 at_pos = username_domain.find('@') 300 if at_pos != -1: 301 username = username_domain[:at_pos] 302 domain = username_domain[at_pos+1:] 303 else: 304 username = username_domain 305 domain = self._domain 306 return (username, domain) 307 308 if self._state == self._INITIAL_STREAM_NEEDED: 309 HandleStream(stanza) 310 self._connection.SendStanza(self._AUTH_STANZA, False) 311 self._state = self._AUTH_NEEDED 312 313 elif self._state == self._AUTH_NEEDED: 314 ExpectStanza(stanza, 'auth') 315 (self._username, self._domain) = GetUserDomain(stanza) 316 self._connection.SendStanza(self._AUTH_SUCCESS_STANZA, False) 317 self._state = self._AUTH_STREAM_NEEDED 318 319 elif self._state == self._AUTH_STREAM_NEEDED: 320 HandleStream(stanza) 321 self._connection.SendStanza(self._BIND_STANZA, False) 322 self._state = self._BIND_NEEDED 323 324 elif self._state == self._BIND_NEEDED: 325 ExpectIq(stanza, 'set', 'bind') 326 stanza_id = GetStanzaId(stanza) 327 resource_element = stanza.getElementsByTagName('resource')[0] 328 resource = resource_element.firstChild.data 329 full_resource = '%s.%s' % (self._resource_prefix, resource) 330 response = CloneXml(self._BIND_RESULT_STANZA) 331 response.setAttribute('id', stanza_id) 332 self._jid = Jid(self._username, self._domain, full_resource) 333 jid_text = response.parentNode.createTextNode(str(self._jid)) 334 response.getElementsByTagName('jid')[0].appendChild(jid_text) 335 self._connection.SendStanza(response) 336 self._state = self._SESSION_NEEDED 337 338 elif self._state == self._SESSION_NEEDED: 339 ExpectIq(stanza, 'set', 'session') 340 stanza_id = GetStanzaId(stanza) 341 xml = CloneXml(self._IQ_RESPONSE_STANZA) 342 xml.setAttribute('id', stanza_id) 343 self._connection.SendStanza(xml) 344 self._state = self._FINISHED 345 self._connection.HandshakeDone(self._jid) 346 347 348def AddrString(addr): 349 return '%s:%d' % addr 350 351 352class XmppConnection(asynchat.async_chat): 353 """A single XMPP client connection. 354 355 This class handles the connection to a single XMPP client (via a 356 socket). It does the XMPP handshake and also implements the (old) 357 Google notification protocol. 358 """ 359 360 # Used for acknowledgements to the client. 361 # 362 # The from and id attributes are filled in later. 363 _IQ_RESPONSE_STANZA = ParseXml('<iq from="" id="" type="result"/>') 364 365 def __init__(self, sock, socket_map, delegate, addr): 366 """Starts up the xmpp connection. 367 368 Args: 369 sock: The socket to the client. 370 socket_map: A map from sockets to their owning objects. 371 delegate: The delegate, which is notified when the XMPP 372 handshake is successful, when the connection is closed, and 373 when a notification has to be broadcast. 374 addr: The host/port of the client. 375 """ 376 # We do this because in versions of python < 2.6, 377 # async_chat.__init__ doesn't take a map argument nor pass it to 378 # dispatcher.__init__. We rely on the fact that 379 # async_chat.__init__ calls dispatcher.__init__ as the last thing 380 # it does, and that calling dispatcher.__init__ with socket=None 381 # and map=None is essentially a no-op. 382 asynchat.async_chat.__init__(self) 383 asyncore.dispatcher.__init__(self, sock, socket_map) 384 385 self.set_terminator(None) 386 387 self._delegate = delegate 388 self._parser = StanzaParser(self) 389 self._jid = None 390 391 self._addr = addr 392 addr_str = AddrString(self._addr) 393 self._handshake_task = HandshakeTask(self, addr_str) 394 print 'Starting connection to %s' % self 395 396 def __str__(self): 397 if self._jid: 398 return str(self._jid) 399 else: 400 return AddrString(self._addr) 401 402 # async_chat implementation. 403 404 def collect_incoming_data(self, data): 405 self._parser.FeedString(data) 406 407 # This is only here to make pychecker happy. 408 def found_terminator(self): 409 asynchat.async_chat.found_terminator(self) 410 411 def close(self): 412 print "Closing connection to %s" % self 413 self._delegate.OnXmppConnectionClosed(self) 414 asynchat.async_chat.close(self) 415 416 # Called by self._parser.FeedString(). 417 def FeedStanza(self, stanza): 418 if self._handshake_task: 419 self._handshake_task.FeedStanza(stanza) 420 elif stanza.tagName == 'iq' and stanza.getAttribute('type') == 'result': 421 # Ignore all client acks. 422 pass 423 elif (stanza.firstChild and 424 stanza.firstChild.namespaceURI == 'google:push'): 425 self._HandlePushCommand(stanza) 426 else: 427 raise UnexpectedXml(stanza) 428 429 # Called by self._handshake_task. 430 def HandshakeDone(self, jid): 431 self._jid = jid 432 self._handshake_task = None 433 self._delegate.OnXmppHandshakeDone(self) 434 print "Handshake done for %s" % self 435 436 def _HandlePushCommand(self, stanza): 437 if stanza.tagName == 'iq' and stanza.firstChild.tagName == 'subscribe': 438 # Subscription request. 439 self._SendIqResponseStanza(stanza) 440 elif stanza.tagName == 'message' and stanza.firstChild.tagName == 'push': 441 # Send notification request. 442 self._delegate.ForwardNotification(self, stanza) 443 else: 444 raise UnexpectedXml(command_xml) 445 446 def _SendIqResponseStanza(self, iq): 447 stanza = CloneXml(self._IQ_RESPONSE_STANZA) 448 stanza.setAttribute('from', str(self._jid.GetBareJid())) 449 stanza.setAttribute('id', iq.getAttribute('id')) 450 self.SendStanza(stanza) 451 452 def SendStanza(self, stanza, unlink=True): 453 """Sends a stanza to the client. 454 455 Args: 456 stanza: The stanza to send. 457 unlink: Whether to unlink stanza after sending it. (Pass in 458 False if stanza is a constant.) 459 """ 460 self.SendData(stanza.toxml()) 461 if unlink: 462 stanza.unlink() 463 464 def SendData(self, data): 465 """Sends raw data to the client. 466 """ 467 # We explicitly encode to ascii as that is what the client expects 468 # (some minidom library functions return unicode strings). 469 self.push(data.encode('ascii')) 470 471 def ForwardNotification(self, notification_stanza): 472 """Forwards a notification to the client.""" 473 notification_stanza.setAttribute('from', str(self._jid.GetBareJid())) 474 notification_stanza.setAttribute('to', str(self._jid)) 475 self.SendStanza(notification_stanza, False) 476 477 478class XmppServer(asyncore.dispatcher): 479 """The main XMPP server class. 480 481 The XMPP server starts accepting connections on the given address 482 and spawns off XmppConnection objects for each one. 483 484 Use like so: 485 486 socket_map = {} 487 xmpp_server = xmppserver.XmppServer(socket_map, ('127.0.0.1', 5222)) 488 asyncore.loop(30.0, False, socket_map) 489 """ 490 491 def __init__(self, socket_map, addr): 492 asyncore.dispatcher.__init__(self, None, socket_map) 493 self.create_socket(socket.AF_INET, socket.SOCK_STREAM) 494 self.set_reuse_addr() 495 self.bind(addr) 496 self.listen(5) 497 self._socket_map = socket_map 498 self._connections = set() 499 self._handshake_done_connections = set() 500 501 def handle_accept(self): 502 (sock, addr) = self.accept() 503 xmpp_connection = XmppConnection(sock, self._socket_map, self, addr) 504 self._connections.add(xmpp_connection) 505 506 def close(self): 507 # A copy is necessary since calling close on each connection 508 # removes it from self._connections. 509 for connection in self._connections.copy(): 510 connection.close() 511 asyncore.dispatcher.close(self) 512 513 # XmppConnection delegate methods. 514 def OnXmppHandshakeDone(self, xmpp_connection): 515 self._handshake_done_connections.add(xmpp_connection) 516 517 def OnXmppConnectionClosed(self, xmpp_connection): 518 self._connections.discard(xmpp_connection) 519 self._handshake_done_connections.discard(xmpp_connection) 520 521 def ForwardNotification(self, unused_xmpp_connection, notification_stanza): 522 for connection in self._handshake_done_connections: 523 print 'Sending notification to %s' % connection 524 connection.ForwardNotification(notification_stanza) 525