use std::{borrow::Cow, fmt::Write as _, str::FromStr};
use mail_auth::{IprevResult, Parameters, SpfResult, spf::verify::SpfParameters};
use smtp_proto::{MAIL_BY_NOTIFY, MAIL_BY_RETURN, MailFrom};
use crate::{
http::dns::is_error_negative_lookup,
network::AsyncReadWrite,
smtp::{
address::EmailAddress,
inbound::{Session, SessionResult},
},
};
impl<S: AsyncReadWrite> Session<S> {
pub async fn handle_mail_from(&mut self, from: MailFrom<Cow<'_, str>>) -> SessionResult<()> {
let Some(helo_hostname) = &self.data.ehlo_hostname else {
return self
.reply("503", "5.5.1", "Polite people say EHLO first.")
.await;
};
if self.data.mail_from.is_some() {
return self
.reply(
"503",
"5.5.1",
"Multiple MAIL FROM commands are not allowed.",
)
.await;
}
if self.cfg.tls_mode.required() && self.tls_info.is_none() {
return self
.reply(
"503",
"5.5.1",
"TLS is required to submit mail on this server.",
)
.await;
}
if (from.flags & (MAIL_BY_NOTIFY | MAIL_BY_RETURN)) != 0 {
return self.ext_unsupported("DELIVERBY").await;
}
if from.mt_priority != 0 {
return self.ext_unsupported("MT-PRIORITY").await;
}
if from.size > self.cfg.max_message_size {
return self.message_too_big().await;
}
if from.hold_for != 0 || from.hold_until != 0 {
return self.ext_unsupported("FUTURERELEASE").await;
}
if from.env_id.is_some() {
return self.ext_unsupported("DSN").await;
}
let Ok(address) = EmailAddress::from_str(&from.address) else {
return self
.reply("550", "5.7.1", "Sender address is incorrect.")
.await;
};
if self.cfg.verify_reverse_ip {
let result = self
.cfg
.authenticator
.verify_iprev(Parameters::from(self.remote_ip))
.await
.result;
if !matches!(result, IprevResult::Pass) {
let (code, ext, msg) = if matches!(result, IprevResult::TempError(_)) {
("451", "4.7.25", "Temporary error validating reverse DNS.")
} else {
("550", "5.7.25", "Reverse DNS validation failed.")
};
return self.reply(code, ext, msg).await;
}
}
if self.cfg.verify_sender_domain {
if address.domain().depth() < 2 {
return self.reply("550", "5.7.2", "Sender must be an FQDN.").await;
};
match self
.cfg
.authenticator
.resolver()
.mx_lookup(&address.domain().to_string())
.await
{
Ok(v) => {
if v.answers().is_empty() {
return self
.reply(
"550",
"5.7.25",
"No MX record matching your sender domain found.",
)
.await;
}
}
Err(e) => {
if is_error_negative_lookup(&e) {
return self
.reply(
"550",
"5.7.25",
"No MX record matching your sender domain found.",
)
.await;
} else {
return self
.reply("451", "4.7.25", "Temporary error validating sender domain.")
.await;
}
}
}
}
if self.cfg.verify_spf {
let output = self
.cfg
.authenticator
.verify_spf(SpfParameters::verify_mail_from(
self.remote_ip,
&helo_hostname.to_string(),
&self.cfg.hostname,
&from.address,
))
.await;
match output.result() {
SpfResult::Pass | SpfResult::Neutral | SpfResult::None => {}
SpfResult::TempError => {
return self
.reply("451", "4.7.24", "Temporary SPF validation error.")
.await;
}
SpfResult::Fail | SpfResult::PermError | SpfResult::SoftFail => {
return self
.reply_with("550", "5.7.23", |buf| {
write!(buf, "SPF validation failed")?;
if let Some(v) = output.explanation() {
write!(buf, ": {v}")?;
}
Ok(())
})
.await;
}
}
}
self.reply("250", "2.1.0", "OK").await?;
self.data.mail_from = Some(address);
Ok(())
}
}