kumo_dmarc/types/
results.rs

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// A coverage of both success and various failure modes
147#[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// A synthetic type to bundle the result with a reason
182#[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}