Commit
Author: Mauro D [mauro@stalw.art]
Hash: e6f451c374af1a0c647dc42aeadde93a8a2df215
Timestamp: Mon, 09 Jan 2023 16:06:59 +0000 (1 year ago)

+148 -22 +/-5 browse
Added parsing for additional headers.
1diff --git a/src/common/auth_results.rs b/src/common/auth_results.rs
2index 8764db3..7e139c8 100644
3--- a/src/common/auth_results.rs
4+++ b/src/common/auth_results.rs
5 @@ -61,17 +61,33 @@ impl<'x> AuthenticationResults<'x> {
6 self
7 }
8
9- pub fn with_spf_result(
10+ pub fn with_spf_ehlo_result(
11 mut self,
12 spf: &SpfOutput,
13 ip_addr: IpAddr,
14- helo: &str,
15- mail_from: &str,
16+ ehlo_domain: &str,
17 ) -> Self {
18- let mail_from = if !mail_from.is_empty() {
19- Cow::from(mail_from)
20+ self.auth_results.push_str(";\r\n\tspf=");
21+ spf.result.as_spf_result(
22+ &mut self.auth_results,
23+ self.hostname,
24+ &format!("postmaster@{}", ehlo_domain),
25+ ip_addr,
26+ );
27+ write!(self.auth_results, " smtp.helo={}", ehlo_domain).ok();
28+ self
29+ }
30+
31+ pub fn with_spf_mailfrom_result(
32+ mut self,
33+ spf: &SpfOutput,
34+ ip_addr: IpAddr,
35+ from: &str,
36+ ) -> Self {
37+ let (mail_from, addr) = if !from.is_empty() {
38+ (Cow::from(from), from)
39 } else {
40- format!("postmaster@{}", helo).into()
41+ (format!("postmaster@{}", from).into(), "<>")
42 };
43 self.auth_results.push_str(";\r\n\tspf=");
44 spf.result.as_spf_result(
45 @@ -80,12 +96,7 @@ impl<'x> AuthenticationResults<'x> {
46 mail_from.as_ref(),
47 ip_addr,
48 );
49- write!(
50- self.auth_results,
51- " smtp.mailfrom={} smtp.helo={}",
52- mail_from, helo
53- )
54- .ok();
55+ write!(self.auth_results, " smtp.mailfrom={}", addr).ok();
56 self
57 }
58
59 diff --git a/src/common/headers.rs b/src/common/headers.rs
60index fe76b9b..56b9ce0 100644
61--- a/src/common/headers.rs
62+++ b/src/common/headers.rs
63 @@ -33,6 +33,9 @@ pub(crate) struct HeaderParser<'x> {
64 message: &'x [u8],
65 iter: Peekable<Enumerate<Iter<'x, u8>>>,
66 start_pos: usize,
67+ pub num_received: usize,
68+ pub has_message_id: bool,
69+ pub has_date: bool,
70 }
71
72 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
73 @@ -58,6 +61,9 @@ impl<'x> HeaderParser<'x> {
74 message,
75 iter: message.iter().enumerate().peekable(),
76 start_pos: 0,
77+ num_received: 0,
78+ has_message_id: false,
79+ has_date: false,
80 }
81 }
82
83 @@ -223,6 +229,10 @@ impl<'x> Iterator for HeaderParser<'x> {
84 .unwrap_or_default();
85 let header_value = self.message.get(colon_pos + 1..pos + 1).unwrap_or_default();
86 let header_name = match hash {
87+ RECEIVED if token_start + 8 == token_end + 1 => {
88+ self.num_received += 1;
89+ AuthenticatedHeader::Other(header_name)
90+ }
91 FROM => AuthenticatedHeader::From(header_name),
92 AS => AuthenticatedHeader::As(header_name),
93 AAR if self
94 @@ -249,6 +259,20 @@ impl<'x> Iterator for HeaderParser<'x> {
95 {
96 AuthenticatedHeader::Ds(header_name)
97 }
98+ MSGID
99+ if self
100+ .message
101+ .get(token_start + 8..token_end + 1)
102+ .unwrap_or_default()
103+ .eq_ignore_ascii_case(b"id") =>
104+ {
105+ self.has_message_id = true;
106+ AuthenticatedHeader::Other(header_name)
107+ }
108+ DATE => {
109+ self.has_date = true;
110+ AuthenticatedHeader::Other(header_name)
111+ }
112 _ => AuthenticatedHeader::Other(header_name),
113 };
114
115 @@ -321,6 +345,23 @@ const AS: u64 = (b'a' as u64)
116 | (b'e' as u64) << 40
117 | (b'a' as u64) << 48
118 | (b'l' as u64) << 56;
119+ const RECEIVED: u64 = (b'r' as u64)
120+ | (b'e' as u64) << 8
121+ | (b'c' as u64) << 16
122+ | (b'e' as u64) << 24
123+ | (b'i' as u64) << 32
124+ | (b'v' as u64) << 40
125+ | (b'e' as u64) << 48
126+ | (b'd' as u64) << 56;
127+ const DATE: u64 = (b'd' as u64) | (b'a' as u64) << 8 | (b't' as u64) << 16 | (b'e' as u64) << 24;
128+ const MSGID: u64 = (b'm' as u64)
129+ | (b'e' as u64) << 8
130+ | (b's' as u64) << 16
131+ | (b's' as u64) << 24
132+ | (b'a' as u64) << 32
133+ | (b'g' as u64) << 40
134+ | (b'e' as u64) << 48
135+ | (b'-' as u64) << 56;
136
137 #[cfg(test)]
138 mod test {
139 @@ -408,12 +449,17 @@ mod test {
140 "From: jdoe@domain\n",
141 "F r o m : jane@domain.com\n",
142 "ARC-Authentication: i=1;\n",
143+ "Received: r1\n",
144+ "Received: r2\n",
145+ "Received: r3\n",
146+ "Received-From: test\n",
147+ "Date: date\n",
148+ "Message-Id: myid\n",
149 "\nhey",
150 );
151+ let mut parser = HeaderParser::new(message.as_bytes());
152 assert_eq!(
153- HeaderParser::new(message.as_bytes())
154- .map(|(h, _)| { h })
155- .collect::<Vec<_>>(),
156+ (&mut parser).map(|(h, _)| { h }).collect::<Vec<_>>(),
157 vec![
158 AuthenticatedHeader::Ams(b"ARC-Message-Signature"),
159 AuthenticatedHeader::Aar(b"ARC-Authentication-Results"),
160 @@ -422,7 +468,16 @@ mod test {
161 AuthenticatedHeader::From(b"From"),
162 AuthenticatedHeader::From(b"F r o m "),
163 AuthenticatedHeader::Other(b"ARC-Authentication"),
164+ AuthenticatedHeader::Other(b"Received"),
165+ AuthenticatedHeader::Other(b"Received"),
166+ AuthenticatedHeader::Other(b"Received"),
167+ AuthenticatedHeader::Other(b"Received-From"),
168+ AuthenticatedHeader::Other(b"Date"),
169+ AuthenticatedHeader::Other(b"Message-Id"),
170 ]
171 );
172+ assert!(parser.has_date);
173+ assert!(parser.has_message_id);
174+ assert_eq!(parser.num_received, 3);
175 }
176 }
177 diff --git a/src/common/message.rs b/src/common/message.rs
178index d55b158..0e35da0 100644
179--- a/src/common/message.rs
180+++ b/src/common/message.rs
181 @@ -22,11 +22,15 @@ impl<'x> AuthenticatedMessage<'x> {
182 headers: Vec::new(),
183 from: Vec::new(),
184 body: b"",
185+ body_offset: 0,
186 body_hashes: Vec::new(),
187 dkim_headers: Vec::new(),
188 ams_headers: Vec::new(),
189 as_headers: Vec::new(),
190 aar_headers: Vec::new(),
191+ received_headers_count: 0,
192+ date_header_present: false,
193+ message_id_header_present: false,
194 };
195
196 let mut headers = HeaderParser::new(raw_message);
197 @@ -138,11 +142,18 @@ impl<'x> AuthenticatedMessage<'x> {
198 return None;
199 }
200
201+ // Update header counts
202+ message.received_headers_count = headers.num_received;
203+ message.message_id_header_present = headers.has_message_id;
204+ message.date_header_present = headers.has_date;
205+
206 // Obtain message body
207- message.body = headers
208- .body_offset()
209- .and_then(|pos| raw_message.get(pos..))
210- .unwrap_or_default();
211+ if let Some(offset) = headers.body_offset() {
212+ message.body_offset = offset;
213+ message.body = raw_message.get(offset..).unwrap_or_default();
214+ } else {
215+ message.body_offset = raw_message.len();
216+ }
217
218 // Calculate body hashes
219 for (cb, ha, l, bh) in &mut message.body_hashes {
220 @@ -179,4 +190,24 @@ impl<'x> AuthenticatedMessage<'x> {
221
222 message.into()
223 }
224+
225+ pub fn received_headers_count(&self) -> usize {
226+ self.received_headers_count
227+ }
228+
229+ pub fn has_message_id_header(&self) -> bool {
230+ self.message_id_header_present
231+ }
232+
233+ pub fn has_date_header(&self) -> bool {
234+ self.date_header_present
235+ }
236+
237+ pub fn body_offset(&self) -> usize {
238+ self.body_offset
239+ }
240+
241+ pub fn from(&self) -> &[String] {
242+ &self.from
243+ }
244 }
245 diff --git a/src/lib.rs b/src/lib.rs
246index 9407d2c..8e9d18b 100644
247--- a/src/lib.rs
248+++ b/src/lib.rs
249 @@ -319,11 +319,15 @@ pub struct AuthenticatedMessage<'x> {
250 pub(crate) headers: Vec<(&'x [u8], &'x [u8])>,
251 pub(crate) from: Vec<String>,
252 pub(crate) body: &'x [u8],
253+ pub(crate) body_offset: usize,
254 pub(crate) body_hashes: Vec<(Canonicalization, HashAlgorithm, u64, Vec<u8>)>,
255 pub(crate) dkim_headers: Vec<Header<'x, crate::Result<dkim::Signature>>>,
256 pub(crate) ams_headers: Vec<Header<'x, crate::Result<arc::Signature>>>,
257 pub(crate) as_headers: Vec<Header<'x, crate::Result<arc::Seal>>>,
258 pub(crate) aar_headers: Vec<Header<'x, crate::Result<arc::Results>>>,
259+ pub(crate) received_headers_count: usize,
260+ pub(crate) date_header_present: bool,
261+ pub(crate) message_id_header_present: bool,
262 }
263
264 #[derive(Debug, Clone, PartialEq, Eq)]
265 @@ -402,8 +406,8 @@ pub enum DmarcResult {
266
267 #[derive(Debug, PartialEq, Eq, Clone)]
268 pub struct IprevOutput {
269- result: IprevResult,
270- ptr: Option<Arc<Vec<String>>>,
271+ pub result: IprevResult,
272+ pub ptr: Option<Arc<Vec<String>>>,
273 }
274
275 #[derive(Debug, PartialEq, Eq, Clone)]
276 @@ -494,6 +498,20 @@ impl Display for Error {
277 }
278 }
279
280+ impl Display for SpfResult {
281+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
282+ f.write_str(match self {
283+ SpfResult::Pass => "Pass",
284+ SpfResult::Fail => "Fail",
285+ SpfResult::SoftFail => "SoftFail",
286+ SpfResult::Neutral => "Neutral",
287+ SpfResult::TempError => "TempError",
288+ SpfResult::PermError => "PermError",
289+ SpfResult::None => "None",
290+ })
291+ }
292+ }
293+
294 impl From<io::Error> for Error {
295 fn from(err: io::Error) -> Self {
296 Error::Io(err.to_string())
297 @@ -512,6 +530,17 @@ impl From<ed25519_dalek::ed25519::Error> for Error {
298 }
299 }
300
301+ impl Default for SpfOutput {
302+ fn default() -> Self {
303+ Self {
304+ result: SpfResult::None,
305+ domain: Default::default(),
306+ report: Default::default(),
307+ explanation: Default::default(),
308+ }
309+ }
310+ }
311+
312 thread_local!(static COUNTER: Cell<u64> = Cell::new(0));
313
314 /// Generates a random value between 0 and 100.
315 diff --git a/src/spf/verify.rs b/src/spf/verify.rs
316index b489a7c..a5610bf 100644
317--- a/src/spf/verify.rs
318+++ b/src/spf/verify.rs
319 @@ -77,7 +77,7 @@ impl Resolver {
320 }
321
322 #[allow(clippy::while_let_on_iterator)]
323- pub(crate) async fn check_host(
324+ pub async fn check_host(
325 &self,
326 ip: IpAddr,
327 domain: &str,