samotop_core/smtp/rfc5321/
mail.rs

1use crate::{
2    common::{Identify, S1Fut},
3    mail::{MailGuard, StartMailResult},
4    smtp::{command::SmtpMail, Action, Esmtp, SmtpContext},
5};
6
7impl Action<SmtpMail> for Esmtp {
8    fn apply<'a, 's, 'f>(&'a self, cmd: SmtpMail, state: &'s mut SmtpContext) -> S1Fut<'f, ()>
9    where
10        'a: 'f,
11        's: 'f,
12    {
13        Box::pin(async move {
14            if state.session.peer_name.is_none() {
15                state.session.say_command_sequence_fail();
16                return;
17            }
18            state.session.reset();
19            state.session.transaction.mail = Some(cmd);
20
21            use StartMailResult as R;
22            match state.service().start_mail(&mut state.session).await {
23                R::Failed(failure, description) => {
24                    state.session.say_mail_failed(failure, description);
25                }
26                R::Accepted => {
27                    if state.session.transaction.id.is_empty() {
28                        let id = format!("{}@{}", Identify::now(), state.session.service_name);
29                        warn!(
30                            "Mail transaction ID is empty. Will use time based ID {}",
31                            id
32                        );
33                        state.session.transaction.id = id;
34                    }
35                    state.session.say_ok_info(format!(
36                        "Ok! Transaction {} started.",
37                        state.session.transaction.id
38                    ));
39                }
40            }
41        })
42    }
43}
44
45#[cfg(test)]
46mod tests {
47    use super::*;
48    use crate::{
49        mail::Recipient,
50        smtp::{command::SmtpMail, DriverControl, Esmtp, SmtpPath},
51    };
52
53    #[test]
54    fn transaction_gets_reset() {
55        async_std::task::block_on(async move {
56            let mut set = SmtpContext::default();
57            set.session.peer_name = Some("xx.io".to_owned());
58            set.session.transaction.id = "someid".to_owned();
59            set.session.transaction.mail = Some(SmtpMail::Mail(SmtpPath::Null, vec![]));
60            set.session.transaction.rcpts.push(Recipient::null());
61            set.session
62                .transaction
63                .extra_headers
64                .insert_str(0, "feeeha");
65
66            Esmtp
67                .apply(SmtpMail::Mail(SmtpPath::Postmaster, vec![]), &mut set)
68                .await;
69            match set.session.pop_control() {
70                Some(DriverControl::Response(bytes)) if bytes.starts_with(b"250 ") => {}
71                otherwise => panic!("Expected OK, got {:?}", otherwise),
72            }
73            assert_ne!(set.session.transaction.id, "someid");
74            assert!(set.session.transaction.rcpts.is_empty());
75            assert!(set.session.transaction.extra_headers.is_empty());
76        })
77    }
78
79    #[test]
80    fn mail_is_set() {
81        async_std::task::block_on(async move {
82            let mut set = SmtpContext::default();
83            set.session.peer_name = Some("xx.io".to_owned());
84
85            Esmtp
86                .apply(SmtpMail::Mail(SmtpPath::Postmaster, vec![]), &mut set)
87                .await;
88            match set.session.pop_control() {
89                Some(DriverControl::Response(bytes)) if bytes.starts_with(b"250 ") => {}
90                otherwise => panic!("Expected OK, got {:?}", otherwise),
91            }
92            assert_eq!(
93                set.session.transaction.mail,
94                Some(SmtpMail::Mail(SmtpPath::Postmaster, vec![]))
95            );
96        })
97    }
98
99    #[test]
100    fn command_sequence_is_enforced() {
101        async_std::task::block_on(async move {
102            // MAIL command requires HELO/EHLO
103            let mut set = SmtpContext::default();
104
105            Esmtp
106                .apply(SmtpMail::Mail(SmtpPath::Postmaster, vec![]), &mut set)
107                .await;
108            match set.session.pop_control() {
109                Some(DriverControl::Response(bytes)) if bytes.starts_with(b"503 ") => {}
110                otherwise => panic!("Expected command sequence failure, got {:?}", otherwise),
111            }
112            assert_eq!(set.session.transaction.mail, None);
113        })
114    }
115}