Queue autoconfig post install

This commit is contained in:
mdecimus
2025-07-13 12:01:32 +02:00
parent 036cb2ecd4
commit aee7337169
21 changed files with 302 additions and 280 deletions

View File

@@ -37,13 +37,9 @@ impl SendDsn for Server {
if !message.message.return_path.is_empty() {
// Build DSN
if let Some(dsn) = message.build_dsn(self).await {
let mut dsn_message = self.new_message("", "", "", message.span_id);
let mut dsn_message = self.new_message("", message.span_id);
dsn_message
.add_recipient_parts(
message.message.return_path.as_str(),
message.message.return_path_lcase.as_str(),
self,
)
.add_recipient_parts(message.message.return_path.as_str(), self)
.await;
// Sign message
@@ -81,7 +77,7 @@ impl SendDsn for Server {
trc::event!(
Delivery(trc::DeliveryEvent::DsnSuccess),
SpanId = message.span_id,
To = rcpt.address_lcase.clone(),
To = rcpt.address.clone(),
Hostname = response.hostname.clone(),
Code = response.response.code,
Details = response.response.message.to_string(),
@@ -91,7 +87,7 @@ impl SendDsn for Server {
trc::event!(
Delivery(trc::DeliveryEvent::DsnTempFail),
SpanId = message.span_id,
To = rcpt.address_lcase.clone(),
To = rcpt.address.clone(),
Hostname = response.entity.clone(),
Details = response.details.to_string(),
NextRetry = trc::Value::Timestamp(rcpt.retry.due),
@@ -105,7 +101,7 @@ impl SendDsn for Server {
trc::event!(
Delivery(trc::DeliveryEvent::DsnPermFail),
SpanId = message.span_id,
To = rcpt.address_lcase.clone(),
To = rcpt.address.clone(),
Hostname = response.entity.clone(),
Details = response.details.to_string(),
Total = rcpt.retry.inner,
@@ -115,7 +111,7 @@ impl SendDsn for Server {
trc::event!(
Delivery(trc::DeliveryEvent::DsnTempFail),
SpanId = message.span_id,
To = rcpt.address_lcase.clone(),
To = rcpt.address.clone(),
Details = "Concurrency limited",
NextRetry = trc::Value::Timestamp(rcpt.retry.due),
Expires = rcpt

View File

@@ -53,14 +53,12 @@ pub struct Message {
pub created: u64,
pub blob_hash: BlobHash,
pub return_path: String,
pub recipients: Vec<Recipient>,
pub received_from_ip: IpAddr,
pub received_via_port: u16,
pub return_path: String,
pub return_path_lcase: String,
pub return_path_domain: String,
pub recipients: Vec<Recipient>,
pub flags: u64,
pub env_id: Option<String>,
pub priority: i16,
@@ -105,7 +103,6 @@ pub enum QuotaKey {
)]
pub struct Recipient {
pub address: String,
pub address_lcase: String,
pub retry: Schedule<u32>,
pub notify: Schedule<u32>,
@@ -268,7 +265,7 @@ impl<'x> QueueEnvelope<'x> {
pub fn new(message: &'x Message, rcpt: &'x Recipient) -> Self {
Self {
message,
domain: rcpt.address_lcase.domain_part(),
domain: rcpt.address.domain_part(),
rcpt,
mx: "",
remote_ip: IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)),
@@ -280,22 +277,24 @@ impl<'x> QueueEnvelope<'x> {
impl<'x> ResolveVariable for QueueEnvelope<'x> {
fn resolve_variable(&self, variable: u32) -> expr::Variable<'x> {
match variable {
V_SENDER => self.message.return_path_lcase.as_str().into(),
V_SENDER_DOMAIN => self.message.return_path_domain.as_str().into(),
V_SENDER => self.message.return_path.as_str().into(),
V_SENDER_DOMAIN => self.message.return_path.domain_part().into(),
V_RECIPIENT_DOMAIN => self.domain.into(),
V_RECIPIENT => self.rcpt.address_lcase.as_str().into(),
V_RECIPIENT => self.rcpt.address.as_str().into(),
V_RECIPIENTS => self
.message
.recipients
.iter()
.map(|r| Variable::from(r.address_lcase.as_str()))
.map(|r| Variable::from(r.address.as_str()))
.collect::<Vec<_>>()
.into(),
V_QUEUE_RETRY_NUM => self.rcpt.retry.inner.into(),
V_QUEUE_NOTIFY_NUM => self.rcpt.notify.inner.into(),
V_QUEUE_EXPIRES_IN => match &self.rcpt.expires {
QueueExpiry::Ttl(time) => (*time + self.message.created).saturating_sub(now()),
QueueExpiry::Attempts(count) => (*count) as u64,
QueueExpiry::Attempts(count) => {
(count.saturating_sub(self.rcpt.retry.inner)) as u64
}
}
.into(),
V_QUEUE_LAST_STATUS => self.rcpt.status.to_compact_string().into(),
@@ -353,12 +352,12 @@ impl<'x> ResolveVariable for QueueEnvelope<'x> {
impl ResolveVariable for Message {
fn resolve_variable(&self, variable: u32) -> expr::Variable<'_> {
match variable {
V_SENDER => self.return_path_lcase.as_str().into(),
V_SENDER_DOMAIN => self.return_path_domain.as_str().into(),
V_SENDER => self.return_path.as_str().into(),
V_SENDER_DOMAIN => self.return_path.domain_part().into(),
V_RECIPIENTS => self
.recipients
.iter()
.map(|r| Variable::from(r.address_lcase.as_str()))
.map(|r| Variable::from(r.address.as_str()))
.collect::<Vec<_>>()
.into(),
V_PRIORITY => self.priority.into(),

View File

@@ -64,7 +64,7 @@ impl HasQueueQuota for Server {
let mut seen_domains = AHashSet::new();
for quota in &self.core.smtp.queue.quota.rcpt_domain {
for (rcpt_idx, rcpt) in message.message.recipients.iter().enumerate() {
if seen_domains.insert(rcpt.address_lcase.domain_part())
if seen_domains.insert(rcpt.address.domain_part())
&& !self
.check_quota(
quota,
@@ -192,7 +192,7 @@ impl MessageWrapper {
&rcpt.status,
Status::Completed(_) | Status::PermanentFailure(_)
) {
if seen_domains.insert(rcpt.address_lcase.domain_part()) {
if seen_domains.insert(rcpt.address.domain_part()) {
quota_ids.push(((pos + 1) as u64) << 32);
}
quota_ids.push((pos + 1) as u64);

View File

@@ -39,13 +39,7 @@ pub struct QueuedMessages {
}
pub trait SmtpSpool: Sync + Send {
fn new_message(
&self,
return_path: impl Into<String>,
return_path_lcase: impl Into<String>,
return_path_domain: impl Into<String>,
span_id: u64,
) -> MessageWrapper;
fn new_message(&self, return_path: impl Into<String>, span_id: u64) -> MessageWrapper;
fn next_event(&self, queue: &mut Queue) -> impl Future<Output = QueuedMessages> + Send;
@@ -74,13 +68,7 @@ pub trait SmtpSpool: Sync + Send {
}
impl SmtpSpool for Server {
fn new_message(
&self,
return_path: impl Into<String>,
return_path_lcase: impl Into<String>,
return_path_domain: impl Into<String>,
span_id: u64,
) -> MessageWrapper {
fn new_message(&self, return_path: impl Into<String>, span_id: u64) -> MessageWrapper {
let created = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.map_or(0, |d| d.as_secs());
@@ -93,8 +81,6 @@ impl SmtpSpool for Server {
message: Message {
created,
return_path: return_path.into(),
return_path_lcase: return_path_lcase.into(),
return_path_domain: return_path_domain.into(),
recipients: Vec::with_capacity(1),
flags: 0,
env_id: None,
@@ -389,7 +375,7 @@ impl MessageWrapper {
.message
.recipients
.iter()
.map(|r| trc::Value::String(r.address_lcase.as_str().into()))
.map(|r| trc::Value::String(r.address.as_str().into()))
.collect::<Vec<_>>(),
Size = self.message.size,
NextRetry = self
@@ -492,16 +478,10 @@ impl MessageWrapper {
true
}
pub async fn add_recipient_parts(
&mut self,
rcpt: impl Into<String>,
rcpt_lcase: impl Into<String>,
server: &Server,
) {
pub async fn add_recipient_parts(&mut self, rcpt: impl Into<String>, server: &Server) {
// Resolve queue
self.message.recipients.push(Recipient {
address: rcpt.into(),
address_lcase: rcpt_lcase.into(),
status: Status::Scheduled,
flags: 0,
orcpt: None,
@@ -530,10 +510,9 @@ impl MessageWrapper {
recipient.queue = queue.virtual_queue;
}
pub async fn add_recipient(&mut self, rcpt: impl Into<String>, server: &Server) {
let rcpt = rcpt.into();
let rcpt_lcase = rcpt.to_lowercase();
self.add_recipient_parts(rcpt, rcpt_lcase, server).await;
pub async fn add_recipient(&mut self, rcpt: impl AsRef<str>, server: &Server) {
let rcpt = rcpt.as_ref().to_lowercase();
self.add_recipient_parts(rcpt, server).await;
}
pub async fn save_changes(mut self, server: &Server, prev_event: Option<u64>) -> bool {
@@ -691,7 +670,7 @@ impl MessageWrapper {
pub fn has_domain(&self, domains: &[String]) -> bool {
self.message.recipients.iter().any(|r| {
let domain = r.address_lcase.domain_part();
let domain = r.address.domain_part();
domains.iter().any(|dd| dd == domain)
}) || self
.message
@@ -704,7 +683,7 @@ impl MessageWrapper {
impl ArchivedMessage {
pub fn has_domain(&self, domains: &[String]) -> bool {
self.recipients.iter().any(|r| {
let domain = r.address_lcase.domain_part();
let domain = r.address.domain_part();
domains.iter().any(|dd| dd == domain)
}) || self
.return_path
@@ -712,22 +691,22 @@ impl ArchivedMessage {
.is_some_and(|(_, domain)| domains.iter().any(|dd| dd == domain))
}
pub fn next_delivery_event(&self) -> u64 {
let mut next_delivery = now();
pub fn next_delivery_event(&self, queue: Option<QueueName>) -> Option<u64> {
let mut next_delivery = None;
for (pos, rcpt) in self
.recipients
.iter()
.filter(|d| {
matches!(
d.status,
ArchivedStatus::Scheduled | ArchivedStatus::TemporaryFailure(_)
)
})
.enumerate()
{
if pos == 0 || rcpt.retry.due < next_delivery {
next_delivery = rcpt.retry.due.into();
for rcpt in self.recipients.iter().filter(|d| {
matches!(
d.status,
ArchivedStatus::Scheduled | ArchivedStatus::TemporaryFailure(_)
) && queue.is_none_or(|q| d.queue == q)
}) {
let retry_due = rcpt.retry.due.to_native();
if let Some(next_delivery) = &mut next_delivery {
if retry_due < *next_delivery {
*next_delivery = retry_due;
}
} else {
next_delivery = Some(retry_due);
}
}