tmpmail/src/smtp.rs

274 lines
7.9 KiB
Rust
Raw Normal View History

2023-07-02 19:59:25 +02:00
use std::io::{BufRead, BufReader, Write};
2023-07-02 20:18:43 +02:00
2023-07-15 14:21:49 +02:00
use crate::state::{Message, State};
2023-07-17 21:50:20 +02:00
use mailparse::{parse_mail, MailHeaderMap, ParsedMail};
2023-07-02 19:59:25 +02:00
use std::net::{TcpListener, TcpStream};
2023-07-15 14:21:49 +02:00
pub fn start_server(state: State) {
2023-07-02 19:59:25 +02:00
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();
2023-07-15 14:21:49 +02:00
2023-07-02 20:15:10 +02:00
if let Err(e) = connection.handle(tcp_stream, reader) {
eprintln!("System error: {}", e)
2023-07-15 14:21:49 +02:00
} else if let Ok(mut state_inner) = state.lock() {
2023-07-02 20:15:10 +02:00
for mail in connection.messages {
2023-07-02 20:18:43 +02:00
println!(
2023-07-17 21:50:20 +02:00
"Mail \"{}\" from {} for {}\n{}",
mail.subject,
2023-07-02 20:18:43 +02:00
mail.sender,
mail.recipients.join(", "),
2023-07-17 21:50:20 +02:00
mail.body
2023-07-15 14:21:49 +02:00
);
for r in &mail.recipients {
if let Some(mailbox) = state_inner.get_mut(&r.to_lowercase()) {
mailbox.messages.push(mail.clone())
}
}
2023-07-02 20:15:10 +02:00
}
}
2023-07-02 19:59:25 +02:00
}
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 <CR><LF>.<CR><LF>";
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";
2023-07-15 14:21:49 +02:00
// <a@b> => 5 chars
const RCPT_MIN_LEN: usize = CMD_RCPT_TO.len() + 5;
const MAIL_MIN_LEN: usize = CMD_MAIL_FROM.len() + 5;
2023-07-02 19:59:25 +02:00
enum ConnectionState {
PreHello,
Hello,
Mail,
PreData,
Data,
Done,
}
2023-07-15 14:21:49 +02:00
struct IncomingMessage {
2023-07-02 19:59:25 +02:00
sender: String,
recipients: Vec<String>,
data: Option<String>,
}
struct Connection {
state: ConnectionState,
messages: Vec<Message>,
2023-07-15 14:21:49 +02:00
current_message: Option<IncomingMessage>,
2023-07-02 19:59:25 +02:00
}
2023-07-15 14:21:49 +02:00
impl IncomingMessage {
2023-07-02 19:59:25 +02:00
fn new(sender: String) -> Self {
2023-07-15 14:21:49 +02:00
IncomingMessage {
2023-07-02 19:59:25 +02:00
sender,
recipients: Vec::new(),
data: None,
}
}
2023-07-15 14:21:49 +02:00
2023-07-17 21:50:20 +02:00
fn parse(self) -> Option<Message> {
let data = self.data?;
let mail = parse_mail(data.as_bytes()).ok()?;
let body_part = part_by_content_type(&mail, "text/html")
.or_else(|| part_by_content_type(&mail, "text/plain"));
let body = match body_part {
Some(c) => c.get_body().ok()?,
None => mail.get_body().unwrap_or("<empty body>".to_owned()),
};
Some(Message {
body,
2023-07-15 14:21:49 +02:00
sender: self.sender,
recipients: self.recipients,
2023-07-17 21:50:20 +02:00
subject: mail
.get_headers()
.get_first_value("Subject")
.unwrap_or("<empty subject>".to_string()),
})
2023-07-15 14:21:49 +02:00
}
2023-07-02 19:59:25 +02:00
}
2023-07-17 21:50:20 +02:00
fn part_by_content_type<'a>(
mail: &'a ParsedMail,
content_type: &str,
) -> Option<&'a ParsedMail<'a>> {
mail.parts().find(|p| p.ctype.mimetype == content_type)
}
2023-07-02 19:59:25 +02:00
impl Connection {
fn new() -> Self {
Connection {
state: ConnectionState::PreHello,
messages: Vec::new(),
current_message: None,
}
}
2023-07-02 20:18:43 +02:00
fn handle(
&mut self,
mut writer: TcpStream,
mut reader: BufReader<TcpStream>,
) -> Result<(), &str> {
2023-07-02 19:59:25 +02:00
send(&mut writer, MSG_BANNER)?;
let mut line = String::new();
loop {
let read_result = reader.read_line(&mut line);
2023-07-02 20:18:43 +02:00
if read_result.is_err() || line.is_empty() {
2023-07-02 19:59:25 +02:00
return Err(ERR_READ_FAILED);
}
2023-07-17 21:50:20 +02:00
println!("Read line: {}", line.trim_end());
2023-07-02 19:59:25 +02:00
let response = self.handle_line(&line);
2023-07-02 20:18:43 +02:00
response?;
2023-07-02 19:59:25 +02:00
if let Ok(Some(out)) = response {
send(&mut writer, out)?;
if out == MSG_BYE {
2023-07-02 20:15:10 +02:00
break;
2023-07-02 19:59:25 +02:00
}
}
line.clear()
}
2023-07-02 20:18:43 +02:00
Ok(())
2023-07-02 19:59:25 +02:00
}
2023-07-02 20:18:43 +02:00
fn handle_line(&mut self, line: &str) -> Result<Option<&str>, &'static str> {
2023-07-02 19:59:25 +02:00
match self.state {
ConnectionState::PreHello => {
if line.starts_with(CMD_HELLO) {
self.state = ConnectionState::Hello;
return Ok(Some(MSG_OK));
}
}
ConnectionState::Hello => {
2023-07-15 14:21:49 +02:00
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(),
));
2023-07-02 19:59:25 +02:00
self.state = ConnectionState::Mail;
return Ok(Some(MSG_OK));
}
}
ConnectionState::Mail => {
2023-07-15 14:21:49 +02:00
if line.starts_with(CMD_RCPT_TO) && line.len() >= RCPT_MIN_LEN {
2023-07-02 19:59:25 +02:00
self.add_recipient(line)?;
return Ok(Some(MSG_OK));
}
}
ConnectionState::PreData => {
2023-07-15 14:21:49 +02:00
if line.starts_with(CMD_RCPT_TO) && line.len() >= RCPT_MIN_LEN {
2023-07-02 19:59:25 +02:00
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;
2023-07-02 20:15:10 +02:00
return Ok(Some(MSG_OK));
2023-07-02 19:59:25 +02:00
}
return match self.current_message {
Some(ref mut _message) => {
2023-07-15 14:21:49 +02:00
if let Some(data) = _message.data.as_mut() {
data.push_str(line);
2023-07-02 19:59:25 +02:00
} else {
2023-07-15 14:21:49 +02:00
_message.data = Some(String::from(line))
2023-07-02 19:59:25 +02:00
}
Ok(None)
}
2023-07-02 20:18:43 +02:00
_ => Err(MSG_FAIL),
2023-07-02 20:15:10 +02:00
};
2023-07-02 19:59:25 +02:00
}
ConnectionState::Done => {
if line.starts_with(CMD_QUIT) {
self.finalize_mail();
2023-07-02 20:15:10 +02:00
return Ok(Some(MSG_BYE));
2023-07-02 19:59:25 +02:00
}
if line.starts_with(CMD_MAIL_FROM) {
self.finalize_mail();
2023-07-02 20:15:10 +02:00
return Ok(Some(MSG_OK));
2023-07-02 19:59:25 +02:00
}
}
}
Ok(Some(MSG_UNEXPECTED))
}
fn finalize_mail(&mut self) {
2023-07-17 21:50:20 +02:00
let current_message = self.current_message.take().and_then(IncomingMessage::parse);
2023-07-02 20:15:10 +02:00
if let Some(mail) = current_message {
2023-07-17 21:50:20 +02:00
self.messages.push(mail);
2023-07-02 19:59:25 +02:00
}
}
2023-07-02 20:18:43 +02:00
fn add_recipient(&mut self, line: &str) -> Result<(), &'static str> {
2023-07-02 19:59:25 +02:00
match self.current_message {
Some(ref mut message) => {
2023-07-15 14:21:49 +02:00
message
.recipients
.push(line[CMD_RCPT_TO.len() + 1..line.len() - 3].to_string());
2023-07-02 19:59:25 +02:00
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(())
2023-07-02 20:18:43 +02:00
}