1use std::{fmt::Display, sync::Arc};
8
9use serde::{Deserialize, Serialize};
10
11use crate::{DmarcOutput, DmarcResult, Error, Version};
12
13pub mod parse;
14pub mod verify;
15
16#[derive(Debug, Hash, Clone, PartialEq, Eq)]
17pub struct Dmarc {
18 pub v: Version,
19 pub adkim: Alignment,
20 pub aspf: Alignment,
21 pub fo: Report,
22 pub np: Policy,
23 pub p: Policy,
24 pub psd: Psd,
25 pub pct: u8,
26 pub rf: u8,
27 pub ri: u32,
28 pub rua: Vec<URI>,
29 pub ruf: Vec<URI>,
30 pub sp: Policy,
31 pub t: bool,
32}
33
34#[derive(Debug, Hash, Clone, PartialEq, Eq, Serialize, Deserialize)]
35#[cfg_attr(
36 feature = "rkyv",
37 derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
38)]
39#[allow(clippy::upper_case_acronyms)]
40pub struct URI {
41 pub uri: String,
42 pub max_size: usize,
43}
44
45#[derive(Debug, Hash, Clone, PartialEq, Eq)]
46pub enum Alignment {
47 Relaxed,
48 Strict,
49}
50
51#[derive(Debug, Hash, Clone, PartialEq, Eq)]
52pub enum Psd {
53 Yes,
54 No,
55 Default,
56}
57
58#[derive(Debug, Hash, Clone, PartialEq, Eq)]
59pub enum Report {
60 All,
61 Any,
62 Dkim,
63 Spf,
64 DkimSpf,
65}
66
67#[derive(Debug, Hash, Clone, Copy, PartialEq, Eq)]
68pub enum Policy {
69 None,
70 Quarantine,
71 Reject,
72 Unspecified,
73}
74
75#[derive(Debug, Clone, PartialEq, Eq)]
76#[repr(u8)]
77pub(crate) enum Format {
78 Afrf = 1,
79}
80
81impl From<Format> for u64 {
82 fn from(f: Format) -> Self {
83 f as u64
84 }
85}
86
87impl URI {
88 #[cfg(test)]
89 pub fn new(uri: impl Into<String>, max_size: usize) -> Self {
90 URI {
91 uri: uri.into(),
92 max_size,
93 }
94 }
95
96 pub fn uri(&self) -> &str {
97 &self.uri
98 }
99
100 pub fn max_size(&self) -> usize {
101 self.max_size
102 }
103}
104
105impl From<Error> for DmarcResult {
106 fn from(err: Error) -> Self {
107 if matches!(&err, Error::DnsError(_)) {
108 DmarcResult::TempError(err)
109 } else {
110 DmarcResult::PermError(err)
111 }
112 }
113}
114
115impl Default for DmarcOutput {
116 fn default() -> Self {
117 Self {
118 domain: String::new(),
119 policy: Policy::None,
120 record: None,
121 spf_result: DmarcResult::None,
122 dkim_result: DmarcResult::None,
123 }
124 }
125}
126
127impl DmarcOutput {
128 pub fn new(domain: String) -> Self {
129 DmarcOutput {
130 domain,
131 ..Default::default()
132 }
133 }
134
135 pub fn with_domain(mut self, domain: &str) -> Self {
136 self.domain = domain.to_string();
137 self
138 }
139
140 pub fn with_spf_result(mut self, result: DmarcResult) -> Self {
141 self.spf_result = result;
142 self
143 }
144
145 pub fn with_dkim_result(mut self, result: DmarcResult) -> Self {
146 self.dkim_result = result;
147 self
148 }
149
150 pub fn with_record(mut self, record: Arc<Dmarc>) -> Self {
151 self.record = record.into();
152 self
153 }
154
155 pub fn domain(&self) -> &str {
156 &self.domain
157 }
158
159 pub fn into_domain(self) -> String {
160 self.domain
161 }
162
163 pub fn policy(&self) -> Policy {
164 self.policy
165 }
166
167 pub fn dkim_result(&self) -> &DmarcResult {
168 &self.dkim_result
169 }
170
171 pub fn spf_result(&self) -> &DmarcResult {
172 &self.spf_result
173 }
174
175 pub fn dmarc_record(&self) -> Option<&Dmarc> {
176 self.record.as_deref()
177 }
178
179 pub fn dmarc_record_cloned(&self) -> Option<Arc<Dmarc>> {
180 self.record.clone()
181 }
182
183 pub fn requested_reports(&self) -> bool {
184 self.record
185 .as_ref()
186 .is_some_and(|r| !r.rua.is_empty() || !r.ruf.is_empty())
187 }
188
189 pub fn failure_report(&self) -> Option<Report> {
191 match &self.record {
193 Some(record)
194 if !record.ruf.is_empty()
195 && ((self.dkim_result != DmarcResult::Pass
196 && matches!(record.fo, Report::Any | Report::Dkim | Report::DkimSpf))
197 || (self.spf_result != DmarcResult::Pass
198 && matches!(
199 record.fo,
200 Report::Any | Report::Spf | Report::DkimSpf
201 ))
202 || (self.dkim_result != DmarcResult::Pass
203 && self.spf_result != DmarcResult::Pass
204 && record.fo == Report::All)) =>
205 {
206 Some(record.fo.clone())
207 }
208 _ => None,
209 }
210 }
211}
212
213impl Dmarc {
214 pub fn pct(&self) -> u8 {
215 self.pct
216 }
217
218 pub fn ruf(&self) -> &[URI] {
219 &self.ruf
220 }
221
222 pub fn rua(&self) -> &[URI] {
223 &self.rua
224 }
225}
226
227impl Display for Policy {
228 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
229 f.write_str(match self {
230 Policy::Quarantine => "quarantine",
231 Policy::Reject => "reject",
232 Policy::None | Policy::Unspecified => "none",
233 })
234 }
235}