1use std::sync::Arc;
2
3use serde::{Deserialize, Serialize};
4use tokio::sync::Mutex;
5
6use crate::{
7 client_message::ClientMessage,
8 connection::{SMTPConnection, SMTPConnectionStatus},
9 errors::SMTPError,
10 mail::EmailAddress,
11 message::Message,
12 server::Controllers,
13 status_code::StatusCodes,
14};
15
16#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
20#[serde(rename_all = "UPPERCASE")]
21pub enum Commands {
22 HELO,
26 EHLO,
30 MAIL,
34 RCPT,
38 DATA,
41 RSET,
45 VRFY,
49 EXPN,
53 HELP,
57 NOOP,
61 QUIT,
65 AUTH,
69 STARTTLS,
73 UNKNOWN(String),
77}
78
79impl Commands {
80 pub fn from_bytes(bytes: &[u8]) -> Self {
84 let bytes_to_string = String::from_utf8_lossy(bytes)
86 .to_uppercase()
87 .trim()
88 .to_string();
89 match bytes_to_string.as_str() {
90 "HELO" => Commands::HELO,
91 "EHLO" => Commands::EHLO,
92 "MAIL" => Commands::MAIL,
93 "RCPT" => Commands::RCPT,
94 "DATA" => Commands::DATA,
95 "RSET" => Commands::RSET,
96 "VRFY" => Commands::VRFY,
97 "EXPN" => Commands::EXPN,
98 "HELP" => Commands::HELP,
99 "NOOP" => Commands::NOOP,
100 "QUIT" => Commands::QUIT,
101 "AUTH" => Commands::AUTH,
102 "STARTTLS" => Commands::STARTTLS,
103 _ => Commands::UNKNOWN(bytes_to_string),
104 }
105 }
106
107 pub fn parse_mail_command_data(data: String) -> Result<EmailAddress, SMTPError> {
111 let data = data.trim();
113
114 let start = data
116 .find('<')
117 .ok_or(SMTPError::ParseError("Invalid email address".to_string()))?;
118 let end = data
119 .find('>')
120 .ok_or(SMTPError::ParseError("Invalid email address".to_string()))?;
121
122 let email_address = &data[start + 1..end];
124 EmailAddress::from_string(email_address)
125 .map_err(|_| SMTPError::ParseError("Invalid email address".to_string()))
126 }
127
128 pub fn parse_rcpt_command_data(data: String) -> Result<EmailAddress, SMTPError> {
132 let data = data.trim();
134
135 let start = data
137 .find('<')
138 .ok_or(SMTPError::ParseError("Invalid email address".to_string()))?;
139 let end = data
140 .find('>')
141 .ok_or(SMTPError::ParseError("Invalid email address".to_string()))?;
142
143 let email_address = &data[start + 1..end];
145 EmailAddress::from_string(email_address)
146 .map_err(|_| SMTPError::ParseError("Invalid email address".to_string()))
147 }
148}
149
150pub async fn handle_command<B>(
154 conn: Arc<Mutex<SMTPConnection<B>>>,
155 controllers: Controllers<B>,
156 client_message: &mut ClientMessage<String>,
157 allowed_commands: Vec<Commands>,
158 max_size: usize,
159) -> Result<(Vec<Message>, SMTPConnectionStatus), SMTPError>
160where
161 B: 'static + Default + Send + Sync + Clone,
162{
163 log::trace!("[⚙️] Handling SMTP command: {:?}", client_message.command);
164
165 if allowed_commands
167 .iter()
168 .find(|&cmd| cmd == &client_message.command)
169 .is_none()
170 {
171 return Err(SMTPError::UnknownCommand(client_message.command.clone()));
172 }
173
174 let result = match client_message.command {
175 Commands::HELO => (
176 vec![Message::builder()
177 .status(StatusCodes::OK)
178 .message(format!("Hello {}", "unknown"))
179 .build()],
180 SMTPConnectionStatus::WaitingCommand,
181 ),
182 Commands::EHLO => {
183 let mut ehlo_messages = vec![
184 Message::builder()
185 .status(StatusCodes::OK)
186 .message("Hello".to_string())
187 .build(),
188 Message::builder()
189 .status(StatusCodes::OK)
190 .message(format!("SIZE {}", max_size))
191 .build(),
192 Message::builder()
193 .status(StatusCodes::OK)
194 .message("8BITMIME".to_string())
195 .build(),
196 Message::builder()
197 .status(StatusCodes::OK)
198 .message("PIPELINING".to_string())
199 .build(),
200 Message::builder()
201 .status(StatusCodes::OK)
202 .message("HELP".to_string())
203 .build(),
204 ];
205
206 let conn = conn.lock().await;
207 if !conn.use_tls {
208 ehlo_messages.push(
209 Message::builder()
210 .status(StatusCodes::OK)
211 .message("STARTTLS".to_string())
212 .build(),
213 )
214 }
215
216 if controllers.on_auth.is_some() {
217 ehlo_messages.push(
218 Message::builder()
219 .status(StatusCodes::OK)
220 .message(
221 "AUTH PLAIN LOGIN CRAM-MD5 DIGEST-MD5 GSSAPI NTLM XOAUTH2".to_string(),
222 )
223 .build(),
224 );
225 }
226
227 drop(conn);
228
229 (ehlo_messages, SMTPConnectionStatus::WaitingCommand)
230 }
231 Commands::MAIL => {
232 if let Some(on_mail_cmd) = &controllers.on_mail_cmd {
233 let on_mail_cmd = on_mail_cmd.0.clone();
234 match on_mail_cmd(conn.clone(), client_message.data.clone()).await {
235 Ok(response) => {
236 return Ok((vec![response], SMTPConnectionStatus::WaitingCommand))
237 }
238 Err(response) => return Ok((vec![response], SMTPConnectionStatus::Closed)),
239 }
240 } else {
241 (
242 vec![Message::builder()
243 .status(StatusCodes::OK)
244 .message("Ok".to_string())
245 .build()],
246 SMTPConnectionStatus::WaitingCommand,
247 )
248 }
249 }
250 Commands::RCPT => {
251 if let Some(on_rcpt_cmd) = &controllers.on_rcpt_cmd {
252 let on_rcpt_cmd = on_rcpt_cmd.0.clone();
253 match on_rcpt_cmd(conn.clone(), client_message.data.clone()).await {
254 Ok(response) => (vec![response], SMTPConnectionStatus::WaitingCommand),
255 Err(response) => (vec![response], SMTPConnectionStatus::Closed),
256 }
257 } else {
258 let last_command = conn.lock().await;
259 let last_command = last_command
260 .tracing_commands
261 .last()
262 .unwrap_or(&Commands::HELO);
263
264 if last_command != &Commands::MAIL && last_command != &Commands::RCPT {
265 (
266 vec![Message::builder()
267 .status(StatusCodes::BadSequenceOfCommands)
268 .message("Bad sequence of commands".to_string())
269 .build()],
270 SMTPConnectionStatus::WaitingCommand,
271 )
272 } else {
273 (
274 vec![Message::builder()
275 .status(StatusCodes::OK)
276 .message("Ok".to_string())
277 .build()],
278 SMTPConnectionStatus::WaitingCommand,
279 )
280 }
281 }
282 }
283 Commands::DATA => (
284 vec![Message::builder()
285 .status(StatusCodes::StartMailInput)
286 .message("Start mail input; end with <CRLF>.<CRLF>".to_string())
287 .build()],
288 SMTPConnectionStatus::WaitingData,
289 ),
290 Commands::RSET => (
291 vec![Message::builder()
292 .status(StatusCodes::OK)
293 .message("Hello".to_string())
294 .build()],
295 SMTPConnectionStatus::WaitingCommand,
296 ),
297 Commands::VRFY => (
298 vec![Message::builder()
299 .status(StatusCodes::CannotVerifyUserButWillAcceptMessageAndAttemptDelivery)
300 .message(
301 "Cannot VRFY user, but will accept message and attempt delivery".to_string(),
302 )
303 .build()],
304 SMTPConnectionStatus::WaitingCommand,
305 ),
306 Commands::EXPN => (
307 vec![Message::builder()
308 .status(StatusCodes::CommandNotImplemented)
309 .message(
310 "Cannot EXPN user, but will accept message and attempt delivery".to_string(),
311 )
312 .build()],
313 SMTPConnectionStatus::WaitingCommand,
314 ),
315 Commands::HELP => (
316 vec![Message::builder()
317 .status(StatusCodes::HelpMessage)
318 .message("Help message".to_string())
319 .build()],
320 SMTPConnectionStatus::WaitingCommand,
321 ),
322 Commands::NOOP => (
323 vec![Message::builder()
324 .status(StatusCodes::OK)
325 .message("NOOP Command successful".to_string())
326 .build()],
327 SMTPConnectionStatus::WaitingCommand,
328 ),
329 Commands::QUIT => (
330 vec![Message::builder()
331 .status(StatusCodes::ServiceClosingTransmissionChannel)
332 .message("Service closing transmission channel".to_string())
333 .build()],
334 SMTPConnectionStatus::Closed,
335 ),
336 Commands::AUTH => {
337 if let Some(on_auth) = &controllers.on_auth {
338 let on_auth = on_auth.0.clone();
339 match on_auth(conn.clone(), client_message.data.clone()).await {
340 Ok(response) => (vec![response], SMTPConnectionStatus::WaitingCommand),
341 Err(response) => return Ok((vec![response], SMTPConnectionStatus::Closed)),
342 }
343 } else {
344 (
345 vec![Message::builder()
346 .status(StatusCodes::CommandNotImplemented)
347 .message("Command not recognized".to_string())
348 .build()],
349 SMTPConnectionStatus::WaitingCommand,
350 )
351 }
352 }
353 Commands::STARTTLS => {
354 let conn = conn.lock().await;
355
356 if conn.use_tls {
357 (
358 vec![Message::builder()
359 .status(StatusCodes::TransactionFailed)
360 .message("Already using TLS".to_string())
361 .build()],
362 SMTPConnectionStatus::WaitingCommand,
363 )
364 } else {
365 (
366 vec![Message::builder()
367 .status(StatusCodes::SMTPServiceReady)
368 .message("Ready to start TLS".to_string())
369 .build()],
370 SMTPConnectionStatus::StartTLS,
371 )
372 }
373 }
374 _ => {
375 if let Some(on_unknown_cmd) = &controllers.on_unknown_cmd {
376 let on_unknown_cmd = on_unknown_cmd.0.clone();
377 match on_unknown_cmd(conn.clone(), client_message.command.clone()).await {
378 Ok(response) => (vec![response], SMTPConnectionStatus::WaitingCommand),
379 Err(response) => (vec![response], SMTPConnectionStatus::Closed),
380 }
381 } else {
382 (
383 vec![Message::builder()
384 .status(StatusCodes::CommandNotImplemented)
385 .message("Command not recognized".to_string())
386 .build()],
387 SMTPConnectionStatus::WaitingCommand,
388 )
389 }
390 }
391 };
392
393 let mut guarded_conn = conn.lock().await;
394 guarded_conn
395 .tracing_commands
396 .push(client_message.command.clone());
397 drop(guarded_conn);
398
399 Ok(result)
400}