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