xmpp_parsers/stream_error.rs
1// Copyright (c) 2024 Jonas Schäfer <jonas@zombofant.net>
2//
3// This Source Code Form is subject to the terms of the Mozilla Public
4// License, v. 2.0. If a copy of the MPL was not distributed with this
5// file, You can obtain one at http://mozilla.org/MPL/2.0/.
6
7use core::{error::Error, fmt};
8
9use minidom::Element;
10use xso::{AsXml, FromXml};
11
12use crate::ns;
13
14/// Enumeration of all stream error conditions as defined in [RFC 6120].
15///
16/// All variant documentation is directly quoted from [RFC 6120].
17///
18/// [RFC 6120]: https://datatracker.ietf.org/doc/html/rfc6120#section-4.9.3
19#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
20#[xml(namespace = ns::STREAM)]
21pub enum DefinedCondition {
22 /// The entity has sent XML that cannot be processed.
23 ///
24 /// This error can be used instead of the more specific XML-related
25 /// errors, such as `<bad-namespace-prefix/>`, `<invalid-xml/>`,
26 /// `<not-well-formed/>`, `<restricted-xml/>`, and
27 /// `<unsupported-encoding/>`. However, the more specific errors are
28 /// RECOMMENDED.
29 #[xml(name = "bad-format")]
30 BadFormat,
31
32 /// The entity has sent a namespace prefix that is unsupported, or has
33 /// sent no namespace prefix on an element that needs such a prefix (see
34 /// [Section 11.2](https://datatracker.ietf.org/doc/html/rfc6120#section-11.2)).
35 #[xml(name = "bad-namespace-prefix")]
36 BadNamespacePrefix,
37
38 /// The server either (1) is closing the existing stream for this entity
39 /// because a new stream has been initiated that conflicts with the
40 /// existing stream, or (2) is refusing a new stream for this entity
41 /// because allowing the new stream would conflict with an existing
42 /// stream (e.g., because the server allows only a certain number of
43 /// connections from the same IP address or allows only one server-to-
44 /// server stream for a given domain pair as a way of helping to ensure
45 /// in-order processing as described under
46 /// [Section 10.1](https://datatracker.ietf.org/doc/html/rfc6120#section-10.1)).
47 ///
48 /// If a client receives a `<conflict/>` stream error, during the resource
49 /// binding aspect of its reconnection attempt it MUST NOT blindly request
50 /// the resourcepart it used during the former session but instead MUST
51 /// choose a different resourcepart; details are provided under
52 /// [Section 7](https://datatracker.ietf.org/doc/html/rfc6120#section-7).
53 #[xml(name = "conflict")]
54 Conflict,
55
56 /// One party is closing the stream because it has reason to believe that
57 /// the other party has permanently lost the ability to communicate over
58 /// the stream. The lack of ability to communicate can be discovered
59 /// using various methods, such as whitespace keepalives as specified
60 /// under
61 /// [Section 4.4](https://datatracker.ietf.org/doc/html/rfc6120#section-4.4),
62 /// XMPP-level pings as defined in
63 /// [XEP-0199](https://xmpp.org/extensions/xep-0199.html), and
64 /// XMPP Stream Management as defined in
65 /// [XEP-0198](https://xmpp.org/extensions/xep-0198.html).
66 ///
67 /// Interoperability Note: RFC 3920 specified that the
68 /// `<connection-timeout/>` stream error is to be used if the peer has not
69 /// generated any traffic over the stream for some period of time.
70 /// That behavior is no longer recommended; instead, the error SHOULD be
71 /// used only if the connected client or peer server has not responded to
72 /// data sent over the stream.
73 #[xml(name = "connection-timeout")]
74 ConnectionTimeout,
75
76 /// The value of the 'to' attribute provided in the initial stream header
77 /// corresponds to an FQDN that is no longer serviced by the receiving
78 /// entity.
79 #[xml(name = "host-gone")]
80 HostGone,
81
82 /// The value of the 'to' attribute provided in the initial stream header
83 /// does not correspond to an FQDN that is serviced by the receiving
84 /// entity.
85 #[xml(name = "host-unknown")]
86 HostUnknown,
87
88 /// A stanza sent between two servers lacks a 'to' or 'from' attribute,
89 /// the 'from' or 'to' attribute has no value, or the value violates the
90 /// rules for XMPP addresses
91 /// (see [RFC 6122](https://datatracker.ietf.org/doc/html/rfc6122)).
92 #[xml(name = "improper-addressing")]
93 ImproperAddressing,
94
95 /// The server has experienced a misconfiguration or other internal error
96 /// that prevents it from servicing the stream.
97 #[xml(name = "internal-server-error")]
98 InternalServerError,
99
100 /// The data provided in a 'from' attribute does not match an authorized
101 /// JID or validated domain as negotiated (1) between two servers using
102 /// SASL or Server Dialback, or (2) between a client and a server via
103 /// SASL authentication and resource binding.
104 #[xml(name = "invalid-from")]
105 InvalidFrom,
106
107 /// The stream namespace name is something other than
108 /// `http://etherx.jabber.org/streams` (see
109 /// [Section 11.2](https://datatracker.ietf.org/doc/html/rfc6120#section-11.2))
110 /// or the content namespace declared as the default namespace is not
111 /// supported (e.g., something other than `jabber:client` or
112 /// `jabber:server`).
113 #[xml(name = "invalid-namespace")]
114 InvalidNamespace,
115
116 /// The entity has sent invalid XML over the stream to a server that
117 /// performs validation (see
118 /// [Section 11.4](https://datatracker.ietf.org/doc/html/rfc6120#section-11.4)).
119 #[xml(name = "invalid-xml")]
120 InvalidXml,
121
122 /// The entity has attempted to send XML stanzas or other outbound data
123 /// before the stream has been authenticated, or otherwise is not
124 /// authorized to perform an action related to stream negotiation; the
125 /// receiving entity MUST NOT process the offending data before sending
126 /// the stream error.
127 #[xml(name = "not-authorized")]
128 NotAuthorized,
129
130 /// The initiating entity has sent XML that violates the well-formedness
131 /// rules of [XML](https://www.w3.org/TR/REC-xml/) or
132 /// [XML-NAMES](https://www.w3.org/TR/REC-xml-names/).
133 #[xml(name = "not-well-formed")]
134 NotWellFormed,
135
136 /// The entity has violated some local service policy (e.g., a stanza
137 /// exceeds a configured size limit); the server MAY choose to specify
138 /// the policy in the `<text/>` element or in an application-specific
139 /// condition element.
140 #[xml(name = "policy-violation")]
141 PolicyViolation,
142
143 /// The server is unable to properly connect to a remote entity that is
144 /// needed for authentication or authorization (e.g., in certain
145 /// scenarios related to Server Dialback
146 /// [XEP-0220](https://xmpp.org/extensions/xep-0220.html)); this condition
147 /// is not to be used when the cause of the error is within the
148 /// administrative domain of the XMPP service provider, in which case the
149 /// `<internal-server-error/>` condition is more appropriate.
150 #[xml(name = "remote-connection-failed")]
151 RemoteConnectionFailed,
152
153 /// The server is closing the stream because it has new (typically
154 /// security-critical) features to offer, because the keys or
155 /// certificates used to establish a secure context for the stream have
156 /// expired or have been revoked during the life of the stream
157 /// ([Section 13.7.2.3](https://datatracker.ietf.org/doc/html/rfc6120#section-13.7.2.3)),
158 /// because the TLS sequence number has wrapped
159 /// ([Section 5.3.5](https://datatracker.ietf.org/doc/html/rfc6120#section-5.3.5)),
160 /// etc. The reset applies to the stream and to any security context
161 /// established for that stream (e.g., via TLS and SASL), which means that
162 /// encryption and authentication need to be negotiated again for the new
163 /// stream (e.g., TLS session resumption cannot be used).
164 #[xml(name = "reset")]
165 Reset,
166
167 /// The server lacks the system resources necessary to service the stream.
168 #[xml(name = "resource-constraint")]
169 ResourceConstraint,
170
171 /// The entity has attempted to send restricted XML features such as a
172 /// comment, processing instruction, DTD subset, or XML entity reference
173 /// (see
174 /// [Section 11.1](https://datatracker.ietf.org/doc/html/rfc6120#section-11.1)).
175 #[xml(name = "restricted-xml")]
176 RestrictedXml,
177
178 /// The server will not provide service to the initiating entity but is
179 /// redirecting traffic to another host under the administrative control
180 /// of the same service provider. The XML character data of the
181 /// `<see-other-host/>` element returned by the server MUST specify the
182 /// alternate FQDN or IP address at which to connect, which MUST be a
183 /// valid domainpart or a domainpart plus port number (separated by the
184 /// ':' character in the form "domainpart:port"). If the domainpart is
185 /// the same as the source domain, derived domain, or resolved IPv4 or
186 /// IPv6 address to which the initiating entity originally connected
187 /// (differing only by the port number), then the initiating entity
188 /// SHOULD simply attempt to reconnect at that address. (The format of
189 /// an IPv6 address MUST follow
190 /// [IPv6-ADDR](https://datatracker.ietf.org/doc/html/rfc6120#ref-IPv6-ADDR),
191 /// which includes the enclosing the IPv6 address in square brackets
192 /// '[' and ']' as originally defined by
193 /// [URI](https://datatracker.ietf.org/doc/html/rfc6120#ref-URI).
194 /// ) Otherwise, the initiating entity MUST resolve the FQDN
195 /// specified in the `<see-other-host/>` element as described under
196 /// [Section 3.2](https://datatracker.ietf.org/doc/html/rfc6120#section-3.2).
197 ///
198 /// When negotiating a stream with the host to which it has been
199 /// redirected, the initiating entity MUST apply the same policies it
200 /// would have applied to the original connection attempt (e.g., a policy
201 /// requiring TLS), MUST specify the same 'to' address on the initial
202 /// stream header, and MUST verify the identity of the new host using the
203 /// same reference identifier(s) it would have used for the original
204 /// connection attempt (in accordance with
205 /// [TLS-CERTS](https://datatracker.ietf.org/doc/html/rfc6120#ref-TLS-CERTS)).
206 /// Even if the receiving entity returns a `<see-other-host/>` error
207 /// before the confidentiality and integrity of the stream have been
208 /// established (thus introducing the possibility of a denial-of-service
209 /// attack), the fact that the initiating entity needs to verify the
210 /// identity of the XMPP service based on the same reference identifiers
211 /// implies that the initiating entity will not connect to a malicious
212 /// entity. To reduce the possibility of a denial-of-service attack, (a)
213 /// the receiving entity SHOULD NOT close the stream with a
214 /// `<see-other-host/>` stream error until after the confidentiality and
215 /// integrity of the stream have been protected via TLS or an equivalent
216 /// security layer (such as the SASL GSSAPI mechanism), and (b) the
217 /// receiving entity MAY have a policy of following redirects only if it
218 /// has authenticated the receiving entity. In addition, the initiating
219 /// entity SHOULD abort the connection attempt after a certain number of
220 /// successive redirects (e.g., at least 2 but no more than 5).
221 #[xml(name = "see-other-host")]
222 SeeOtherHost(#[xml(text)] String),
223
224 /// The server is being shut down and all active streams are being closed.
225 #[xml(name = "system-shutdown")]
226 SystemShutdown,
227
228 /// The error condition is not one of those defined by the other
229 /// conditions in this list; this error condition SHOULD NOT be used
230 /// except in conjunction with an application-specific condition.
231 #[xml(name = "undefined-condition")]
232 UndefinedCondition,
233
234 /// The initiating entity has encoded the stream in an encoding that is
235 /// not supported by the server (see
236 /// [Section 11.6](https://datatracker.ietf.org/doc/html/rfc6120#section-11.6))
237 /// or has otherwise improperly encoded the stream (e.g., by violating the
238 /// rules of the
239 /// [UTF-8](https://datatracker.ietf.org/doc/html/rfc6120#ref-UTF-8)
240 /// encoding).
241 #[xml(name = "unsupported-encoding")]
242 UnsupportedEncoding,
243
244 /// The receiving entity has advertised a mandatory-to-negotiate stream
245 /// feature that the initiating entity does not support, and has offered
246 /// no other mandatory-to-negotiate feature alongside the unsupported
247 /// feature.
248 #[xml(name = "unsupported-feature")]
249 UnsupportedFeature,
250
251 /// The initiating entity has sent a first-level child of the stream that
252 /// is not supported by the server, either because the receiving entity
253 /// does not understand the namespace or because the receiving entity
254 /// does not understand the element name for the applicable namespace
255 /// (which might be the content namespace declared as the default
256 /// namespace).
257 #[xml(name = "unsupported-stanza-type")]
258 UnsupportedStanzaType,
259
260 /// The 'version' attribute provided by the initiating entity in the
261 /// stream header specifies a version of XMPP that is not supported by
262 /// the server.
263 #[xml(name = "unsupported-version")]
264 UnsupportedVersion,
265}
266
267impl fmt::Display for DefinedCondition {
268 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
269 let s = match self {
270 Self::BadFormat => "bad-format",
271 Self::BadNamespacePrefix => "bad-namespace-prefix",
272 Self::Conflict => "conflict",
273 Self::ConnectionTimeout => "connection-timeout",
274 Self::HostGone => "host-gone",
275 Self::HostUnknown => "host-unknown",
276 Self::ImproperAddressing => "improper-addressing",
277 Self::InternalServerError => "internal-server-error",
278 Self::InvalidFrom => "invalid-from",
279 Self::InvalidNamespace => "invalid-namespace",
280 Self::InvalidXml => "invalid-xml",
281 Self::NotAuthorized => "not-authorized",
282 Self::NotWellFormed => "not-well-formed",
283 Self::PolicyViolation => "policy-violation",
284 Self::RemoteConnectionFailed => "remote-connection-failed",
285 Self::Reset => "reset",
286 Self::ResourceConstraint => "resource-constraint",
287 Self::RestrictedXml => "restricted-xml",
288 Self::SeeOtherHost(ref host) => return write!(f, "see-other-host: {}", host),
289 Self::SystemShutdown => "system-shutdown",
290 Self::UndefinedCondition => "undefined-condition",
291 Self::UnsupportedEncoding => "unsupported-encoding",
292 Self::UnsupportedFeature => "unsupported-feature",
293 Self::UnsupportedStanzaType => "unsupported-stanza-type",
294 Self::UnsupportedVersion => "unsupported-version",
295 };
296 f.write_str(s)
297 }
298}
299
300/// Stream error as specified in RFC 6120.
301#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
302#[xml(namespace = ns::STREAM, name = "error")]
303pub struct StreamError {
304 /// The enumerated error condition which triggered this stream error.
305 #[xml(child)]
306 pub condition: DefinedCondition,
307
308 /// Optional error text. The first part is the optional `xml:lang`
309 /// language tag, the second part is the actual text content.
310 #[xml(extract(default, fields(attribute(name = "xml:lang", default, type_ = Option<String>), text(type_ = String))))]
311 pub text: Option<(Option<String>, String)>,
312
313 /// Optional application-defined element which refines the specified
314 /// [`Self::condition`].
315 // TODO: use n = 1 once we have it.
316 #[xml(element(n = ..))]
317 pub application_specific: Vec<Element>,
318}
319
320impl fmt::Display for StreamError {
321 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
322 <DefinedCondition as fmt::Display>::fmt(&self.condition, f)?;
323 match self.text {
324 Some((_, ref text)) => write!(f, " ({:?})", text)?,
325 None => (),
326 };
327 match self.application_specific.get(0) {
328 Some(cond) => {
329 f.write_str(&String::from(cond))?;
330 }
331 None => (),
332 }
333 Ok(())
334 }
335}
336
337/// Wrapper around [`StreamError`] which implements [`core::error::Error`]
338/// with an appropriate error message.
339#[derive(FromXml, AsXml, Debug)]
340#[xml(transparent)]
341pub struct ReceivedStreamError(pub StreamError);
342
343impl fmt::Display for ReceivedStreamError {
344 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
345 write!(f, "received stream error: {}", self.0)
346 }
347}
348
349impl Error for ReceivedStreamError {}
350
351/// Wrapper around [`StreamError`] which implements [`core::error::Error`]
352/// with an appropriate error message.
353#[derive(FromXml, AsXml, Debug)]
354#[xml(transparent)]
355pub struct SentStreamError(pub StreamError);
356
357impl fmt::Display for SentStreamError {
358 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
359 write!(f, "sent stream error: {}", self.0)
360 }
361}
362
363impl Error for SentStreamError {}