1pub mod generate;
8pub mod parse;
9
10use std::fmt::Write;
11use std::net::IpAddr;
12
13use crate::{
14 dmarc::Dmarc,
15 report::{
16 ActionDisposition, Alignment, DKIMAuthResult, Disposition, DkimResult, DmarcResult,
17 PolicyOverride, PolicyOverrideReason, Record, Report, SPFAuthResult, SPFDomainScope,
18 SpfResult,
19 },
20 ArcOutput, DkimOutput, DmarcOutput, SpfOutput,
21};
22
23use super::PolicyPublished;
24
25impl Report {
26 pub fn new() -> Self {
27 Self::default()
28 }
29
30 pub fn version(&self) -> f32 {
31 self.version
32 }
33
34 pub fn with_version(mut self, version: f32) -> Self {
35 self.version = version;
36 self
37 }
38
39 pub fn org_name(&self) -> &str {
40 &self.report_metadata.org_name
41 }
42
43 pub fn with_org_name(mut self, org_name: impl Into<String>) -> Self {
44 self.report_metadata.org_name = org_name.into();
45 self
46 }
47
48 pub fn email(&self) -> &str {
49 &self.report_metadata.email
50 }
51
52 pub fn with_email(mut self, email: impl Into<String>) -> Self {
53 self.report_metadata.email = email.into();
54 self
55 }
56
57 pub fn extra_contact_info(&self) -> Option<&str> {
58 self.report_metadata.extra_contact_info.as_deref()
59 }
60
61 pub fn with_extra_contact_info(mut self, extra_contact_info: impl Into<String>) -> Self {
62 self.report_metadata.extra_contact_info = Some(extra_contact_info.into());
63 self
64 }
65
66 pub fn report_id(&self) -> &str {
67 &self.report_metadata.report_id
68 }
69
70 pub fn with_report_id(mut self, report_id: impl Into<String>) -> Self {
71 self.report_metadata.report_id = report_id.into();
72 self
73 }
74
75 pub fn date_range_begin(&self) -> u64 {
76 self.report_metadata.date_range.begin
77 }
78
79 pub fn with_date_range_begin(mut self, date_range_begin: u64) -> Self {
80 self.report_metadata.date_range.begin = date_range_begin;
81 self
82 }
83
84 pub fn date_range_end(&self) -> u64 {
85 self.report_metadata.date_range.end
86 }
87
88 pub fn with_date_range_end(mut self, date_range_end: u64) -> Self {
89 self.report_metadata.date_range.end = date_range_end;
90 self
91 }
92
93 pub fn error(&self) -> &[String] {
94 &self.report_metadata.error
95 }
96
97 pub fn with_error(mut self, error: impl Into<String>) -> Self {
98 self.report_metadata.error.push(error.into());
99 self
100 }
101
102 pub fn domain(&self) -> &str {
103 &self.policy_published.domain
104 }
105
106 pub fn with_domain(mut self, domain: impl Into<String>) -> Self {
107 self.policy_published.domain = domain.into();
108 self
109 }
110
111 pub fn fo(&self) -> Option<&str> {
112 self.policy_published.fo.as_deref()
113 }
114
115 pub fn with_fo(mut self, fo: impl Into<String>) -> Self {
116 self.policy_published.fo = Some(fo.into());
117 self
118 }
119
120 pub fn version_published(&self) -> Option<f32> {
121 self.policy_published.version_published
122 }
123
124 pub fn with_version_published(mut self, version_published: f32) -> Self {
125 self.policy_published.version_published = Some(version_published);
126 self
127 }
128
129 pub fn adkim(&self) -> Alignment {
130 self.policy_published.adkim
131 }
132
133 pub fn with_adkim(mut self, adkim: Alignment) -> Self {
134 self.policy_published.adkim = adkim;
135 self
136 }
137
138 pub fn aspf(&self) -> Alignment {
139 self.policy_published.aspf
140 }
141
142 pub fn with_aspf(mut self, aspf: Alignment) -> Self {
143 self.policy_published.aspf = aspf;
144 self
145 }
146
147 pub fn p(&self) -> Disposition {
148 self.policy_published.p
149 }
150
151 pub fn with_p(mut self, p: Disposition) -> Self {
152 self.policy_published.p = p;
153 self
154 }
155
156 pub fn sp(&self) -> Disposition {
157 self.policy_published.sp
158 }
159
160 pub fn with_sp(mut self, sp: Disposition) -> Self {
161 self.policy_published.sp = sp;
162 self
163 }
164
165 pub fn testing(&self) -> bool {
166 self.policy_published.testing
167 }
168
169 pub fn with_testing(mut self, testing: bool) -> Self {
170 self.policy_published.testing = testing;
171 self
172 }
173
174 pub fn records(&self) -> &[Record] {
175 &self.record
176 }
177
178 pub fn with_record(mut self, record: Record) -> Self {
179 self.record.push(record);
180 self
181 }
182
183 pub fn add_record(&mut self, record: Record) {
184 self.record.push(record);
185 }
186
187 pub fn with_policy_published(mut self, policy_published: PolicyPublished) -> Self {
188 self.policy_published = policy_published;
189 self
190 }
191}
192
193impl Record {
194 pub fn new() -> Self {
195 Record::default()
196 }
197
198 pub fn with_dkim_output(mut self, dkim_output: &[DkimOutput]) -> Self {
199 for dkim in dkim_output {
200 if let Some(signature) = &dkim.signature {
201 let (result, human_result) = match &dkim.result {
202 crate::DkimResult::Pass => (DkimResult::Pass, None),
203 crate::DkimResult::Neutral(err) => {
204 (DkimResult::Neutral, err.to_string().into())
205 }
206 crate::DkimResult::Fail(err) => (DkimResult::Fail, err.to_string().into()),
207 crate::DkimResult::PermError(err) => {
208 (DkimResult::PermError, err.to_string().into())
209 }
210 crate::DkimResult::TempError(err) => {
211 (DkimResult::TempError, err.to_string().into())
212 }
213 crate::DkimResult::None => (DkimResult::None, None),
214 };
215
216 self.auth_results.dkim.push(DKIMAuthResult {
217 domain: signature.d.to_string(),
218 selector: signature.s.to_string(),
219 result,
220 human_result,
221 });
222 }
223 }
224 self
225 }
226
227 pub fn with_spf_output(mut self, spf_output: &SpfOutput, scope: SPFDomainScope) -> Self {
228 self.auth_results.spf.push(SPFAuthResult {
229 domain: spf_output.domain.to_string(),
230 scope,
231 result: match spf_output.result {
232 crate::SpfResult::Pass => SpfResult::Pass,
233 crate::SpfResult::Fail => SpfResult::Fail,
234 crate::SpfResult::SoftFail => SpfResult::SoftFail,
235 crate::SpfResult::Neutral => SpfResult::Neutral,
236 crate::SpfResult::TempError => SpfResult::TempError,
237 crate::SpfResult::PermError => SpfResult::PermError,
238 crate::SpfResult::None => SpfResult::None,
239 },
240 human_result: None,
241 });
242 self
243 }
244
245 pub fn with_dmarc_output(mut self, dmarc_output: &DmarcOutput) -> Self {
246 self.row.policy_evaluated.disposition = if dmarc_output.dkim_result
247 == crate::DmarcResult::Pass
248 || dmarc_output.spf_result == crate::DmarcResult::Pass
249 {
250 ActionDisposition::Pass
251 } else {
252 match dmarc_output.policy {
253 crate::dmarc::Policy::None => ActionDisposition::None,
254 crate::dmarc::Policy::Quarantine => ActionDisposition::Quarantine,
255 crate::dmarc::Policy::Reject => ActionDisposition::Reject,
256 crate::dmarc::Policy::Unspecified => ActionDisposition::None,
257 }
258 };
259 self.row.policy_evaluated.dkim = (&dmarc_output.dkim_result).into();
260 self.row.policy_evaluated.spf = (&dmarc_output.spf_result).into();
261 self
262 }
263
264 pub fn with_arc_output(mut self, arc_output: &ArcOutput) -> Self {
265 if arc_output.result == crate::DkimResult::Pass {
266 let mut comment = "arc=pass".to_string();
267 for set in arc_output.set.iter().rev() {
268 let seal = &set.seal.header;
269 write!(
270 &mut comment,
271 " as[{}].d={} as[{}].s={}",
272 seal.i, seal.d, seal.i, seal.s
273 )
274 .ok();
275 }
276 self.row
277 .policy_evaluated
278 .reason
279 .push(PolicyOverrideReason::new(PolicyOverride::LocalPolicy).with_comment(comment));
280 }
281 self
282 }
283
284 pub fn source_ip(&self) -> Option<IpAddr> {
285 self.row.source_ip
286 }
287
288 pub fn with_source_ip(mut self, source_ip: IpAddr) -> Self {
289 self.row.source_ip = source_ip.into();
290 self
291 }
292
293 pub fn count(&self) -> u32 {
294 self.row.count
295 }
296
297 pub fn with_count(mut self, count: u32) -> Self {
298 self.row.count = count;
299 self
300 }
301
302 pub fn action_disposition(&self) -> ActionDisposition {
303 self.row.policy_evaluated.disposition
304 }
305
306 pub fn with_action_disposition(mut self, disposition: ActionDisposition) -> Self {
307 self.row.policy_evaluated.disposition = disposition;
308 self
309 }
310
311 pub fn dmarc_dkim_result(&self) -> DmarcResult {
312 self.row.policy_evaluated.dkim
313 }
314
315 pub fn with_dmarc_dkim_result(mut self, dkim: DmarcResult) -> Self {
316 self.row.policy_evaluated.dkim = dkim;
317 self
318 }
319
320 pub fn dmarc_spf_result(&self) -> DmarcResult {
321 self.row.policy_evaluated.spf
322 }
323
324 pub fn with_dmarc_spf_result(mut self, spf: DmarcResult) -> Self {
325 self.row.policy_evaluated.spf = spf;
326 self
327 }
328
329 pub fn policy_override_reason(&self) -> &[PolicyOverrideReason] {
330 &self.row.policy_evaluated.reason
331 }
332
333 pub fn with_policy_override_reason(mut self, reason: PolicyOverrideReason) -> Self {
334 self.row.policy_evaluated.reason.push(reason);
335 self
336 }
337
338 pub fn envelope_from(&self) -> &str {
339 &self.identifiers.envelope_from
340 }
341
342 pub fn with_envelope_from(mut self, envelope_from: impl Into<String>) -> Self {
343 self.identifiers.envelope_from = envelope_from.into();
344 self
345 }
346
347 pub fn header_from(&self) -> &str {
348 &self.identifiers.header_from
349 }
350
351 pub fn with_header_from(mut self, header_from: impl Into<String>) -> Self {
352 self.identifiers.header_from = header_from.into();
353 self
354 }
355
356 pub fn envelope_to(&self) -> Option<&str> {
357 self.identifiers.envelope_to.as_deref()
358 }
359
360 pub fn with_envelope_to(mut self, envelope_to: impl Into<String>) -> Self {
361 self.identifiers.envelope_to = Some(envelope_to.into());
362 self
363 }
364
365 pub fn dkim_auth_result(&self) -> &[DKIMAuthResult] {
366 &self.auth_results.dkim
367 }
368
369 pub fn with_dkim_auth_result(mut self, auth_result: DKIMAuthResult) -> Self {
370 self.auth_results.dkim.push(auth_result);
371 self
372 }
373
374 pub fn spf_auth_result(&self) -> &[SPFAuthResult] {
375 &self.auth_results.spf
376 }
377
378 pub fn with_spf_auth_result(mut self, auth_result: SPFAuthResult) -> Self {
379 self.auth_results.spf.push(auth_result);
380 self
381 }
382}
383
384impl PolicyPublished {
385 pub fn from_record(domain: impl Into<String>, dmarc: &Dmarc) -> Self {
386 PolicyPublished {
387 domain: domain.into(),
388 adkim: (&dmarc.adkim).into(),
389 aspf: (&dmarc.aspf).into(),
390 p: (&dmarc.p).into(),
391 sp: (&dmarc.sp).into(),
392 testing: dmarc.t,
393 fo: match &dmarc.fo {
394 crate::dmarc::Report::All => "0",
395 crate::dmarc::Report::Any => "1",
396 crate::dmarc::Report::Dkim => "d",
397 crate::dmarc::Report::Spf => "s",
398 crate::dmarc::Report::DkimSpf => "d:s",
399 }
400 .to_string()
401 .into(),
402 version_published: None,
403 }
404 }
405}
406
407impl DKIMAuthResult {
408 pub fn new() -> Self {
409 DKIMAuthResult::default()
410 }
411
412 pub fn domain(&self) -> &str {
413 &self.domain
414 }
415
416 pub fn with_domain(mut self, domain: impl Into<String>) -> Self {
417 self.domain = domain.into();
418 self
419 }
420
421 pub fn selector(&self) -> &str {
422 &self.selector
423 }
424
425 pub fn with_selector(mut self, selector: impl Into<String>) -> Self {
426 self.selector = selector.into();
427 self
428 }
429
430 pub fn result(&self) -> DkimResult {
431 self.result
432 }
433
434 pub fn with_result(mut self, result: DkimResult) -> Self {
435 self.result = result;
436 self
437 }
438
439 pub fn human_result(&self) -> Option<&str> {
440 self.human_result.as_deref()
441 }
442
443 pub fn with_human_result(mut self, human_result: impl Into<String>) -> Self {
444 self.human_result = Some(human_result.into());
445 self
446 }
447}
448
449impl SPFAuthResult {
450 pub fn new() -> Self {
451 SPFAuthResult::default()
452 }
453
454 pub fn domain(&self) -> &str {
455 &self.domain
456 }
457
458 pub fn with_domain(mut self, domain: impl Into<String>) -> Self {
459 self.domain = domain.into();
460 self
461 }
462
463 pub fn scope(&self) -> SPFDomainScope {
464 self.scope
465 }
466
467 pub fn with_scope(mut self, scope: SPFDomainScope) -> Self {
468 self.scope = scope;
469 self
470 }
471
472 pub fn result(&self) -> SpfResult {
473 self.result
474 }
475
476 pub fn with_result(mut self, result: SpfResult) -> Self {
477 self.result = result;
478 self
479 }
480
481 pub fn human_result(&self) -> Option<&str> {
482 self.human_result.as_deref()
483 }
484
485 pub fn with_human_result(mut self, human_result: impl Into<String>) -> Self {
486 self.human_result = Some(human_result.into());
487 self
488 }
489}
490
491impl PolicyOverrideReason {
492 pub fn new(type_: PolicyOverride) -> Self {
493 PolicyOverrideReason {
494 type_,
495 comment: None,
496 }
497 }
498
499 pub fn with_comment(mut self, comment: impl Into<String>) -> Self {
500 self.comment = Some(comment.into());
501 self
502 }
503
504 pub fn comment(&self) -> Option<&str> {
505 self.comment.as_deref()
506 }
507
508 pub fn policy_override(&self) -> PolicyOverride {
509 self.type_
510 }
511}
512
513impl From<&crate::DmarcResult> for DmarcResult {
514 fn from(result: &crate::DmarcResult) -> Self {
515 match result {
516 crate::DmarcResult::Pass => DmarcResult::Pass,
517 _ => DmarcResult::Fail,
518 }
519 }
520}
521
522impl From<&crate::dmarc::Alignment> for Alignment {
523 fn from(aligment: &crate::dmarc::Alignment) -> Self {
524 match aligment {
525 crate::dmarc::Alignment::Relaxed => Alignment::Relaxed,
526 crate::dmarc::Alignment::Strict => Alignment::Strict,
527 }
528 }
529}
530
531impl From<&crate::dmarc::Policy> for Disposition {
532 fn from(policy: &crate::dmarc::Policy) -> Self {
533 match policy {
534 crate::dmarc::Policy::None => Disposition::None,
535 crate::dmarc::Policy::Quarantine => Disposition::Quarantine,
536 crate::dmarc::Policy::Reject => Disposition::Reject,
537 crate::dmarc::Policy::Unspecified => Disposition::None,
538 }
539 }
540}