# -*- test-case-name: twisted.test.test_xmlstream -*-
#
# Twisted, the Framework of Your Internet
# Copyright (C) 2001 Matthew W. Lefkowitz
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of version 2.1 of the GNU Lesser General Public
# License as published by the Free Software Foundation.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
from twisted.internet import reactor, protocol, defer
from twisted.xish import domish, utility
STREAM_CONNECTED_EVENT = intern("//event/stream/connected")
STREAM_START_EVENT = intern("//event/stream/start")
STREAM_END_EVENT = intern("//event/stream/end")
STREAM_ERROR_EVENT = intern("//event/stream/error")
STREAM_AUTHD_EVENT = intern("//event/stream/authd")
RAWDATA_IN_EVENT = intern("//event/rawdata/in")
RAWDATA_OUT_EVENT = intern("//event/rawdata/out")
def hashPassword(sid, password):
"""Create a SHA1-digest string of a session identifier and password """
import sha
return sha.new("%s%s" % (sid, password)).hexdigest()
class Authenticator:
""" Base class for business logic of authenticating an XmlStream
Subclass this object to enable an XmlStream to authenticate to different
types of stream hosts (such as clients, components, etc.).
Rules:
1. The Authenticator MUST dispatch a L{STREAM_AUTHD_EVENT} when the stream
has been completely authenticated.
2. The Authenticator SHOULD reset all state information when
L{associateWithStream} is called.
3. The Authenticator SHOULD override L{streamStarted}, and start
authentication there.
@type namespace: C{str}
@cvar namespace: Default namespace for the XmlStream
@type version: C{int}
@cvar version: Version attribute for XmlStream. 0.0 will cause the
XmlStream to not include a C{version} attribute in the
header.
@type streamHost: C{str}
@ivar streamHost: Target host for this stream (used as the 'to' attribute)
@type xmlstream: C{XmlStream}
@ivar xmlstream: The XmlStream that needs authentication
"""
namespace = 'invalid' # Default namespace for stream
version = 0.0 # Stream version
def __init__(self, streamHost):
self.streamHost = streamHost
self.xmlstream = None
def connectionMade(self):
"""
Called by the XmlStream when the underlying socket connection is
in place. This allows the Authenticator to send an initial root
element, if it's connecting, or wait for an inbound root from
the peer if it's accepting the connection
Subclasses can use self.xmlstream.send() with the provided xmlstream
parameter to send any initial data to the peer
"""
def streamStarted(self, rootelem):
"""
Called by the XmlStream when it has received a root element from
the connected peer.
@type rootelem: C{Element}
@param rootelem: The root element of the XmlStream received from
the streamHost
"""
def associateWithStream(self, xmlstream):
"""
Called by the XmlStreamFactory when a connection has been made
to the requested peer, and an XmlStream object has been
instantiated.
The default implementation just saves a handle to the new
XmlStream.
@type xmlstream: C{XmlStream}
@param xmlstream: The XmlStream that will be passing events to this
Authenticator.
"""
self.xmlstream = xmlstream
class ConnectAuthenticator(Authenticator):
def connectionMade(self):
# Generate stream header
if self.version == 1.0:
sh = "<stream:stream xmlns='%s' xmlns:stream='http://etherx.jabber.org/streams' version='1.0'>" % \
(self.namespace)
else:
sh = "<stream:stream xmlns='%s' xmlns:stream='http://etherx.jabber.org/streams' to='%s'>" % \
(self.namespace, self.streamHost)
self.xmlstream.send(sh)
class XmlStream(protocol.Protocol, utility.EventDispatcher):
def __init__(self, authenticator):
utility.EventDispatcher.__init__(self)
self.stream = None
self.authenticator = authenticator
self.sid = None
self.rawDataOutFn = None
self.rawDataInFn = None
# Reset the authenticator
authenticator.associateWithStream(self)
# Setup watcher for stream errors
self.addObserver("/error[@xmlns='http://etherx.jabber.org/streams']", self.streamError)
def streamError(self, errelem):
self.dispatch(errelem, STREAM_ERROR_EVENT)
self.transport.loseConnection()
### --------------------------------------------------------------
###
### Protocol events
###
### --------------------------------------------------------------
def connectionMade(self):
# Setup the parser
self.stream = domish.elementStream()
self.stream.DocumentStartEvent = self.onDocumentStart
self.stream.ElementEvent = self.onElement
self.stream.DocumentEndEvent = self.onDocumentEnd
self.dispatch(self, STREAM_CONNECTED_EVENT)
self.authenticator.connectionMade()
def dataReceived(self, buf):
try:
if self.rawDataInFn: self.rawDataInFn(buf)
self.stream.parse(buf)
except domish.ParserError:
self.dispatch(self, STREAM_ERROR_EVENT)
self.transport.loseConnection()
def connectionLost(self, _):
self.dispatch(self, STREAM_END_EVENT)
self.stream = None
### --------------------------------------------------------------
###
### DOM events
###
### --------------------------------------------------------------
def onDocumentStart(self, rootelem):
if rootelem.hasAttribute("id"):
self.sid = rootelem["id"] # Extract stream identifier
self.authenticator.streamStarted(rootelem) # Notify authenticator
self.dispatch(self, STREAM_START_EVENT)
def onElement(self, element):
self.dispatch(element)
def onDocumentEnd(self):
self.transport.loseConnection()
def setDispatchFn(self, fn):
self.stream.ElementEvent = fn
def resetDispatchFn(self):
self.stream.ElementEvent = self.onElement
def send(self, obj):
if isinstance(obj, domish.Element):
obj = obj.toXml()
if self.rawDataOutFn:
self.rawDataOutFn(obj)
self.transport.write(obj)
class XmlStreamFactory(protocol.ReconnectingClientFactory):
def __init__(self, authenticator):
self.authenticator = authenticator
self.bootstraps = []
def buildProtocol(self, _):
self.resetDelay()
# Create the stream and register all the bootstrap observers
xs = XmlStream(self.authenticator)
xs.factory = self
for event, fn in self.bootstraps: xs.addObserver(event, fn)
return xs
def addBootstrap(self, event, fn):
self.bootstraps.append((event, fn))
def removeBootstrap(self, event, fn):
self.bootstraps.remove((event, fn))
syntax highlighted by Code2HTML, v. 0.9.1