parsers: add stream error XSO

Jonas Schäfer created

Change summary

parsers/ChangeLog           |   1 
parsers/src/lib.rs          |   2 
parsers/src/stream_error.rs | 283 +++++++++++++++++++++++++++++++++++++++
3 files changed, 286 insertions(+)

Detailed changes

parsers/ChangeLog 🔗

@@ -29,6 +29,7 @@ XXXX-YY-ZZ RELEASER <admin@example.com>
         - Extensible SASL Profile (XEP-0388)
         - SASL Channel-Binding Type Capability (XEP-0440)
         - Stream Limits Advertisement (XEP-0478)
+        - RFC 6120 stream errors
 
 Version 0.21.0:
 2024-07-25 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>

parsers/src/lib.rs 🔗

@@ -59,6 +59,8 @@ pub mod starttls;
 /// RFC 6120: Extensible Messaging and Presence Protocol (XMPP): Core
 pub mod stream;
 /// RFC 6120: Extensible Messaging and Presence Protocol (XMPP): Core
+pub mod stream_error;
+/// RFC 6120: Extensible Messaging and Presence Protocol (XMPP): Core
 pub mod stream_features;
 
 /// RFC 6121: Extensible Messaging and Presence Protocol (XMPP): Instant Messaging and Presence

parsers/src/stream_error.rs 🔗

@@ -0,0 +1,283 @@
+// Copyright (c) 2024 Jonas Schäfer <jonas@zombofant.net>
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+use minidom::Element;
+use xso::{AsXml, FromXml};
+
+use crate::ns;
+
+/// Enumeration of all stream error conditions as defined in [RFC 6120].
+///
+/// All variant documentation is directly quoted from [RFC 6120].
+///
+///    [RFC 6120]: https://datatracker.ietf.org/doc/html/rfc6120#section-4.9.3
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
+#[xml(namespace = ns::STREAM)]
+pub enum DefinedCondition {
+    /// The entity has sent XML that cannot be processed.
+    ///
+    /// This error can be used instead of the more specific XML-related
+    /// errors, such as `<bad-namespace-prefix/>`, `<invalid-xml/>`,
+    /// `<not-well-formed/>`, `<restricted-xml/>`, and
+    /// `<unsupported-encoding/>`.  However, the more specific errors are
+    /// RECOMMENDED.
+    #[xml(name = "bad-format")]
+    BadFormat,
+
+    /// The entity has sent a namespace prefix that is unsupported, or has
+    /// sent no namespace prefix on an element that needs such a prefix (see
+    /// [Section 11.2](https://datatracker.ietf.org/doc/html/rfc6120#section-11.2)).
+    #[xml(name = "bad-namespace-prefix")]
+    BadNamespacePrefix,
+
+    /// The server either (1) is closing the existing stream for this entity
+    /// because a new stream has been initiated that conflicts with the
+    /// existing stream, or (2) is refusing a new stream for this entity
+    /// because allowing the new stream would conflict with an existing
+    /// stream (e.g., because the server allows only a certain number of
+    /// connections from the same IP address or allows only one server-to-
+    /// server stream for a given domain pair as a way of helping to ensure
+    /// in-order processing as described under
+    /// [Section 10.1](https://datatracker.ietf.org/doc/html/rfc6120#section-10.1)).
+    ///
+    /// If a client receives a `<conflict/>` stream error, during the resource
+    /// binding aspect of its reconnection attempt it MUST NOT blindly request
+    /// the resourcepart it used during the former session but instead MUST
+    /// choose a different resourcepart; details are provided under
+    /// [Section 7](https://datatracker.ietf.org/doc/html/rfc6120#section-7).
+    #[xml(name = "conflict")]
+    Conflict,
+
+    /// One party is closing the stream because it has reason to believe that
+    /// the other party has permanently lost the ability to communicate over
+    /// the stream.  The lack of ability to communicate can be discovered
+    /// using various methods, such as whitespace keepalives as specified
+    /// under
+    /// [Section 4.4](https://datatracker.ietf.org/doc/html/rfc6120#section-4.4),
+    /// XMPP-level pings as defined in
+    /// [XEP-0199](https://xmpp.org/extensions/xep-0199.html), and
+    /// XMPP Stream Management as defined in
+    /// [XEP-0198](https://xmpp.org/extensions/xep-0198.html).
+    ///
+    /// Interoperability Note: RFC 3920 specified that the
+    /// `<connection-timeout/>` stream error is to be used if the peer has not
+    /// generated any traffic over the stream for some period of time.
+    /// That behavior is no longer recommended; instead, the error SHOULD be
+    /// used only if the connected client or peer server has not responded to
+    /// data sent over the stream.
+    #[xml(name = "connection-timeout")]
+    ConnectionTimeout,
+
+    /// The value of the 'to' attribute provided in the initial stream header
+    /// corresponds to an FQDN that is no longer serviced by the receiving
+    /// entity.
+    #[xml(name = "host-gone")]
+    HostGone,
+
+    /// The value of the 'to' attribute provided in the initial stream header
+    /// does not correspond to an FQDN that is serviced by the receiving
+    /// entity.
+    #[xml(name = "host-unknown")]
+    HostUnknown,
+
+    /// A stanza sent between two servers lacks a 'to' or 'from' attribute,
+    /// the 'from' or 'to' attribute has no value, or the value violates the
+    /// rules for XMPP addresses
+    /// (see [RFC 6122](https://datatracker.ietf.org/doc/html/rfc6122)).
+    #[xml(name = "improper-addressing")]
+    ImproperAddressing,
+
+    /// The server has experienced a misconfiguration or other internal error
+    /// that prevents it from servicing the stream.
+    #[xml(name = "internal-server-error")]
+    InternalServerError,
+
+    /// The data provided in a 'from' attribute does not match an authorized
+    /// JID or validated domain as negotiated (1) between two servers using
+    /// SASL or Server Dialback, or (2) between a client and a server via
+    /// SASL authentication and resource binding.
+    #[xml(name = "invalid-from")]
+    InvalidFrom,
+
+    /// The stream namespace name is something other than
+    /// `http://etherx.jabber.org/streams` (see
+    /// [Section 11.2](https://datatracker.ietf.org/doc/html/rfc6120#section-11.2))
+    /// or the content namespace declared as the default namespace is not
+    /// supported (e.g., something other than `jabber:client` or
+    /// `jabber:server`).
+    #[xml(name = "invalid-namespace")]
+    InvalidNamespace,
+
+    /// The entity has sent invalid XML over the stream to a server that
+    /// performs validation (see
+    /// [Section 11.4](https://datatracker.ietf.org/doc/html/rfc6120#section-11.4)).
+    #[xml(name = "invalid-xml")]
+    InvalidXml,
+
+    /// The entity has attempted to send XML stanzas or other outbound data
+    /// before the stream has been authenticated, or otherwise is not
+    /// authorized to perform an action related to stream negotiation; the
+    /// receiving entity MUST NOT process the offending data before sending
+    /// the stream error.
+    #[xml(name = "not-authorized")]
+    NotAuthorized,
+
+    /// The initiating entity has sent XML that violates the well-formedness
+    /// rules of [XML](https://www.w3.org/TR/REC-xml/) or
+    /// [XML-NAMES](https://www.w3.org/TR/REC-xml-names/).
+    #[xml(name = "not-well-formed")]
+    NotWellFormed,
+
+    /// The entity has violated some local service policy (e.g., a stanza
+    /// exceeds a configured size limit); the server MAY choose to specify
+    /// the policy in the `<text/>` element or in an application-specific
+    /// condition element.
+    #[xml(name = "policy-violation")]
+    PolicyViolation,
+
+    /// The server is unable to properly connect to a remote entity that is
+    /// needed for authentication or authorization (e.g., in certain
+    /// scenarios related to Server Dialback
+    /// [XEP-0220](https://xmpp.org/extensions/xep-0220.html)); this condition
+    /// is not to be used when the cause of the error is within the
+    /// administrative domain of the XMPP service provider, in which case the
+    /// `<internal-server-error/>` condition is more appropriate.
+    #[xml(name = "remote-connection-failed")]
+    RemoteConnectionFailed,
+
+    /// The server is closing the stream because it has new (typically
+    /// security-critical) features to offer, because the keys or
+    /// certificates used to establish a secure context for the stream have
+    /// expired or have been revoked during the life of the stream
+    /// ([Section 13.7.2.3](https://datatracker.ietf.org/doc/html/rfc6120#section-13.7.2.3)),
+    /// because the TLS sequence number has wrapped
+    /// ([Section 5.3.5](https://datatracker.ietf.org/doc/html/rfc6120#section-5.3.5)),
+    /// etc.  The reset applies to the stream and to any security context
+    /// established for that stream (e.g., via TLS and SASL), which means that
+    /// encryption and authentication need to be negotiated again for the new
+    /// stream (e.g., TLS session resumption cannot be used).
+    #[xml(name = "reset")]
+    Reset,
+
+    /// The server lacks the system resources necessary to service the stream.
+    #[xml(name = "resource-constraint")]
+    ResourceConstraint,
+
+    /// The entity has attempted to send restricted XML features such as a
+    /// comment, processing instruction, DTD subset, or XML entity reference
+    /// (see
+    /// [Section 11.1](https://datatracker.ietf.org/doc/html/rfc6120#section-11.1)).
+    #[xml(name = "restricted-xml")]
+    RestrictedXml,
+
+    /// The server will not provide service to the initiating entity but is
+    /// redirecting traffic to another host under the administrative control
+    /// of the same service provider.  The XML character data of the
+    /// `<see-other-host/>` element returned by the server MUST specify the
+    /// alternate FQDN or IP address at which to connect, which MUST be a
+    /// valid domainpart or a domainpart plus port number (separated by the
+    /// ':' character in the form "domainpart:port").  If the domainpart is
+    /// the same as the source domain, derived domain, or resolved IPv4 or
+    /// IPv6 address to which the initiating entity originally connected
+    /// (differing only by the port number), then the initiating entity
+    /// SHOULD simply attempt to reconnect at that address.  (The format of
+    /// an IPv6 address MUST follow
+    /// [IPv6-ADDR](https://datatracker.ietf.org/doc/html/rfc6120#ref-IPv6-ADDR),
+    /// which includes the enclosing the IPv6 address in square brackets
+    /// '[' and ']' as originally defined by
+    /// [URI](https://datatracker.ietf.org/doc/html/rfc6120#ref-URI).
+    /// )  Otherwise, the initiating entity MUST resolve the FQDN
+    /// specified in the `<see-other-host/>` element as described under
+    /// [Section 3.2](https://datatracker.ietf.org/doc/html/rfc6120#section-3.2).
+    ///
+    /// When negotiating a stream with the host to which it has been
+    /// redirected, the initiating entity MUST apply the same policies it
+    /// would have applied to the original connection attempt (e.g., a policy
+    /// requiring TLS), MUST specify the same 'to' address on the initial
+    /// stream header, and MUST verify the identity of the new host using the
+    /// same reference identifier(s) it would have used for the original
+    /// connection attempt (in accordance with
+    /// [TLS-CERTS](https://datatracker.ietf.org/doc/html/rfc6120#ref-TLS-CERTS)).
+    /// Even if the receiving entity returns a `<see-other-host/>` error
+    /// before the confidentiality and integrity of the stream have been
+    /// established (thus introducing the possibility of a denial-of-service
+    /// attack), the fact that the initiating entity needs to verify the
+    /// identity of the XMPP service based on the same reference identifiers
+    /// implies that the initiating entity will not connect to a malicious
+    /// entity.  To reduce the possibility of a denial-of-service attack, (a)
+    /// the receiving entity SHOULD NOT close the stream with a
+    /// `<see-other-host/>` stream error until after the confidentiality and
+    /// integrity of the stream have been protected via TLS or an equivalent
+    /// security layer (such as the SASL GSSAPI mechanism), and (b) the
+    /// receiving entity MAY have a policy of following redirects only if it
+    /// has authenticated the receiving entity.  In addition, the initiating
+    /// entity SHOULD abort the connection attempt after a certain number of
+    /// successive redirects (e.g., at least 2 but no more than 5).
+    #[xml(name = "see-other-host")]
+    SeeOtherHost(#[xml(text)] String),
+
+    /// The server is being shut down and all active streams are being closed.
+    #[xml(name = "system-shutdown")]
+    SystemShutdown,
+
+    /// The error condition is not one of those defined by the other
+    /// conditions in this list; this error condition SHOULD NOT be used
+    /// except in conjunction with an application-specific condition.
+    #[xml(name = "undefined-condition")]
+    UndefinedCondition,
+
+    /// The initiating entity has encoded the stream in an encoding that is
+    /// not supported by the server (see
+    /// [Section 11.6](https://datatracker.ietf.org/doc/html/rfc6120#section-11.6))
+    /// or has otherwise improperly encoded the stream (e.g., by violating the
+    /// rules of the
+    /// [UTF-8](https://datatracker.ietf.org/doc/html/rfc6120#ref-UTF-8)
+    /// encoding).
+    #[xml(name = "unsupported-encoding")]
+    UnsupportedEncoding,
+
+    /// The receiving entity has advertised a mandatory-to-negotiate stream
+    /// feature that the initiating entity does not support, and has offered
+    /// no other mandatory-to-negotiate feature alongside the unsupported
+    /// feature.
+    #[xml(name = "unsupported-feature")]
+    UnsupportedFeature,
+
+    /// The initiating entity has sent a first-level child of the stream that
+    /// is not supported by the server, either because the receiving entity
+    /// does not understand the namespace or because the receiving entity
+    /// does not understand the element name for the applicable namespace
+    /// (which might be the content namespace declared as the default
+    /// namespace).
+    #[xml(name = "unsupported-stanza-type")]
+    UnsupportedStanzaType,
+
+    /// The 'version' attribute provided by the initiating entity in the
+    /// stream header specifies a version of XMPP that is not supported by
+    /// the server.
+    #[xml(name = "unsupported-version")]
+    UnsupportedVersion,
+}
+
+/// Stream error as specified in RFC 6120.
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
+#[xml(namespace = ns::STREAM, name = "error")]
+pub struct StreamError {
+    /// The enumerated error condition which triggered this stream error.
+    #[xml(child)]
+    pub condition: DefinedCondition,
+
+    /// Optional error text. The first part is the optional `xml:lang`
+    /// language tag, the second part is the actual text content.
+    #[xml(extract(default, fields(attribute(name = "xml:lang", default, type_ = Option<String>), text(type_ = String))))]
+    pub text: Option<(Option<String>, String)>,
+
+    /// Optional application-defined element which refines the specified
+    /// [`Self::condition`].
+    // TODO: use n = 1 once we have it.
+    #[xml(element(n = ..))]
+    pub application_specific: Vec<Element>,
+}