Skip to main content

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}