use std::io::{BufRead, BufReader, Write}; use std::net::{TcpListener, TcpStream}; pub fn start_server() { let listener = TcpListener::bind("localhost:8025").unwrap(); for stream in listener.incoming() { match stream { Ok(tcp_stream) => { println!("Incoming connection"); 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 { for mail in connection.messages { println!( "Mail from {} for {}\n{}", mail.sender, mail.recipients.join(", "), mail.data.unwrap_or("".to_string()) ) } } } Err(e) => { eprintln!("Failed to accept connection: {}", e) } } } } const CMD_MAIL_FROM: &str = "MAIL FROM:"; const CMD_HELLO: &str = "HELO"; const CMD_RCPT_TO: &str = "RCPT TO:"; const CMD_DATA: &str = "DATA"; const CMD_DOT: &str = "."; const CMD_QUIT: &str = "QUIT"; const MSG_BANNER: &str = "220 very good mailserver"; const MSG_OK: &str = "250 ok"; const MSG_DATA: &str = "354 End data with ."; const MSG_UNEXPECTED: &str = "500 unexpected line"; const MSG_FAIL: &str = "500 mailserver is broken"; const MSG_BYE: &str = "221 Bye"; const ERR_WRITE_FAILED: &str = "Failed to write"; const ERR_READ_FAILED: &str = "Failed to read"; enum ConnectionState { PreHello, Hello, Mail, PreData, Data, Done, } struct Message { sender: String, recipients: Vec, data: Option, } struct Connection { state: ConnectionState, messages: Vec, current_message: Option, } impl Message { fn new(sender: String) -> Self { Message { sender, recipients: Vec::new(), data: None, } } } impl Connection { fn new() -> Self { Connection { state: ConnectionState::PreHello, messages: Vec::new(), current_message: None, } } fn handle( &mut self, mut writer: TcpStream, mut reader: BufReader, ) -> Result<(), &str> { send(&mut writer, MSG_BANNER)?; let mut line = String::new(); loop { let read_result = reader.read_line(&mut line); if read_result.is_err() || line.is_empty() { return Err(ERR_READ_FAILED); } println!("Read line: {}", line); let response = self.handle_line(&line); response?; if let Ok(Some(out)) = response { send(&mut writer, out)?; if out == MSG_BYE { break; } } line.clear() } Ok(()) } fn handle_line(&mut self, line: &str) -> Result, &'static str> { match self.state { ConnectionState::PreHello => { if line.starts_with(CMD_HELLO) { self.state = ConnectionState::Hello; return Ok(Some(MSG_OK)); } } ConnectionState::Hello => { if line.starts_with(CMD_MAIL_FROM) { self.current_message = Some(Message::new(line.to_owned())); self.state = ConnectionState::Mail; return Ok(Some(MSG_OK)); } } ConnectionState::Mail => { if line.starts_with(CMD_RCPT_TO) { self.add_recipient(line)?; return Ok(Some(MSG_OK)); } } ConnectionState::PreData => { if line.starts_with(CMD_RCPT_TO) { self.add_recipient(line)?; return Ok(Some(MSG_OK)); } if line.starts_with(CMD_DATA) { self.state = ConnectionState::Data; return Ok(Some(MSG_DATA)); } } ConnectionState::Data => { if line.trim_end() == CMD_DOT { self.state = ConnectionState::Done; return Ok(Some(MSG_OK)); } return match self.current_message { Some(ref mut _message) => { if let Some(ref mut d) = _message.data { *d += line.trim_end() } else { _message.data = Some(String::from(line.trim_end())) } Ok(None) } _ => Err(MSG_FAIL), }; } ConnectionState::Done => { if line.starts_with(CMD_QUIT) { self.finalize_mail(); return Ok(Some(MSG_BYE)); } if line.starts_with(CMD_MAIL_FROM) { self.finalize_mail(); return Ok(Some(MSG_OK)); } } } Ok(Some(MSG_UNEXPECTED)) } fn finalize_mail(&mut self) { let current_message = self.current_message.take(); if let Some(mail) = current_message { self.messages.push(mail) } } fn add_recipient(&mut self, line: &str) -> Result<(), &'static str> { match self.current_message { Some(ref mut message) => { message.recipients.push(line.to_owned()); self.state = ConnectionState::PreData } _ => { return Err(MSG_FAIL); } } Ok(()) } } fn send(writer: &mut TcpStream, message: &str) -> Result<(), &'static str> { if writeln!(writer, "{}", message).is_err() { return Err(ERR_WRITE_FAILED); } Ok(()) }