aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/python/Twisted/py3/twisted/words/protocols/jabber
diff options
context:
space:
mode:
authorshmel1k <shmel1k@ydb.tech>2023-11-26 18:16:14 +0300
committershmel1k <shmel1k@ydb.tech>2023-11-26 18:43:30 +0300
commitb8cf9e88f4c5c64d9406af533d8948deb050d695 (patch)
tree218eb61fb3c3b96ec08b4d8cdfef383104a87d63 /contrib/python/Twisted/py3/twisted/words/protocols/jabber
parent523f645a83a0ec97a0332dbc3863bb354c92a328 (diff)
downloadydb-b8cf9e88f4c5c64d9406af533d8948deb050d695.tar.gz
add kikimr_configure
Diffstat (limited to 'contrib/python/Twisted/py3/twisted/words/protocols/jabber')
-rw-r--r--contrib/python/Twisted/py3/twisted/words/protocols/jabber/__init__.py8
-rw-r--r--contrib/python/Twisted/py3/twisted/words/protocols/jabber/client.py394
-rw-r--r--contrib/python/Twisted/py3/twisted/words/protocols/jabber/component.py456
-rw-r--r--contrib/python/Twisted/py3/twisted/words/protocols/jabber/error.py323
-rw-r--r--contrib/python/Twisted/py3/twisted/words/protocols/jabber/ijabber.py188
-rw-r--r--contrib/python/Twisted/py3/twisted/words/protocols/jabber/jid.py259
-rw-r--r--contrib/python/Twisted/py3/twisted/words/protocols/jabber/jstrports.py34
-rw-r--r--contrib/python/Twisted/py3/twisted/words/protocols/jabber/sasl.py229
-rw-r--r--contrib/python/Twisted/py3/twisted/words/protocols/jabber/sasl_mechanisms.py307
-rw-r--r--contrib/python/Twisted/py3/twisted/words/protocols/jabber/xmlstream.py1145
-rw-r--r--contrib/python/Twisted/py3/twisted/words/protocols/jabber/xmpp_stringprep.py257
11 files changed, 3600 insertions, 0 deletions
diff --git a/contrib/python/Twisted/py3/twisted/words/protocols/jabber/__init__.py b/contrib/python/Twisted/py3/twisted/words/protocols/jabber/__init__.py
new file mode 100644
index 0000000000..ad95b6853e
--- /dev/null
+++ b/contrib/python/Twisted/py3/twisted/words/protocols/jabber/__init__.py
@@ -0,0 +1,8 @@
+# -*- test-case-name: twisted.words.test -*-
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+Twisted Jabber: Jabber Protocol Helpers
+"""
diff --git a/contrib/python/Twisted/py3/twisted/words/protocols/jabber/client.py b/contrib/python/Twisted/py3/twisted/words/protocols/jabber/client.py
new file mode 100644
index 0000000000..21de4774e2
--- /dev/null
+++ b/contrib/python/Twisted/py3/twisted/words/protocols/jabber/client.py
@@ -0,0 +1,394 @@
+# -*- test-case-name: twisted.words.test.test_jabberclient -*-
+#
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+from twisted.words.protocols.jabber import error, sasl, xmlstream
+from twisted.words.protocols.jabber.jid import JID
+from twisted.words.xish import domish, utility, xpath
+
+NS_XMPP_STREAMS = "urn:ietf:params:xml:ns:xmpp-streams"
+NS_XMPP_BIND = "urn:ietf:params:xml:ns:xmpp-bind"
+NS_XMPP_SESSION = "urn:ietf:params:xml:ns:xmpp-session"
+NS_IQ_AUTH_FEATURE = "http://jabber.org/features/iq-auth"
+
+DigestAuthQry = xpath.internQuery("/iq/query/digest")
+PlaintextAuthQry = xpath.internQuery("/iq/query/password")
+
+
+def basicClientFactory(jid, secret):
+ a = BasicAuthenticator(jid, secret)
+ return xmlstream.XmlStreamFactory(a)
+
+
+class IQ(domish.Element):
+ """
+ Wrapper for a Info/Query packet.
+
+ This provides the necessary functionality to send IQs and get notified when
+ a result comes back. It's a subclass from L{domish.Element}, so you can use
+ the standard DOM manipulation calls to add data to the outbound request.
+
+ @type callbacks: L{utility.CallbackList}
+ @cvar callbacks: Callback list to be notified when response comes back
+
+ """
+
+ def __init__(self, xmlstream, type="set"):
+ """
+ @type xmlstream: L{xmlstream.XmlStream}
+ @param xmlstream: XmlStream to use for transmission of this IQ
+
+ @type type: C{str}
+ @param type: IQ type identifier ('get' or 'set')
+ """
+
+ domish.Element.__init__(self, ("jabber:client", "iq"))
+ self.addUniqueId()
+ self["type"] = type
+ self._xmlstream = xmlstream
+ self.callbacks = utility.CallbackList()
+
+ def addCallback(self, fn, *args, **kwargs):
+ """
+ Register a callback for notification when the IQ result is available.
+ """
+
+ self.callbacks.addCallback(True, fn, *args, **kwargs)
+
+ def send(self, to=None):
+ """
+ Call this method to send this IQ request via the associated XmlStream.
+
+ @param to: Jabber ID of the entity to send the request to
+ @type to: C{str}
+
+ @returns: Callback list for this IQ. Any callbacks added to this list
+ will be fired when the result comes back.
+ """
+ if to != None:
+ self["to"] = to
+ self._xmlstream.addOnetimeObserver(
+ "/iq[@id='%s']" % self["id"], self._resultEvent
+ )
+ self._xmlstream.send(self)
+
+ def _resultEvent(self, iq):
+ self.callbacks.callback(iq)
+ self.callbacks = None
+
+
+class IQAuthInitializer:
+ """
+ Non-SASL Authentication initializer for the initiating entity.
+
+ This protocol is defined in
+ U{JEP-0078<http://www.jabber.org/jeps/jep-0078.html>} and mainly serves for
+ compatibility with pre-XMPP-1.0 server implementations.
+
+ @cvar INVALID_USER_EVENT: Token to signal that authentication failed, due
+ to invalid username.
+ @type INVALID_USER_EVENT: L{str}
+
+ @cvar AUTH_FAILED_EVENT: Token to signal that authentication failed, due to
+ invalid password.
+ @type AUTH_FAILED_EVENT: L{str}
+ """
+
+ INVALID_USER_EVENT = "//event/client/basicauth/invaliduser"
+ AUTH_FAILED_EVENT = "//event/client/basicauth/authfailed"
+
+ def __init__(self, xs):
+ self.xmlstream = xs
+
+ def initialize(self):
+ # Send request for auth fields
+ iq = xmlstream.IQ(self.xmlstream, "get")
+ iq.addElement(("jabber:iq:auth", "query"))
+ jid = self.xmlstream.authenticator.jid
+ iq.query.addElement("username", content=jid.user)
+
+ d = iq.send()
+ d.addCallbacks(self._cbAuthQuery, self._ebAuthQuery)
+ return d
+
+ def _cbAuthQuery(self, iq):
+ jid = self.xmlstream.authenticator.jid
+ password = self.xmlstream.authenticator.password
+
+ # Construct auth request
+ reply = xmlstream.IQ(self.xmlstream, "set")
+ reply.addElement(("jabber:iq:auth", "query"))
+ reply.query.addElement("username", content=jid.user)
+ reply.query.addElement("resource", content=jid.resource)
+
+ # Prefer digest over plaintext
+ if DigestAuthQry.matches(iq):
+ digest = xmlstream.hashPassword(self.xmlstream.sid, password)
+ reply.query.addElement("digest", content=str(digest))
+ else:
+ reply.query.addElement("password", content=password)
+
+ d = reply.send()
+ d.addCallbacks(self._cbAuth, self._ebAuth)
+ return d
+
+ def _ebAuthQuery(self, failure):
+ failure.trap(error.StanzaError)
+ e = failure.value
+ if e.condition == "not-authorized":
+ self.xmlstream.dispatch(e.stanza, self.INVALID_USER_EVENT)
+ else:
+ self.xmlstream.dispatch(e.stanza, self.AUTH_FAILED_EVENT)
+
+ return failure
+
+ def _cbAuth(self, iq):
+ pass
+
+ def _ebAuth(self, failure):
+ failure.trap(error.StanzaError)
+ self.xmlstream.dispatch(failure.value.stanza, self.AUTH_FAILED_EVENT)
+ return failure
+
+
+class BasicAuthenticator(xmlstream.ConnectAuthenticator):
+ """
+ Authenticates an XmlStream against a Jabber server as a Client.
+
+ This only implements non-SASL authentication, per
+ U{JEP-0078<http://www.jabber.org/jeps/jep-0078.html>}. Additionally, this
+ authenticator provides the ability to perform inline registration, per
+ U{JEP-0077<http://www.jabber.org/jeps/jep-0077.html>}.
+
+ Under normal circumstances, the BasicAuthenticator generates the
+ L{xmlstream.STREAM_AUTHD_EVENT} once the stream has authenticated. However,
+ it can also generate other events, such as:
+ - L{INVALID_USER_EVENT} : Authentication failed, due to invalid username
+ - L{AUTH_FAILED_EVENT} : Authentication failed, due to invalid password
+ - L{REGISTER_FAILED_EVENT} : Registration failed
+
+ If authentication fails for any reason, you can attempt to register by
+ calling the L{registerAccount} method. If the registration succeeds, a
+ L{xmlstream.STREAM_AUTHD_EVENT} will be fired. Otherwise, one of the above
+ errors will be generated (again).
+
+
+ @cvar INVALID_USER_EVENT: See L{IQAuthInitializer.INVALID_USER_EVENT}.
+ @type INVALID_USER_EVENT: L{str}
+
+ @cvar AUTH_FAILED_EVENT: See L{IQAuthInitializer.AUTH_FAILED_EVENT}.
+ @type AUTH_FAILED_EVENT: L{str}
+
+ @cvar REGISTER_FAILED_EVENT: Token to signal that registration failed.
+ @type REGISTER_FAILED_EVENT: L{str}
+
+ """
+
+ namespace = "jabber:client"
+
+ INVALID_USER_EVENT = IQAuthInitializer.INVALID_USER_EVENT
+ AUTH_FAILED_EVENT = IQAuthInitializer.AUTH_FAILED_EVENT
+ REGISTER_FAILED_EVENT = "//event/client/basicauth/registerfailed"
+
+ def __init__(self, jid, password):
+ xmlstream.ConnectAuthenticator.__init__(self, jid.host)
+ self.jid = jid
+ self.password = password
+
+ def associateWithStream(self, xs):
+ xs.version = (0, 0)
+ xmlstream.ConnectAuthenticator.associateWithStream(self, xs)
+
+ xs.initializers = [
+ xmlstream.TLSInitiatingInitializer(xs, required=False),
+ IQAuthInitializer(xs),
+ ]
+
+ # TODO: move registration into an Initializer?
+
+ def registerAccount(self, username=None, password=None):
+ if username:
+ self.jid.user = username
+ if password:
+ self.password = password
+
+ iq = IQ(self.xmlstream, "set")
+ iq.addElement(("jabber:iq:register", "query"))
+ iq.query.addElement("username", content=self.jid.user)
+ iq.query.addElement("password", content=self.password)
+
+ iq.addCallback(self._registerResultEvent)
+
+ iq.send()
+
+ def _registerResultEvent(self, iq):
+ if iq["type"] == "result":
+ # Registration succeeded -- go ahead and auth
+ self.streamStarted()
+ else:
+ # Registration failed
+ self.xmlstream.dispatch(iq, self.REGISTER_FAILED_EVENT)
+
+
+class CheckVersionInitializer:
+ """
+ Initializer that checks if the minimum common stream version number is 1.0.
+ """
+
+ def __init__(self, xs):
+ self.xmlstream = xs
+
+ def initialize(self):
+ if self.xmlstream.version < (1, 0):
+ raise error.StreamError("unsupported-version")
+
+
+class BindInitializer(xmlstream.BaseFeatureInitiatingInitializer):
+ """
+ Initializer that implements Resource Binding for the initiating entity.
+
+ This protocol is documented in U{RFC 3920, section
+ 7<http://www.xmpp.org/specs/rfc3920.html#bind>}.
+ """
+
+ feature = (NS_XMPP_BIND, "bind")
+
+ def start(self):
+ iq = xmlstream.IQ(self.xmlstream, "set")
+ bind = iq.addElement((NS_XMPP_BIND, "bind"))
+ resource = self.xmlstream.authenticator.jid.resource
+ if resource:
+ bind.addElement("resource", content=resource)
+ d = iq.send()
+ d.addCallback(self.onBind)
+ return d
+
+ def onBind(self, iq):
+ if iq.bind:
+ self.xmlstream.authenticator.jid = JID(str(iq.bind.jid))
+
+
+class SessionInitializer(xmlstream.BaseFeatureInitiatingInitializer):
+ """
+ Initializer that implements session establishment for the initiating
+ entity.
+
+ This protocol is defined in U{RFC 3921, section
+ 3<http://www.xmpp.org/specs/rfc3921.html#session>}.
+ """
+
+ feature = (NS_XMPP_SESSION, "session")
+
+ def start(self):
+ iq = xmlstream.IQ(self.xmlstream, "set")
+ iq.addElement((NS_XMPP_SESSION, "session"))
+ return iq.send()
+
+
+def XMPPClientFactory(jid, password, configurationForTLS=None):
+ """
+ Client factory for XMPP 1.0 (only).
+
+ This returns a L{xmlstream.XmlStreamFactory} with an L{XMPPAuthenticator}
+ object to perform the stream initialization steps (such as authentication).
+
+ @see: The notes at L{XMPPAuthenticator} describe how the C{jid} and
+ C{password} parameters are to be used.
+
+ @param jid: Jabber ID to connect with.
+ @type jid: L{jid.JID}
+
+ @param password: password to authenticate with.
+ @type password: L{unicode}
+
+ @param configurationForTLS: An object which creates appropriately
+ configured TLS connections. This is passed to C{startTLS} on the
+ transport and is preferably created using
+ L{twisted.internet.ssl.optionsForClientTLS}. If L{None}, the default is
+ to verify the server certificate against the trust roots as provided by
+ the platform. See L{twisted.internet._sslverify.platformTrust}.
+ @type configurationForTLS: L{IOpenSSLClientConnectionCreator} or L{None}
+
+ @return: XML stream factory.
+ @rtype: L{xmlstream.XmlStreamFactory}
+ """
+ a = XMPPAuthenticator(jid, password, configurationForTLS=configurationForTLS)
+ return xmlstream.XmlStreamFactory(a)
+
+
+class XMPPAuthenticator(xmlstream.ConnectAuthenticator):
+ """
+ Initializes an XmlStream connecting to an XMPP server as a Client.
+
+ This authenticator performs the initialization steps needed to start
+ exchanging XML stanzas with an XMPP server as an XMPP client. It checks if
+ the server advertises XML stream version 1.0, negotiates TLS (when
+ available), performs SASL authentication, binds a resource and establishes
+ a session.
+
+ Upon successful stream initialization, the L{xmlstream.STREAM_AUTHD_EVENT}
+ event will be dispatched through the XML stream object. Otherwise, the
+ L{xmlstream.INIT_FAILED_EVENT} event will be dispatched with a failure
+ object.
+
+ After inspection of the failure, initialization can then be restarted by
+ calling L{ConnectAuthenticator.initializeStream}. For example, in case of
+ authentication failure, a user may be given the opportunity to input the
+ correct password. By setting the L{password} instance variable and restarting
+ initialization, the stream authentication step is then retried, and subsequent
+ steps are performed if successful.
+
+ @ivar jid: Jabber ID to authenticate with. This may contain a resource
+ part, as a suggestion to the server for resource binding. A
+ server may override this, though. If the resource part is left
+ off, the server will generate a unique resource identifier.
+ The server will always return the full Jabber ID in the
+ resource binding step, and this is stored in this instance
+ variable.
+ @type jid: L{jid.JID}
+
+ @ivar password: password to be used during SASL authentication.
+ @type password: L{unicode}
+ """
+
+ namespace = "jabber:client"
+
+ def __init__(self, jid, password, configurationForTLS=None):
+ """
+ @param configurationForTLS: An object which creates appropriately
+ configured TLS connections. This is passed to C{startTLS} on the
+ transport and is preferably created using
+ L{twisted.internet.ssl.optionsForClientTLS}. If C{None}, the
+ default is to verify the server certificate against the trust roots
+ as provided by the platform. See
+ L{twisted.internet._sslverify.platformTrust}.
+ @type configurationForTLS: L{IOpenSSLClientConnectionCreator} or
+ C{None}
+ """
+ xmlstream.ConnectAuthenticator.__init__(self, jid.host)
+ self.jid = jid
+ self.password = password
+ self._configurationForTLS = configurationForTLS
+
+ def associateWithStream(self, xs):
+ """
+ Register with the XML stream.
+
+ Populates stream's list of initializers, along with their
+ requiredness. This list is used by
+ L{ConnectAuthenticator.initializeStream} to perform the initialization
+ steps.
+ """
+ xmlstream.ConnectAuthenticator.associateWithStream(self, xs)
+
+ xs.initializers = [
+ CheckVersionInitializer(xs),
+ xmlstream.TLSInitiatingInitializer(
+ xs, required=True, configurationForTLS=self._configurationForTLS
+ ),
+ sasl.SASLInitiatingInitializer(xs, required=True),
+ BindInitializer(xs, required=True),
+ SessionInitializer(xs, required=False),
+ ]
diff --git a/contrib/python/Twisted/py3/twisted/words/protocols/jabber/component.py b/contrib/python/Twisted/py3/twisted/words/protocols/jabber/component.py
new file mode 100644
index 0000000000..d07c4ee9d7
--- /dev/null
+++ b/contrib/python/Twisted/py3/twisted/words/protocols/jabber/component.py
@@ -0,0 +1,456 @@
+# -*- test-case-name: twisted.words.test.test_jabbercomponent -*-
+#
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+External server-side components.
+
+Most Jabber server implementations allow for add-on components that act as a
+separate entity on the Jabber network, but use the server-to-server
+functionality of a regular Jabber IM server. These so-called 'external
+components' are connected to the Jabber server using the Jabber Component
+Protocol as defined in U{JEP-0114<http://www.jabber.org/jeps/jep-0114.html>}.
+
+This module allows for writing external server-side component by assigning one
+or more services implementing L{ijabber.IService} up to L{ServiceManager}. The
+ServiceManager connects to the Jabber server and is responsible for the
+corresponding XML stream.
+"""
+
+from zope.interface import implementer
+
+from twisted.application import service
+from twisted.internet import defer
+from twisted.python import log
+from twisted.words.protocols.jabber import error, ijabber, jstrports, xmlstream
+from twisted.words.protocols.jabber.jid import internJID as JID
+from twisted.words.xish import domish
+
+NS_COMPONENT_ACCEPT = "jabber:component:accept"
+
+
+def componentFactory(componentid, password):
+ """
+ XML stream factory for external server-side components.
+
+ @param componentid: JID of the component.
+ @type componentid: L{unicode}
+ @param password: password used to authenticate to the server.
+ @type password: C{str}
+ """
+ a = ConnectComponentAuthenticator(componentid, password)
+ return xmlstream.XmlStreamFactory(a)
+
+
+class ComponentInitiatingInitializer:
+ """
+ External server-side component authentication initializer for the
+ initiating entity.
+
+ @ivar xmlstream: XML stream between server and component.
+ @type xmlstream: L{xmlstream.XmlStream}
+ """
+
+ def __init__(self, xs):
+ self.xmlstream = xs
+ self._deferred = None
+
+ def initialize(self):
+ xs = self.xmlstream
+ hs = domish.Element((self.xmlstream.namespace, "handshake"))
+ digest = xmlstream.hashPassword(xs.sid, xs.authenticator.password)
+ hs.addContent(str(digest))
+
+ # Setup observer to watch for handshake result
+ xs.addOnetimeObserver("/handshake", self._cbHandshake)
+ xs.send(hs)
+ self._deferred = defer.Deferred()
+ return self._deferred
+
+ def _cbHandshake(self, _):
+ # we have successfully shaken hands and can now consider this
+ # entity to represent the component JID.
+ self.xmlstream.thisEntity = self.xmlstream.otherEntity
+ self._deferred.callback(None)
+
+
+class ConnectComponentAuthenticator(xmlstream.ConnectAuthenticator):
+ """
+ Authenticator to permit an XmlStream to authenticate against a Jabber
+ server as an external component (where the Authenticator is initiating the
+ stream).
+ """
+
+ namespace = NS_COMPONENT_ACCEPT
+
+ def __init__(self, componentjid, password):
+ """
+ @type componentjid: C{str}
+ @param componentjid: Jabber ID that this component wishes to bind to.
+
+ @type password: C{str}
+ @param password: Password/secret this component uses to authenticate.
+ """
+ # Note that we are sending 'to' our desired component JID.
+ xmlstream.ConnectAuthenticator.__init__(self, componentjid)
+ self.password = password
+
+ def associateWithStream(self, xs):
+ xs.version = (0, 0)
+ xmlstream.ConnectAuthenticator.associateWithStream(self, xs)
+
+ xs.initializers = [ComponentInitiatingInitializer(xs)]
+
+
+class ListenComponentAuthenticator(xmlstream.ListenAuthenticator):
+ """
+ Authenticator for accepting components.
+
+ @since: 8.2
+ @ivar secret: The shared secret used to authorized incoming component
+ connections.
+ @type secret: C{unicode}.
+ """
+
+ namespace = NS_COMPONENT_ACCEPT
+
+ def __init__(self, secret):
+ self.secret = secret
+ xmlstream.ListenAuthenticator.__init__(self)
+
+ def associateWithStream(self, xs):
+ """
+ Associate the authenticator with a stream.
+
+ This sets the stream's version to 0.0, because the XEP-0114 component
+ protocol was not designed for XMPP 1.0.
+ """
+ xs.version = (0, 0)
+ xmlstream.ListenAuthenticator.associateWithStream(self, xs)
+
+ def streamStarted(self, rootElement):
+ """
+ Called by the stream when it has started.
+
+ This examines the default namespace of the incoming stream and whether
+ there is a requested hostname for the component. Then it generates a
+ stream identifier, sends a response header and adds an observer for
+ the first incoming element, triggering L{onElement}.
+ """
+
+ xmlstream.ListenAuthenticator.streamStarted(self, rootElement)
+
+ if rootElement.defaultUri != self.namespace:
+ exc = error.StreamError("invalid-namespace")
+ self.xmlstream.sendStreamError(exc)
+ return
+
+ # self.xmlstream.thisEntity is set to the address the component
+ # wants to assume.
+ if not self.xmlstream.thisEntity:
+ exc = error.StreamError("improper-addressing")
+ self.xmlstream.sendStreamError(exc)
+ return
+
+ self.xmlstream.sendHeader()
+ self.xmlstream.addOnetimeObserver("/*", self.onElement)
+
+ def onElement(self, element):
+ """
+ Called on incoming XML Stanzas.
+
+ The very first element received should be a request for handshake.
+ Otherwise, the stream is dropped with a 'not-authorized' error. If a
+ handshake request was received, the hash is extracted and passed to
+ L{onHandshake}.
+ """
+ if (element.uri, element.name) == (self.namespace, "handshake"):
+ self.onHandshake(str(element))
+ else:
+ exc = error.StreamError("not-authorized")
+ self.xmlstream.sendStreamError(exc)
+
+ def onHandshake(self, handshake):
+ """
+ Called upon receiving the handshake request.
+
+ This checks that the given hash in C{handshake} is equal to a
+ calculated hash, responding with a handshake reply or a stream error.
+ If the handshake was ok, the stream is authorized, and XML Stanzas may
+ be exchanged.
+ """
+ calculatedHash = xmlstream.hashPassword(self.xmlstream.sid, str(self.secret))
+ if handshake != calculatedHash:
+ exc = error.StreamError("not-authorized", text="Invalid hash")
+ self.xmlstream.sendStreamError(exc)
+ else:
+ self.xmlstream.send("<handshake/>")
+ self.xmlstream.dispatch(self.xmlstream, xmlstream.STREAM_AUTHD_EVENT)
+
+
+@implementer(ijabber.IService)
+class Service(service.Service):
+ """
+ External server-side component service.
+ """
+
+ def componentConnected(self, xs):
+ pass
+
+ def componentDisconnected(self):
+ pass
+
+ def transportConnected(self, xs):
+ pass
+
+ def send(self, obj):
+ """
+ Send data over service parent's XML stream.
+
+ @note: L{ServiceManager} maintains a queue for data sent using this
+ method when there is no current established XML stream. This data is
+ then sent as soon as a new stream has been established and initialized.
+ Subsequently, L{componentConnected} will be called again. If this
+ queueing is not desired, use C{send} on the XmlStream object (passed to
+ L{componentConnected}) directly.
+
+ @param obj: data to be sent over the XML stream. This is usually an
+ object providing L{domish.IElement}, or serialized XML. See
+ L{xmlstream.XmlStream} for details.
+ """
+
+ self.parent.send(obj)
+
+
+class ServiceManager(service.MultiService):
+ """
+ Business logic for a managed component connection to a Jabber router.
+
+ This service maintains a single connection to a Jabber router and provides
+ facilities for packet routing and transmission. Business logic modules are
+ services implementing L{ijabber.IService} (like subclasses of L{Service}),
+ and added as sub-service.
+ """
+
+ def __init__(self, jid, password):
+ service.MultiService.__init__(self)
+
+ # Setup defaults
+ self.jabberId = jid
+ self.xmlstream = None
+
+ # Internal buffer of packets
+ self._packetQueue = []
+
+ # Setup the xmlstream factory
+ self._xsFactory = componentFactory(self.jabberId, password)
+
+ # Register some lambda functions to keep the self.xmlstream var up to
+ # date
+ self._xsFactory.addBootstrap(xmlstream.STREAM_CONNECTED_EVENT, self._connected)
+ self._xsFactory.addBootstrap(xmlstream.STREAM_AUTHD_EVENT, self._authd)
+ self._xsFactory.addBootstrap(xmlstream.STREAM_END_EVENT, self._disconnected)
+
+ # Map addBootstrap and removeBootstrap to the underlying factory -- is
+ # this right? I have no clue...but it'll work for now, until i can
+ # think about it more.
+ self.addBootstrap = self._xsFactory.addBootstrap
+ self.removeBootstrap = self._xsFactory.removeBootstrap
+
+ def getFactory(self):
+ return self._xsFactory
+
+ def _connected(self, xs):
+ self.xmlstream = xs
+ for c in self:
+ if ijabber.IService.providedBy(c):
+ c.transportConnected(xs)
+
+ def _authd(self, xs):
+ # Flush all pending packets
+ for p in self._packetQueue:
+ self.xmlstream.send(p)
+ self._packetQueue = []
+
+ # Notify all child services which implement the IService interface
+ for c in self:
+ if ijabber.IService.providedBy(c):
+ c.componentConnected(xs)
+
+ def _disconnected(self, _):
+ self.xmlstream = None
+
+ # Notify all child services which implement
+ # the IService interface
+ for c in self:
+ if ijabber.IService.providedBy(c):
+ c.componentDisconnected()
+
+ def send(self, obj):
+ """
+ Send data over the XML stream.
+
+ When there is no established XML stream, the data is queued and sent
+ out when a new XML stream has been established and initialized.
+
+ @param obj: data to be sent over the XML stream. This is usually an
+ object providing L{domish.IElement}, or serialized XML. See
+ L{xmlstream.XmlStream} for details.
+ """
+
+ if self.xmlstream != None:
+ self.xmlstream.send(obj)
+ else:
+ self._packetQueue.append(obj)
+
+
+def buildServiceManager(jid, password, strport):
+ """
+ Constructs a pre-built L{ServiceManager}, using the specified strport
+ string.
+ """
+
+ svc = ServiceManager(jid, password)
+ client_svc = jstrports.client(strport, svc.getFactory())
+ client_svc.setServiceParent(svc)
+ return svc
+
+
+class Router:
+ """
+ XMPP Server's Router.
+
+ A router connects the different components of the XMPP service and routes
+ messages between them based on the given routing table.
+
+ Connected components are trusted to have correct addressing in the
+ stanzas they offer for routing.
+
+ A route destination of L{None} adds a default route. Traffic for which no
+ specific route exists, will be routed to this default route.
+
+ @since: 8.2
+ @ivar routes: Routes based on the host part of JIDs. Maps host names to the
+ L{EventDispatcher<utility.EventDispatcher>}s that should
+ receive the traffic. A key of L{None} means the default
+ route.
+ @type routes: C{dict}
+ """
+
+ def __init__(self):
+ self.routes = {}
+
+ def addRoute(self, destination, xs):
+ """
+ Add a new route.
+
+ The passed XML Stream C{xs} will have an observer for all stanzas
+ added to route its outgoing traffic. In turn, traffic for
+ C{destination} will be passed to this stream.
+
+ @param destination: Destination of the route to be added as a host name
+ or L{None} for the default route.
+ @type destination: C{str} or L{None}.
+ @param xs: XML Stream to register the route for.
+ @type xs: L{EventDispatcher<utility.EventDispatcher>}.
+ """
+ self.routes[destination] = xs
+ xs.addObserver("/*", self.route)
+
+ def removeRoute(self, destination, xs):
+ """
+ Remove a route.
+
+ @param destination: Destination of the route that should be removed.
+ @type destination: C{str}.
+ @param xs: XML Stream to remove the route for.
+ @type xs: L{EventDispatcher<utility.EventDispatcher>}.
+ """
+ xs.removeObserver("/*", self.route)
+ if xs == self.routes[destination]:
+ del self.routes[destination]
+
+ def route(self, stanza):
+ """
+ Route a stanza.
+
+ @param stanza: The stanza to be routed.
+ @type stanza: L{domish.Element}.
+ """
+ destination = JID(stanza["to"])
+
+ log.msg(f"Routing to {destination.full()}: {stanza.toXml()!r}")
+
+ if destination.host in self.routes:
+ self.routes[destination.host].send(stanza)
+ else:
+ self.routes[None].send(stanza)
+
+
+class XMPPComponentServerFactory(xmlstream.XmlStreamServerFactory):
+ """
+ XMPP Component Server factory.
+
+ This factory accepts XMPP external component connections and makes
+ the router service route traffic for a component's bound domain
+ to that component.
+
+ @since: 8.2
+ """
+
+ logTraffic = False
+
+ def __init__(self, router, secret="secret"):
+ self.router = router
+ self.secret = secret
+
+ def authenticatorFactory():
+ return ListenComponentAuthenticator(self.secret)
+
+ xmlstream.XmlStreamServerFactory.__init__(self, authenticatorFactory)
+ self.addBootstrap(xmlstream.STREAM_CONNECTED_EVENT, self.onConnectionMade)
+ self.addBootstrap(xmlstream.STREAM_AUTHD_EVENT, self.onAuthenticated)
+
+ self.serial = 0
+
+ def onConnectionMade(self, xs):
+ """
+ Called when a component connection was made.
+
+ This enables traffic debugging on incoming streams.
+ """
+ xs.serial = self.serial
+ self.serial += 1
+
+ def logDataIn(buf):
+ log.msg("RECV (%d): %r" % (xs.serial, buf))
+
+ def logDataOut(buf):
+ log.msg("SEND (%d): %r" % (xs.serial, buf))
+
+ if self.logTraffic:
+ xs.rawDataInFn = logDataIn
+ xs.rawDataOutFn = logDataOut
+
+ xs.addObserver(xmlstream.STREAM_ERROR_EVENT, self.onError)
+
+ def onAuthenticated(self, xs):
+ """
+ Called when a component has successfully authenticated.
+
+ Add the component to the routing table and establish a handler
+ for a closed connection.
+ """
+ destination = xs.thisEntity.host
+
+ self.router.addRoute(destination, xs)
+ xs.addObserver(
+ xmlstream.STREAM_END_EVENT, self.onConnectionLost, 0, destination, xs
+ )
+
+ def onError(self, reason):
+ log.err(reason, "Stream Error")
+
+ def onConnectionLost(self, destination, xs, reason):
+ self.router.removeRoute(destination, xs)
diff --git a/contrib/python/Twisted/py3/twisted/words/protocols/jabber/error.py b/contrib/python/Twisted/py3/twisted/words/protocols/jabber/error.py
new file mode 100644
index 0000000000..4d1644767d
--- /dev/null
+++ b/contrib/python/Twisted/py3/twisted/words/protocols/jabber/error.py
@@ -0,0 +1,323 @@
+# -*- test-case-name: twisted.words.test.test_jabbererror -*-
+#
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+XMPP Error support.
+"""
+
+
+import copy
+from typing import Optional
+
+from twisted.words.xish import domish
+
+NS_XML = "http://www.w3.org/XML/1998/namespace"
+NS_XMPP_STREAMS = "urn:ietf:params:xml:ns:xmpp-streams"
+NS_XMPP_STANZAS = "urn:ietf:params:xml:ns:xmpp-stanzas"
+
+STANZA_CONDITIONS = {
+ "bad-request": {"code": "400", "type": "modify"},
+ "conflict": {"code": "409", "type": "cancel"},
+ "feature-not-implemented": {"code": "501", "type": "cancel"},
+ "forbidden": {"code": "403", "type": "auth"},
+ "gone": {"code": "302", "type": "modify"},
+ "internal-server-error": {"code": "500", "type": "wait"},
+ "item-not-found": {"code": "404", "type": "cancel"},
+ "jid-malformed": {"code": "400", "type": "modify"},
+ "not-acceptable": {"code": "406", "type": "modify"},
+ "not-allowed": {"code": "405", "type": "cancel"},
+ "not-authorized": {"code": "401", "type": "auth"},
+ "payment-required": {"code": "402", "type": "auth"},
+ "recipient-unavailable": {"code": "404", "type": "wait"},
+ "redirect": {"code": "302", "type": "modify"},
+ "registration-required": {"code": "407", "type": "auth"},
+ "remote-server-not-found": {"code": "404", "type": "cancel"},
+ "remote-server-timeout": {"code": "504", "type": "wait"},
+ "resource-constraint": {"code": "500", "type": "wait"},
+ "service-unavailable": {"code": "503", "type": "cancel"},
+ "subscription-required": {"code": "407", "type": "auth"},
+ "undefined-condition": {"code": "500", "type": None},
+ "unexpected-request": {"code": "400", "type": "wait"},
+}
+
+CODES_TO_CONDITIONS = {
+ "302": ("gone", "modify"),
+ "400": ("bad-request", "modify"),
+ "401": ("not-authorized", "auth"),
+ "402": ("payment-required", "auth"),
+ "403": ("forbidden", "auth"),
+ "404": ("item-not-found", "cancel"),
+ "405": ("not-allowed", "cancel"),
+ "406": ("not-acceptable", "modify"),
+ "407": ("registration-required", "auth"),
+ "408": ("remote-server-timeout", "wait"),
+ "409": ("conflict", "cancel"),
+ "500": ("internal-server-error", "wait"),
+ "501": ("feature-not-implemented", "cancel"),
+ "502": ("service-unavailable", "wait"),
+ "503": ("service-unavailable", "cancel"),
+ "504": ("remote-server-timeout", "wait"),
+ "510": ("service-unavailable", "cancel"),
+}
+
+
+class BaseError(Exception):
+ """
+ Base class for XMPP error exceptions.
+
+ @cvar namespace: The namespace of the C{error} element generated by
+ C{getElement}.
+ @type namespace: C{str}
+ @ivar condition: The error condition. The valid values are defined by
+ subclasses of L{BaseError}.
+ @type contition: C{str}
+ @ivar text: Optional text message to supplement the condition or application
+ specific condition.
+ @type text: C{unicode}
+ @ivar textLang: Identifier of the language used for the message in C{text}.
+ Values are as described in RFC 3066.
+ @type textLang: C{str}
+ @ivar appCondition: Application specific condition element, supplementing
+ the error condition in C{condition}.
+ @type appCondition: object providing L{domish.IElement}.
+ """
+
+ namespace: Optional[str] = None
+
+ def __init__(self, condition, text=None, textLang=None, appCondition=None):
+ Exception.__init__(self)
+ self.condition = condition
+ self.text = text
+ self.textLang = textLang
+ self.appCondition = appCondition
+
+ def __str__(self) -> str:
+ message = "{} with condition {!r}".format(
+ self.__class__.__name__, self.condition
+ )
+
+ if self.text:
+ message += ": " + self.text
+
+ return message
+
+ def getElement(self):
+ """
+ Get XML representation from self.
+
+ The method creates an L{domish} representation of the
+ error data contained in this exception.
+
+ @rtype: L{domish.Element}
+ """
+ error = domish.Element((None, "error"))
+ error.addElement((self.namespace, self.condition))
+ if self.text:
+ text = error.addElement((self.namespace, "text"), content=self.text)
+ if self.textLang:
+ text[(NS_XML, "lang")] = self.textLang
+ if self.appCondition:
+ error.addChild(self.appCondition)
+ return error
+
+
+class StreamError(BaseError):
+ """
+ Stream Error exception.
+
+ Refer to RFC 3920, section 4.7.3, for the allowed values for C{condition}.
+ """
+
+ namespace = NS_XMPP_STREAMS
+
+ def getElement(self):
+ """
+ Get XML representation from self.
+
+ Overrides the base L{BaseError.getElement} to make sure the returned
+ element is in the XML Stream namespace.
+
+ @rtype: L{domish.Element}
+ """
+ from twisted.words.protocols.jabber.xmlstream import NS_STREAMS
+
+ error = BaseError.getElement(self)
+ error.uri = NS_STREAMS
+ return error
+
+
+class StanzaError(BaseError):
+ """
+ Stanza Error exception.
+
+ Refer to RFC 3920, section 9.3, for the allowed values for C{condition} and
+ C{type}.
+
+ @ivar type: The stanza error type. Gives a suggestion to the recipient
+ of the error on how to proceed.
+ @type type: C{str}
+ @ivar code: A numeric identifier for the error condition for backwards
+ compatibility with pre-XMPP Jabber implementations.
+ """
+
+ namespace = NS_XMPP_STANZAS
+
+ def __init__(
+ self, condition, type=None, text=None, textLang=None, appCondition=None
+ ):
+ BaseError.__init__(self, condition, text, textLang, appCondition)
+
+ if type is None:
+ try:
+ type = STANZA_CONDITIONS[condition]["type"]
+ except KeyError:
+ pass
+ self.type = type
+
+ try:
+ self.code = STANZA_CONDITIONS[condition]["code"]
+ except KeyError:
+ self.code = None
+
+ self.children = []
+ self.iq = None
+
+ def getElement(self):
+ """
+ Get XML representation from self.
+
+ Overrides the base L{BaseError.getElement} to make sure the returned
+ element has a C{type} attribute and optionally a legacy C{code}
+ attribute.
+
+ @rtype: L{domish.Element}
+ """
+ error = BaseError.getElement(self)
+ error["type"] = self.type
+ if self.code:
+ error["code"] = self.code
+ return error
+
+ def toResponse(self, stanza):
+ """
+ Construct error response stanza.
+
+ The C{stanza} is transformed into an error response stanza by
+ swapping the C{to} and C{from} addresses and inserting an error
+ element.
+
+ @note: This creates a shallow copy of the list of child elements of the
+ stanza. The child elements themselves are not copied themselves,
+ and references to their parent element will still point to the
+ original stanza element.
+
+ The serialization of an element does not use the reference to
+ its parent, so the typical use case of immediately sending out
+ the constructed error response is not affected.
+
+ @param stanza: the stanza to respond to
+ @type stanza: L{domish.Element}
+ """
+ from twisted.words.protocols.jabber.xmlstream import toResponse
+
+ response = toResponse(stanza, stanzaType="error")
+ response.children = copy.copy(stanza.children)
+ response.addChild(self.getElement())
+ return response
+
+
+def _parseError(error, errorNamespace):
+ """
+ Parses an error element.
+
+ @param error: The error element to be parsed
+ @type error: L{domish.Element}
+ @param errorNamespace: The namespace of the elements that hold the error
+ condition and text.
+ @type errorNamespace: C{str}
+ @return: Dictionary with extracted error information. If present, keys
+ C{condition}, C{text}, C{textLang} have a string value,
+ and C{appCondition} has an L{domish.Element} value.
+ @rtype: C{dict}
+ """
+ condition = None
+ text = None
+ textLang = None
+ appCondition = None
+
+ for element in error.elements():
+ if element.uri == errorNamespace:
+ if element.name == "text":
+ text = str(element)
+ textLang = element.getAttribute((NS_XML, "lang"))
+ else:
+ condition = element.name
+ else:
+ appCondition = element
+
+ return {
+ "condition": condition,
+ "text": text,
+ "textLang": textLang,
+ "appCondition": appCondition,
+ }
+
+
+def exceptionFromStreamError(element):
+ """
+ Build an exception object from a stream error.
+
+ @param element: the stream error
+ @type element: L{domish.Element}
+ @return: the generated exception object
+ @rtype: L{StreamError}
+ """
+ error = _parseError(element, NS_XMPP_STREAMS)
+
+ exception = StreamError(
+ error["condition"], error["text"], error["textLang"], error["appCondition"]
+ )
+
+ return exception
+
+
+def exceptionFromStanza(stanza):
+ """
+ Build an exception object from an error stanza.
+
+ @param stanza: the error stanza
+ @type stanza: L{domish.Element}
+ @return: the generated exception object
+ @rtype: L{StanzaError}
+ """
+ children = []
+ condition = text = textLang = appCondition = type = code = None
+
+ for element in stanza.elements():
+ if element.name == "error" and element.uri == stanza.uri:
+ code = element.getAttribute("code")
+ type = element.getAttribute("type")
+ error = _parseError(element, NS_XMPP_STANZAS)
+ condition = error["condition"]
+ text = error["text"]
+ textLang = error["textLang"]
+ appCondition = error["appCondition"]
+
+ if not condition and code:
+ condition, type = CODES_TO_CONDITIONS[code]
+ text = str(stanza.error)
+ else:
+ children.append(element)
+
+ if condition is None:
+ # TODO: raise exception instead?
+ return StanzaError(None)
+
+ exception = StanzaError(condition, type, text, textLang, appCondition)
+
+ exception.children = children
+ exception.stanza = stanza
+
+ return exception
diff --git a/contrib/python/Twisted/py3/twisted/words/protocols/jabber/ijabber.py b/contrib/python/Twisted/py3/twisted/words/protocols/jabber/ijabber.py
new file mode 100644
index 0000000000..5408a9ae6c
--- /dev/null
+++ b/contrib/python/Twisted/py3/twisted/words/protocols/jabber/ijabber.py
@@ -0,0 +1,188 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Public Jabber Interfaces.
+"""
+
+from zope.interface import Attribute, Interface
+
+
+class IInitializer(Interface):
+ """
+ Interface for XML stream initializers.
+
+ Initializers perform a step in getting the XML stream ready to be
+ used for the exchange of XML stanzas.
+ """
+
+
+class IInitiatingInitializer(IInitializer):
+ """
+ Interface for XML stream initializers for the initiating entity.
+ """
+
+ xmlstream = Attribute("""The associated XML stream""")
+
+ def initialize():
+ """
+ Initiate the initialization step.
+
+ May return a deferred when the initialization is done asynchronously.
+ """
+
+
+class IIQResponseTracker(Interface):
+ """
+ IQ response tracker interface.
+
+ The XMPP stanza C{iq} has a request-response nature that fits
+ naturally with deferreds. You send out a request and when the response
+ comes back a deferred is fired.
+
+ The L{twisted.words.protocols.jabber.client.IQ} class implements a C{send}
+ method that returns a deferred. This deferred is put in a dictionary that
+ is kept in an L{XmlStream} object, keyed by the request stanzas C{id}
+ attribute.
+
+ An object providing this interface (usually an instance of L{XmlStream}),
+ keeps the said dictionary and sets observers on the iq stanzas of type
+ C{result} and C{error} and lets the callback fire the associated deferred.
+ """
+
+ iqDeferreds = Attribute("Dictionary of deferreds waiting for an iq " "response")
+
+
+class IXMPPHandler(Interface):
+ """
+ Interface for XMPP protocol handlers.
+
+ Objects that provide this interface can be added to a stream manager to
+ handle of (part of) an XMPP extension protocol.
+ """
+
+ parent = Attribute("""XML stream manager for this handler""")
+ xmlstream = Attribute("""The managed XML stream""")
+
+ def setHandlerParent(parent):
+ """
+ Set the parent of the handler.
+
+ @type parent: L{IXMPPHandlerCollection}
+ """
+
+ def disownHandlerParent(parent):
+ """
+ Remove the parent of the handler.
+
+ @type parent: L{IXMPPHandlerCollection}
+ """
+
+ def makeConnection(xs):
+ """
+ A connection over the underlying transport of the XML stream has been
+ established.
+
+ At this point, no traffic has been exchanged over the XML stream
+ given in C{xs}.
+
+ This should setup L{xmlstream} and call L{connectionMade}.
+
+ @type xs:
+ L{twisted.words.protocols.jabber.xmlstream.XmlStream}
+ """
+
+ def connectionMade():
+ """
+ Called after a connection has been established.
+
+ This method can be used to change properties of the XML Stream, its
+ authenticator or the stream manager prior to stream initialization
+ (including authentication).
+ """
+
+ def connectionInitialized():
+ """
+ The XML stream has been initialized.
+
+ At this point, authentication was successful, and XML stanzas can be
+ exchanged over the XML stream L{xmlstream}. This method can be
+ used to setup observers for incoming stanzas.
+ """
+
+ def connectionLost(reason):
+ """
+ The XML stream has been closed.
+
+ Subsequent use of C{parent.send} will result in data being queued
+ until a new connection has been established.
+
+ @type reason: L{twisted.python.failure.Failure}
+ """
+
+
+class IXMPPHandlerCollection(Interface):
+ """
+ Collection of handlers.
+
+ Contain several handlers and manage their connection.
+ """
+
+ def __iter__():
+ """
+ Get an iterator over all child handlers.
+ """
+
+ def addHandler(handler):
+ """
+ Add a child handler.
+
+ @type handler: L{IXMPPHandler}
+ """
+
+ def removeHandler(handler):
+ """
+ Remove a child handler.
+
+ @type handler: L{IXMPPHandler}
+ """
+
+
+class IService(Interface):
+ """
+ External server-side component service interface.
+
+ Services that provide this interface can be added to L{ServiceManager} to
+ implement (part of) the functionality of the server-side component.
+ """
+
+ def componentConnected(xs):
+ """
+ Parent component has established a connection.
+
+ At this point, authentication was successful, and XML stanzas
+ can be exchanged over the XML stream C{xs}. This method can be used
+ to setup observers for incoming stanzas.
+
+ @param xs: XML Stream that represents the established connection.
+ @type xs: L{xmlstream.XmlStream}
+ """
+
+ def componentDisconnected():
+ """
+ Parent component has lost the connection to the Jabber server.
+
+ Subsequent use of C{self.parent.send} will result in data being
+ queued until a new connection has been established.
+ """
+
+ def transportConnected(xs):
+ """
+ Parent component has established a connection over the underlying
+ transport.
+
+ At this point, no traffic has been exchanged over the XML stream. This
+ method can be used to change properties of the XML Stream (in C{xs}),
+ the service manager or it's authenticator prior to stream
+ initialization (including authentication).
+ """
diff --git a/contrib/python/Twisted/py3/twisted/words/protocols/jabber/jid.py b/contrib/python/Twisted/py3/twisted/words/protocols/jabber/jid.py
new file mode 100644
index 0000000000..52e154fee4
--- /dev/null
+++ b/contrib/python/Twisted/py3/twisted/words/protocols/jabber/jid.py
@@ -0,0 +1,259 @@
+# -*- test-case-name: twisted.words.test.test_jabberjid -*-
+#
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Jabber Identifier support.
+
+This module provides an object to represent Jabber Identifiers (JIDs) and
+parse string representations into them with proper checking for illegal
+characters, case folding and canonicalisation through
+L{stringprep<twisted.words.protocols.jabber.xmpp_stringprep>}.
+"""
+
+from typing import Dict, Tuple, Union
+
+from twisted.words.protocols.jabber.xmpp_stringprep import (
+ nameprep,
+ nodeprep,
+ resourceprep,
+)
+
+
+class InvalidFormat(Exception):
+ """
+ The given string could not be parsed into a valid Jabber Identifier (JID).
+ """
+
+
+def parse(jidstring: str) -> Tuple[Union[str, None], str, Union[str, None]]:
+ """
+ Parse given JID string into its respective parts and apply stringprep.
+
+ @param jidstring: string representation of a JID.
+ @type jidstring: L{str}
+ @return: tuple of (user, host, resource), each of type L{str} as
+ the parsed and stringprep'd parts of the given JID. If the
+ given string did not have a user or resource part, the respective
+ field in the tuple will hold L{None}.
+ @rtype: L{tuple}
+ """
+ user = None
+ host = None
+ resource = None
+
+ # Search for delimiters
+ user_sep = jidstring.find("@")
+ res_sep = jidstring.find("/")
+
+ if user_sep == -1:
+ if res_sep == -1:
+ # host
+ host = jidstring
+ else:
+ # host/resource
+ host = jidstring[0:res_sep]
+ resource = jidstring[res_sep + 1 :] or None
+ else:
+ if res_sep == -1:
+ # user@host
+ user = jidstring[0:user_sep] or None
+ host = jidstring[user_sep + 1 :]
+ else:
+ if user_sep < res_sep:
+ # user@host/resource
+ user = jidstring[0:user_sep] or None
+ host = jidstring[user_sep + 1 : user_sep + (res_sep - user_sep)]
+ resource = jidstring[res_sep + 1 :] or None
+ else:
+ # host/resource (with an @ in resource)
+ host = jidstring[0:res_sep]
+ resource = jidstring[res_sep + 1 :] or None
+
+ return prep(user, host, resource)
+
+
+def prep(
+ user: Union[str, None], host: str, resource: Union[str, None]
+) -> Tuple[Union[str, None], str, Union[str, None]]:
+ """
+ Perform stringprep on all JID fragments.
+
+ @param user: The user part of the JID.
+ @type user: L{str}
+ @param host: The host part of the JID.
+ @type host: L{str}
+ @param resource: The resource part of the JID.
+ @type resource: L{str}
+ @return: The given parts with stringprep applied.
+ @rtype: L{tuple}
+ """
+
+ if user:
+ try:
+ user = nodeprep.prepare(str(user))
+ except UnicodeError:
+ raise InvalidFormat("Invalid character in username")
+ else:
+ user = None
+
+ if not host:
+ raise InvalidFormat("Server address required.")
+ else:
+ try:
+ host = nameprep.prepare(str(host))
+ except UnicodeError:
+ raise InvalidFormat("Invalid character in hostname")
+
+ if resource:
+ try:
+ resource = resourceprep.prepare(str(resource))
+ except UnicodeError:
+ raise InvalidFormat("Invalid character in resource")
+ else:
+ resource = None
+
+ return (user, host, resource)
+
+
+__internJIDs: Dict[str, "JID"] = {}
+
+
+def internJID(jidstring):
+ """
+ Return interned JID.
+
+ @rtype: L{JID}
+ """
+
+ if jidstring in __internJIDs:
+ return __internJIDs[jidstring]
+ else:
+ j = JID(jidstring)
+ __internJIDs[jidstring] = j
+ return j
+
+
+class JID:
+ """
+ Represents a stringprep'd Jabber ID.
+
+ JID objects are hashable so they can be used in sets and as keys in
+ dictionaries.
+ """
+
+ def __init__(
+ self,
+ str: Union[str, None] = None,
+ tuple: Union[Tuple[Union[str, None], str, Union[str, None]], None] = None,
+ ):
+ if str:
+ user, host, res = parse(str)
+ elif tuple:
+ user, host, res = prep(*tuple)
+ else:
+ raise RuntimeError(
+ "You must provide a value for either 'str' or 'tuple' arguments."
+ )
+
+ self.user = user
+ self.host = host
+ self.resource = res
+
+ def userhost(self):
+ """
+ Extract the bare JID as a unicode string.
+
+ A bare JID does not have a resource part, so this returns either
+ C{user@host} or just C{host}.
+
+ @rtype: L{str}
+ """
+ if self.user:
+ return f"{self.user}@{self.host}"
+ else:
+ return self.host
+
+ def userhostJID(self):
+ """
+ Extract the bare JID.
+
+ A bare JID does not have a resource part, so this returns a
+ L{JID} object representing either C{user@host} or just C{host}.
+
+ If the object this method is called upon doesn't have a resource
+ set, it will return itself. Otherwise, the bare JID object will
+ be created, interned using L{internJID}.
+
+ @rtype: L{JID}
+ """
+ if self.resource:
+ return internJID(self.userhost())
+ else:
+ return self
+
+ def full(self):
+ """
+ Return the string representation of this JID.
+
+ @rtype: L{str}
+ """
+ if self.user:
+ if self.resource:
+ return f"{self.user}@{self.host}/{self.resource}"
+ else:
+ return f"{self.user}@{self.host}"
+ else:
+ if self.resource:
+ return f"{self.host}/{self.resource}"
+ else:
+ return self.host
+
+ def __eq__(self, other: object) -> bool:
+ """
+ Equality comparison.
+
+ L{JID}s compare equal if their user, host and resource parts all
+ compare equal. When comparing against instances of other types, it
+ uses the default comparison.
+ """
+ if isinstance(other, JID):
+ return (
+ self.user == other.user
+ and self.host == other.host
+ and self.resource == other.resource
+ )
+ else:
+ return NotImplemented
+
+ def __hash__(self):
+ """
+ Calculate hash.
+
+ L{JID}s with identical constituent user, host and resource parts have
+ equal hash values. In combination with the comparison defined on JIDs,
+ this allows for using L{JID}s in sets and as dictionary keys.
+ """
+ return hash((self.user, self.host, self.resource))
+
+ def __unicode__(self):
+ """
+ Get unicode representation.
+
+ Return the string representation of this JID as a unicode string.
+ @see: L{full}
+ """
+
+ return self.full()
+
+ __str__ = __unicode__
+
+ def __repr__(self) -> str:
+ """
+ Get object representation.
+
+ Returns a string that would create a new JID object that compares equal
+ to this one.
+ """
+ return "JID(%r)" % self.full()
diff --git a/contrib/python/Twisted/py3/twisted/words/protocols/jabber/jstrports.py b/contrib/python/Twisted/py3/twisted/words/protocols/jabber/jstrports.py
new file mode 100644
index 0000000000..b564fe3512
--- /dev/null
+++ b/contrib/python/Twisted/py3/twisted/words/protocols/jabber/jstrports.py
@@ -0,0 +1,34 @@
+# -*- test-case-name: twisted.words.test -*-
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+""" A temporary placeholder for client-capable strports, until we
+sufficient use cases get identified """
+
+
+from twisted.internet.endpoints import _parse
+
+
+def _parseTCPSSL(factory, domain, port):
+ """For the moment, parse TCP or SSL connections the same"""
+ return (domain, int(port), factory), {}
+
+
+def _parseUNIX(factory, address):
+ return (address, factory), {}
+
+
+_funcs = {"tcp": _parseTCPSSL, "unix": _parseUNIX, "ssl": _parseTCPSSL}
+
+
+def parse(description, factory):
+ args, kw = _parse(description)
+ return (args[0].upper(),) + _funcs[args[0]](factory, *args[1:], **kw)
+
+
+def client(description, factory):
+ from twisted.application import internet
+
+ name, args, kw = parse(description, factory)
+ return getattr(internet, name + "Client")(*args, **kw)
diff --git a/contrib/python/Twisted/py3/twisted/words/protocols/jabber/sasl.py b/contrib/python/Twisted/py3/twisted/words/protocols/jabber/sasl.py
new file mode 100644
index 0000000000..8bcf1a534a
--- /dev/null
+++ b/contrib/python/Twisted/py3/twisted/words/protocols/jabber/sasl.py
@@ -0,0 +1,229 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+XMPP-specific SASL profile.
+"""
+
+
+import re
+from base64 import b64decode, b64encode
+
+from twisted.internet import defer
+from twisted.words.protocols.jabber import sasl_mechanisms, xmlstream
+from twisted.words.xish import domish
+
+NS_XMPP_SASL = "urn:ietf:params:xml:ns:xmpp-sasl"
+
+
+def get_mechanisms(xs):
+ """
+ Parse the SASL feature to extract the available mechanism names.
+ """
+ mechanisms = []
+ for element in xs.features[(NS_XMPP_SASL, "mechanisms")].elements():
+ if element.name == "mechanism":
+ mechanisms.append(str(element))
+
+ return mechanisms
+
+
+class SASLError(Exception):
+ """
+ SASL base exception.
+ """
+
+
+class SASLNoAcceptableMechanism(SASLError):
+ """
+ The server did not present an acceptable SASL mechanism.
+ """
+
+
+class SASLAuthError(SASLError):
+ """
+ SASL Authentication failed.
+ """
+
+ def __init__(self, condition=None):
+ self.condition = condition
+
+ def __str__(self) -> str:
+ return "SASLAuthError with condition %r" % self.condition
+
+
+class SASLIncorrectEncodingError(SASLError):
+ """
+ SASL base64 encoding was incorrect.
+
+ RFC 3920 specifies that any characters not in the base64 alphabet
+ and padding characters present elsewhere than at the end of the string
+ MUST be rejected. See also L{fromBase64}.
+
+ This exception is raised whenever the encoded string does not adhere
+ to these additional restrictions or when the decoding itself fails.
+
+ The recommended behaviour for so-called receiving entities (like servers in
+ client-to-server connections, see RFC 3920 for terminology) is to fail the
+ SASL negotiation with a C{'incorrect-encoding'} condition. For initiating
+ entities, one should assume the receiving entity to be either buggy or
+ malevolent. The stream should be terminated and reconnecting is not
+ advised.
+ """
+
+
+base64Pattern = re.compile("^[0-9A-Za-z+/]*[0-9A-Za-z+/=]{,2}$")
+
+
+def fromBase64(s):
+ """
+ Decode base64 encoded string.
+
+ This helper performs regular decoding of a base64 encoded string, but also
+ rejects any characters that are not in the base64 alphabet and padding
+ occurring elsewhere from the last or last two characters, as specified in
+ section 14.9 of RFC 3920. This safeguards against various attack vectors
+ among which the creation of a covert channel that "leaks" information.
+ """
+
+ if base64Pattern.match(s) is None:
+ raise SASLIncorrectEncodingError()
+
+ try:
+ return b64decode(s)
+ except Exception as e:
+ raise SASLIncorrectEncodingError(str(e))
+
+
+class SASLInitiatingInitializer(xmlstream.BaseFeatureInitiatingInitializer):
+ """
+ Stream initializer that performs SASL authentication.
+
+ The supported mechanisms by this initializer are C{DIGEST-MD5}, C{PLAIN}
+ and C{ANONYMOUS}. The C{ANONYMOUS} SASL mechanism is used when the JID, set
+ on the authenticator, does not have a localpart (username), requesting an
+ anonymous session where the username is generated by the server.
+ Otherwise, C{DIGEST-MD5} and C{PLAIN} are attempted, in that order.
+ """
+
+ feature = (NS_XMPP_SASL, "mechanisms")
+ _deferred = None
+
+ def setMechanism(self):
+ """
+ Select and setup authentication mechanism.
+
+ Uses the authenticator's C{jid} and C{password} attribute for the
+ authentication credentials. If no supported SASL mechanisms are
+ advertized by the receiving party, a failing deferred is returned with
+ a L{SASLNoAcceptableMechanism} exception.
+ """
+
+ jid = self.xmlstream.authenticator.jid
+ password = self.xmlstream.authenticator.password
+
+ mechanisms = get_mechanisms(self.xmlstream)
+ if jid.user is not None:
+ if "DIGEST-MD5" in mechanisms:
+ self.mechanism = sasl_mechanisms.DigestMD5(
+ "xmpp", jid.host, None, jid.user, password
+ )
+ elif "PLAIN" in mechanisms:
+ self.mechanism = sasl_mechanisms.Plain(None, jid.user, password)
+ else:
+ raise SASLNoAcceptableMechanism()
+ else:
+ if "ANONYMOUS" in mechanisms:
+ self.mechanism = sasl_mechanisms.Anonymous()
+ else:
+ raise SASLNoAcceptableMechanism()
+
+ def start(self):
+ """
+ Start SASL authentication exchange.
+ """
+
+ self.setMechanism()
+ self._deferred = defer.Deferred()
+ self.xmlstream.addObserver("/challenge", self.onChallenge)
+ self.xmlstream.addOnetimeObserver("/success", self.onSuccess)
+ self.xmlstream.addOnetimeObserver("/failure", self.onFailure)
+ self.sendAuth(self.mechanism.getInitialResponse())
+ return self._deferred
+
+ def sendAuth(self, data=None):
+ """
+ Initiate authentication protocol exchange.
+
+ If an initial client response is given in C{data}, it will be
+ sent along.
+
+ @param data: initial client response.
+ @type data: C{str} or L{None}.
+ """
+
+ auth = domish.Element((NS_XMPP_SASL, "auth"))
+ auth["mechanism"] = self.mechanism.name
+ if data is not None:
+ auth.addContent(b64encode(data).decode("ascii") or "=")
+ self.xmlstream.send(auth)
+
+ def sendResponse(self, data=b""):
+ """
+ Send response to a challenge.
+
+ @param data: client response.
+ @type data: L{bytes}.
+ """
+
+ response = domish.Element((NS_XMPP_SASL, "response"))
+ if data:
+ response.addContent(b64encode(data).decode("ascii"))
+ self.xmlstream.send(response)
+
+ def onChallenge(self, element):
+ """
+ Parse challenge and send response from the mechanism.
+
+ @param element: the challenge protocol element.
+ @type element: L{domish.Element}.
+ """
+
+ try:
+ challenge = fromBase64(str(element))
+ except SASLIncorrectEncodingError:
+ self._deferred.errback()
+ else:
+ self.sendResponse(self.mechanism.getResponse(challenge))
+
+ def onSuccess(self, success):
+ """
+ Clean up observers, reset the XML stream and send a new header.
+
+ @param success: the success protocol element. For now unused, but
+ could hold additional data.
+ @type success: L{domish.Element}
+ """
+
+ self.xmlstream.removeObserver("/challenge", self.onChallenge)
+ self.xmlstream.removeObserver("/failure", self.onFailure)
+ self.xmlstream.reset()
+ self.xmlstream.sendHeader()
+ self._deferred.callback(xmlstream.Reset)
+
+ def onFailure(self, failure):
+ """
+ Clean up observers, parse the failure and errback the deferred.
+
+ @param failure: the failure protocol element. Holds details on
+ the error condition.
+ @type failure: L{domish.Element}
+ """
+
+ self.xmlstream.removeObserver("/challenge", self.onChallenge)
+ self.xmlstream.removeObserver("/success", self.onSuccess)
+ try:
+ condition = failure.firstChildElement().name
+ except AttributeError:
+ condition = None
+ self._deferred.errback(SASLAuthError(condition))
diff --git a/contrib/python/Twisted/py3/twisted/words/protocols/jabber/sasl_mechanisms.py b/contrib/python/Twisted/py3/twisted/words/protocols/jabber/sasl_mechanisms.py
new file mode 100644
index 0000000000..8d9c8fabcc
--- /dev/null
+++ b/contrib/python/Twisted/py3/twisted/words/protocols/jabber/sasl_mechanisms.py
@@ -0,0 +1,307 @@
+# -*- test-case-name: twisted.words.test.test_jabbersaslmechanisms -*-
+#
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Protocol agnostic implementations of SASL authentication mechanisms.
+"""
+
+
+import binascii
+import os
+import random
+import time
+from hashlib import md5
+
+from zope.interface import Attribute, Interface, implementer
+
+from twisted.python.compat import networkString
+
+
+class ISASLMechanism(Interface):
+ name = Attribute("""Common name for the SASL Mechanism.""")
+
+ def getInitialResponse():
+ """
+ Get the initial client response, if defined for this mechanism.
+
+ @return: initial client response string.
+ @rtype: C{str}.
+ """
+
+ def getResponse(challenge):
+ """
+ Get the response to a server challenge.
+
+ @param challenge: server challenge.
+ @type challenge: C{str}.
+ @return: client response.
+ @rtype: C{str}.
+ """
+
+
+@implementer(ISASLMechanism)
+class Anonymous:
+ """
+ Implements the ANONYMOUS SASL authentication mechanism.
+
+ This mechanism is defined in RFC 2245.
+ """
+
+ name = "ANONYMOUS"
+
+ def getInitialResponse(self):
+ return None
+
+ def getResponse(self, challenge):
+ # ISASLMechanism.getResponse
+ pass
+
+
+@implementer(ISASLMechanism)
+class Plain:
+ """
+ Implements the PLAIN SASL authentication mechanism.
+
+ The PLAIN SASL authentication mechanism is defined in RFC 2595.
+ """
+
+ name = "PLAIN"
+
+ def __init__(self, authzid, authcid, password):
+ """
+ @param authzid: The authorization identity.
+ @type authzid: L{unicode}
+
+ @param authcid: The authentication identity.
+ @type authcid: L{unicode}
+
+ @param password: The plain-text password.
+ @type password: L{unicode}
+ """
+
+ self.authzid = authzid or ""
+ self.authcid = authcid or ""
+ self.password = password or ""
+
+ def getInitialResponse(self):
+ return (
+ self.authzid.encode("utf-8")
+ + b"\x00"
+ + self.authcid.encode("utf-8")
+ + b"\x00"
+ + self.password.encode("utf-8")
+ )
+
+ def getResponse(self, challenge):
+ # ISASLMechanism.getResponse
+ pass
+
+
+@implementer(ISASLMechanism)
+class DigestMD5:
+ """
+ Implements the DIGEST-MD5 SASL authentication mechanism.
+
+ The DIGEST-MD5 SASL authentication mechanism is defined in RFC 2831.
+ """
+
+ name = "DIGEST-MD5"
+
+ def __init__(self, serv_type, host, serv_name, username, password):
+ """
+ @param serv_type: An indication of what kind of server authentication
+ is being attempted against. For example, C{u"xmpp"}.
+ @type serv_type: C{unicode}
+
+ @param host: The authentication hostname. Also known as the realm.
+ This is used as a scope to help select the right credentials.
+ @type host: C{unicode}
+
+ @param serv_name: An additional identifier for the server.
+ @type serv_name: C{unicode}
+
+ @param username: The authentication username to use to respond to a
+ challenge.
+ @type username: C{unicode}
+
+ @param password: The authentication password to use to respond to a
+ challenge.
+ @type password: C{unicode}
+ """
+ self.username = username
+ self.password = password
+ self.defaultRealm = host
+
+ self.digest_uri = f"{serv_type}/{host}"
+ if serv_name is not None:
+ self.digest_uri += f"/{serv_name}"
+
+ def getInitialResponse(self):
+ return None
+
+ def getResponse(self, challenge):
+ directives = self._parse(challenge)
+
+ # Compat for implementations that do not send this along with
+ # a successful authentication.
+ if b"rspauth" in directives:
+ return b""
+
+ charset = directives[b"charset"].decode("ascii")
+
+ try:
+ realm = directives[b"realm"]
+ except KeyError:
+ realm = self.defaultRealm.encode(charset)
+
+ return self._genResponse(charset, realm, directives[b"nonce"])
+
+ def _parse(self, challenge):
+ """
+ Parses the server challenge.
+
+ Splits the challenge into a dictionary of directives with values.
+
+ @return: challenge directives and their values.
+ @rtype: C{dict} of C{str} to C{str}.
+ """
+ s = challenge
+ paramDict = {}
+ cur = 0
+ remainingParams = True
+ while remainingParams:
+ # Parse a param. We can't just split on commas, because there can
+ # be some commas inside (quoted) param values, e.g.:
+ # qop="auth,auth-int"
+
+ middle = s.index(b"=", cur)
+ name = s[cur:middle].lstrip()
+ middle += 1
+ if s[middle : middle + 1] == b'"':
+ middle += 1
+ end = s.index(b'"', middle)
+ value = s[middle:end]
+ cur = s.find(b",", end) + 1
+ if cur == 0:
+ remainingParams = False
+ else:
+ end = s.find(b",", middle)
+ if end == -1:
+ value = s[middle:].rstrip()
+ remainingParams = False
+ else:
+ value = s[middle:end].rstrip()
+ cur = end + 1
+ paramDict[name] = value
+
+ for param in (b"qop", b"cipher"):
+ if param in paramDict:
+ paramDict[param] = paramDict[param].split(b",")
+
+ return paramDict
+
+ def _unparse(self, directives):
+ """
+ Create message string from directives.
+
+ @param directives: dictionary of directives (names to their values).
+ For certain directives, extra quotes are added, as
+ needed.
+ @type directives: C{dict} of C{str} to C{str}
+ @return: message string.
+ @rtype: C{str}.
+ """
+
+ directive_list = []
+ for name, value in directives.items():
+ if name in (
+ b"username",
+ b"realm",
+ b"cnonce",
+ b"nonce",
+ b"digest-uri",
+ b"authzid",
+ b"cipher",
+ ):
+ directive = name + b"=" + value
+ else:
+ directive = name + b"=" + value
+
+ directive_list.append(directive)
+
+ return b",".join(directive_list)
+
+ def _calculateResponse(self, cnonce, nc, nonce, username, password, realm, uri):
+ """
+ Calculates response with given encoded parameters.
+
+ @return: The I{response} field of a response to a Digest-MD5 challenge
+ of the given parameters.
+ @rtype: L{bytes}
+ """
+
+ def H(s):
+ return md5(s).digest()
+
+ def HEX(n):
+ return binascii.b2a_hex(n)
+
+ def KD(k, s):
+ return H(k + b":" + s)
+
+ a1 = H(username + b":" + realm + b":" + password) + b":" + nonce + b":" + cnonce
+ a2 = b"AUTHENTICATE:" + uri
+
+ response = HEX(
+ KD(
+ HEX(H(a1)),
+ nonce + b":" + nc + b":" + cnonce + b":" + b"auth" + b":" + HEX(H(a2)),
+ )
+ )
+ return response
+
+ def _genResponse(self, charset, realm, nonce):
+ """
+ Generate response-value.
+
+ Creates a response to a challenge according to section 2.1.2.1 of
+ RFC 2831 using the C{charset}, C{realm} and C{nonce} directives
+ from the challenge.
+ """
+ try:
+ username = self.username.encode(charset)
+ password = self.password.encode(charset)
+ digest_uri = self.digest_uri.encode(charset)
+ except UnicodeError:
+ # TODO - add error checking
+ raise
+
+ nc = networkString(f"{1:08x}") # TODO: support subsequent auth.
+ cnonce = self._gen_nonce()
+ qop = b"auth"
+
+ # TODO - add support for authzid
+ response = self._calculateResponse(
+ cnonce, nc, nonce, username, password, realm, digest_uri
+ )
+
+ directives = {
+ b"username": username,
+ b"realm": realm,
+ b"nonce": nonce,
+ b"cnonce": cnonce,
+ b"nc": nc,
+ b"qop": qop,
+ b"digest-uri": digest_uri,
+ b"response": response,
+ b"charset": charset.encode("ascii"),
+ }
+
+ return self._unparse(directives)
+
+ def _gen_nonce(self):
+ nonceString = "%f:%f:%d" % (random.random(), time.time(), os.getpid())
+ nonceBytes = networkString(nonceString)
+ return md5(nonceBytes).hexdigest().encode("ascii")
diff --git a/contrib/python/Twisted/py3/twisted/words/protocols/jabber/xmlstream.py b/contrib/python/Twisted/py3/twisted/words/protocols/jabber/xmlstream.py
new file mode 100644
index 0000000000..601a879aa8
--- /dev/null
+++ b/contrib/python/Twisted/py3/twisted/words/protocols/jabber/xmlstream.py
@@ -0,0 +1,1145 @@
+# -*- test-case-name: twisted.words.test.test_jabberxmlstream -*-
+#
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+XMPP XML Streams
+
+Building blocks for setting up XML Streams, including helping classes for
+doing authentication on either client or server side, and working with XML
+Stanzas.
+
+@var STREAM_AUTHD_EVENT: Token dispatched by L{Authenticator} when the
+ stream has been completely initialized
+@type STREAM_AUTHD_EVENT: L{str}.
+
+@var INIT_FAILED_EVENT: Token dispatched by L{Authenticator} when the
+ stream has failed to be initialized
+@type INIT_FAILED_EVENT: L{str}.
+
+@var Reset: Token to signal that the XML stream has been reset.
+@type Reset: Basic object.
+"""
+
+
+from binascii import hexlify
+from hashlib import sha1
+from sys import intern
+from typing import Optional, Tuple
+
+from zope.interface import directlyProvides, implementer
+
+from twisted.internet import defer, protocol
+from twisted.internet.error import ConnectionLost
+from twisted.python import failure, log, randbytes
+from twisted.words.protocols.jabber import error, ijabber, jid
+from twisted.words.xish import domish, xmlstream
+from twisted.words.xish.xmlstream import (
+ STREAM_CONNECTED_EVENT,
+ STREAM_END_EVENT,
+ STREAM_ERROR_EVENT,
+ STREAM_START_EVENT,
+)
+
+try:
+ from twisted.internet import ssl as _ssl
+except ImportError:
+ ssl = None
+else:
+ if not _ssl.supported:
+ ssl = None
+ else:
+ ssl = _ssl
+
+STREAM_AUTHD_EVENT = intern("//event/stream/authd")
+INIT_FAILED_EVENT = intern("//event/xmpp/initfailed")
+
+NS_STREAMS = "http://etherx.jabber.org/streams"
+NS_XMPP_TLS = "urn:ietf:params:xml:ns:xmpp-tls"
+
+Reset = object()
+
+
+def hashPassword(sid, password):
+ """
+ Create a SHA1-digest string of a session identifier and password.
+
+ @param sid: The stream session identifier.
+ @type sid: C{unicode}.
+ @param password: The password to be hashed.
+ @type password: C{unicode}.
+ """
+ if not isinstance(sid, str):
+ raise TypeError("The session identifier must be a unicode object")
+ if not isinstance(password, str):
+ raise TypeError("The password must be a unicode object")
+ input = f"{sid}{password}"
+ return sha1(input.encode("utf-8")).hexdigest()
+
+
+class Authenticator:
+ """
+ Base class for business logic of initializing an XmlStream
+
+ Subclass this object to enable an XmlStream to initialize and 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 initialized.
+ 2. The Authenticator SHOULD reset all state information when
+ L{associateWithStream} is called.
+ 3. The Authenticator SHOULD override L{streamStarted}, and start
+ initialization there.
+
+ @type xmlstream: L{XmlStream}
+ @ivar xmlstream: The XmlStream that needs authentication
+
+ @note: the term authenticator is historical. Authenticators perform
+ all steps required to prepare the stream for the exchange
+ of XML stanzas.
+ """
+
+ def __init__(self):
+ 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() to send any initial data to
+ the peer.
+ """
+
+ def streamStarted(self, rootElement):
+ """
+ Called by the XmlStream when the stream has started.
+
+ A stream is considered to have started when the start tag of the root
+ element has been received.
+
+ This examines C{rootElement} to see if there is a version attribute.
+ If absent, C{0.0} is assumed per RFC 3920. Subsequently, the
+ minimum of the version from the received stream header and the
+ value stored in L{xmlstream} is taken and put back in L{xmlstream}.
+
+ Extensions of this method can extract more information from the
+ stream header and perform checks on them, optionally sending
+ stream errors and closing the stream.
+ """
+ if rootElement.hasAttribute("version"):
+ version = rootElement["version"].split(".")
+ try:
+ version = (int(version[0]), int(version[1]))
+ except (IndexError, ValueError):
+ version = (0, 0)
+ else:
+ version = (0, 0)
+
+ self.xmlstream.version = min(self.xmlstream.version, version)
+
+ 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: L{XmlStream}
+ @param xmlstream: The XmlStream that will be passing events to this
+ Authenticator.
+
+ """
+ self.xmlstream = xmlstream
+
+
+class ConnectAuthenticator(Authenticator):
+ """
+ Authenticator for initiating entities.
+ """
+
+ namespace: Optional[str] = None
+
+ def __init__(self, otherHost):
+ self.otherHost = otherHost
+
+ def connectionMade(self):
+ self.xmlstream.namespace = self.namespace
+ self.xmlstream.otherEntity = jid.internJID(self.otherHost)
+ self.xmlstream.sendHeader()
+
+ def initializeStream(self):
+ """
+ Perform stream initialization procedures.
+
+ An L{XmlStream} holds a list of initializer objects in its
+ C{initializers} attribute. This method calls these initializers in
+ order and dispatches the L{STREAM_AUTHD_EVENT} event when the list has
+ been successfully processed. Otherwise it dispatches the
+ C{INIT_FAILED_EVENT} event with the failure.
+
+ Initializers may return the special L{Reset} object to halt the
+ initialization processing. It signals that the current initializer was
+ successfully processed, but that the XML Stream has been reset. An
+ example is the TLSInitiatingInitializer.
+ """
+
+ def remove_first(result):
+ self.xmlstream.initializers.pop(0)
+
+ return result
+
+ def do_next(result):
+ """
+ Take the first initializer and process it.
+
+ On success, the initializer is removed from the list and
+ then next initializer will be tried.
+ """
+
+ if result is Reset:
+ return None
+
+ try:
+ init = self.xmlstream.initializers[0]
+ except IndexError:
+ self.xmlstream.dispatch(self.xmlstream, STREAM_AUTHD_EVENT)
+ return None
+ else:
+ d = defer.maybeDeferred(init.initialize)
+ d.addCallback(remove_first)
+ d.addCallback(do_next)
+ return d
+
+ d = defer.succeed(None)
+ d.addCallback(do_next)
+ d.addErrback(self.xmlstream.dispatch, INIT_FAILED_EVENT)
+
+ def streamStarted(self, rootElement):
+ """
+ Called by the XmlStream when the stream has started.
+
+ This extends L{Authenticator.streamStarted} to extract further stream
+ headers from C{rootElement}, optionally wait for stream features being
+ received and then call C{initializeStream}.
+ """
+
+ Authenticator.streamStarted(self, rootElement)
+
+ self.xmlstream.sid = rootElement.getAttribute("id")
+
+ if rootElement.hasAttribute("from"):
+ self.xmlstream.otherEntity = jid.internJID(rootElement["from"])
+
+ # Setup observer for stream features, if applicable
+ if self.xmlstream.version >= (1, 0):
+
+ def onFeatures(element):
+ features = {}
+ for feature in element.elements():
+ features[(feature.uri, feature.name)] = feature
+
+ self.xmlstream.features = features
+ self.initializeStream()
+
+ self.xmlstream.addOnetimeObserver(
+ '/features[@xmlns="%s"]' % NS_STREAMS, onFeatures
+ )
+ else:
+ self.initializeStream()
+
+
+class ListenAuthenticator(Authenticator):
+ """
+ Authenticator for receiving entities.
+ """
+
+ namespace: Optional[str] = None
+
+ def associateWithStream(self, xmlstream):
+ """
+ Called by the XmlStreamFactory when a connection has been made.
+
+ Extend L{Authenticator.associateWithStream} to set the L{XmlStream}
+ to be non-initiating.
+ """
+ Authenticator.associateWithStream(self, xmlstream)
+ self.xmlstream.initiating = False
+
+ def streamStarted(self, rootElement):
+ """
+ Called by the XmlStream when the stream has started.
+
+ This extends L{Authenticator.streamStarted} to extract further
+ information from the stream headers from C{rootElement}.
+ """
+ Authenticator.streamStarted(self, rootElement)
+
+ self.xmlstream.namespace = rootElement.defaultUri
+
+ if rootElement.hasAttribute("to"):
+ self.xmlstream.thisEntity = jid.internJID(rootElement["to"])
+
+ self.xmlstream.prefixes = {}
+ for prefix, uri in rootElement.localPrefixes.items():
+ self.xmlstream.prefixes[uri] = prefix
+
+ self.xmlstream.sid = hexlify(randbytes.secureRandom(8)).decode("ascii")
+
+
+class FeatureNotAdvertized(Exception):
+ """
+ Exception indicating a stream feature was not advertized, while required by
+ the initiating entity.
+ """
+
+
+@implementer(ijabber.IInitiatingInitializer)
+class BaseFeatureInitiatingInitializer:
+ """
+ Base class for initializers with a stream feature.
+
+ This assumes the associated XmlStream represents the initiating entity
+ of the connection.
+
+ @cvar feature: tuple of (uri, name) of the stream feature root element.
+ @type feature: tuple of (C{str}, C{str})
+
+ @ivar required: whether the stream feature is required to be advertized
+ by the receiving entity.
+ @type required: C{bool}
+ """
+
+ feature: Optional[Tuple[str, str]] = None
+
+ def __init__(self, xs, required=False):
+ self.xmlstream = xs
+ self.required = required
+
+ def initialize(self):
+ """
+ Initiate the initialization.
+
+ Checks if the receiving entity advertizes the stream feature. If it
+ does, the initialization is started. If it is not advertized, and the
+ C{required} instance variable is C{True}, it raises
+ L{FeatureNotAdvertized}. Otherwise, the initialization silently
+ succeeds.
+ """
+
+ if self.feature in self.xmlstream.features:
+ return self.start()
+ elif self.required:
+ raise FeatureNotAdvertized
+ else:
+ return None
+
+ def start(self):
+ """
+ Start the actual initialization.
+
+ May return a deferred for asynchronous initialization.
+ """
+
+
+class TLSError(Exception):
+ """
+ TLS base exception.
+ """
+
+
+class TLSFailed(TLSError):
+ """
+ Exception indicating failed TLS negotiation
+ """
+
+
+class TLSRequired(TLSError):
+ """
+ Exception indicating required TLS negotiation.
+
+ This exception is raised when the receiving entity requires TLS
+ negotiation and the initiating does not desire to negotiate TLS.
+ """
+
+
+class TLSNotSupported(TLSError):
+ """
+ Exception indicating missing TLS support.
+
+ This exception is raised when the initiating entity wants and requires to
+ negotiate TLS when the OpenSSL library is not available.
+ """
+
+
+class TLSInitiatingInitializer(BaseFeatureInitiatingInitializer):
+ """
+ TLS stream initializer for the initiating entity.
+
+ It is strongly required to include this initializer in the list of
+ initializers for an XMPP stream. By default it will try to negotiate TLS.
+ An XMPP server may indicate that TLS is required. If TLS is not desired,
+ set the C{wanted} attribute to False instead of removing it from the list
+ of initializers, so a proper exception L{TLSRequired} can be raised.
+
+ @ivar wanted: indicates if TLS negotiation is wanted.
+ @type wanted: C{bool}
+ """
+
+ feature = (NS_XMPP_TLS, "starttls")
+ wanted = True
+ _deferred = None
+ _configurationForTLS = None
+
+ def __init__(self, xs, required=True, configurationForTLS=None):
+ """
+ @param configurationForTLS: An object which creates appropriately
+ configured TLS connections. This is passed to C{startTLS} on the
+ transport and is preferably created using
+ L{twisted.internet.ssl.optionsForClientTLS}. If C{None}, the
+ default is to verify the server certificate against the trust roots
+ as provided by the platform. See
+ L{twisted.internet._sslverify.platformTrust}.
+ @type configurationForTLS: L{IOpenSSLClientConnectionCreator} or
+ C{None}
+ """
+ super().__init__(xs, required=required)
+ self._configurationForTLS = configurationForTLS
+
+ def onProceed(self, obj):
+ """
+ Proceed with TLS negotiation and reset the XML stream.
+ """
+
+ self.xmlstream.removeObserver("/failure", self.onFailure)
+ if self._configurationForTLS:
+ ctx = self._configurationForTLS
+ else:
+ ctx = ssl.optionsForClientTLS(self.xmlstream.otherEntity.host)
+ self.xmlstream.transport.startTLS(ctx)
+ self.xmlstream.reset()
+ self.xmlstream.sendHeader()
+ self._deferred.callback(Reset)
+
+ def onFailure(self, obj):
+ self.xmlstream.removeObserver("/proceed", self.onProceed)
+ self._deferred.errback(TLSFailed())
+
+ def start(self):
+ """
+ Start TLS negotiation.
+
+ This checks if the receiving entity requires TLS, the SSL library is
+ available and uses the C{required} and C{wanted} instance variables to
+ determine what to do in the various different cases.
+
+ For example, if the SSL library is not available, and wanted and
+ required by the user, it raises an exception. However if it is not
+ required by both parties, initialization silently succeeds, moving
+ on to the next step.
+ """
+ if self.wanted:
+ if ssl is None:
+ if self.required:
+ return defer.fail(TLSNotSupported())
+ else:
+ return defer.succeed(None)
+ else:
+ pass
+ elif self.xmlstream.features[self.feature].required:
+ return defer.fail(TLSRequired())
+ else:
+ return defer.succeed(None)
+
+ self._deferred = defer.Deferred()
+ self.xmlstream.addOnetimeObserver("/proceed", self.onProceed)
+ self.xmlstream.addOnetimeObserver("/failure", self.onFailure)
+ self.xmlstream.send(domish.Element((NS_XMPP_TLS, "starttls")))
+ return self._deferred
+
+
+class XmlStream(xmlstream.XmlStream):
+ """
+ XMPP XML Stream protocol handler.
+
+ @ivar version: XML stream version as a tuple (major, minor). Initially,
+ this is set to the minimally supported version. Upon
+ receiving the stream header of the peer, it is set to the
+ minimum of that value and the version on the received
+ header.
+ @type version: (C{int}, C{int})
+ @ivar namespace: default namespace URI for stream
+ @type namespace: C{unicode}
+ @ivar thisEntity: JID of this entity
+ @type thisEntity: L{JID}
+ @ivar otherEntity: JID of the peer entity
+ @type otherEntity: L{JID}
+ @ivar sid: session identifier
+ @type sid: C{unicode}
+ @ivar initiating: True if this is the initiating stream
+ @type initiating: C{bool}
+ @ivar features: map of (uri, name) to stream features element received from
+ the receiving entity.
+ @type features: C{dict} of (C{unicode}, C{unicode}) to L{domish.Element}.
+ @ivar prefixes: map of URI to prefixes that are to appear on stream
+ header.
+ @type prefixes: C{dict} of C{unicode} to C{unicode}
+ @ivar initializers: list of stream initializer objects
+ @type initializers: C{list} of objects that provide L{IInitializer}
+ @ivar authenticator: associated authenticator that uses C{initializers} to
+ initialize the XML stream.
+ """
+
+ version = (1, 0)
+ namespace = "invalid"
+ thisEntity = None
+ otherEntity = None
+ sid = None
+ initiating = True
+
+ _headerSent = False # True if the stream header has been sent
+
+ def __init__(self, authenticator):
+ xmlstream.XmlStream.__init__(self)
+
+ self.prefixes = {NS_STREAMS: "stream"}
+ self.authenticator = authenticator
+ self.initializers = []
+ self.features = {}
+
+ # Reset the authenticator
+ authenticator.associateWithStream(self)
+
+ def _callLater(self, *args, **kwargs):
+ from twisted.internet import reactor
+
+ return reactor.callLater(*args, **kwargs)
+
+ def reset(self):
+ """
+ Reset XML Stream.
+
+ Resets the XML Parser for incoming data. This is to be used after
+ successfully negotiating a new layer, e.g. TLS and SASL. Note that
+ registered event observers will continue to be in place.
+ """
+ self._headerSent = False
+ self._initializeStream()
+
+ def onStreamError(self, errelem):
+ """
+ Called when a stream:error element has been received.
+
+ Dispatches a L{STREAM_ERROR_EVENT} event with the error element to
+ allow for cleanup actions and drops the connection.
+
+ @param errelem: The received error element.
+ @type errelem: L{domish.Element}
+ """
+ self.dispatch(
+ failure.Failure(error.exceptionFromStreamError(errelem)), STREAM_ERROR_EVENT
+ )
+ self.transport.loseConnection()
+
+ def sendHeader(self):
+ """
+ Send stream header.
+ """
+ # set up optional extra namespaces
+ localPrefixes = {}
+ for uri, prefix in self.prefixes.items():
+ if uri != NS_STREAMS:
+ localPrefixes[prefix] = uri
+
+ rootElement = domish.Element(
+ (NS_STREAMS, "stream"), self.namespace, localPrefixes=localPrefixes
+ )
+
+ if self.otherEntity:
+ rootElement["to"] = self.otherEntity.userhost()
+
+ if self.thisEntity:
+ rootElement["from"] = self.thisEntity.userhost()
+
+ if not self.initiating and self.sid:
+ rootElement["id"] = self.sid
+
+ if self.version >= (1, 0):
+ rootElement["version"] = "%d.%d" % self.version
+
+ self.send(rootElement.toXml(prefixes=self.prefixes, closeElement=0))
+ self._headerSent = True
+
+ def sendFooter(self):
+ """
+ Send stream footer.
+ """
+ self.send("</stream:stream>")
+
+ def sendStreamError(self, streamError):
+ """
+ Send stream level error.
+
+ If we are the receiving entity, and haven't sent the header yet,
+ we sent one first.
+
+ After sending the stream error, the stream is closed and the transport
+ connection dropped.
+
+ @param streamError: stream error instance
+ @type streamError: L{error.StreamError}
+ """
+ if not self._headerSent and not self.initiating:
+ self.sendHeader()
+
+ if self._headerSent:
+ self.send(streamError.getElement())
+ self.sendFooter()
+
+ self.transport.loseConnection()
+
+ def send(self, obj):
+ """
+ Send data over the stream.
+
+ This overrides L{xmlstream.XmlStream.send} to use the default namespace
+ of the stream header when serializing L{domish.IElement}s. It is
+ assumed that if you pass an object that provides L{domish.IElement},
+ it represents a direct child of the stream's root element.
+ """
+ if domish.IElement.providedBy(obj):
+ obj = obj.toXml(
+ prefixes=self.prefixes,
+ defaultUri=self.namespace,
+ prefixesInScope=list(self.prefixes.values()),
+ )
+
+ xmlstream.XmlStream.send(self, obj)
+
+ def connectionMade(self):
+ """
+ Called when a connection is made.
+
+ Notifies the authenticator when a connection has been made.
+ """
+ xmlstream.XmlStream.connectionMade(self)
+ self.authenticator.connectionMade()
+
+ def onDocumentStart(self, rootElement):
+ """
+ Called when the stream header has been received.
+
+ Extracts the header's C{id} and C{version} attributes from the root
+ element. The C{id} attribute is stored in our C{sid} attribute and the
+ C{version} attribute is parsed and the minimum of the version we sent
+ and the parsed C{version} attribute is stored as a tuple (major, minor)
+ in this class' C{version} attribute. If no C{version} attribute was
+ present, we assume version 0.0.
+
+ If appropriate (we are the initiating stream and the minimum of our and
+ the other party's version is at least 1.0), a one-time observer is
+ registered for getting the stream features. The registered function is
+ C{onFeatures}.
+
+ Ultimately, the authenticator's C{streamStarted} method will be called.
+
+ @param rootElement: The root element.
+ @type rootElement: L{domish.Element}
+ """
+ xmlstream.XmlStream.onDocumentStart(self, rootElement)
+
+ # Setup observer for stream errors
+ self.addOnetimeObserver("/error[@xmlns='%s']" % NS_STREAMS, self.onStreamError)
+
+ self.authenticator.streamStarted(rootElement)
+
+
+class XmlStreamFactory(xmlstream.XmlStreamFactory):
+ """
+ Factory for Jabber XmlStream objects as a reconnecting client.
+
+ Note that this differs from L{xmlstream.XmlStreamFactory} in that
+ it generates Jabber specific L{XmlStream} instances that have
+ authenticators.
+ """
+
+ protocol = XmlStream
+
+ def __init__(self, authenticator):
+ xmlstream.XmlStreamFactory.__init__(self, authenticator)
+ self.authenticator = authenticator
+
+
+class XmlStreamServerFactory(xmlstream.BootstrapMixin, protocol.ServerFactory):
+ """
+ Factory for Jabber XmlStream objects as a server.
+
+ @since: 8.2.
+ @ivar authenticatorFactory: Factory callable that takes no arguments, to
+ create a fresh authenticator to be associated
+ with the XmlStream.
+ """
+
+ # Type is wrong. See: https://twistedmatrix.com/trac/ticket/10007#ticket
+ protocol = XmlStream # type: ignore[assignment]
+
+ def __init__(self, authenticatorFactory):
+ xmlstream.BootstrapMixin.__init__(self)
+ self.authenticatorFactory = authenticatorFactory
+
+ def buildProtocol(self, addr):
+ """
+ Create an instance of XmlStream.
+
+ A new authenticator instance will be created and passed to the new
+ XmlStream. Registered bootstrap event observers are installed as well.
+ """
+ authenticator = self.authenticatorFactory()
+ xs = self.protocol(authenticator)
+ xs.factory = self
+ self.installBootstraps(xs)
+ return xs
+
+
+class TimeoutError(Exception):
+ """
+ Exception raised when no IQ response has been received before the
+ configured timeout.
+ """
+
+
+def upgradeWithIQResponseTracker(xs):
+ """
+ Enhances an XmlStream for iq response tracking.
+
+ This makes an L{XmlStream} object provide L{IIQResponseTracker}. When a
+ response is an error iq stanza, the deferred has its errback invoked with a
+ failure that holds a L{StanzaError<error.StanzaError>} that is
+ easier to examine.
+ """
+
+ def callback(iq):
+ """
+ Handle iq response by firing associated deferred.
+ """
+ if getattr(iq, "handled", False):
+ return
+
+ try:
+ d = xs.iqDeferreds[iq["id"]]
+ except KeyError:
+ pass
+ else:
+ del xs.iqDeferreds[iq["id"]]
+ iq.handled = True
+ if iq["type"] == "error":
+ d.errback(error.exceptionFromStanza(iq))
+ else:
+ d.callback(iq)
+
+ def disconnected(_):
+ """
+ Make sure deferreds do not linger on after disconnect.
+
+ This errbacks all deferreds of iq's for which no response has been
+ received with a L{ConnectionLost} failure. Otherwise, the deferreds
+ will never be fired.
+ """
+ iqDeferreds = xs.iqDeferreds
+ xs.iqDeferreds = {}
+ for d in iqDeferreds.values():
+ d.errback(ConnectionLost())
+
+ xs.iqDeferreds = {}
+ xs.iqDefaultTimeout = getattr(xs, "iqDefaultTimeout", None)
+ xs.addObserver(xmlstream.STREAM_END_EVENT, disconnected)
+ xs.addObserver('/iq[@type="result"]', callback)
+ xs.addObserver('/iq[@type="error"]', callback)
+ directlyProvides(xs, ijabber.IIQResponseTracker)
+
+
+class IQ(domish.Element):
+ """
+ Wrapper for an iq stanza.
+
+ Iq stanzas are used for communications with a request-response behaviour.
+ Each iq request is associated with an XML stream and has its own unique id
+ to be able to track the response.
+
+ @ivar timeout: if set, a timeout period after which the deferred returned
+ by C{send} will have its errback called with a
+ L{TimeoutError} failure.
+ @type timeout: C{float}
+ """
+
+ timeout = None
+
+ def __init__(self, xmlstream, stanzaType="set"):
+ """
+ @type xmlstream: L{xmlstream.XmlStream}
+ @param xmlstream: XmlStream to use for transmission of this IQ
+
+ @type stanzaType: C{str}
+ @param stanzaType: IQ type identifier ('get' or 'set')
+ """
+ domish.Element.__init__(self, (None, "iq"))
+ self.addUniqueId()
+ self["type"] = stanzaType
+ self._xmlstream = xmlstream
+
+ def send(self, to=None):
+ """
+ Send out this iq.
+
+ Returns a deferred that is fired when an iq response with the same id
+ is received. Result responses will be passed to the deferred callback.
+ Error responses will be transformed into a
+ L{StanzaError<error.StanzaError>} and result in the errback of the
+ deferred being invoked.
+
+ @rtype: L{defer.Deferred}
+ """
+ if to is not None:
+ self["to"] = to
+
+ if not ijabber.IIQResponseTracker.providedBy(self._xmlstream):
+ upgradeWithIQResponseTracker(self._xmlstream)
+
+ d = defer.Deferred()
+ self._xmlstream.iqDeferreds[self["id"]] = d
+
+ timeout = self.timeout or self._xmlstream.iqDefaultTimeout
+ if timeout is not None:
+
+ def onTimeout():
+ del self._xmlstream.iqDeferreds[self["id"]]
+ d.errback(TimeoutError("IQ timed out"))
+
+ call = self._xmlstream._callLater(timeout, onTimeout)
+
+ def cancelTimeout(result):
+ if call.active():
+ call.cancel()
+
+ return result
+
+ d.addBoth(cancelTimeout)
+
+ self._xmlstream.send(self)
+ return d
+
+
+def toResponse(stanza, stanzaType=None):
+ """
+ Create a response stanza from another stanza.
+
+ This takes the addressing and id attributes from a stanza to create a (new,
+ empty) response stanza. The addressing attributes are swapped and the id
+ copied. Optionally, the stanza type of the response can be specified.
+
+ @param stanza: the original stanza
+ @type stanza: L{domish.Element}
+ @param stanzaType: optional response stanza type
+ @type stanzaType: C{str}
+ @return: the response stanza.
+ @rtype: L{domish.Element}
+ """
+
+ toAddr = stanza.getAttribute("from")
+ fromAddr = stanza.getAttribute("to")
+ stanzaID = stanza.getAttribute("id")
+
+ response = domish.Element((None, stanza.name))
+ if toAddr:
+ response["to"] = toAddr
+ if fromAddr:
+ response["from"] = fromAddr
+ if stanzaID:
+ response["id"] = stanzaID
+ if stanzaType:
+ response["type"] = stanzaType
+
+ return response
+
+
+@implementer(ijabber.IXMPPHandler)
+class XMPPHandler:
+ """
+ XMPP protocol handler.
+
+ Classes derived from this class implement (part of) one or more XMPP
+ extension protocols, and are referred to as a subprotocol implementation.
+ """
+
+ def __init__(self):
+ self.parent = None
+ self.xmlstream = None
+
+ def setHandlerParent(self, parent):
+ self.parent = parent
+ self.parent.addHandler(self)
+
+ def disownHandlerParent(self, parent):
+ self.parent.removeHandler(self)
+ self.parent = None
+
+ def makeConnection(self, xs):
+ self.xmlstream = xs
+ self.connectionMade()
+
+ def connectionMade(self):
+ """
+ Called after a connection has been established.
+
+ Can be overridden to perform work before stream initialization.
+ """
+
+ def connectionInitialized(self):
+ """
+ The XML stream has been initialized.
+
+ Can be overridden to perform work after stream initialization, e.g. to
+ set up observers and start exchanging XML stanzas.
+ """
+
+ def connectionLost(self, reason):
+ """
+ The XML stream has been closed.
+
+ This method can be extended to inspect the C{reason} argument and
+ act on it.
+ """
+ self.xmlstream = None
+
+ def send(self, obj):
+ """
+ Send data over the managed XML stream.
+
+ @note: The stream manager maintains a queue for data sent using this
+ method when there is no current initialized XML stream. This
+ data is then sent as soon as a new stream has been established
+ and initialized. Subsequently, L{connectionInitialized} will be
+ called again. If this queueing is not desired, use C{send} on
+ C{self.xmlstream}.
+
+ @param obj: data to be sent over the XML stream. This is usually an
+ object providing L{domish.IElement}, or serialized XML. See
+ L{xmlstream.XmlStream} for details.
+ """
+ self.parent.send(obj)
+
+
+@implementer(ijabber.IXMPPHandlerCollection)
+class XMPPHandlerCollection:
+ """
+ Collection of XMPP subprotocol handlers.
+
+ This allows for grouping of subprotocol handlers, but is not an
+ L{XMPPHandler} itself, so this is not recursive.
+
+ @ivar handlers: List of protocol handlers.
+ @type handlers: C{list} of objects providing
+ L{IXMPPHandler}
+ """
+
+ def __init__(self):
+ self.handlers = []
+
+ def __iter__(self):
+ """
+ Act as a container for handlers.
+ """
+ return iter(self.handlers)
+
+ def addHandler(self, handler):
+ """
+ Add protocol handler.
+
+ Protocol handlers are expected to provide L{ijabber.IXMPPHandler}.
+ """
+ self.handlers.append(handler)
+
+ def removeHandler(self, handler):
+ """
+ Remove protocol handler.
+ """
+ self.handlers.remove(handler)
+
+
+class StreamManager(XMPPHandlerCollection):
+ """
+ Business logic representing a managed XMPP connection.
+
+ This maintains a single XMPP connection and provides facilities for packet
+ routing and transmission. Business logic modules are objects providing
+ L{ijabber.IXMPPHandler} (like subclasses of L{XMPPHandler}), and added
+ using L{addHandler}.
+
+ @ivar xmlstream: currently managed XML stream
+ @type xmlstream: L{XmlStream}
+ @ivar logTraffic: if true, log all traffic.
+ @type logTraffic: C{bool}
+ @ivar _initialized: Whether the stream represented by L{xmlstream} has
+ been initialized. This is used when caching outgoing
+ stanzas.
+ @type _initialized: C{bool}
+ @ivar _packetQueue: internal buffer of unsent data. See L{send} for details.
+ @type _packetQueue: C{list}
+ """
+
+ logTraffic = False
+
+ def __init__(self, factory):
+ XMPPHandlerCollection.__init__(self)
+ self.xmlstream = None
+ self._packetQueue = []
+ self._initialized = False
+
+ factory.addBootstrap(STREAM_CONNECTED_EVENT, self._connected)
+ factory.addBootstrap(STREAM_AUTHD_EVENT, self._authd)
+ factory.addBootstrap(INIT_FAILED_EVENT, self.initializationFailed)
+ factory.addBootstrap(STREAM_END_EVENT, self._disconnected)
+ self.factory = factory
+
+ def addHandler(self, handler):
+ """
+ Add protocol handler.
+
+ When an XML stream has already been established, the handler's
+ C{connectionInitialized} will be called to get it up to speed.
+ """
+ XMPPHandlerCollection.addHandler(self, handler)
+
+ # get protocol handler up to speed when a connection has already
+ # been established
+ if self.xmlstream and self._initialized:
+ handler.makeConnection(self.xmlstream)
+ handler.connectionInitialized()
+
+ def _connected(self, xs):
+ """
+ Called when the transport connection has been established.
+
+ Here we optionally set up traffic logging (depending on L{logTraffic})
+ and call each handler's C{makeConnection} method with the L{XmlStream}
+ instance.
+ """
+
+ def logDataIn(buf):
+ log.msg("RECV: %r" % buf)
+
+ def logDataOut(buf):
+ log.msg("SEND: %r" % buf)
+
+ if self.logTraffic:
+ xs.rawDataInFn = logDataIn
+ xs.rawDataOutFn = logDataOut
+
+ self.xmlstream = xs
+
+ for e in self:
+ e.makeConnection(xs)
+
+ def _authd(self, xs):
+ """
+ Called when the stream has been initialized.
+
+ Send out cached stanzas and call each handler's
+ C{connectionInitialized} method.
+ """
+ # Flush all pending packets
+ for p in self._packetQueue:
+ xs.send(p)
+ self._packetQueue = []
+ self._initialized = True
+
+ # Notify all child services which implement
+ # the IService interface
+ for e in self:
+ e.connectionInitialized()
+
+ def initializationFailed(self, reason):
+ """
+ Called when stream initialization has failed.
+
+ Stream initialization has halted, with the reason indicated by
+ C{reason}. It may be retried by calling the authenticator's
+ C{initializeStream}. See the respective authenticators for details.
+
+ @param reason: A failure instance indicating why stream initialization
+ failed.
+ @type reason: L{failure.Failure}
+ """
+
+ def _disconnected(self, reason):
+ """
+ Called when the stream has been closed.
+
+ From this point on, the manager doesn't interact with the
+ L{XmlStream} anymore and notifies each handler that the connection
+ was lost by calling its C{connectionLost} method.
+ """
+ self.xmlstream = None
+ self._initialized = False
+
+ # Notify all child services which implement
+ # the IService interface
+ for e in self:
+ e.connectionLost(reason)
+
+ def send(self, obj):
+ """
+ Send data over the XML stream.
+
+ When there is no established XML stream, the data is queued and sent
+ out when a new XML stream has been established and initialized.
+
+ @param obj: data to be sent over the XML stream. See
+ L{xmlstream.XmlStream.send} for details.
+ """
+ if self._initialized:
+ self.xmlstream.send(obj)
+ else:
+ self._packetQueue.append(obj)
+
+
+__all__ = [
+ "Authenticator",
+ "BaseFeatureInitiatingInitializer",
+ "ConnectAuthenticator",
+ "FeatureNotAdvertized",
+ "INIT_FAILED_EVENT",
+ "IQ",
+ "ListenAuthenticator",
+ "NS_STREAMS",
+ "NS_XMPP_TLS",
+ "Reset",
+ "STREAM_AUTHD_EVENT",
+ "STREAM_CONNECTED_EVENT",
+ "STREAM_END_EVENT",
+ "STREAM_ERROR_EVENT",
+ "STREAM_START_EVENT",
+ "StreamManager",
+ "TLSError",
+ "TLSFailed",
+ "TLSInitiatingInitializer",
+ "TLSNotSupported",
+ "TLSRequired",
+ "TimeoutError",
+ "XMPPHandler",
+ "XMPPHandlerCollection",
+ "XmlStream",
+ "XmlStreamFactory",
+ "XmlStreamServerFactory",
+ "hashPassword",
+ "toResponse",
+ "upgradeWithIQResponseTracker",
+]
diff --git a/contrib/python/Twisted/py3/twisted/words/protocols/jabber/xmpp_stringprep.py b/contrib/python/Twisted/py3/twisted/words/protocols/jabber/xmpp_stringprep.py
new file mode 100644
index 0000000000..4ffafa7060
--- /dev/null
+++ b/contrib/python/Twisted/py3/twisted/words/protocols/jabber/xmpp_stringprep.py
@@ -0,0 +1,257 @@
+# -*- test-case-name: twisted.words.test.test_jabberxmppstringprep -*-
+#
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+import stringprep
+from encodings import idna
+from itertools import chain
+
+# We require Unicode version 3.2.
+from unicodedata import ucd_3_2_0 as unicodedata
+
+from zope.interface import Interface, implementer
+
+from incremental import Version
+
+from twisted.python.deprecate import deprecatedModuleAttribute
+
+crippled = False
+deprecatedModuleAttribute(
+ Version("Twisted", 13, 1, 0), "crippled is always False", __name__, "crippled"
+)
+
+
+class ILookupTable(Interface):
+ """
+ Interface for character lookup classes.
+ """
+
+ def lookup(c):
+ """
+ Return whether character is in this table.
+ """
+
+
+class IMappingTable(Interface):
+ """
+ Interface for character mapping classes.
+ """
+
+ def map(c):
+ """
+ Return mapping for character.
+ """
+
+
+@implementer(ILookupTable)
+class LookupTableFromFunction:
+ def __init__(self, in_table_function):
+ self.lookup = in_table_function
+
+
+@implementer(ILookupTable)
+class LookupTable:
+ def __init__(self, table):
+ self._table = table
+
+ def lookup(self, c):
+ return c in self._table
+
+
+@implementer(IMappingTable)
+class MappingTableFromFunction:
+ def __init__(self, map_table_function):
+ self.map = map_table_function
+
+
+@implementer(IMappingTable)
+class EmptyMappingTable:
+ def __init__(self, in_table_function):
+ self._in_table_function = in_table_function
+
+ def map(self, c):
+ if self._in_table_function(c):
+ return None
+ else:
+ return c
+
+
+class Profile:
+ def __init__(
+ self,
+ mappings=[],
+ normalize=True,
+ prohibiteds=[],
+ check_unassigneds=True,
+ check_bidi=True,
+ ):
+ self.mappings = mappings
+ self.normalize = normalize
+ self.prohibiteds = prohibiteds
+ self.do_check_unassigneds = check_unassigneds
+ self.do_check_bidi = check_bidi
+
+ def prepare(self, string):
+ result = self.map(string)
+ if self.normalize:
+ result = unicodedata.normalize("NFKC", result)
+ self.check_prohibiteds(result)
+ if self.do_check_unassigneds:
+ self.check_unassigneds(result)
+ if self.do_check_bidi:
+ self.check_bidirectionals(result)
+ return result
+
+ def map(self, string):
+ result = []
+
+ for c in string:
+ result_c = c
+
+ for mapping in self.mappings:
+ result_c = mapping.map(c)
+ if result_c != c:
+ break
+
+ if result_c is not None:
+ result.append(result_c)
+
+ return "".join(result)
+
+ def check_prohibiteds(self, string):
+ for c in string:
+ for table in self.prohibiteds:
+ if table.lookup(c):
+ raise UnicodeError("Invalid character %s" % repr(c))
+
+ def check_unassigneds(self, string):
+ for c in string:
+ if stringprep.in_table_a1(c):
+ raise UnicodeError("Unassigned code point %s" % repr(c))
+
+ def check_bidirectionals(self, string):
+ found_LCat = False
+ found_RandALCat = False
+
+ for c in string:
+ if stringprep.in_table_d1(c):
+ found_RandALCat = True
+ if stringprep.in_table_d2(c):
+ found_LCat = True
+
+ if found_LCat and found_RandALCat:
+ raise UnicodeError("Violation of BIDI Requirement 2")
+
+ if found_RandALCat and not (
+ stringprep.in_table_d1(string[0]) and stringprep.in_table_d1(string[-1])
+ ):
+ raise UnicodeError("Violation of BIDI Requirement 3")
+
+
+class NamePrep:
+ """Implements preparation of internationalized domain names.
+
+ This class implements preparing internationalized domain names using the
+ rules defined in RFC 3491, section 4 (Conversion operations).
+
+ We do not perform step 4 since we deal with unicode representations of
+ domain names and do not convert from or to ASCII representations using
+ punycode encoding. When such a conversion is needed, the C{idna} standard
+ library provides the C{ToUnicode()} and C{ToASCII()} functions. Note that
+ C{idna} itself assumes UseSTD3ASCIIRules to be false.
+
+ The following steps are performed by C{prepare()}:
+
+ - Split the domain name in labels at the dots (RFC 3490, 3.1)
+ - Apply nameprep proper on each label (RFC 3491)
+ - Enforce the restrictions on ASCII characters in host names by
+ assuming STD3ASCIIRules to be true. (STD 3)
+ - Rejoin the labels using the label separator U+002E (full stop).
+
+ """
+
+ # Prohibited characters.
+ prohibiteds = [
+ chr(n)
+ for n in chain(
+ range(0x00, 0x2C + 1),
+ range(0x2E, 0x2F + 1),
+ range(0x3A, 0x40 + 1),
+ range(0x5B, 0x60 + 1),
+ range(0x7B, 0x7F + 1),
+ )
+ ]
+
+ def prepare(self, string):
+ result = []
+
+ labels = idna.dots.split(string)
+
+ if labels and len(labels[-1]) == 0:
+ trailing_dot = "."
+ del labels[-1]
+ else:
+ trailing_dot = ""
+
+ for label in labels:
+ result.append(self.nameprep(label))
+
+ return ".".join(result) + trailing_dot
+
+ def check_prohibiteds(self, string):
+ for c in string:
+ if c in self.prohibiteds:
+ raise UnicodeError("Invalid character %s" % repr(c))
+
+ def nameprep(self, label):
+ label = idna.nameprep(label)
+ self.check_prohibiteds(label)
+ if label[0] == "-":
+ raise UnicodeError("Invalid leading hyphen-minus")
+ if label[-1] == "-":
+ raise UnicodeError("Invalid trailing hyphen-minus")
+ return label
+
+
+C_11 = LookupTableFromFunction(stringprep.in_table_c11)
+C_12 = LookupTableFromFunction(stringprep.in_table_c12)
+C_21 = LookupTableFromFunction(stringprep.in_table_c21)
+C_22 = LookupTableFromFunction(stringprep.in_table_c22)
+C_3 = LookupTableFromFunction(stringprep.in_table_c3)
+C_4 = LookupTableFromFunction(stringprep.in_table_c4)
+C_5 = LookupTableFromFunction(stringprep.in_table_c5)
+C_6 = LookupTableFromFunction(stringprep.in_table_c6)
+C_7 = LookupTableFromFunction(stringprep.in_table_c7)
+C_8 = LookupTableFromFunction(stringprep.in_table_c8)
+C_9 = LookupTableFromFunction(stringprep.in_table_c9)
+
+B_1 = EmptyMappingTable(stringprep.in_table_b1)
+B_2 = MappingTableFromFunction(stringprep.map_table_b2)
+
+nodeprep = Profile(
+ mappings=[B_1, B_2],
+ prohibiteds=[
+ C_11,
+ C_12,
+ C_21,
+ C_22,
+ C_3,
+ C_4,
+ C_5,
+ C_6,
+ C_7,
+ C_8,
+ C_9,
+ LookupTable(['"', "&", "'", "/", ":", "<", ">", "@"]),
+ ],
+)
+
+resourceprep = Profile(
+ mappings=[
+ B_1,
+ ],
+ prohibiteds=[C_12, C_21, C_22, C_3, C_4, C_5, C_6, C_7, C_8, C_9],
+)
+
+nameprep = NamePrep()