Skip to main content

mail_auth/report/tlsrpt/
mod.rs

1/*
2 * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
3 *
4 * SPDX-License-Identifier: Apache-2.0 OR MIT
5 */
6
7use mail_parser::DateTime;
8use serde::{Deserialize, Deserializer, Serialize, Serializer};
9use std::net::IpAddr;
10
11pub mod generate;
12pub mod parse;
13
14#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)]
15#[cfg_attr(
16    feature = "rkyv",
17    derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
18)]
19pub struct TlsReport {
20    #[serde(rename = "organization-name")]
21    #[serde(default)]
22    pub organization_name: Option<String>,
23
24    #[serde(rename = "date-range")]
25    pub date_range: DateRange,
26
27    #[serde(rename = "contact-info")]
28    #[serde(default)]
29    pub contact_info: Option<String>,
30
31    #[serde(rename = "report-id")]
32    #[serde(default)]
33    pub report_id: String,
34
35    #[serde(rename = "policies")]
36    #[serde(default)]
37    pub policies: Vec<Policy>,
38}
39
40#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)]
41#[cfg_attr(
42    feature = "rkyv",
43    derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
44)]
45pub struct Policy {
46    #[serde(rename = "policy")]
47    pub policy: PolicyDetails,
48
49    #[serde(rename = "summary")]
50    pub summary: Summary,
51
52    #[serde(rename = "failure-details")]
53    #[serde(default)]
54    pub failure_details: Vec<FailureDetails>,
55}
56
57#[derive(Debug, Default, PartialEq, Eq, Serialize, Deserialize, Clone)]
58#[cfg_attr(
59    feature = "rkyv",
60    derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
61)]
62pub struct PolicyDetails {
63    #[serde(rename = "policy-type")]
64    pub policy_type: PolicyType,
65
66    #[serde(rename = "policy-string")]
67    #[serde(default)]
68    pub policy_string: Vec<String>,
69
70    #[serde(rename = "policy-domain")]
71    #[serde(default)]
72    pub policy_domain: String,
73
74    #[serde(rename = "mx-host")]
75    #[serde(default)]
76    pub mx_host: Vec<String>,
77}
78
79#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)]
80#[cfg_attr(
81    feature = "rkyv",
82    derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
83)]
84pub struct Summary {
85    #[serde(rename = "total-successful-session-count")]
86    #[serde(default)]
87    pub total_success: u32,
88
89    #[serde(rename = "total-failure-session-count")]
90    #[serde(default)]
91    pub total_failure: u32,
92}
93
94#[derive(Debug, Default, Hash, PartialEq, Eq, Serialize, Deserialize, Clone)]
95#[cfg_attr(
96    feature = "rkyv",
97    derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
98)]
99pub struct FailureDetails {
100    #[serde(rename = "result-type")]
101    pub result_type: ResultType,
102
103    #[serde(rename = "sending-mta-ip")]
104    pub sending_mta_ip: Option<IpAddr>,
105
106    #[serde(rename = "receiving-mx-hostname")]
107    pub receiving_mx_hostname: Option<String>,
108
109    #[serde(rename = "receiving-mx-helo")]
110    pub receiving_mx_helo: Option<String>,
111
112    #[serde(rename = "receiving-ip")]
113    pub receiving_ip: Option<IpAddr>,
114
115    #[serde(rename = "failed-session-count")]
116    #[serde(default)]
117    pub failed_session_count: u32,
118
119    #[serde(rename = "additional-information")]
120    pub additional_information: Option<String>,
121
122    #[serde(rename = "failure-reason-code")]
123    pub failure_reason_code: Option<String>,
124}
125
126#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)]
127#[cfg_attr(
128    feature = "rkyv",
129    derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
130)]
131pub struct DateRange {
132    #[serde(rename = "start-datetime")]
133    #[serde(serialize_with = "serialize_datetime")]
134    #[serde(deserialize_with = "deserialize_datetime")]
135    pub start_datetime: DateTime,
136    #[serde(rename = "end-datetime")]
137    #[serde(serialize_with = "serialize_datetime")]
138    #[serde(deserialize_with = "deserialize_datetime")]
139    pub end_datetime: DateTime,
140}
141
142#[derive(Debug, Default, PartialEq, Eq, Serialize, Deserialize, Clone, Copy)]
143#[cfg_attr(
144    feature = "rkyv",
145    derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
146)]
147pub enum PolicyType {
148    #[serde(rename = "tlsa")]
149    Tlsa,
150    #[serde(rename = "sts")]
151    Sts,
152    #[serde(rename = "no-policy-found")]
153    NoPolicyFound,
154    #[serde(other)]
155    #[default]
156    Other,
157}
158
159#[derive(Debug, Default, Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize)]
160#[cfg_attr(
161    feature = "rkyv",
162    derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
163)]
164pub enum ResultType {
165    #[serde(rename = "starttls-not-supported")]
166    StartTlsNotSupported,
167    #[serde(rename = "certificate-host-mismatch")]
168    CertificateHostMismatch,
169    #[serde(rename = "certificate-expired")]
170    CertificateExpired,
171    #[serde(rename = "certificate-not-trusted")]
172    CertificateNotTrusted,
173    #[serde(rename = "validation-failure")]
174    ValidationFailure,
175    #[serde(rename = "tlsa-invalid")]
176    TlsaInvalid,
177    #[serde(rename = "dnssec-invalid")]
178    DnssecInvalid,
179    #[serde(rename = "dane-required")]
180    DaneRequired,
181    #[serde(rename = "sts-policy-fetch-error")]
182    StsPolicyFetchError,
183    #[serde(rename = "sts-policy-invalid")]
184    StsPolicyInvalid,
185    #[serde(rename = "sts-webpki-invalid")]
186    StsWebpkiInvalid,
187    #[serde(other)]
188    #[default]
189    Other,
190}
191
192fn deserialize_datetime<'de, D>(deserializer: D) -> Result<DateTime, D::Error>
193where
194    D: Deserializer<'de>,
195{
196    Ok(
197        DateTime::parse_rfc3339(Deserialize::deserialize(deserializer)?)
198            .unwrap_or_else(|| DateTime::from_timestamp(0)),
199    )
200}
201
202fn serialize_datetime<S>(datetime: &DateTime, serializer: S) -> Result<S::Ok, S::Error>
203where
204    S: Serializer,
205{
206    serializer.serialize_str(&datetime.to_rfc3339())
207}
208
209impl PolicyDetails {
210    pub fn new(policy_type: PolicyType, policy_domain: impl Into<String>) -> Self {
211        Self {
212            policy_type,
213            policy_string: vec![],
214            policy_domain: policy_domain.into(),
215            mx_host: vec![],
216        }
217    }
218}
219
220impl FailureDetails {
221    pub fn new(result_type: impl Into<ResultType>) -> Self {
222        FailureDetails {
223            result_type: result_type.into(),
224            ..Default::default()
225        }
226    }
227
228    pub fn with_failure_reason_code(mut self, value: impl Into<String>) -> Self {
229        self.failure_reason_code = Some(value.into());
230        self
231    }
232
233    pub fn with_receiving_mx_hostname(mut self, value: impl Into<String>) -> Self {
234        self.receiving_mx_hostname = Some(value.into());
235        self
236    }
237
238    pub fn with_receiving_ip(mut self, value: IpAddr) -> Self {
239        self.receiving_ip = Some(value);
240        self
241    }
242}
243
244impl DateRange {
245    pub fn from_timestamps(start_datetime: i64, end_datetime: i64) -> Self {
246        Self {
247            start_datetime: DateTime::from_timestamp(start_datetime),
248            end_datetime: DateTime::from_timestamp(end_datetime),
249        }
250    }
251}