1 use crate::send_commands::send_commands;
2 use futures::StreamExt;
3 use tokio::io::{AsyncRead, AsyncWrite};
4 use tokio_util::codec::{Framed, LinesCodec};
5
6 enum SmtpState {
7 Command,
8 Data,
9 Quit,
10 }
11
12 pub async fn handle_session<S>(stream: S) -> anyhow::Result<()>
13 where
14 S: AsyncRead + AsyncWrite + Unpin,
15 {
16 let re_smtp_mail = regex::Regex::new(r"(?i)from: ?<(.+)>").unwrap();
17 let re_smtp_rcpt = regex::Regex::new(r"(?i)to: ?<(.+)>").unwrap();
18 let mut message = String::new();
19 let mut state = SmtpState::Command;
20 let mut mailfrom: Option<String> = None;
21 let mut rcpts: Vec<String> = Vec::new();
22 let mut framed = Framed::new(stream, LinesCodec::new());
23 while let Some(line_str) = framed.next().await {
24 let line = line_str?;
25 match state {
26 SmtpState::Command => {
27 let space_pos = line.find(" ").unwrap_or(line.len());
28 let (command, arg) = line.split_at(space_pos);
29 let arg = arg.trim();
30 match &*command.trim().to_uppercase() {
31 "HELO" | "EHLO" => {
32 send_commands(&mut framed, vec!["250 Hello".to_string()]).await?;
33 }
34 "MAIL" => {
35 // Handle MAIL FROM command
36 if let Some(address) = re_smtp_mail.captures(arg).and_then(|cap| cap.get(1))
37 {
38 mailfrom = Some(address.as_str().to_string());
39 send_commands(&mut framed, vec!["250 OK".to_string()]).await?;
40 } else {
41 send_commands(
42 &mut framed,
43 vec!["501 Syntax: MAIL From: <address>".to_string()],
44 )
45 .await?;
46 }
47 }
48 "RCPT" => {
49 // Handle RCPT TO command
50 if mailfrom.is_none() {
51 send_commands(
52 &mut framed,
53 vec!["503 Error: Send MAIL first".to_string()],
54 )
55 .await?;
56 } else if let Some(address) =
57 re_smtp_rcpt.captures(arg).and_then(|cap| cap.get(1))
58 {
59 rcpts.push(address.as_str().to_string());
60 send_commands(&mut framed, vec!["250 OK".to_string()]).await?;
61 } else {
62 send_commands(
63 &mut framed,
64 vec!["501 Syntax: RCPT TO: <address>".to_string()],
65 )
66 .await?;
67 }
68 }
69 "DATA" => {
70 if rcpts.is_empty() {
71 send_commands(&mut framed, vec!["503 Error: MAIL FROM and RCPT TO must be set before sending DATA".to_string()]).await?;
72 } else {
73 state = SmtpState::Data;
74 send_commands(
75 &mut framed,
76 vec!["354 End data with <CR><LF>.<CR><LF>".to_string()],
77 )
78 .await?;
79 }
80 }
81 "NOOP" => {
82 send_commands(&mut framed, vec!["250 OK".to_string()]).await?;
83 }
84 "RSET" => {
85 mailfrom = None;
86 rcpts = Vec::new();
87 message = String::new();
88 send_commands(&mut framed, vec!["250 OK".to_string()]).await?;
89 }
90 "QUIT" => {
91 send_commands(&mut framed, vec!["221 Bye".to_string()]).await?;
92 state = SmtpState::Quit;
93 }
94 _ => {
95 send_commands(&mut framed, vec!["500 Unknown command".to_string()]).await?;
96 }
97 }
98 }
99 SmtpState::Data => {
100 if line.trim() == "." {
101 // The end of the email content has been received
102 send_commands(&mut framed, vec!["250 OK".to_string()]).await?;
103
104 // Log the received email
105 tracing::info!(
106 "Email received - From: {:?}, To: {:?}, Size: {} bytes",
107 mailfrom,
108 rcpts,
109 message.len()
110 );
111
112 // reset the state and variables for the next email
113 mailfrom = None;
114 rcpts = Vec::new();
115 message = String::new();
116 state = SmtpState::Command;
117 } else {
118 // Add the received line to the email content
119 message.push_str(&line);
120 message.push('\n');
121 }
122 }
123 SmtpState::Quit => {
124 break;
125 }
126 }
127 }
128 Ok(())
129 }