From 8d55b7c1c3cac3e6cff00be866f1b48a7300268b Mon Sep 17 00:00:00 2001 From: Mroik Date: Thu, 9 Apr 2026 01:26:36 +0200 Subject: Add SMTP recipient command Validation for the recipients' email addresses should be added later on. This is not strictly necessary as the request at this point will already have gone through another MTA, but it should be done just for good measure in case someone decides to expose this software directly to the internet. Signed-off-by: Mroik --- src/smtp.rs | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) (limited to 'src/smtp.rs') diff --git a/src/smtp.rs b/src/smtp.rs index 2ab79f8..00e2ae8 100644 --- a/src/smtp.rs +++ b/src/smtp.rs @@ -31,6 +31,7 @@ impl SmtpServer { state: SessionState::default(), client_host: String::new(), from: None, + to: Vec::new(), }; spawn(session.run(stream)); } @@ -43,6 +44,7 @@ struct SessionHandler { state: SessionState, client_host: String, from: Option, + to: Vec, } impl SessionHandler { @@ -84,6 +86,7 @@ impl SessionHandler { match &command { Command::HELO(hostname) => self.helo(hostname).await, Command::MAIL(from) => self.mail(from).await, + Command::RCPT(to) => self.rcpt(to).await, } } @@ -92,6 +95,7 @@ impl SessionHandler { self.client_host = String::from(hostname); self.state = SessionState::Normal; self.from = None; + self.to.clear(); Ok(Reply::Completed(String::from(SERVER_NAME))) } @@ -103,12 +107,29 @@ impl SessionHandler { } self.from = Some(String::from(from)); + self.to.clear(); self.state = SessionState::MailTransaction; Ok(Reply::Completed(String::from("Ok"))) } + + /// TODO: Validate email addresses + /// Only after having started a mail transaction + async fn rcpt(&mut self, to: &str) -> Result { + if self.state != SessionState::MailTransaction { + return Ok(Reply::BadSequence); + } + + if to.is_empty() { + return Ok(Reply::InvalidParameter); + } + + self.to.push(String::from(to)); + Ok(Reply::Completed(String::from("Ok"))) + } } +#[derive(PartialEq)] enum SessionState { WaitingHelo, MailTransaction, @@ -126,6 +147,7 @@ enum Reply { Completed(String), InvalidCommand, InvalidParameter, + BadSequence, } impl ToString for Reply { @@ -135,6 +157,7 @@ impl ToString for Reply { Reply::Completed(hostname) => format!("250 {}", hostname), Reply::InvalidCommand => format!("500 Command not recognized"), Reply::InvalidParameter => format!("501 Parameter error"), + Reply::BadSequence => format!("503 Bad sequence of commands"), } } } @@ -142,6 +165,7 @@ impl ToString for Reply { enum Command { HELO(String), MAIL(String), + RCPT(String), } impl TryFrom<&str> for Command { @@ -160,6 +184,12 @@ impl TryFrom<&str> for Command { return Ok(Command::MAIL(String::from(from))); } + if value.len() >= 9 && value.to_lowercase().starts_with("rcpt to:<") && value.contains('>') + { + let to = &value[9..value.find('>').unwrap()]; + return Ok(Command::RCPT(String::from(to))); + } + Err(anyhow::format_err!("Invalid command")) } } -- cgit v1.3