From e0fd858c2f571ee1964786bdd132af1cfa5eaaac Mon Sep 17 00:00:00 2001 From: mawalu Date: Sat, 15 Jul 2023 14:21:49 +0200 Subject: [PATCH] Wire up SMTP and HTTP --- src/http.rs | 12 +++++++---- src/main.rs | 11 ++++++---- src/smtp.rs | 58 +++++++++++++++++++++++++++++++++++++--------------- src/state.rs | 20 +++++++++--------- 4 files changed, 66 insertions(+), 35 deletions(-) diff --git a/src/http.rs b/src/http.rs index 994d396..f0da425 100644 --- a/src/http.rs +++ b/src/http.rs @@ -17,20 +17,24 @@ pub fn http_handler(request: &Request, state: &State) -> Response { domain: String })); - let email = format!("{}@{}", get_name(), data.domain); + let email = format!("{}@{}", get_name().to_lowercase(), data.domain); println!("{}", email); - state.lock().unwrap().insert(email.clone(), Mailbox::new(email)); + state.lock().unwrap().insert(email.clone(), Mailbox::new(email.clone())); - rouille::Response::text("ok") + rouille::Response::text(email) }, (GET) (/ffff/{email: String}) => { println!("{}", email); if let Some(mailbox) = state.lock().unwrap().get(&email) { - rouille::Response::text(format!("{}: {}", &mailbox.email, &mailbox.messages.len())) + let body: &Vec = &mailbox.messages.iter().map(|msg| { + return format!("

From {}


{}

", msg.sender, msg.data) + }).collect(); + + rouille::Response::html(body.join("")) } else { rouille::Response::text("Mailbox not found") } diff --git a/src/main.rs b/src/main.rs index 84a3cd3..a4296d9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,7 @@ extern crate rand; extern crate rouille; +use std::sync::Arc; use std::thread; mod http; @@ -13,14 +14,16 @@ fn main() { let state = state::fresh_state(); - let http_thread = thread::spawn(|| { + let http_state = Arc::clone(&state); + let http_thread = thread::spawn(move || { rouille::start_server("localhost:8005", move |request| { - http::http_handler(request, &state) + http::http_handler(request, &http_state) }) }); - let smtp_thread = thread::spawn(|| { - smtp::start_server(); + let smtp_state = Arc::clone(&state); + let smtp_thread = thread::spawn(move || { + smtp::start_server(smtp_state); }); http_thread.join().unwrap(); diff --git a/src/smtp.rs b/src/smtp.rs index 8c3bfb1..b3405bc 100644 --- a/src/smtp.rs +++ b/src/smtp.rs @@ -1,8 +1,9 @@ use std::io::{BufRead, BufReader, Write}; +use crate::state::{Message, State}; use std::net::{TcpListener, TcpStream}; -pub fn start_server() { +pub fn start_server(state: State) { let listener = TcpListener::bind("localhost:8025").unwrap(); for stream in listener.incoming() { @@ -13,16 +14,23 @@ pub fn start_server() { let reader = BufReader::new(tcp_stream.try_clone().unwrap()); let mut connection = Connection::new(); + if let Err(e) = connection.handle(tcp_stream, reader) { eprintln!("System error: {}", e) - } else { + } else if let Ok(mut state_inner) = state.lock() { for mail in connection.messages { println!( "Mail from {} for {}\n{}", mail.sender, mail.recipients.join(", "), - mail.data.unwrap_or("".to_string()) - ) + mail.data + ); + + for r in &mail.recipients { + if let Some(mailbox) = state_inner.get_mut(&r.to_lowercase()) { + mailbox.messages.push(mail.clone()) + } + } } } } @@ -50,6 +58,10 @@ const MSG_BYE: &str = "221 Bye"; const ERR_WRITE_FAILED: &str = "Failed to write"; const ERR_READ_FAILED: &str = "Failed to read"; +// => 5 chars +const RCPT_MIN_LEN: usize = CMD_RCPT_TO.len() + 5; +const MAIL_MIN_LEN: usize = CMD_MAIL_FROM.len() + 5; + enum ConnectionState { PreHello, Hello, @@ -59,7 +71,7 @@ enum ConnectionState { Done, } -struct Message { +struct IncomingMessage { sender: String, recipients: Vec, data: Option, @@ -68,17 +80,25 @@ struct Message { struct Connection { state: ConnectionState, messages: Vec, - current_message: Option, + current_message: Option, } -impl Message { +impl IncomingMessage { fn new(sender: String) -> Self { - Message { + IncomingMessage { sender, recipients: Vec::new(), data: None, } } + + fn finalize(self) -> Message { + Message { + sender: self.sender, + recipients: self.recipients, + data: self.data.unwrap_or("".to_string()), + } + } } impl Connection { @@ -135,20 +155,22 @@ impl Connection { } } ConnectionState::Hello => { - if line.starts_with(CMD_MAIL_FROM) { - self.current_message = Some(Message::new(line.to_owned())); + if line.starts_with(CMD_MAIL_FROM) && line.len() >= MAIL_MIN_LEN { + self.current_message = Some(IncomingMessage::new( + line[CMD_MAIL_FROM.len() + 1..line.len() - 3].to_owned(), + )); self.state = ConnectionState::Mail; return Ok(Some(MSG_OK)); } } ConnectionState::Mail => { - if line.starts_with(CMD_RCPT_TO) { + if line.starts_with(CMD_RCPT_TO) && line.len() >= RCPT_MIN_LEN { self.add_recipient(line)?; return Ok(Some(MSG_OK)); } } ConnectionState::PreData => { - if line.starts_with(CMD_RCPT_TO) { + if line.starts_with(CMD_RCPT_TO) && line.len() >= RCPT_MIN_LEN { self.add_recipient(line)?; return Ok(Some(MSG_OK)); } @@ -166,10 +188,10 @@ impl Connection { return match self.current_message { Some(ref mut _message) => { - if let Some(ref mut d) = _message.data { - *d += line.trim_end() + if let Some(data) = _message.data.as_mut() { + data.push_str(line); } else { - _message.data = Some(String::from(line.trim_end())) + _message.data = Some(String::from(line)) } Ok(None) @@ -197,14 +219,16 @@ impl Connection { let current_message = self.current_message.take(); if let Some(mail) = current_message { - self.messages.push(mail) + self.messages.push(mail.finalize()) } } fn add_recipient(&mut self, line: &str) -> Result<(), &'static str> { match self.current_message { Some(ref mut message) => { - message.recipients.push(line.to_owned()); + message + .recipients + .push(line[CMD_RCPT_TO.len() + 1..line.len() - 3].to_string()); self.state = ConnectionState::PreData } _ => { diff --git a/src/state.rs b/src/state.rs index 894018e..e39dadb 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,18 +1,11 @@ use chrono::{DateTime, Utc}; use std::collections::HashMap; -use std::sync::Mutex; +use std::sync::{Arc, Mutex}; -pub type State = Mutex>; +pub type State = Arc>>; pub fn fresh_state() -> State { - Mutex::new(HashMap::new()) -} - -pub struct Message { - pub from: String, - pub received: DateTime, - pub subject: String, - pub body: String, + Arc::new(Mutex::new(HashMap::new())) } pub struct Mailbox { @@ -21,6 +14,13 @@ pub struct Mailbox { pub created_at: DateTime, } +#[derive(Clone)] +pub struct Message { + pub sender: String, + pub recipients: Vec, + pub data: String, +} + impl Mailbox { pub fn new(email: String) -> Self { Self {