1use crate::types::identifier::Identifier;
2use crate::types::policy::Policy;
3use crate::types::policy_override::PolicyOverrideReason;
4use bstr::BString;
5use instant_xml::{FromXml, ToXml};
6use kumo_spf::SpfDisposition;
7use mailparsing::AuthenticationResult;
8use serde::{Deserialize, Serialize};
9use std::fmt;
10use std::net::IpAddr;
11
12#[derive(Debug, Eq, FromXml, PartialEq, ToXml, Serialize, Deserialize, Clone, Copy)]
13#[xml(scalar, rename_all = "lowercase")]
14pub enum SpfScope {
15 Helo,
16 Mfrom,
17}
18
19#[derive(Debug, Eq, PartialEq, Serialize, Deserialize, Clone)]
20pub struct SpfAuthResult {
21 domain: BString,
22 scope: SpfScope,
23 result: SpfDisposition,
24}
25
26impl ToXml for SpfAuthResult {
27 fn serialize<W: fmt::Write + ?Sized>(
28 &self,
29 field: Option<instant_xml::Id<'_>>,
30 serializer: &mut instant_xml::Serializer<W>,
31 ) -> Result<(), instant_xml::Error> {
32 #[derive(ToXml)]
33 #[xml(rename = "spf")]
34 struct SpfAuth {
35 domain: String,
36 scope: SpfScope,
37 result: SpfDisposition,
38 }
39
40 SpfAuth {
41 domain: self.domain.to_string(),
42 scope: self.scope.clone(),
43 result: self.result.clone(),
44 }
45 .serialize(field, serializer)
46 }
47}
48
49impl From<AuthenticationResult> for SpfAuthResult {
50 fn from(value: AuthenticationResult) -> Self {
51 let d = value.props.get("header.d");
52
53 Self {
54 domain: d.cloned().unwrap_or_default(),
55 scope: SpfScope::Mfrom,
56 result: value.result.into(),
57 }
58 }
59}
60
61#[derive(Debug, Eq, PartialEq, ToXml, Serialize, Deserialize, Clone)]
62#[xml(rename = "auth_results")]
63pub struct AuthResults {
64 pub(crate) dkim: Vec<DkimAuthResult>,
65 pub(crate) spf: Vec<SpfAuthResult>,
66}
67
68#[derive(Debug, Eq, PartialEq, ToXml)]
69#[xml(rename = "record")]
70pub struct Results {
71 pub(crate) row: Row,
72 pub(crate) identifiers: Identifier,
73 pub(crate) auth_results: AuthResults,
74}
75
76#[derive(Debug, Eq, PartialEq, ToXml, Serialize, Deserialize, Clone, Copy)]
77#[xml(scalar, rename_all = "lowercase")]
78pub enum DkimResult {
79 None,
80 Pass,
81 Fail,
82 Policy,
83 Neutral,
84 TempError,
85 PermError,
86}
87
88impl From<String> for DkimResult {
89 fn from(value: String) -> Self {
90 match value.to_lowercase().as_str() {
91 "none" => DkimResult::None,
92 "pass" => DkimResult::Pass,
93 "fail" => DkimResult::Fail,
94 "policy" => DkimResult::Policy,
95 "neutral" => DkimResult::Neutral,
96 "temperror" => DkimResult::TempError,
97 "permerror" => DkimResult::PermError,
98 _ => DkimResult::None,
99 }
100 }
101}
102
103#[derive(Debug, Eq, PartialEq, ToXml, Serialize, Deserialize, Clone)]
104pub struct DkimAuthResult {
105 domain: String,
106 selector: Option<String>,
107 result: DkimResult,
108 human_result: Option<String>,
109}
110
111impl From<AuthenticationResult> for DkimAuthResult {
112 fn from(value: AuthenticationResult) -> Self {
113 let d = value.props.get("header.d");
114 let s = value.props.get("header.s");
115 Self {
116 domain: d.cloned().unwrap_or_default().to_string(),
117 selector: s.cloned().map(|x| x.to_string()),
118 result: value.result.clone().into(),
119 human_result: Some(value.result.to_string()),
120 }
121 }
122}
123
124#[derive(Debug, Eq, PartialEq, Clone, Copy, ToXml, Serialize, Deserialize)]
125#[xml(scalar, rename_all = "lowercase")]
126pub enum DmarcResult {
127 Pass,
128 Fail,
129}
130
131impl DmarcResult {
132 pub fn as_str(&self) -> &'static str {
133 match self {
134 Self::Pass => "pass",
135 Self::Fail => "fail",
136 }
137 }
138}
139
140impl fmt::Display for DmarcResult {
141 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
142 write!(f, "{}", self.as_str())
143 }
144}
145
146#[derive(Debug, Eq, PartialEq, ToXml, Serialize, Clone, Copy)]
148#[xml(scalar)]
149pub enum Disposition {
150 Pass,
151 None,
152 Quarantine,
153 Reject,
154 TempError,
155 PermError,
156}
157
158impl ToString for Disposition {
159 fn to_string(&self) -> String {
160 match self {
161 Disposition::None => "None".to_string(),
162 Disposition::Pass => "Pass".to_string(),
163 Disposition::Quarantine => "Quarantine".to_string(),
164 Disposition::Reject => "Reject".to_string(),
165 Disposition::TempError => "TempError".to_string(),
166 Disposition::PermError => "PermError".to_string(),
167 }
168 }
169}
170
171impl Into<Disposition> for Policy {
172 fn into(self) -> Disposition {
173 match self {
174 Policy::None => Disposition::None,
175 Policy::Quarantine => Disposition::Quarantine,
176 Policy::Reject => Disposition::Reject,
177 }
178 }
179}
180
181#[derive(Debug, Eq, PartialEq, ToXml, Serialize)]
183#[xml(rename_all = "lowercase")]
184pub struct DispositionWithContext {
185 pub result: Disposition,
186 pub context: String,
187}
188
189#[derive(Debug, Eq, PartialEq, ToXml, Serialize, Deserialize, Clone)]
190#[xml(rename = "policy_evaluated")]
191pub struct PolicyEvaluated {
192 pub(crate) disposition: Policy,
193 pub(crate) dkim: DmarcResult,
194 pub(crate) spf: DmarcResult,
195 pub(crate) reason: Vec<PolicyOverrideReason>,
196}
197
198#[derive(Debug, Eq, PartialEq, ToXml)]
199#[xml(rename = "row")]
200pub struct Row {
201 pub(crate) source_ip: IpAddr,
202 pub(crate) count: u64,
203 pub(crate) policy_evaluated: PolicyEvaluated,
204}