mail_auth/error.rs
1/*
2 * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
3 *
4 * SPDX-License-Identifier: Apache-2.0 OR MIT
5 */
6
7use crate::crypto::CryptoError;
8#[cfg(not(feature = "dns-doh"))]
9use hickory_resolver::proto::op::ResponseCode;
10use std::{fmt::Display, io};
11
12/// A DNS lookup failure, carried by [`Error::Dns`].
13///
14/// Verifiers map these errors to result codes: [`DnsError::Resolver`] becomes
15/// a `temperror` result, while [`DnsError::RecordNotFound`] and
16/// [`DnsError::InvalidRecordType`] become `none` or `permerror` depending on
17/// the protocol.
18#[derive(Debug, Clone, PartialEq, Eq)]
19pub enum DnsError {
20 /// The query could not be completed (network failure, timeout, server
21 /// failure). The string holds the resolver's error message. Treated as a
22 /// transient error.
23 Resolver(String),
24 /// The query completed but returned no records of the requested type.
25 /// Holds the DNS response code (`NXDOMAIN` or `NOERROR` with an empty
26 /// answer section).
27 #[cfg(not(feature = "dns-doh"))]
28 RecordNotFound(ResponseCode),
29 /// The query completed but returned no records of the requested type.
30 /// Holds the numeric DNS response code (3 for `NXDOMAIN`).
31 #[cfg(feature = "dns-doh")]
32 RecordNotFound(u16),
33 /// Records were returned, but none of them could be parsed as the expected
34 /// record type (for example, a TXT record without `v=spf1` when an SPF
35 /// record was requested).
36 InvalidRecordType,
37}
38
39/// The error type for every fallible operation in this crate.
40///
41/// Verifiers do not return this type directly; they embed it in their result
42/// enums (for example `DkimResult::Fail(Error)`) to explain why a check did not
43/// pass. Parsers, signers and DNS lookup helpers return it through [`Result`].
44#[derive(Debug, Clone, PartialEq, Eq)]
45pub enum Error {
46 /// A header, DNS record or key could not be parsed.
47 Parse,
48 /// A required tag or parameter was missing from a header or record.
49 MissingParameters,
50 /// None of the headers selected for signing are present in the message,
51 /// so there is nothing to sign.
52 NoHeadersFound,
53 /// A Base64 value could not be decoded.
54 Base64,
55 /// An identifier did not align with the domain it had to match (for
56 /// example, `iprev` forward lookups that do not return the client
57 /// address, or a DMARC identifier alignment failure).
58 NotAligned,
59 /// An I/O error. The string holds the underlying error message.
60 Io(String),
61 /// A signing or verification failure reported by the cryptography backend.
62 Crypto(CryptoError),
63 /// A DNS lookup failure.
64 Dns(DnsError),
65 /// A DKIM specific failure (RFC 6376).
66 Dkim(crate::dkim::DkimError),
67 /// An ARC specific failure (RFC 8617).
68 #[cfg(feature = "arc")]
69 Arc(crate::arc::ArcError),
70 /// A DKIM2 specific failure (draft-ietf-dkim-dkim2-spec).
71 Dkim2(crate::dkim2::Dkim2Error),
72}
73
74/// A [`std::result::Result`] with the error type fixed to [`Error`].
75pub type Result<T> = std::result::Result<T, Error>;
76
77impl std::error::Error for Error {}
78
79impl Display for Error {
80 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
81 match self {
82 Error::Parse => write!(f, "Parse error"),
83 Error::MissingParameters => write!(f, "Missing parameters"),
84 Error::NoHeadersFound => write!(f, "No headers found"),
85 Error::Io(e) => write!(f, "I/O error: {e}"),
86 Error::Base64 => write!(f, "Base64 encode or decode error."),
87 Error::NotAligned => write!(f, "Policy not aligned"),
88 Error::Crypto(e) => e.fmt(f),
89 Error::Dns(e) => e.fmt(f),
90 Error::Dkim(e) => e.fmt(f),
91 #[cfg(feature = "arc")]
92 Error::Arc(e) => e.fmt(f),
93 Error::Dkim2(e) => e.fmt(f),
94 }
95 }
96}
97
98impl Display for DnsError {
99 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
100 match self {
101 DnsError::Resolver(err) => write!(f, "DNS resolution error: {err}"),
102 DnsError::RecordNotFound(code) => write!(f, "DNS record not found: {code}"),
103 DnsError::InvalidRecordType => write!(f, "Invalid record"),
104 }
105 }
106}
107
108impl From<io::Error> for Error {
109 fn from(err: io::Error) -> Self {
110 Error::Io(err.to_string())
111 }
112}
113
114#[cfg(feature = "rsa")]
115impl From<rsa::errors::Error> for Error {
116 fn from(err: rsa::errors::Error) -> Self {
117 Error::Crypto(CryptoError::Library(err.to_string()))
118 }
119}