Wire up SMTP and HTTP

This commit is contained in:
Martin 2023-07-15 14:21:49 +02:00
parent 0e2736f2b4
commit e0fd858c2f
Signed by: mawalu
GPG Key ID: BF556F989760A7C8
4 changed files with 66 additions and 35 deletions

View File

@ -17,20 +17,24 @@ pub fn http_handler(request: &Request, state: &State) -> Response {
domain: String domain: String
})); }));
let email = format!("{}@{}", get_name(), data.domain); let email = format!("{}@{}", get_name().to_lowercase(), data.domain);
println!("{}", email); 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}) => { (GET) (/ffff/{email: String}) => {
println!("{}", email); println!("{}", email);
if let Some(mailbox) = state.lock().unwrap().get(&email) { if let Some(mailbox) = state.lock().unwrap().get(&email) {
rouille::Response::text(format!("{}: {}", &mailbox.email, &mailbox.messages.len())) let body: &Vec<String> = &mailbox.messages.iter().map(|msg| {
return format!("<h3>From {}<h3><br><pre>{}</pre><br>", msg.sender, msg.data)
}).collect();
rouille::Response::html(body.join(""))
} else { } else {
rouille::Response::text("Mailbox not found") rouille::Response::text("Mailbox not found")
} }

View File

@ -1,6 +1,7 @@
extern crate rand; extern crate rand;
extern crate rouille; extern crate rouille;
use std::sync::Arc;
use std::thread; use std::thread;
mod http; mod http;
@ -13,14 +14,16 @@ fn main() {
let state = state::fresh_state(); 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| { rouille::start_server("localhost:8005", move |request| {
http::http_handler(request, &state) http::http_handler(request, &http_state)
}) })
}); });
let smtp_thread = thread::spawn(|| { let smtp_state = Arc::clone(&state);
smtp::start_server(); let smtp_thread = thread::spawn(move || {
smtp::start_server(smtp_state);
}); });
http_thread.join().unwrap(); http_thread.join().unwrap();

View File

@ -1,8 +1,9 @@
use std::io::{BufRead, BufReader, Write}; use std::io::{BufRead, BufReader, Write};
use crate::state::{Message, State};
use std::net::{TcpListener, TcpStream}; use std::net::{TcpListener, TcpStream};
pub fn start_server() { pub fn start_server(state: State) {
let listener = TcpListener::bind("localhost:8025").unwrap(); let listener = TcpListener::bind("localhost:8025").unwrap();
for stream in listener.incoming() { for stream in listener.incoming() {
@ -13,16 +14,23 @@ pub fn start_server() {
let reader = BufReader::new(tcp_stream.try_clone().unwrap()); let reader = BufReader::new(tcp_stream.try_clone().unwrap());
let mut connection = Connection::new(); let mut connection = Connection::new();
if let Err(e) = connection.handle(tcp_stream, reader) { if let Err(e) = connection.handle(tcp_stream, reader) {
eprintln!("System error: {}", e) eprintln!("System error: {}", e)
} else { } else if let Ok(mut state_inner) = state.lock() {
for mail in connection.messages { for mail in connection.messages {
println!( println!(
"Mail from {} for {}\n{}", "Mail from {} for {}\n{}",
mail.sender, mail.sender,
mail.recipients.join(", "), mail.recipients.join(", "),
mail.data.unwrap_or("<empty>".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_WRITE_FAILED: &str = "Failed to write";
const ERR_READ_FAILED: &str = "Failed to read"; const ERR_READ_FAILED: &str = "Failed to read";
// <a@b> => 5 chars
const RCPT_MIN_LEN: usize = CMD_RCPT_TO.len() + 5;
const MAIL_MIN_LEN: usize = CMD_MAIL_FROM.len() + 5;
enum ConnectionState { enum ConnectionState {
PreHello, PreHello,
Hello, Hello,
@ -59,7 +71,7 @@ enum ConnectionState {
Done, Done,
} }
struct Message { struct IncomingMessage {
sender: String, sender: String,
recipients: Vec<String>, recipients: Vec<String>,
data: Option<String>, data: Option<String>,
@ -68,17 +80,25 @@ struct Message {
struct Connection { struct Connection {
state: ConnectionState, state: ConnectionState,
messages: Vec<Message>, messages: Vec<Message>,
current_message: Option<Message>, current_message: Option<IncomingMessage>,
} }
impl Message { impl IncomingMessage {
fn new(sender: String) -> Self { fn new(sender: String) -> Self {
Message { IncomingMessage {
sender, sender,
recipients: Vec::new(), recipients: Vec::new(),
data: None, data: None,
} }
} }
fn finalize(self) -> Message {
Message {
sender: self.sender,
recipients: self.recipients,
data: self.data.unwrap_or("<empty>".to_string()),
}
}
} }
impl Connection { impl Connection {
@ -135,20 +155,22 @@ impl Connection {
} }
} }
ConnectionState::Hello => { ConnectionState::Hello => {
if line.starts_with(CMD_MAIL_FROM) { if line.starts_with(CMD_MAIL_FROM) && line.len() >= MAIL_MIN_LEN {
self.current_message = Some(Message::new(line.to_owned())); self.current_message = Some(IncomingMessage::new(
line[CMD_MAIL_FROM.len() + 1..line.len() - 3].to_owned(),
));
self.state = ConnectionState::Mail; self.state = ConnectionState::Mail;
return Ok(Some(MSG_OK)); return Ok(Some(MSG_OK));
} }
} }
ConnectionState::Mail => { 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)?; self.add_recipient(line)?;
return Ok(Some(MSG_OK)); return Ok(Some(MSG_OK));
} }
} }
ConnectionState::PreData => { 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)?; self.add_recipient(line)?;
return Ok(Some(MSG_OK)); return Ok(Some(MSG_OK));
} }
@ -166,10 +188,10 @@ impl Connection {
return match self.current_message { return match self.current_message {
Some(ref mut _message) => { Some(ref mut _message) => {
if let Some(ref mut d) = _message.data { if let Some(data) = _message.data.as_mut() {
*d += line.trim_end() data.push_str(line);
} else { } else {
_message.data = Some(String::from(line.trim_end())) _message.data = Some(String::from(line))
} }
Ok(None) Ok(None)
@ -197,14 +219,16 @@ impl Connection {
let current_message = self.current_message.take(); let current_message = self.current_message.take();
if let Some(mail) = current_message { 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> { fn add_recipient(&mut self, line: &str) -> Result<(), &'static str> {
match self.current_message { match self.current_message {
Some(ref mut 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 self.state = ConnectionState::PreData
} }
_ => { _ => {

View File

@ -1,18 +1,11 @@
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::Mutex; use std::sync::{Arc, Mutex};
pub type State = Mutex<HashMap<String, Mailbox>>; pub type State = Arc<Mutex<HashMap<String, Mailbox>>>;
pub fn fresh_state() -> State { pub fn fresh_state() -> State {
Mutex::new(HashMap::new()) Arc::new(Mutex::new(HashMap::new()))
}
pub struct Message {
pub from: String,
pub received: DateTime<Utc>,
pub subject: String,
pub body: String,
} }
pub struct Mailbox { pub struct Mailbox {
@ -21,6 +14,13 @@ pub struct Mailbox {
pub created_at: DateTime<Utc>, pub created_at: DateTime<Utc>,
} }
#[derive(Clone)]
pub struct Message {
pub sender: String,
pub recipients: Vec<String>,
pub data: String,
}
impl Mailbox { impl Mailbox {
pub fn new(email: String) -> Self { pub fn new(email: String) -> Self {
Self { Self {