1use 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}