use mail_parser::MessageParser;
use tokio::sync::broadcast::Sender;
use tracing::{error, info};
use crate::{
VERSION,
error::{Error, Result},
types::MailMessage,
};
#[derive(Clone, Debug)]
pub(super) struct MailHandler {
tx: Sender<MailMessage>,
parser: MessageParser,
buffer: Vec<u8>,
envelope_from: String,
envelope_recipients: Vec<String>,
}
impl MailHandler {
pub(super) fn create(tx: Sender<MailMessage>) -> Self {
MailHandler {
tx,
parser: MessageParser::new(),
buffer: Vec::new(),
envelope_from: String::new(),
envelope_recipients: Vec::new(),
}
}
}
impl MailHandler {
fn parse_mail(&mut self) -> Result<MailMessage> {
let parsed = self
.parser
.parse(&self.buffer)
.ok_or_else(|| Error::Smtp("failed to parse message".to_owned()))?;
let mut message: MailMessage = parsed.try_into()?;
message.envelope_from = std::mem::take(&mut self.envelope_from);
message.envelope_recipients = std::mem::take(&mut self.envelope_recipients);
self.buffer.clear();
self.tx
.send(message.clone())
.map_err(|e| Error::Smtp(e.to_string()))?;
Ok(message)
}
}
impl mailin::Handler for MailHandler {
fn helo(&mut self, _ip: std::net::IpAddr, _domain: &str) -> mailin::Response {
mailin::response::OK
}
fn mail(&mut self, _ip: std::net::IpAddr, _domain: &str, from: &str) -> mailin::Response {
self.envelope_from = from.to_string();
mailin::response::Response::custom(
250,
format!("Pleased to meet you! This is Mailcrab version {VERSION}",),
)
}
fn rcpt(&mut self, to: &str) -> mailin::Response {
self.envelope_recipients.push(to.to_string());
mailin::response::OK
}
fn data_start(
&mut self,
domain: &str,
from: &str,
_is8bit: bool,
to: &[String],
) -> mailin::Response {
info!("Incoming message on {domain} from {from} to {to:?}");
mailin::response::OK
}
fn data(&mut self, buf: &[u8]) -> std::io::Result<()> {
self.buffer.extend_from_slice(buf);
Ok(())
}
fn data_end(&mut self) -> mailin::Response {
match self.parse_mail() {
Err(e) => {
error!("{e}");
mailin::response::Response::custom(500, "Error parsing message".to_string())
}
Ok(message) => mailin::response::Response::custom(
250,
format!("2.0.0 Ok: queued as {}", message.id),
),
}
}
fn auth_plain(
&mut self,
_authorization_id: &str,
_authentication_id: &str,
_password: &str,
) -> mailin::Response {
mailin::response::AUTH_OK
}
fn auth_login(&mut self, _username: &str, _password: &str) -> mailin::Response {
mailin::response::AUTH_OK
}
}