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