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