Skip to main content

email_auth/dmarc/
types.rs

1/// DMARC policy.
2#[derive(Debug, Clone, Copy, PartialEq, Eq)]
3pub enum Policy {
4    /// No action, monitoring only.
5    None,
6    /// Treat as suspicious (spam folder).
7    Quarantine,
8    /// Reject the message.
9    Reject,
10}
11
12impl Policy {
13    /// Parse policy string (case-insensitive).
14    pub fn parse(s: &str) -> Option<Self> {
15        match s.to_ascii_lowercase().as_str() {
16            "none" => Some(Policy::None),
17            "quarantine" => Some(Policy::Quarantine),
18            "reject" => Some(Policy::Reject),
19            _ => Option::None,
20        }
21    }
22}
23
24/// Alignment mode for DKIM/SPF.
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26pub enum AlignmentMode {
27    /// Organizational domain match.
28    Relaxed,
29    /// Exact domain match.
30    Strict,
31}
32
33impl AlignmentMode {
34    /// Parse alignment mode: "r" → Relaxed, "s" → Strict.
35    pub fn parse(s: &str) -> Option<Self> {
36        match s.to_ascii_lowercase().as_str() {
37            "r" => Some(AlignmentMode::Relaxed),
38            "s" => Some(AlignmentMode::Strict),
39            _ => Option::None,
40        }
41    }
42}
43
44/// Failure reporting option (fo= tag).
45#[derive(Debug, Clone, Copy, PartialEq, Eq)]
46pub enum FailureOption {
47    /// Generate report if all mechanisms fail.
48    Zero,
49    /// Generate report if any mechanism fails.
50    One,
51    /// Generate report if DKIM fails.
52    D,
53    /// Generate report if SPF fails.
54    S,
55}
56
57impl FailureOption {
58    /// Parse a single failure option character (case-insensitive).
59    pub fn parse(s: &str) -> Option<Self> {
60        match s.to_ascii_lowercase().as_str() {
61            "0" => Some(FailureOption::Zero),
62            "1" => Some(FailureOption::One),
63            "d" => Some(FailureOption::D),
64            "s" => Some(FailureOption::S),
65            _ => Option::None,
66        }
67    }
68}
69
70/// Report format.
71#[derive(Debug, Clone, Copy, PartialEq, Eq)]
72pub enum ReportFormat {
73    /// Authentication Failure Reporting Format (RFC 6591).
74    Afrf,
75}
76
77impl ReportFormat {
78    pub fn parse(s: &str) -> Option<Self> {
79        match s.to_ascii_lowercase().as_str() {
80            "afrf" => Some(ReportFormat::Afrf),
81            _ => Option::None,
82        }
83    }
84}
85
86/// Report URI (mailto: address with optional size limit).
87#[derive(Debug, Clone, PartialEq, Eq)]
88pub struct ReportUri {
89    /// Email address (after stripping mailto: prefix).
90    pub address: String,
91    /// Maximum report size in bytes.
92    pub max_size: Option<u64>,
93}
94
95/// Parsed DMARC record.
96#[derive(Debug, Clone, PartialEq, Eq)]
97pub struct DmarcRecord {
98    /// Policy for organizational domain (p= tag).
99    pub policy: Policy,
100    /// Subdomain policy (sp= tag, defaults to p=).
101    pub subdomain_policy: Policy,
102    /// Non-existent subdomain policy (np= tag, RFC 9091).
103    pub non_existent_subdomain_policy: Option<Policy>,
104    /// DKIM alignment mode (adkim= tag, default: Relaxed).
105    pub dkim_alignment: AlignmentMode,
106    /// SPF alignment mode (aspf= tag, default: Relaxed).
107    pub spf_alignment: AlignmentMode,
108    /// Percentage of messages to apply policy (pct= tag, default: 100).
109    pub percent: u8,
110    /// Failure reporting options (fo= tag).
111    pub failure_options: Vec<FailureOption>,
112    /// Report format (rf= tag, default: AFRF).
113    pub report_format: ReportFormat,
114    /// Aggregate report interval in seconds (ri= tag, default: 86400).
115    pub report_interval: u32,
116    /// Aggregate report URIs (rua= tag).
117    pub rua: Vec<ReportUri>,
118    /// Failure report URIs (ruf= tag).
119    pub ruf: Vec<ReportUri>,
120}
121
122/// DMARC evaluation result.
123#[derive(Debug, Clone, PartialEq, Eq)]
124pub struct DmarcResult {
125    /// What to do with the message.
126    pub disposition: Disposition,
127    /// Whether any DKIM signature aligned.
128    pub dkim_aligned: bool,
129    /// Whether SPF passed and aligned.
130    pub spf_aligned: bool,
131    /// The policy that was applied.
132    pub applied_policy: Option<Policy>,
133    /// The DMARC record found (if any).
134    pub record: Option<DmarcRecord>,
135}
136
137/// DMARC disposition.
138#[derive(Debug, Clone, Copy, PartialEq, Eq)]
139pub enum Disposition {
140    /// Message passed DMARC.
141    Pass,
142    /// Quarantine per policy.
143    Quarantine,
144    /// Reject per policy.
145    Reject,
146    /// No policy (monitoring, pct sampling excluded, or no record).
147    None,
148    /// DNS temporary failure during record discovery.
149    TempFail,
150}