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