Skip to main content

mail_parser/view/
received.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::Resolver;
8use crate::{
9    DateTime,
10    store::{HostEntry, ReceivedEntry},
11};
12use std::{fmt, net::IpAddr};
13
14/// A parsed Received trace field (RFC 5321 section 4.4). Every clause is
15/// optional; values compare by content.
16#[derive(Clone, Copy)]
17pub struct Received<'m> {
18    resolver: Resolver<'m>,
19    entry: &'m ReceivedEntry,
20}
21
22/// A host name or an IP address.
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub enum Host<'m> {
25    /// A host name, or an address literal that is not an IP address.
26    Name(&'m str),
27    /// An IPv4 or IPv6 address.
28    IpAddr(IpAddr),
29}
30
31/// The TLS version of a Received `with` clause comment.
32#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
33#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
34pub enum TlsVersion {
35    /// SSL 2.
36    SSLv2,
37    /// SSL 3.
38    SSLv3,
39    /// TLS 1.0.
40    TLSv1_0,
41    /// TLS 1.1.
42    TLSv1_1,
43    /// TLS 1.2.
44    TLSv1_2,
45    /// TLS 1.3.
46    TLSv1_3,
47    /// DTLS 1.0.
48    DTLSv1_0,
49    /// DTLS 1.2.
50    DTLSv1_2,
51    /// DTLS 1.3.
52    DTLSv1_3,
53}
54
55/// The greeting command of a Received `from` clause comment.
56#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
57#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
58pub enum Greeting {
59    /// `HELO` (SMTP).
60    Helo,
61    /// `EHLO` (ESMTP).
62    Ehlo,
63    /// `LHLO` (LMTP).
64    Lhlo,
65}
66
67/// The protocol of a Received `with` clause (the IANA mail transmission
68/// types of RFC 3848 and later).
69#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
70#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
71#[allow(clippy::upper_case_acronyms)]
72pub enum Protocol {
73    /// `SMTP`.
74    SMTP,
75    /// `ESMTP`.
76    ESMTP,
77    /// `ESMTPA`: ESMTP with authentication.
78    ESMTPA,
79    /// `ESMTPS`: ESMTP with TLS.
80    ESMTPS,
81    /// `ESMTPSA`: ESMTP with TLS and authentication.
82    ESMTPSA,
83    /// `LMTP`.
84    LMTP,
85    /// `LMTPA`: LMTP with authentication.
86    LMTPA,
87    /// `LMTPS`: LMTP with TLS.
88    LMTPS,
89    /// `LMTPSA`: LMTP with TLS and authentication.
90    LMTPSA,
91    /// `MMS`.
92    MMS,
93    /// `UTF8SMTP`.
94    UTF8SMTP,
95    /// `UTF8SMTPA`.
96    UTF8SMTPA,
97    /// `UTF8SMTPS`.
98    UTF8SMTPS,
99    /// `UTF8SMTPSA`.
100    UTF8SMTPSA,
101    /// `UTF8LMTP`.
102    UTF8LMTP,
103    /// `UTF8LMTPA`.
104    UTF8LMTPA,
105    /// `UTF8LMTPS`.
106    UTF8LMTPS,
107    /// `UTF8LMTPSA`.
108    UTF8LMTPSA,
109    /// `HTTP`.
110    HTTP,
111    /// `HTTPS`.
112    HTTPS,
113    /// `IMAP`.
114    IMAP,
115    /// `POP3`.
116    POP3,
117    /// `Local`.
118    Local,
119}
120
121impl<'m> Received<'m> {
122    pub(crate) fn new(resolver: Resolver<'m>, entry: &'m ReceivedEntry) -> Self {
123        Received { resolver, entry }
124    }
125
126    fn host(&self, host: HostEntry) -> Option<Host<'m>> {
127        match host {
128            HostEntry::None => None,
129            HostEntry::Name(name) => Some(Host::Name(self.resolver.str(name))),
130            HostEntry::Ip(ip) => Some(Host::IpAddr(ip)),
131        }
132    }
133
134    /// Host that sent the message.
135    pub fn from(&self) -> Option<Host<'m>> {
136        self.host(self.entry.from)
137    }
138
139    /// IP address of the sending host: an address literal or an address in
140    /// a comment.
141    pub fn from_ip(&self) -> Option<IpAddr> {
142        self.entry.from_ip
143    }
144
145    /// The name the sending address resolved to, from a comment.
146    pub fn from_iprev(&self) -> Option<&'m str> {
147        self.resolver.opt(self.entry.from_iprev)
148    }
149
150    /// Host that received the message.
151    pub fn by(&self) -> Option<Host<'m>> {
152        self.host(self.entry.by)
153    }
154
155    /// The recipient of the `for` clause.
156    pub fn for_(&self) -> Option<&'m str> {
157        self.resolver.opt(self.entry.for_)
158    }
159
160    /// The protocol of the `with` clause.
161    pub fn with(&self) -> Option<Protocol> {
162        self.entry.with
163    }
164
165    /// The TLS version named in a comment.
166    pub fn tls_version(&self) -> Option<TlsVersion> {
167        self.entry.tls_version
168    }
169
170    /// The TLS cipher suite, when one is named.
171    pub fn tls_cipher(&self) -> Option<&'m str> {
172        self.resolver.opt(self.entry.tls_cipher)
173    }
174
175    /// The identifier of the `id` clause.
176    pub fn id(&self) -> Option<&'m str> {
177        self.resolver.opt(self.entry.id)
178    }
179
180    /// The ident (RFC 1413) user, from a comment.
181    pub fn ident(&self) -> Option<&'m str> {
182        self.resolver.opt(self.entry.ident)
183    }
184
185    /// The name the sender greeted with (`helo=` in a comment).
186    pub fn helo(&self) -> Option<Host<'m>> {
187        self.host(self.entry.helo)
188    }
189
190    /// The greeting command named in a comment.
191    pub fn helo_cmd(&self) -> Option<Greeting> {
192        self.entry.helo_cmd
193    }
194
195    /// The link of the `via` clause.
196    pub fn via(&self) -> Option<&'m str> {
197        self.resolver.opt(self.entry.via)
198    }
199
200    /// The date after the `;`.
201    pub fn date(&self) -> Option<DateTime> {
202        self.entry.date
203    }
204}
205
206impl fmt::Debug for Received<'_> {
207    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
208        f.debug_struct("Received")
209            .field("from", &self.from())
210            .field("from_ip", &self.from_ip())
211            .field("from_iprev", &self.from_iprev())
212            .field("by", &self.by())
213            .field("for", &self.for_())
214            .field("with", &self.with())
215            .field("tls_version", &self.tls_version())
216            .field("tls_cipher", &self.tls_cipher())
217            .field("id", &self.id())
218            .field("ident", &self.ident())
219            .field("helo", &self.helo())
220            .field("helo_cmd", &self.helo_cmd())
221            .field("via", &self.via())
222            .field("date", &self.date())
223            .finish()
224    }
225}
226
227impl PartialEq for Received<'_> {
228    fn eq(&self, other: &Self) -> bool {
229        self.from() == other.from()
230            && self.from_ip() == other.from_ip()
231            && self.from_iprev() == other.from_iprev()
232            && self.by() == other.by()
233            && self.for_() == other.for_()
234            && self.with() == other.with()
235            && self.tls_version() == other.tls_version()
236            && self.tls_cipher() == other.tls_cipher()
237            && self.id() == other.id()
238            && self.ident() == other.ident()
239            && self.helo() == other.helo()
240            && self.helo_cmd() == other.helo_cmd()
241            && self.via() == other.via()
242            && self.date() == other.date()
243    }
244}
245
246impl Eq for Received<'_> {}
247
248impl TlsVersion {
249    /// The version as usually written (`TLSv1.3`).
250    pub fn as_str(&self) -> &'static str {
251        match self {
252            TlsVersion::SSLv2 => "SSLv2",
253            TlsVersion::SSLv3 => "SSLv3",
254            TlsVersion::TLSv1_0 => "TLSv1.0",
255            TlsVersion::TLSv1_1 => "TLSv1.1",
256            TlsVersion::TLSv1_2 => "TLSv1.2",
257            TlsVersion::TLSv1_3 => "TLSv1.3",
258            TlsVersion::DTLSv1_0 => "DTLSv1.0",
259            TlsVersion::DTLSv1_2 => "DTLSv1.2",
260            TlsVersion::DTLSv1_3 => "DTLSv1.3",
261        }
262    }
263}
264
265impl Greeting {
266    /// The command in uppercase.
267    pub fn as_str(&self) -> &'static str {
268        match self {
269            Greeting::Helo => "HELO",
270            Greeting::Ehlo => "EHLO",
271            Greeting::Lhlo => "LHLO",
272        }
273    }
274}
275
276impl Protocol {
277    /// The protocol name as written in a `with` clause.
278    pub fn as_str(&self) -> &'static str {
279        match self {
280            Protocol::SMTP => "SMTP",
281            Protocol::LMTP => "LMTP",
282            Protocol::ESMTP => "ESMTP",
283            Protocol::ESMTPS => "ESMTPS",
284            Protocol::ESMTPA => "ESMTPA",
285            Protocol::ESMTPSA => "ESMTPSA",
286            Protocol::LMTPA => "LMTPA",
287            Protocol::LMTPS => "LMTPS",
288            Protocol::LMTPSA => "LMTPSA",
289            Protocol::UTF8SMTP => "UTF8SMTP",
290            Protocol::UTF8SMTPA => "UTF8SMTPA",
291            Protocol::UTF8SMTPS => "UTF8SMTPS",
292            Protocol::UTF8SMTPSA => "UTF8SMTPSA",
293            Protocol::UTF8LMTP => "UTF8LMTP",
294            Protocol::UTF8LMTPA => "UTF8LMTPA",
295            Protocol::UTF8LMTPS => "UTF8LMTPS",
296            Protocol::UTF8LMTPSA => "UTF8LMTPSA",
297            Protocol::HTTP => "HTTP",
298            Protocol::HTTPS => "HTTPS",
299            Protocol::IMAP => "IMAP",
300            Protocol::POP3 => "POP3",
301            Protocol::MMS => "MMS",
302            Protocol::Local => "Local",
303        }
304    }
305}
306
307impl fmt::Display for Host<'_> {
308    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
309        match self {
310            Host::Name(name) => f.write_str(name),
311            Host::IpAddr(ip) => ip.fmt(f),
312        }
313    }
314}
315
316impl fmt::Display for Protocol {
317    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
318        f.write_str(self.as_str())
319    }
320}
321
322impl fmt::Display for Greeting {
323    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
324        f.write_str(self.as_str())
325    }
326}
327
328impl fmt::Display for TlsVersion {
329    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
330        f.write_str(self.as_str())
331    }
332}