diff options
| author | Mroik <mroik@delayed.space> | 2026-04-07 13:13:16 +0200 |
|---|---|---|
| committer | Mroik <mroik@delayed.space> | 2026-04-13 06:56:10 +0200 |
| commit | 2818801042e44961ac2787ff9cd88d0579061a7b (patch) | |
| tree | 289d45860a5d558a4535e8d4869df8b5779634e3 /src | |
| parent | ceaa25c8baf2294458c1560b3292c00c57b0fa1b (diff) | |
Implement HELO command interaction
EHLO most likely won't be implemented as this software is meant to be
ran in a containerized environment, where only the main MTA is supposed
to be able to reach the container of this software.
Signed-off-by: Mroik <mroik@delayed.space>
Diffstat (limited to 'src')
| -rw-r--r-- | src/smtp.rs | 76 |
1 files changed, 63 insertions, 13 deletions
diff --git a/src/smtp.rs b/src/smtp.rs index affac8b..f8cf0ee 100644 --- a/src/smtp.rs +++ b/src/smtp.rs @@ -1,11 +1,13 @@ use std::{ - io::{BufRead, BufReader}, + io::{BufRead, BufReader, Write}, net::{IpAddr, SocketAddr, TcpListener, TcpStream}, }; use anyhow::Result; use tokio::spawn; +const SERVER_NAME: &str = ""; + pub struct SmtpServer { listener: TcpListener, running: bool, @@ -25,47 +27,95 @@ impl SmtpServer { while self.running { let (stream, addr) = self.listener.accept()?; let session = SessionHandler { - stream, addr, state: SessionState::default(), + client_host: String::new(), }; - spawn(session.run()); + spawn(session.run(stream)); } Ok(()) } } struct SessionHandler { - stream: TcpStream, addr: SocketAddr, state: SessionState, + client_host: String, } impl SessionHandler { - async fn run(mut self) -> Result<()> { - let mut r = BufReader::new(&self.stream); + async fn run(mut self, stream: TcpStream) -> Result<()> { + let mut writer = stream.try_clone()?; + let mut r = BufReader::new(&stream); let mut buffer = String::new(); loop { if r.read_line(&mut buffer)? == 0 { break; } - log::debug!("Received '{}' from '{}'", buffer.trim(), self.addr); - self.state.apply().await; + + let command = Command::try_from(buffer.as_str())?; + let res = self.apply(command).await?; + writer.write_all(res.to_string().as_bytes())?; + buffer.clear(); } log::info!("Connection closed by {}", self.addr); Ok(()) } + + async fn apply(&mut self, command: Command) -> Result<Reply> { + match &command { + Command::HELO(hostname) => self.helo(hostname).await, + } + } + + /// HELO resets buffers + async fn helo(&mut self, hostname: &str) -> Result<Reply> { + self.client_host = String::from(hostname); + self.state = SessionState::NextState; + Ok(Reply::Completed(String::from(SERVER_NAME))) + } +} + +enum SessionState { + WaitingHelo, + /// This is a dev state + NextState, +} + +impl Default for SessionState { + fn default() -> Self { + Self::WaitingHelo + } +} + +enum Reply { + Completed(String), +} + +impl ToString for Reply { + fn to_string(&self) -> String { + match self { + Reply::Completed(hostname) => format!("250 {}", hostname), + } + } +} + +enum Command { + HELO(String), } -#[derive(Default)] -struct SessionState {} +impl TryFrom<&str> for Command { + type Error = anyhow::Error; + + fn try_from(value: &str) -> std::result::Result<Self, Self::Error> { + if value.len() >= 5 && value.to_lowercase().starts_with("helo ") { + return Ok(Command::HELO(String::from(&value[5..]))); + } -impl SessionState { - async fn apply(&mut self) { - todo!() + Err(anyhow::format_err!("Invalid command")) } } |
