samotop_core/smtp/rfc5321/
mail.rs1use 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 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}