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