1pub mod arf;
8pub mod dmarc;
9pub mod tlsrpt;
10use serde::{Deserialize, Serialize};
11use std::{borrow::Cow, net::IpAddr};
12
13#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
14#[cfg_attr(
15 feature = "rkyv",
16 derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
17)]
18pub struct DateRange {
19 pub begin: u64,
20 pub end: u64,
21}
22
23#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
24#[cfg_attr(
25 feature = "rkyv",
26 derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
27)]
28pub struct ReportMetadata {
29 pub org_name: String,
30 pub email: String,
31 pub extra_contact_info: Option<String>,
32 pub report_id: String,
33 pub date_range: DateRange,
34 pub error: Vec<String>,
35 #[serde(default)]
36 pub generator: Option<String>,
37}
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
40#[cfg_attr(
41 feature = "rkyv",
42 derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
43)]
44pub enum Alignment {
45 Relaxed,
46 Strict,
47 #[default]
48 Unspecified,
49}
50
51#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
52#[cfg_attr(
53 feature = "rkyv",
54 derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
55)]
56pub enum Disposition {
57 None,
58 Quarantine,
59 Reject,
60 #[default]
61 Unspecified,
62}
63
64#[derive(Debug, Hash, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
65#[cfg_attr(
66 feature = "rkyv",
67 derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
68)]
69pub enum ActionDisposition {
70 None,
71 Pass,
72 Quarantine,
73 Reject,
74 #[default]
75 Unspecified,
76}
77
78#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
79#[cfg_attr(
80 feature = "rkyv",
81 derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
82)]
83pub struct PolicyPublished {
84 pub domain: String,
85 pub version_published: Option<f32>,
86 pub adkim: Alignment,
87 pub aspf: Alignment,
88 pub p: Disposition,
89 pub sp: Disposition,
90 #[serde(default)]
91 pub np: Disposition,
92 pub testing: bool,
93 #[serde(default)]
94 pub discovery_method: Discovery,
95 pub fo: Option<String>,
96}
97
98impl Eq for PolicyPublished {}
99
100#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
101#[cfg_attr(
102 feature = "rkyv",
103 derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
104)]
105pub enum Discovery {
106 Psl,
107 Treewalk,
108 #[default]
109 Unspecified,
110}
111
112#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize, Default)]
113#[cfg_attr(
114 feature = "rkyv",
115 derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
116)]
117pub enum DmarcResult {
118 Pass,
119 Fail,
120 #[default]
121 Unspecified,
122}
123
124#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize, Default)]
125#[cfg_attr(
126 feature = "rkyv",
127 derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
128)]
129pub enum PolicyOverride {
130 LocalPolicy,
131 MailingList,
132 PolicyTestMode,
133 TrustedForwarder,
134 #[default]
135 Other,
136}
137
138#[derive(Debug, Clone, Hash, PartialEq, Eq, Default, Serialize, Deserialize)]
139#[cfg_attr(
140 feature = "rkyv",
141 derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
142)]
143pub struct PolicyOverrideReason {
144 pub type_: PolicyOverride,
145 pub comment: Option<String>,
146}
147
148#[derive(Debug, Clone, Hash, PartialEq, Eq, Default, Serialize, Deserialize)]
149#[cfg_attr(
150 feature = "rkyv",
151 derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
152)]
153pub struct PolicyEvaluated {
154 pub disposition: ActionDisposition,
155 pub dkim: DmarcResult,
156 pub spf: DmarcResult,
157 pub reason: Vec<PolicyOverrideReason>,
158}
159
160#[derive(Debug, Clone, Hash, Default, PartialEq, Eq, Serialize, Deserialize)]
161#[cfg_attr(
162 feature = "rkyv",
163 derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
164)]
165pub struct Row {
166 pub source_ip: Option<IpAddr>,
167 pub count: u32,
168 pub policy_evaluated: PolicyEvaluated,
169}
170
171#[derive(Debug, Clone, Hash, PartialEq, Eq, Default, Serialize, Deserialize)]
172#[cfg_attr(
173 feature = "rkyv",
174 derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
175)]
176pub struct Extension {
177 pub name: String,
178 pub definition: String,
179}
180
181#[derive(Debug, Clone, Hash, PartialEq, Eq, Default, Serialize, Deserialize)]
182#[cfg_attr(
183 feature = "rkyv",
184 derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
185)]
186pub struct Identifier {
187 pub envelope_to: Option<String>,
188 pub envelope_from: String,
189 pub header_from: String,
190}
191
192#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize, Default)]
193#[cfg_attr(
194 feature = "rkyv",
195 derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
196)]
197pub enum DkimResult {
198 #[default]
199 None,
200 Pass,
201 Fail,
202 Policy,
203 Neutral,
204 TempError,
205 PermError,
206}
207
208#[derive(Debug, Clone, Hash, PartialEq, Eq, Default, Serialize, Deserialize)]
209#[cfg_attr(
210 feature = "rkyv",
211 derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
212)]
213pub struct DKIMAuthResult {
214 pub domain: String,
215 pub selector: String,
216 pub result: DkimResult,
217 pub human_result: Option<String>,
218}
219
220#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize, Default)]
221#[cfg_attr(
222 feature = "rkyv",
223 derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
224)]
225pub enum SPFDomainScope {
226 Helo,
227 MailFrom,
228 #[default]
229 Unspecified,
230}
231
232#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize, Default)]
233#[cfg_attr(
234 feature = "rkyv",
235 derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
236)]
237pub enum SpfResult {
238 #[default]
239 None,
240 Neutral,
241 Pass,
242 Fail,
243 SoftFail,
244 TempError,
245 PermError,
246}
247
248#[derive(Debug, Clone, Hash, PartialEq, Eq, Default, Serialize, Deserialize)]
249#[cfg_attr(
250 feature = "rkyv",
251 derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
252)]
253pub struct SPFAuthResult {
254 pub domain: String,
255 pub scope: SPFDomainScope,
256 pub result: SpfResult,
257 pub human_result: Option<String>,
258}
259
260#[derive(Debug, Clone, Hash, PartialEq, Eq, Default, Serialize, Deserialize)]
261#[cfg_attr(
262 feature = "rkyv",
263 derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
264)]
265pub struct AuthResult {
266 pub dkim: Vec<DKIMAuthResult>,
267 pub spf: Vec<SPFAuthResult>,
268}
269
270#[derive(Debug, Clone, Hash, PartialEq, Eq, Default, Serialize, Deserialize)]
271#[cfg_attr(
272 feature = "rkyv",
273 derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
274)]
275pub struct Record {
276 pub row: Row,
277 pub identifiers: Identifier,
278 pub auth_results: AuthResult,
279 pub extensions: Vec<Extension>,
280}
281
282#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
283#[cfg_attr(
284 feature = "rkyv",
285 derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
286)]
287pub struct Report {
288 pub version: f32,
289 pub report_metadata: ReportMetadata,
290 pub policy_published: PolicyPublished,
291 pub record: Vec<Record>,
292 pub extensions: Vec<Extension>,
293}
294
295impl Eq for Report {}
296
297#[derive(Debug, Clone, PartialEq, Eq)]
298pub enum Error {
299 MailParseError,
300 ReportParseError(String),
301 UncompressError(String),
302 NoReportsFound,
303}
304
305impl From<String> for Error {
306 fn from(err: String) -> Self {
307 Error::ReportParseError(err)
308 }
309}
310
311#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
312#[cfg_attr(
313 feature = "rkyv",
314 derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
315)]
316pub struct Feedback<'x> {
317 pub feedback_type: FeedbackType,
318 pub arrival_date: Option<i64>,
319 #[cfg_attr(feature = "rkyv", rkyv(with = rkyv::with::Map<rkyv::with::AsOwned>))]
320 pub authentication_results: Vec<Cow<'x, str>>,
321 pub incidents: u32,
322 #[cfg_attr(feature = "rkyv", rkyv(with = rkyv::with::Map<rkyv::with::AsOwned>))]
323 pub original_envelope_id: Option<Cow<'x, str>>,
324 #[cfg_attr(feature = "rkyv", rkyv(with = rkyv::with::Map<rkyv::with::AsOwned>))]
325 pub original_mail_from: Option<Cow<'x, str>>,
326 #[cfg_attr(feature = "rkyv", rkyv(with = rkyv::with::Map<rkyv::with::AsOwned>))]
327 pub original_rcpt_to: Option<Cow<'x, str>>,
328 #[cfg_attr(feature = "rkyv", rkyv(with = rkyv::with::Map<rkyv::with::AsOwned>))]
329 pub reported_domain: Vec<Cow<'x, str>>,
330 #[cfg_attr(feature = "rkyv", rkyv(with = rkyv::with::Map<rkyv::with::AsOwned>))]
331 pub reported_uri: Vec<Cow<'x, str>>,
332 #[cfg_attr(feature = "rkyv", rkyv(with = rkyv::with::Map<rkyv::with::AsOwned>))]
333 pub reporting_mta: Option<Cow<'x, str>>,
334 pub source_ip: Option<IpAddr>,
335 #[cfg_attr(feature = "rkyv", rkyv(with = rkyv::with::Map<rkyv::with::AsOwned>))]
336 pub user_agent: Option<Cow<'x, str>>,
337 pub version: u32,
338 pub source_port: u32,
339
340 pub auth_failure: AuthFailureType,
342 pub delivery_result: DeliveryResult,
343 #[cfg_attr(feature = "rkyv", rkyv(with = rkyv::with::Map<rkyv::with::AsOwned>))]
344 pub dkim_adsp_dns: Option<Cow<'x, str>>,
345 #[cfg_attr(feature = "rkyv", rkyv(with = rkyv::with::Map<rkyv::with::AsOwned>))]
346 pub dkim_canonicalized_body: Option<Cow<'x, str>>,
347 #[cfg_attr(feature = "rkyv", rkyv(with = rkyv::with::Map<rkyv::with::AsOwned>))]
348 pub dkim_canonicalized_header: Option<Cow<'x, str>>,
349 #[cfg_attr(feature = "rkyv", rkyv(with = rkyv::with::Map<rkyv::with::AsOwned>))]
350 pub dkim_domain: Option<Cow<'x, str>>,
351 #[cfg_attr(feature = "rkyv", rkyv(with = rkyv::with::Map<rkyv::with::AsOwned>))]
352 pub dkim_identity: Option<Cow<'x, str>>,
353 #[cfg_attr(feature = "rkyv", rkyv(with = rkyv::with::Map<rkyv::with::AsOwned>))]
354 pub dkim_selector: Option<Cow<'x, str>>,
355 #[cfg_attr(feature = "rkyv", rkyv(with = rkyv::with::Map<rkyv::with::AsOwned>))]
356 pub dkim_selector_dns: Option<Cow<'x, str>>,
357 #[cfg_attr(feature = "rkyv", rkyv(with = rkyv::with::Map<rkyv::with::AsOwned>))]
358 pub spf_dns: Option<Cow<'x, str>>,
359 pub identity_alignment: IdentityAlignment,
360
361 #[cfg_attr(feature = "rkyv", rkyv(with = rkyv::with::Map<rkyv::with::AsOwned>))]
362 pub message: Option<Cow<'x, str>>,
363 #[cfg_attr(feature = "rkyv", rkyv(with = rkyv::with::Map<rkyv::with::AsOwned>))]
364 pub headers: Option<Cow<'x, str>>,
365}
366
367#[derive(Debug, Clone, PartialEq, Eq, Copy, Serialize, Deserialize, Default)]
368#[cfg_attr(
369 feature = "rkyv",
370 derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
371)]
372pub enum AuthFailureType {
373 Adsp,
374 BodyHash,
375 Revoked,
376 Signature,
377 Spf,
378 Dmarc,
379 #[default]
380 Unspecified,
381}
382
383#[derive(Debug, Clone, PartialEq, Eq, Copy, Serialize, Deserialize, Default)]
384#[cfg_attr(
385 feature = "rkyv",
386 derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
387)]
388pub enum IdentityAlignment {
389 None,
390 Spf,
391 Dkim,
392 DkimSpf,
393 #[default]
394 Unspecified,
395}
396
397#[derive(Debug, Clone, PartialEq, Eq, Copy, Serialize, Deserialize, Default)]
398#[cfg_attr(
399 feature = "rkyv",
400 derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
401)]
402pub enum DeliveryResult {
403 Delivered,
404 Spam,
405 Policy,
406 Reject,
407 Other,
408 #[default]
409 Unspecified,
410}
411
412#[derive(Debug, Clone, PartialEq, Eq, Copy, Serialize, Deserialize, Default)]
413#[cfg_attr(
414 feature = "rkyv",
415 derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
416)]
417pub enum FeedbackType {
418 Abuse,
419 AuthFailure,
420 Fraud,
421 NotSpam,
422 #[default]
423 Other,
424 Virus,
425}
426
427impl From<&crate::DkimResult> for AuthFailureType {
428 fn from(value: &crate::DkimResult) -> Self {
429 match value {
430 crate::DkimResult::Neutral(err)
431 | crate::DkimResult::Fail(err)
432 | crate::DkimResult::PermError(err)
433 | crate::DkimResult::TempError(err) => match err {
434 crate::Error::Dkim(crate::dkim::DkimError::FailedBodyHashMatch) => {
435 AuthFailureType::BodyHash
436 }
437 #[cfg(feature = "arc")]
438 crate::Error::Arc(crate::arc::ArcError::FailedBodyHashMatch) => {
439 AuthFailureType::BodyHash
440 }
441 crate::Error::Dkim(crate::dkim::DkimError::RevokedPublicKey) => {
442 AuthFailureType::Revoked
443 }
444 _ => AuthFailureType::Signature,
445 },
446 crate::DkimResult::Pass | crate::DkimResult::None => AuthFailureType::Signature,
447 }
448 }
449}