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}