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, Disposition, DkimResult, DmarcResult,
18 PolicyOverride, PolicyOverrideReason, Record, Report, SPFAuthResult, SPFDomainScope,
19 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 records(&self) -> &[Record] {
176 &self.record
177 }
178
179 pub fn with_record(mut self, record: Record) -> Self {
180 self.record.push(record);
181 self
182 }
183
184 pub fn add_record(&mut self, record: Record) {
185 self.record.push(record);
186 }
187
188 pub fn with_policy_published(mut self, policy_published: PolicyPublished) -> Self {
189 self.policy_published = policy_published;
190 self
191 }
192}
193
194impl Record {
195 pub fn new() -> Self {
196 Record::default()
197 }
198
199 pub fn with_dkim_output(mut self, dkim_output: &[DkimOutput]) -> Self {
200 for dkim in dkim_output {
201 if let Some(signature) = &dkim.signature {
202 let (result, human_result) = match &dkim.result {
203 crate::DkimResult::Pass => (DkimResult::Pass, None),
204 crate::DkimResult::Neutral(err) => {
205 (DkimResult::Neutral, err.to_string().into())
206 }
207 crate::DkimResult::Fail(err) => (DkimResult::Fail, err.to_string().into()),
208 crate::DkimResult::PermError(err) => {
209 (DkimResult::PermError, err.to_string().into())
210 }
211 crate::DkimResult::TempError(err) => {
212 (DkimResult::TempError, err.to_string().into())
213 }
214 crate::DkimResult::None => (DkimResult::None, None),
215 };
216
217 self.auth_results.dkim.push(DKIMAuthResult {
218 domain: signature.d.to_string(),
219 selector: signature.s.to_string(),
220 result,
221 human_result,
222 });
223 }
224 }
225 self
226 }
227
228 pub fn with_dkim2_output(mut self, dkim2_output: &Dkim2Output) -> Self {
229 for link in dkim2_output.chain() {
230 let (result, human_result) = match &link.result {
231 Dkim2Result::Pass => (DkimResult::Pass, None),
232 Dkim2Result::Fail(err) => (DkimResult::Fail, err.to_string().into()),
233 Dkim2Result::PermError(err) => (DkimResult::PermError, err.to_string().into()),
234 Dkim2Result::TempError(err) => (DkimResult::TempError, err.to_string().into()),
235 Dkim2Result::None => (DkimResult::None, None),
236 };
237
238 self.auth_results.dkim.push(DKIMAuthResult {
239 domain: link.signature.d.to_string(),
240 selector: link
241 .signature
242 .s
243 .first()
244 .map(|value| value.selector.clone())
245 .unwrap_or_default(),
246 result,
247 human_result,
248 });
249 }
250 self
251 }
252
253 pub fn with_spf_output(mut self, spf_output: &SpfOutput, scope: SPFDomainScope) -> Self {
254 self.auth_results.spf.push(SPFAuthResult {
255 domain: spf_output.domain.to_string(),
256 scope,
257 result: match spf_output.result {
258 crate::SpfResult::Pass => SpfResult::Pass,
259 crate::SpfResult::Fail => SpfResult::Fail,
260 crate::SpfResult::SoftFail => SpfResult::SoftFail,
261 crate::SpfResult::Neutral => SpfResult::Neutral,
262 crate::SpfResult::TempError => SpfResult::TempError,
263 crate::SpfResult::PermError => SpfResult::PermError,
264 crate::SpfResult::None => SpfResult::None,
265 },
266 human_result: None,
267 });
268 self
269 }
270
271 pub fn with_dmarc_output(mut self, dmarc_output: &DmarcOutput) -> Self {
272 self.row.policy_evaluated.disposition = if dmarc_output.dkim_result
273 == crate::DmarcResult::Pass
274 || dmarc_output.spf_result == crate::DmarcResult::Pass
275 {
276 ActionDisposition::Pass
277 } else {
278 match dmarc_output.policy {
279 crate::dmarc::Policy::None => ActionDisposition::None,
280 crate::dmarc::Policy::Quarantine => ActionDisposition::Quarantine,
281 crate::dmarc::Policy::Reject => ActionDisposition::Reject,
282 crate::dmarc::Policy::Unspecified => ActionDisposition::None,
283 }
284 };
285 self.row.policy_evaluated.dkim = (&dmarc_output.dkim_result).into();
286 self.row.policy_evaluated.spf = (&dmarc_output.spf_result).into();
287 self
288 }
289
290 #[cfg(feature = "arc")]
291 pub fn with_arc_output(mut self, arc_output: &ArcOutput) -> Self {
292 if arc_output.result == crate::DkimResult::Pass {
293 let mut comment = "arc=pass".to_string();
294 for set in arc_output.set.iter().rev() {
295 let seal = &set.seal.header;
296 write!(
297 &mut comment,
298 " as[{}].d={} as[{}].s={}",
299 seal.i, seal.d, seal.i, seal.s
300 )
301 .ok();
302 }
303 self.row
304 .policy_evaluated
305 .reason
306 .push(PolicyOverrideReason::new(PolicyOverride::LocalPolicy).with_comment(comment));
307 }
308 self
309 }
310
311 pub fn source_ip(&self) -> Option<IpAddr> {
312 self.row.source_ip
313 }
314
315 pub fn with_source_ip(mut self, source_ip: IpAddr) -> Self {
316 self.row.source_ip = source_ip.into();
317 self
318 }
319
320 pub fn count(&self) -> u32 {
321 self.row.count
322 }
323
324 pub fn with_count(mut self, count: u32) -> Self {
325 self.row.count = count;
326 self
327 }
328
329 pub fn action_disposition(&self) -> ActionDisposition {
330 self.row.policy_evaluated.disposition
331 }
332
333 pub fn with_action_disposition(mut self, disposition: ActionDisposition) -> Self {
334 self.row.policy_evaluated.disposition = disposition;
335 self
336 }
337
338 pub fn dmarc_dkim_result(&self) -> DmarcResult {
339 self.row.policy_evaluated.dkim
340 }
341
342 pub fn with_dmarc_dkim_result(mut self, dkim: DmarcResult) -> Self {
343 self.row.policy_evaluated.dkim = dkim;
344 self
345 }
346
347 pub fn dmarc_spf_result(&self) -> DmarcResult {
348 self.row.policy_evaluated.spf
349 }
350
351 pub fn with_dmarc_spf_result(mut self, spf: DmarcResult) -> Self {
352 self.row.policy_evaluated.spf = spf;
353 self
354 }
355
356 pub fn policy_override_reason(&self) -> &[PolicyOverrideReason] {
357 &self.row.policy_evaluated.reason
358 }
359
360 pub fn with_policy_override_reason(mut self, reason: PolicyOverrideReason) -> Self {
361 self.row.policy_evaluated.reason.push(reason);
362 self
363 }
364
365 pub fn envelope_from(&self) -> &str {
366 &self.identifiers.envelope_from
367 }
368
369 pub fn with_envelope_from(mut self, envelope_from: impl Into<String>) -> Self {
370 self.identifiers.envelope_from = envelope_from.into();
371 self
372 }
373
374 pub fn header_from(&self) -> &str {
375 &self.identifiers.header_from
376 }
377
378 pub fn with_header_from(mut self, header_from: impl Into<String>) -> Self {
379 self.identifiers.header_from = header_from.into();
380 self
381 }
382
383 pub fn envelope_to(&self) -> Option<&str> {
384 self.identifiers.envelope_to.as_deref()
385 }
386
387 pub fn with_envelope_to(mut self, envelope_to: impl Into<String>) -> Self {
388 self.identifiers.envelope_to = Some(envelope_to.into());
389 self
390 }
391
392 pub fn dkim_auth_result(&self) -> &[DKIMAuthResult] {
393 &self.auth_results.dkim
394 }
395
396 pub fn with_dkim_auth_result(mut self, auth_result: DKIMAuthResult) -> Self {
397 self.auth_results.dkim.push(auth_result);
398 self
399 }
400
401 pub fn spf_auth_result(&self) -> &[SPFAuthResult] {
402 &self.auth_results.spf
403 }
404
405 pub fn with_spf_auth_result(mut self, auth_result: SPFAuthResult) -> Self {
406 self.auth_results.spf.push(auth_result);
407 self
408 }
409}
410
411impl PolicyPublished {
412 pub fn from_record(domain: impl Into<String>, dmarc: &Dmarc) -> Self {
413 PolicyPublished {
414 domain: domain.into(),
415 adkim: (&dmarc.adkim).into(),
416 aspf: (&dmarc.aspf).into(),
417 p: (&dmarc.p).into(),
418 sp: (&dmarc.sp).into(),
419 testing: dmarc.t,
420 fo: match &dmarc.fo {
421 crate::dmarc::Report::All => "0",
422 crate::dmarc::Report::Any => "1",
423 crate::dmarc::Report::Dkim => "d",
424 crate::dmarc::Report::Spf => "s",
425 crate::dmarc::Report::DkimSpf => "d:s",
426 }
427 .to_string()
428 .into(),
429 version_published: None,
430 }
431 }
432}
433
434impl DKIMAuthResult {
435 pub fn new() -> Self {
436 DKIMAuthResult::default()
437 }
438
439 pub fn domain(&self) -> &str {
440 &self.domain
441 }
442
443 pub fn with_domain(mut self, domain: impl Into<String>) -> Self {
444 self.domain = domain.into();
445 self
446 }
447
448 pub fn selector(&self) -> &str {
449 &self.selector
450 }
451
452 pub fn with_selector(mut self, selector: impl Into<String>) -> Self {
453 self.selector = selector.into();
454 self
455 }
456
457 pub fn result(&self) -> DkimResult {
458 self.result
459 }
460
461 pub fn with_result(mut self, result: DkimResult) -> Self {
462 self.result = result;
463 self
464 }
465
466 pub fn human_result(&self) -> Option<&str> {
467 self.human_result.as_deref()
468 }
469
470 pub fn with_human_result(mut self, human_result: impl Into<String>) -> Self {
471 self.human_result = Some(human_result.into());
472 self
473 }
474}
475
476impl SPFAuthResult {
477 pub fn new() -> Self {
478 SPFAuthResult::default()
479 }
480
481 pub fn domain(&self) -> &str {
482 &self.domain
483 }
484
485 pub fn with_domain(mut self, domain: impl Into<String>) -> Self {
486 self.domain = domain.into();
487 self
488 }
489
490 pub fn scope(&self) -> SPFDomainScope {
491 self.scope
492 }
493
494 pub fn with_scope(mut self, scope: SPFDomainScope) -> Self {
495 self.scope = scope;
496 self
497 }
498
499 pub fn result(&self) -> SpfResult {
500 self.result
501 }
502
503 pub fn with_result(mut self, result: SpfResult) -> Self {
504 self.result = result;
505 self
506 }
507
508 pub fn human_result(&self) -> Option<&str> {
509 self.human_result.as_deref()
510 }
511
512 pub fn with_human_result(mut self, human_result: impl Into<String>) -> Self {
513 self.human_result = Some(human_result.into());
514 self
515 }
516}
517
518impl PolicyOverrideReason {
519 pub fn new(type_: PolicyOverride) -> Self {
520 PolicyOverrideReason {
521 type_,
522 comment: None,
523 }
524 }
525
526 pub fn with_comment(mut self, comment: impl Into<String>) -> Self {
527 self.comment = Some(comment.into());
528 self
529 }
530
531 pub fn comment(&self) -> Option<&str> {
532 self.comment.as_deref()
533 }
534
535 pub fn policy_override(&self) -> PolicyOverride {
536 self.type_
537 }
538}
539
540impl From<&crate::DmarcResult> for DmarcResult {
541 fn from(result: &crate::DmarcResult) -> Self {
542 match result {
543 crate::DmarcResult::Pass => DmarcResult::Pass,
544 _ => DmarcResult::Fail,
545 }
546 }
547}
548
549impl From<&crate::dmarc::Alignment> for Alignment {
550 fn from(aligment: &crate::dmarc::Alignment) -> Self {
551 match aligment {
552 crate::dmarc::Alignment::Relaxed => Alignment::Relaxed,
553 crate::dmarc::Alignment::Strict => Alignment::Strict,
554 }
555 }
556}
557
558impl From<&crate::dmarc::Policy> for Disposition {
559 fn from(policy: &crate::dmarc::Policy) -> Self {
560 match policy {
561 crate::dmarc::Policy::None => Disposition::None,
562 crate::dmarc::Policy::Quarantine => Disposition::Quarantine,
563 crate::dmarc::Policy::Reject => Disposition::Reject,
564 crate::dmarc::Policy::Unspecified => Disposition::None,
565 }
566 }
567}