Files
Stalwart/crates/smtp/src/inbound/mail.rs

591 lines
21 KiB
Rust

/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs Ltd <hello@stalw.art>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use std::time::{Duration, Instant, SystemTime};
use common::{config::smtp::session::Stage, listener::SessionStream, scripts::ScriptModification};
use mail_auth::{IprevOutput, IprevResult, SpfOutput, SpfResult, spf::verify::SpfParameters};
use smtp_proto::{MAIL_BY_NOTIFY, MAIL_BY_RETURN, MAIL_REQUIRETLS, MailFrom, MtPriority};
use trc::SmtpEvent;
use utils::config::Rate;
use crate::{
core::{Session, SessionAddress},
queue::DomainPart,
scripts::ScriptResult,
};
impl<T: SessionStream> Session<T> {
pub async fn handle_mail_from(&mut self, from: MailFrom<String>) -> Result<(), ()> {
if self.data.helo_domain.is_empty()
&& (self.params.ehlo_require
|| self.params.spf_ehlo.verify()
|| self.params.spf_mail_from.verify())
{
trc::event!(
Smtp(SmtpEvent::DidNotSayEhlo),
SpanId = self.data.session_id,
);
return self
.write(b"503 5.5.1 Polite people say EHLO first.\r\n")
.await;
} else if self.data.mail_from.is_some() {
trc::event!(
Smtp(SmtpEvent::MultipleMailFrom),
SpanId = self.data.session_id,
);
return self
.write(b"503 5.5.1 Multiple MAIL commands not allowed.\r\n")
.await;
} else if self.params.auth_require && !self.is_authenticated() {
trc::event!(
Smtp(SmtpEvent::MailFromUnauthenticated),
SpanId = self.data.session_id,
);
return self
.write(b"503 5.5.1 You must authenticate first.\r\n")
.await;
} else if self.data.iprev.is_none() && self.params.iprev.verify() {
let time = Instant::now();
let iprev = self
.server
.core
.smtp
.resolvers
.dns
.verify_iprev(
self.server
.inner
.cache
.build_auth_parameters(self.data.remote_ip),
)
.await;
trc::event!(
Smtp(if matches!(iprev.result(), IprevResult::Pass) {
SmtpEvent::IprevPass
} else {
SmtpEvent::IprevFail
}),
SpanId = self.data.session_id,
Domain = self.data.helo_domain.clone(),
Result = trc::Error::from(&iprev),
Elapsed = time.elapsed(),
);
self.data.iprev = iprev.into();
}
// In strict mode reject messages from hosts that fail the reverse DNS lookup check
if self.params.iprev.is_strict()
&& !matches!(
&self.data.iprev,
Some(IprevOutput {
result: IprevResult::Pass,
..
})
)
{
let message = if matches!(
&self.data.iprev,
Some(IprevOutput {
result: IprevResult::TempError(_),
..
})
) {
&b"451 4.7.25 Temporary error validating reverse DNS.\r\n"[..]
} else {
&b"550 5.7.25 Reverse DNS validation failed.\r\n"[..]
};
return self.write(message).await;
}
let (address, address_lcase, domain) = if !from.address.is_empty() {
let address_lcase = from.address.to_lowercase();
let domain = address_lcase.domain_part().to_string();
(from.address, address_lcase, domain)
} else {
(String::new(), String::new(), String::new())
};
let has_dsn = from.env_id.is_some();
self.data.mail_from = SessionAddress {
address,
address_lcase,
domain,
flags: from.flags,
dsn_info: from.env_id,
}
.into();
// Check whether the address is allowed
if !self
.server
.eval_if::<bool, _>(
&self.server.core.smtp.session.mail.is_allowed,
self,
self.data.session_id,
)
.await
.unwrap_or(true)
{
let mail_from = self.data.mail_from.take().unwrap();
trc::event!(
Smtp(SmtpEvent::MailFromNotAllowed),
From = mail_from.address_lcase,
SpanId = self.data.session_id,
);
return self
.write(b"550 5.7.1 Sender address not allowed.\r\n")
.await;
}
// Sieve filtering
if let Some((script, script_id)) = self
.server
.eval_if::<String, _>(
&self.server.core.smtp.session.mail.script,
self,
self.data.session_id,
)
.await
.and_then(|name| {
self.server
.get_trusted_sieve_script(&name, self.data.session_id)
.map(|s| (s, name))
})
{
match self
.run_script(
script_id,
script.clone(),
self.build_script_parameters("mail"),
)
.await
{
ScriptResult::Accept { modifications } => {
if !modifications.is_empty() {
for modification in modifications {
if let ScriptModification::SetEnvelope { name, value } = modification {
self.data.apply_envelope_modification(name, value);
}
}
}
}
ScriptResult::Reject(message) => {
self.data.mail_from = None;
return self.write(message.as_bytes()).await;
}
_ => (),
}
}
// Milter filtering
if let Err(message) = self.run_milters(Stage::Mail, None).await {
self.data.mail_from = None;
return self.write(message.message.as_bytes()).await;
}
// MTAHook filtering
if let Err(message) = self.run_mta_hooks(Stage::Mail, None, None).await {
self.data.mail_from = None;
return self.write(message.message.as_bytes()).await;
}
// Address rewriting
if let Some(new_address) = self
.server
.eval_if::<String, _>(
&self.server.core.smtp.session.mail.rewrite,
self,
self.data.session_id,
)
.await
{
let mail_from = self.data.mail_from.as_mut().unwrap();
trc::event!(
Smtp(SmtpEvent::MailFromRewritten),
SpanId = self.data.session_id,
Details = mail_from.address_lcase.clone(),
From = new_address.clone(),
);
if new_address.contains('@') {
mail_from.address_lcase = new_address.to_lowercase();
mail_from.domain = mail_from.address_lcase.domain_part().to_string();
mail_from.address = new_address;
} else if new_address.is_empty() {
mail_from.address_lcase.clear();
mail_from.domain.clear();
mail_from.address.clear();
}
}
// Make sure that the authenticated user is allowed to send from this address
match self.authenticated_as() {
Some(authenticated_as)
if self
.server
.eval_if(
&self.server.core.smtp.session.auth.must_match_sender,
self,
self.data.session_id,
)
.await
.unwrap_or(true) =>
{
let address_lcase = self.data.mail_from.as_ref().unwrap().address_lcase.as_str();
if authenticated_as != address_lcase
&& !self.authenticated_emails().iter().any(|e| {
e == address_lcase || (e.starts_with('@') && address_lcase.ends_with(e))
})
{
trc::event!(
Smtp(SmtpEvent::MailFromUnauthorized),
SpanId = self.data.session_id,
From = address_lcase.to_string(),
Details = [trc::Value::String(authenticated_as.to_string())]
.into_iter()
.chain(
self.authenticated_emails()
.iter()
.map(|e| trc::Value::String(e.to_string()))
)
.collect::<Vec<_>>()
);
self.data.mail_from = None;
return self
.write(b"501 5.5.4 You are not allowed to send from this address.\r\n")
.await;
}
}
_ => (),
}
// Validate parameters
let config = &self.server.core.smtp.session.extensions;
let config_data = &self.server.core.smtp.session.data;
if (from.flags & MAIL_REQUIRETLS) != 0
&& !self
.server
.eval_if(&config.requiretls, self, self.data.session_id)
.await
.unwrap_or(false)
{
trc::event!(
Smtp(SmtpEvent::RequireTlsDisabled),
SpanId = self.data.session_id,
);
self.data.mail_from = None;
return self
.write(b"501 5.5.4 REQUIRETLS has been disabled.\r\n")
.await;
}
if (from.flags & (MAIL_BY_NOTIFY | MAIL_BY_RETURN)) != 0 {
if let Some(duration) = self
.server
.eval_if::<Duration, _>(&config.deliver_by, self, self.data.session_id)
.await
{
if from.by.checked_abs().unwrap_or(0) as u64 <= duration.as_secs()
&& (from.by.is_positive() || (from.flags & MAIL_BY_NOTIFY) != 0)
{
self.data.delivery_by = from.by;
} else {
self.data.mail_from = None;
trc::event!(
Smtp(SmtpEvent::DeliverByInvalid),
SpanId = self.data.session_id,
Details = from.by,
);
return self
.write(
format!(
"501 5.5.4 BY parameter exceeds maximum of {} seconds.\r\n",
duration.as_secs()
)
.as_bytes(),
)
.await;
}
} else {
trc::event!(
Smtp(SmtpEvent::DeliverByDisabled),
SpanId = self.data.session_id,
);
self.data.mail_from = None;
return self
.write(b"501 5.5.4 DELIVERBY extension has been disabled.\r\n")
.await;
}
}
if from.mt_priority != 0 {
if self
.server
.eval_if::<MtPriority, _>(&config.mt_priority, self, self.data.session_id)
.await
.is_some()
{
if (-6..6).contains(&from.mt_priority) {
self.data.priority = from.mt_priority as i16;
} else {
trc::event!(
Smtp(SmtpEvent::MtPriorityInvalid),
SpanId = self.data.session_id,
Details = from.mt_priority,
);
self.data.mail_from = None;
return self.write(b"501 5.5.4 Invalid priority value.\r\n").await;
}
} else {
trc::event!(
Smtp(SmtpEvent::MtPriorityDisabled),
SpanId = self.data.session_id,
);
self.data.mail_from = None;
return self
.write(b"501 5.5.4 MT-PRIORITY extension has been disabled.\r\n")
.await;
}
}
if from.size > 0
&& from.size
> self
.server
.eval_if(&config_data.max_message_size, self, self.data.session_id)
.await
.unwrap_or(25 * 1024 * 1024)
{
trc::event!(
Smtp(SmtpEvent::MessageTooLarge),
SpanId = self.data.session_id,
Size = from.size,
);
self.data.mail_from = None;
return self
.write(b"552 5.3.4 Message too big for system.\r\n")
.await;
}
if from.hold_for != 0 || from.hold_until != 0 {
if let Some(max_hold) = self
.server
.eval_if::<Duration, _>(&config.future_release, self, self.data.session_id)
.await
{
let max_hold = max_hold.as_secs();
let hold_for = if from.hold_for != 0 {
from.hold_for
} else {
let now = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.map_or(0, |d| d.as_secs());
if from.hold_until > now {
from.hold_until - now
} else {
0
}
};
if hold_for <= max_hold {
self.data.future_release = hold_for;
} else {
trc::event!(
Smtp(SmtpEvent::FutureReleaseInvalid),
SpanId = self.data.session_id,
Details = hold_for,
);
self.data.mail_from = None;
return self
.write(
format!(
"501 5.5.4 Requested hold time exceeds maximum of {max_hold} seconds.\r\n"
)
.as_bytes(),
)
.await;
}
} else {
trc::event!(
Smtp(SmtpEvent::FutureReleaseDisabled),
SpanId = self.data.session_id,
);
self.data.mail_from = None;
return self
.write(b"501 5.5.4 FUTURERELEASE extension has been disabled.\r\n")
.await;
}
}
if has_dsn
&& !self
.server
.eval_if(&config.dsn, self, self.data.session_id)
.await
.unwrap_or(false)
{
trc::event!(Smtp(SmtpEvent::DsnDisabled), SpanId = self.data.session_id,);
self.data.mail_from = None;
return self
.write(b"501 5.5.4 DSN extension has been disabled.\r\n")
.await;
}
if self.is_allowed().await {
// Verify SPF
if self.params.spf_mail_from.verify() {
let time = Instant::now();
let mail_from = self.data.mail_from.as_ref().unwrap();
let spf_output = if !mail_from.address.is_empty() {
self.server
.core
.smtp
.resolvers
.dns
.check_host(self.server.inner.cache.build_auth_parameters(
SpfParameters::new(
self.data.remote_ip,
&mail_from.domain,
&self.data.helo_domain,
&self.hostname,
&mail_from.address_lcase,
),
))
.await
} else {
self.server
.core
.smtp
.resolvers
.dns
.check_host(self.server.inner.cache.build_auth_parameters(
SpfParameters::new(
self.data.remote_ip,
&self.data.helo_domain,
&self.data.helo_domain,
&self.hostname,
&format!("postmaster@{}", self.data.helo_domain),
),
))
.await
};
trc::event!(
Smtp(if matches!(spf_output.result(), SpfResult::Pass) {
SmtpEvent::SpfFromPass
} else {
SmtpEvent::SpfFromFail
}),
SpanId = self.data.session_id,
Domain = self.data.helo_domain.clone(),
From = if !mail_from.address.is_empty() {
mail_from.address.as_str()
} else {
"<>"
}
.to_string(),
Result = trc::Error::from(&spf_output),
Elapsed = time.elapsed(),
);
if self
.handle_spf(&spf_output, self.params.spf_mail_from.is_strict())
.await?
{
self.data.spf_mail_from = spf_output.into();
} else {
self.data.mail_from = None;
return Ok(());
}
}
trc::event!(
Smtp(SmtpEvent::MailFrom),
SpanId = self.data.session_id,
From = self.data.mail_from.as_ref().unwrap().address_lcase.clone(),
);
self.eval_rcpt_params().await;
self.write(b"250 2.1.0 OK\r\n").await
} else {
trc::event!(
Smtp(SmtpEvent::RateLimitExceeded),
SpanId = self.data.session_id,
From = self.data.mail_from.as_ref().unwrap().address_lcase.clone(),
);
self.data.mail_from = None;
self.write(b"452 4.4.5 Rate limit exceeded, try again later.\r\n")
.await
}
}
pub async fn handle_spf(&mut self, spf_output: &SpfOutput, strict: bool) -> Result<bool, ()> {
let result = match spf_output.result() {
SpfResult::Pass => true,
SpfResult::TempError if strict => {
self.write(b"451 4.7.24 Temporary SPF validation error.\r\n")
.await?;
false
}
result => {
if strict {
self.write(
format!("550 5.7.23 SPF validation failed, status: {result}.\r\n")
.as_bytes(),
)
.await?;
false
} else {
true
}
}
};
// Send report
if let (Some(recipient), Some(rate)) = (
spf_output.report_address(),
self.server
.eval_if::<Rate, _>(
&self.server.core.smtp.report.spf.send,
self,
self.data.session_id,
)
.await,
) {
// Do not send SPF auth failures to local domains, as they are likely relay attempts (which are blocked later on)
match self
.server
.core
.storage
.directory
.is_local_domain(recipient.domain_part())
.await
{
Ok(true) => return Ok(result),
Ok(false) => (),
Err(err) => {
trc::error!(
err.caused_by(trc::location!())
.span_id(self.data.session_id)
.details("Failed to lookup local domain")
);
}
}
self.send_spf_report(recipient, &rate, !result, spf_output)
.await;
}
Ok(result)
}
}