Files
prosody-podman/tests/venv2/lib/python3.11/site-packages/aioxmpp/connector.py
T
2026-08-02 18:57:40 +02:00

383 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
########################################################################
# File name: connector.py
# This file is part of: aioxmpp
#
# LICENSE
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this program. If not, see
# <http://www.gnu.org/licenses/>.
#
########################################################################
"""
:mod:`~aioxmpp.connector` --- Ways to establish XML streams
###########################################################
This module provides classes to establish XML streams. Currently, there are two
different ways to establish XML streams: normal TCP connection which is then
upgraded using STARTTLS, and directly using TLS.
.. versionadded:: 0.6
The whole module was added in version 0.6.
Abstract base class
===================
The connectors share a common abstract base class, :class:`BaseConnector`:
.. autoclass:: BaseConnector
Specific connectors
===================
.. autoclass:: STARTTLSConnector
.. autoclass:: XMPPOverTLSConnector
"""
import abc
import asyncio
import logging
from datetime import timedelta
import aioxmpp.errors as errors
import aioxmpp.nonza as nonza
import aioxmpp.protocol as protocol
import aioxmpp.ssl_transport as ssl_transport
def to_ascii(s):
return s.encode("idna").decode("ascii")
class BaseConnector(metaclass=abc.ABCMeta):
"""
This is the base class for connectors. It defines the public interface of
all connectors.
.. autoattribute:: tls_supported
.. automethod:: connect
Existing connectors:
.. autosummary::
STARTTLSConnector
XMPPOverTLSConnector
"""
@abc.abstractproperty
def tls_supported(self):
"""
Boolean which indicates whether TLS is supported by this connector.
"""
@abc.abstractproperty
def dane_supported(self):
"""
Boolean which indicates whether DANE is supported by this connector.
"""
@abc.abstractmethod
async def connect(self, loop, metadata, domain, host, port,
negotiation_timeout,
base_logger=None):
"""
Establish a :class:`.protocol.XMLStream` for `domain` with the given
`host` at the given TCP `port`.
`metadata` must be a :class:`.security_layer.SecurityLayer` instance to
use for the connection. `loop` must be a :class:`asyncio.BaseEventLoop`
to use.
`negotiation_timeout` must be the maximum time in seconds to wait for
the server to reply in each negotiation step. The `negotiation_timeout`
is used as value for
:attr:`~aioxmpp.protocol.XMLStream.deadtime_hard_limit` in the returned
stream.
Return a triple consisting of the :class:`asyncio.Transport`, the
:class:`.protocol.XMLStream` and the
:class:`aioxmpp.nonza.StreamFeatures` of the stream.
To detect the use of TLS on the stream, check whether
:meth:`asyncio.Transport.get_extra_info` returns a non-:data:`None`
value for ``"ssl_object"``.
`base_logger` is passed to :class:`aioxmpp.protocol.XMLStream`.
.. versionchanged:: 0.10
Assignment of
:attr:`~aioxmpp.protocol.XMLStream.deadtime_hard_limit` was added.
"""
class STARTTLSConnector(BaseConnector):
"""
Establish an XML stream using STARTTLS.
.. automethod:: connect
"""
@property
def tls_supported(self):
return True
@property
def dane_supported(self):
return False
async def connect(self, loop, metadata, domain: str, host, port,
negotiation_timeout, base_logger=None):
"""
.. seealso::
:meth:`BaseConnector.connect`
For general information on the :meth:`connect` method.
Connect to `host` at TCP port number `port`. The
:class:`aioxmpp.security_layer.SecurityLayer` object `metadata` is used
to determine the parameters of the TLS connection.
First, a normal TCP connection is opened and the stream header is sent.
The stream features are waited for, and then STARTTLS is negotiated if
possible.
:attr:`~.security_layer.SecurityLayer.tls_required` is honoured: if it
is true and TLS negotiation fails, :class:`~.errors.TLSUnavailable` is
raised. TLS negotiation is always attempted if
:attr:`~.security_layer.SecurityLayer.tls_required` is true, even if
the server does not advertise a STARTTLS stream feature. This might
help to prevent trivial downgrade attacks, and we dont have anything
to lose at this point anymore anyways.
:attr:`~.security_layer.SecurityLayer.ssl_context_factory` and
:attr:`~.security_layer.SecurityLayer.certificate_verifier_factory` are
used to configure the TLS connection.
.. versionchanged:: 0.10
The `negotiation_timeout` is set as
:attr:`~.XMLStream.deadtime_hard_limit` on the returned XML stream.
"""
features_future = asyncio.Future(loop=loop)
stream = protocol.XMLStream(
to=domain,
features_future=features_future,
base_logger=base_logger,
)
if base_logger is not None:
logger = base_logger.getChild(type(self).__name__)
else:
logger = logging.getLogger(".".join([
__name__, type(self).__qualname__,
]))
try:
transport, _ = await ssl_transport.create_starttls_connection(
loop,
lambda: stream,
host=host,
port=port,
peer_hostname=host,
server_hostname=to_ascii(domain),
use_starttls=True,
)
except: # NOQA
stream.abort()
raise
stream.deadtime_hard_limit = timedelta(seconds=negotiation_timeout)
features = await features_future
try:
features[nonza.StartTLSFeature]
except KeyError:
if not metadata.tls_required:
return transport, stream, await features_future
logger.debug(
"attempting STARTTLS despite not announced since it is"
" required")
try:
response = await protocol.send_and_wait_for(
stream,
[
nonza.StartTLS(),
],
[
nonza.StartTLSFailure,
nonza.StartTLSProceed,
]
)
except errors.StreamError:
raise errors.TLSUnavailable(
"STARTTLS not supported by server, but required by client"
)
if not isinstance(response, nonza.StartTLSProceed):
if metadata.tls_required:
message = (
"server failed to STARTTLS"
)
protocol.send_stream_error_and_close(
stream,
condition=errors.StreamErrorCondition.POLICY_VIOLATION,
text=message,
)
raise errors.TLSUnavailable(message)
return transport, stream, await features_future
verifier = metadata.certificate_verifier_factory()
await verifier.pre_handshake(
domain,
host,
port,
metadata,
)
ssl_context = metadata.ssl_context_factory()
verifier.setup_context(ssl_context, transport)
await stream.starttls(
ssl_context=ssl_context,
post_handshake_callback=verifier.post_handshake,
)
features = await protocol.reset_stream_and_get_features(
stream,
timeout=negotiation_timeout,
)
return transport, stream, features
class XMPPOverTLSConnector(BaseConnector):
"""
Establish an XML stream using XMPP-over-TLS, as per :xep:`368`.
.. automethod:: connect
"""
@property
def dane_supported(self):
return False
@property
def tls_supported(self):
return True
def _context_factory_factory(self, logger, metadata, verifier):
def context_factory(transport):
ssl_context = metadata.ssl_context_factory()
if hasattr(ssl_context, "set_alpn_protos"):
try:
ssl_context.set_alpn_protos([b'xmpp-client'])
except NotImplementedError:
logger.warning(
"the underlying OpenSSL library does not support ALPN"
)
else:
logger.warning(
"OpenSSL.SSL.Context lacks set_alpn_protos - "
"please update pyOpenSSL to a recent version"
)
verifier.setup_context(ssl_context, transport)
return ssl_context
return context_factory
async def connect(self, loop, metadata, domain, host, port,
negotiation_timeout, base_logger=None):
"""
.. seealso::
:meth:`BaseConnector.connect`
For general information on the :meth:`connect` method.
Connect to `host` at TCP port number `port`. The
:class:`aioxmpp.security_layer.SecurityLayer` object `metadata` is used
to determine the parameters of the TLS connection.
The connector connects to the server by directly establishing TLS; no
XML stream is started before TLS negotiation, in accordance to
:xep:`368` and how legacy SSL was handled in the past.
:attr:`~.security_layer.SecurityLayer.ssl_context_factory` and
:attr:`~.security_layer.SecurityLayer.certificate_verifier_factory` are
used to configure the TLS connection.
.. versionchanged:: 0.10
The `negotiation_timeout` is set as
:attr:`~.XMLStream.deadtime_hard_limit` on the returned XML stream.
"""
features_future = asyncio.Future(loop=loop)
stream = protocol.XMLStream(
to=domain,
features_future=features_future,
base_logger=base_logger,
)
if base_logger is not None:
logger = base_logger.getChild(type(self).__name__)
else:
logger = logging.getLogger(".".join([
__name__, type(self).__qualname__,
]))
verifier = metadata.certificate_verifier_factory()
await verifier.pre_handshake(
domain,
host,
port,
metadata,
)
context_factory = self._context_factory_factory(logger, metadata,
verifier)
try:
transport, _ = await ssl_transport.create_starttls_connection(
loop,
lambda: stream,
host=host,
port=port,
peer_hostname=host,
server_hostname=to_ascii(domain),
post_handshake_callback=verifier.post_handshake,
ssl_context_factory=context_factory,
use_starttls=False,
)
except: # NOQA
stream.abort()
raise
stream.deadtime_hard_limit = timedelta(seconds=negotiation_timeout)
return transport, stream, await features_future