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