diff options
| author | Mroik <mroik@delayed.space> | 2026-04-09 01:26:36 +0200 |
|---|---|---|
| committer | Mroik <mroik@delayed.space> | 2026-04-13 06:56:10 +0200 |
| commit | 8d55b7c1c3cac3e6cff00be866f1b48a7300268b (patch) | |
| tree | 893382d645e87ef25a8b5e484152f294460fb532 /src | |
| parent | 6d5043fc90f327addd587548a061d2d52ce1766e (diff) | |
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 <mroik@delayed.space>
Diffstat (limited to 'src')
| -rw-r--r-- | src/smtp.rs | 30 |
1 files changed, 30 insertions, 0 deletions
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<String>, + to: Vec<String>, } 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<Reply> { + 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")) } } |
