Skip to main content

mail_auth/common/
message.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 super::headers::{AuthenticatedHeader, Header, HeaderParser};
8#[cfg(feature = "arc")]
9use crate::arc;
10use crate::{AuthenticatedMessage, Error, common::crypto::HashAlgorithm, dkim, dkim2};
11use mail_parser::{Address, HeaderName, HeaderValue, Message, parsers::MessageStream};
12
13impl<'x> AuthenticatedMessage<'x> {
14    pub fn parse(raw_message: &'x [u8]) -> Option<Self> {
15        Self::parse_with_opts(raw_message, None, true)
16    }
17
18    pub fn from_parsed<'y>(parsed: &'y Message<'x>, raw_message: &'x [u8], strict: bool) -> Self {
19        let root = parsed.root_part();
20        let mut message = AuthenticatedMessage {
21            raw_message,
22            body_offset: root.raw_body_offset(),
23            headers: Vec::with_capacity(root.headers.len()),
24            ..Default::default()
25        };
26
27        for header in root.headers() {
28            let name = &raw_message[header.offset_field as usize..header.offset_start as usize - 1];
29            let value = &raw_message[header.offset_start as usize..header.offset_end as usize];
30
31            match &header.name {
32                HeaderName::From => {
33                    message.parse_from(&header.value);
34                }
35                HeaderName::Date => {
36                    message.date_header_present = true;
37                }
38                HeaderName::Received => {
39                    message.received_headers_count += 1;
40                }
41                HeaderName::MessageId => {
42                    message.message_id_header_present = true;
43                }
44                HeaderName::DkimSignature => {
45                    message.parse_dkim(name, value, strict);
46                }
47                #[cfg(feature = "arc")]
48                HeaderName::ArcAuthenticationResults => {
49                    message.parse_aar(name, value);
50                }
51                #[cfg(feature = "arc")]
52                HeaderName::ArcSeal => {
53                    message.parse_as(name, value);
54                }
55                #[cfg(feature = "arc")]
56                HeaderName::ArcMessageSignature => {
57                    message.parse_ams(name, value, strict);
58                }
59                HeaderName::Other(other) if other.eq_ignore_ascii_case("DKIM2-Signature") => {
60                    message.parse_dkim2_signature(name, value);
61                }
62                HeaderName::Other(other) if other.eq_ignore_ascii_case("Message-Instance") => {
63                    message.parse_dkim2_instance(name, value);
64                }
65                _ => (),
66            }
67
68            message.headers.push((name, value))
69        }
70
71        message.finalize()
72    }
73
74    pub fn parse_with_opts(
75        raw_message: &'x [u8],
76        prepend_headers: Option<&'x [u8]>,
77        strict: bool,
78    ) -> Option<Self> {
79        let mut message = AuthenticatedMessage {
80            raw_message,
81            ..Default::default()
82        };
83
84        if let Some(headers) = prepend_headers {
85            message.parse_headers(headers, strict);
86        }
87
88        let body_offset = message.parse_headers(raw_message, strict);
89
90        if !message.headers.is_empty() {
91            // Obtain message body
92            if let Some(offset) = body_offset {
93                message.body_offset = offset as u32;
94            } else {
95                message.body_offset = raw_message.len() as u32;
96            }
97            Some(message.finalize())
98        } else {
99            None
100        }
101    }
102
103    fn parse_headers(&mut self, headers: &'x [u8], strict: bool) -> Option<usize> {
104        let mut headers = HeaderParser::new(headers);
105
106        for (header, value) in &mut headers {
107            let name = match header {
108                AuthenticatedHeader::Ds(name) => {
109                    self.parse_dkim(name, value, strict);
110                    name
111                }
112                AuthenticatedHeader::D2s(name) => {
113                    self.parse_dkim2_signature(name, value);
114                    name
115                }
116                AuthenticatedHeader::D2i(name) => {
117                    self.parse_dkim2_instance(name, value);
118                    name
119                }
120                #[cfg(feature = "arc")]
121                AuthenticatedHeader::Aar(name) => {
122                    self.parse_aar(name, value);
123                    name
124                }
125                #[cfg(feature = "arc")]
126                AuthenticatedHeader::Ams(name) => {
127                    self.parse_ams(name, value, strict);
128                    name
129                }
130                #[cfg(feature = "arc")]
131                AuthenticatedHeader::As(name) => {
132                    self.parse_as(name, value);
133                    name
134                }
135                AuthenticatedHeader::From(name) => {
136                    self.parse_from(&MessageStream::new(value).parse_address());
137                    name
138                }
139                AuthenticatedHeader::Other(name) => name,
140            };
141
142            self.headers.push((name, value));
143        }
144
145        // Update header counts
146        self.received_headers_count += headers.num_received;
147        self.message_id_header_present |= headers.has_message_id;
148        self.date_header_present |= headers.has_date;
149
150        headers.body_offset()
151    }
152
153    fn parse_dkim(&mut self, name: &'x [u8], value: &'x [u8], strict: bool) {
154        match dkim::Signature::parse(value) {
155            Ok(signature) if signature.l == 0 || !strict => {
156                let ha = HashAlgorithm::from(signature.a);
157                if !self
158                    .body_hashes
159                    .iter()
160                    .any(|(c, h, l, _)| c == &signature.cb && h == &ha && l == &signature.l)
161                {
162                    self.body_hashes
163                        .push((signature.cb, ha, signature.l, Vec::new()));
164                }
165                self.dkim_headers.push(Header::new(name, value, signature));
166            }
167            Ok(_) => {
168                self.push_dkim_error(name, value, Error::Dkim(dkim::DkimError::SignatureLength));
169            }
170            Err(err) => self.push_dkim_error(name, value, err),
171        }
172    }
173
174    fn parse_dkim2_signature(&mut self, name: &'x [u8], value: &'x [u8]) {
175        match dkim2::Signature::parse(value) {
176            Ok(signature) => self
177                .dkim2_signatures
178                .push(Header::new(name, value, signature)),
179            Err(err) => self.push_dkim2_error(name, value, err),
180        }
181    }
182
183    fn parse_dkim2_instance(&mut self, name: &'x [u8], value: &'x [u8]) {
184        match dkim2::MessageInstance::parse(value) {
185            Ok(instance) => self
186                .dkim2_instances
187                .push(Header::new(name, value, instance)),
188            Err(err) => self.push_dkim2_error(name, value, err),
189        }
190    }
191
192    #[cfg(feature = "arc")]
193    fn parse_aar(&mut self, name: &'x [u8], value: &'x [u8]) {
194        match arc::Results::parse(value) {
195            Ok(results) => self.aar_headers.push(Header::new(name, value, results)),
196            Err(err) => self.push_arc_error(name, value, err),
197        }
198    }
199
200    #[cfg(feature = "arc")]
201    fn parse_ams(&mut self, name: &'x [u8], value: &'x [u8], strict: bool) {
202        match arc::Signature::parse(value) {
203            Ok(signature) if signature.l == 0 || !strict => {
204                let ha = HashAlgorithm::from(signature.a);
205                if !self
206                    .body_hashes
207                    .iter()
208                    .any(|(c, h, l, _)| c == &signature.cb && h == &ha && l == &signature.l)
209                {
210                    self.body_hashes
211                        .push((signature.cb, ha, signature.l, Vec::new()));
212                }
213                self.ams_headers.push(Header::new(name, value, signature));
214            }
215            Ok(_) => {
216                self.push_arc_error(name, value, Error::Arc(arc::ArcError::SignatureLength));
217            }
218            Err(err) => self.push_arc_error(name, value, err),
219        }
220    }
221
222    #[cfg(feature = "arc")]
223    fn parse_as(&mut self, name: &'x [u8], value: &'x [u8]) {
224        match arc::Seal::parse(value) {
225            Ok(seal) => self.as_headers.push(Header::new(name, value, seal)),
226            Err(err) => self.push_arc_error(name, value, err),
227        }
228    }
229
230    fn push_dkim_error(&mut self, name: &'x [u8], value: &'x [u8], err: Error) {
231        self.has_dkim_errors = true;
232        self.errors.push(Header::new(name, value, err));
233    }
234
235    fn push_dkim2_error(&mut self, name: &'x [u8], value: &'x [u8], err: Error) {
236        self.has_dkim2_errors = true;
237        self.errors.push(Header::new(name, value, err));
238    }
239
240    #[cfg(feature = "arc")]
241    fn push_arc_error(&mut self, name: &'x [u8], value: &'x [u8], err: Error) {
242        self.has_arc_errors = true;
243        self.errors.push(Header::new(name, value, err));
244    }
245
246    fn parse_from(&mut self, value: &HeaderValue<'x>) {
247        match value {
248            HeaderValue::Address(Address::List(list)) => {
249                self.from.extend(
250                    list.iter()
251                        .filter_map(|a| a.address.as_ref().map(|a| a.to_lowercase())),
252                );
253            }
254            HeaderValue::Address(Address::Group(group_list)) => {
255                self.from.extend(group_list.iter().flat_map(|group| {
256                    group
257                        .addresses
258                        .iter()
259                        .filter_map(|a| a.address.as_ref().map(|a| a.to_lowercase()))
260                }))
261            }
262            _ => (),
263        }
264    }
265
266    fn finalize(mut self) -> Self {
267        let body = self
268            .raw_message
269            .get(self.body_offset as usize..)
270            .unwrap_or_default();
271
272        // Calculate body hashes
273        for (cb, ha, l, bh) in &mut self.body_hashes {
274            *bh = ha.hash(cb.canonical_body(body, *l)).as_ref().to_vec();
275        }
276
277        // Sort ARC headers
278        #[cfg(feature = "arc")]
279        if !self.as_headers.is_empty() && !self.has_arc_errors {
280            self.as_headers.sort_unstable_by_key(|h| h.header.i);
281            self.ams_headers.sort_unstable_by_key(|h| h.header.i);
282            self.aar_headers.sort_unstable_by_key(|h| h.header.i);
283        }
284
285        // Sort DKIM2 signatures and instances
286        if !self.has_dkim2_errors {
287            self.dkim2_signatures.sort_unstable_by_key(|h| h.header.i);
288            self.dkim2_instances.sort_unstable_by_key(|h| h.header.m);
289        }
290
291        self
292    }
293
294    pub fn received_headers_count(&self) -> usize {
295        self.received_headers_count
296    }
297
298    pub fn has_message_id_header(&self) -> bool {
299        self.message_id_header_present
300    }
301
302    pub fn has_date_header(&self) -> bool {
303        self.date_header_present
304    }
305
306    pub fn raw_message(&self) -> &[u8] {
307        self.raw_message
308    }
309
310    pub fn raw_headers(&self) -> &[u8] {
311        self.raw_message
312            .get(..self.body_offset as usize)
313            .unwrap_or_default()
314    }
315
316    pub fn raw_parsed_headers(&self) -> &[(&[u8], &[u8])] {
317        &self.headers
318    }
319
320    pub fn raw_body(&self) -> &[u8] {
321        self.raw_message
322            .get(self.body_offset as usize..)
323            .unwrap_or_default()
324    }
325
326    pub fn body_offset(&self) -> usize {
327        self.body_offset as usize
328    }
329
330    pub fn froms(&self) -> &[String] {
331        &self.from
332    }
333
334    pub fn from(&self) -> &str {
335        self.from.first().map_or("", |f| f.as_str())
336    }
337}