diff options
| -rw-r--r-- | src/smtp.rs | 58 |
1 files changed, 55 insertions, 3 deletions
diff --git a/src/smtp.rs b/src/smtp.rs index 00e2ae8..30cf490 100644 --- a/src/smtp.rs +++ b/src/smtp.rs @@ -32,6 +32,7 @@ impl SmtpServer { client_host: String::new(), from: None, to: Vec::new(), + data: String::new(), }; spawn(session.run(stream)); } @@ -45,6 +46,7 @@ struct SessionHandler { client_host: String, from: Option<String>, to: Vec<String>, + data: String, } impl SessionHandler { @@ -60,11 +62,24 @@ impl SessionHandler { )?; loop { + buffer.clear(); if r.read_line(&mut buffer)? == 0 { break; } log::debug!("Received '{}' from '{}'", buffer.trim(), self.addr); + // In this state the server is not expecting commands as the input is part of the email + // message + if self.state == SessionState::AwaitingMailInput { + if buffer.starts_with(".\r\n") { + self.process_mail().await?; + continue; + } + + self.data.push_str(&buffer); + continue; + } + let command = match Command::try_from(buffer.as_str()) { Err(_) => { writer.write_all(Reply::InvalidCommand.to_string().as_bytes())?; @@ -74,8 +89,6 @@ impl SessionHandler { }; let res = self.apply(command).await?; writer.write_all(res.to_string().as_bytes())?; - - buffer.clear(); } log::info!("Connection closed by {}", self.addr); @@ -87,6 +100,7 @@ impl SessionHandler { Command::HELO(hostname) => self.helo(hostname).await, Command::MAIL(from) => self.mail(from).await, Command::RCPT(to) => self.rcpt(to).await, + Command::DATA => self.start_data().await, } } @@ -127,12 +141,43 @@ impl SessionHandler { self.to.push(String::from(to)); Ok(Reply::Completed(String::from("Ok"))) } + + /// Only after having started a mail transaction + async fn start_data(&mut self) -> std::result::Result<Reply, anyhow::Error> { + if self.state != SessionState::MailTransaction { + return Ok(Reply::BadSequence); + } + + // Should I return a more meaningful string with this error code? + if self.from.is_none() || self.to.is_empty() { + return Ok(Reply::InvalidCommand); + } + + self.state = SessionState::AwaitingMailInput; + + Ok(Reply::StartMailInput) + } + + // TODO: Forward and store email + async fn process_mail(&mut self) -> Result<Reply> { + if self.from.is_none() || self.to.is_empty() || self.data.trim().is_empty() { + return Ok(Reply::InvalidCommand); + } + + self.state = SessionState::Normal; + self.from = None; + self.to.clear(); + self.data.clear(); + + todo!() + } } #[derive(PartialEq)] enum SessionState { WaitingHelo, MailTransaction, + AwaitingMailInput, Normal, } @@ -145,6 +190,7 @@ impl Default for SessionState { enum Reply { Ready(String), Completed(String), + StartMailInput, InvalidCommand, InvalidParameter, BadSequence, @@ -155,9 +201,10 @@ impl ToString for Reply { match self { Reply::Ready(hostname) => format!("220 {}", hostname), Reply::Completed(hostname) => format!("250 {}", hostname), - Reply::InvalidCommand => format!("500 Command not recognized"), + Reply::InvalidCommand => format!("500 Invalid command"), Reply::InvalidParameter => format!("501 Parameter error"), Reply::BadSequence => format!("503 Bad sequence of commands"), + Reply::StartMailInput => format!("354 <CRLF>.<CRLF>"), } } } @@ -166,6 +213,7 @@ enum Command { HELO(String), MAIL(String), RCPT(String), + DATA, } impl TryFrom<&str> for Command { @@ -190,6 +238,10 @@ impl TryFrom<&str> for Command { return Ok(Command::RCPT(String::from(to))); } + if value.to_lowercase().starts_with("data") { + return Ok(Command::DATA); + } + Err(anyhow::format_err!("Invalid command")) } } |
