diff options
37 files changed, 576 insertions, 560 deletions
diff --git a/crates/directory/src/lib.rs b/crates/directory/src/lib.rs index 5a72405b..7659f69e 100644 --- a/crates/directory/src/lib.rs +++ b/crates/directory/src/lib.rs @@ -181,7 +181,7 @@ pub enum AddressMapping { Enable, Custom { regex: regex::Regex, - mapping: DynValue, + mapping: DynValue<String>, }, #[default] Disable, @@ -320,7 +320,7 @@ impl AddressMapping { } if !regex_capture.is_empty() { - return mapping.apply(regex_capture); + return mapping.apply(regex_capture, &()); } } AddressMapping::Disable => (), @@ -343,7 +343,7 @@ impl AddressMapping { } } if !regex_capture.is_empty() { - Some(mapping.apply(regex_capture)) + Some(mapping.apply(regex_capture, &())) } else { None } diff --git a/crates/smtp/src/config/auth.rs b/crates/smtp/src/config/auth.rs index 2c976e3b..65f770ea 100644 --- a/crates/smtp/src/config/auth.rs +++ b/crates/smtp/src/config/auth.rs @@ -69,7 +69,11 @@ impl ConfigAuth for Config { .parse_if_block("auth.dkim.verify", ctx, &envelope_sender_keys)? .unwrap_or_else(|| IfBlock::new(VerifyStrategy::Relaxed)), sign: self - .parse_if_block::<Vec<DynValue>>("auth.dkim.sign", ctx, &envelope_sender_keys)? + .parse_if_block::<Vec<DynValue<EnvelopeKey>>>( + "auth.dkim.sign", + ctx, + &envelope_sender_keys, + )? .unwrap_or_default() .map_if_block(&ctx.signers, "auth.dkim.sign", "signature")?, }, @@ -78,7 +82,7 @@ impl ConfigAuth for Config { .parse_if_block("auth.arc.verify", ctx, &envelope_sender_keys)? .unwrap_or_else(|| IfBlock::new(VerifyStrategy::Relaxed)), seal: self - .parse_if_block::<Option<DynValue>>( + .parse_if_block::<Option<DynValue<EnvelopeKey>>>( "auth.arc.seal", ctx, &envelope_sender_keys, diff --git a/crates/smtp/src/config/if_block.rs b/crates/smtp/src/config/if_block.rs index 2d01ea5c..8414b9f9 100644 --- a/crates/smtp/src/config/if_block.rs +++ b/crates/smtp/src/config/if_block.rs @@ -237,50 +237,7 @@ impl IfBlock<Option<String>> { } } -/* -impl IfBlock<Vec<String>> { - pub fn map_if_block<T: ?Sized>( - self, - map: &AHashMap<String, Arc<T>>, - key_name: &str, - object_name: &str, - ) -> super::Result<IfBlock<Vec<Arc<T>>>> { - let mut if_then = Vec::with_capacity(self.if_then.len()); - for if_clause in self.if_then.into_iter() { - if_then.push(IfThen { - conditions: if_clause.conditions, - then: Self::map_value(map, if_clause.then, object_name, key_name)?, - }); - } - - Ok(IfBlock { - if_then, - default: Self::map_value(map, self.default, object_name, key_name)?, - }) - } - - fn map_value<T: ?Sized>( - map: &AHashMap<String, Arc<T>>, - values: Vec<String>, - object_name: &str, - key_name: &str, - ) -> super::Result<Vec<Arc<T>>> { - let mut result = Vec::with_capacity(values.len()); - for value in values { - if let Some(value) = map.get(&value) { - result.push(value.clone()); - } else { - return Err(format!( - "Unable to find {object_name} {value:?} declared for {key_name:?}", - )); - } - } - Ok(result) - } -} -*/ - -impl IfBlock<Vec<DynValue>> { +impl IfBlock<Vec<DynValue<EnvelopeKey>>> { pub fn map_if_block<T: ?Sized>( self, map: &AHashMap<String, Arc<T>>, @@ -303,7 +260,7 @@ impl IfBlock<Vec<DynValue>> { fn map_value<T: ?Sized>( map: &AHashMap<String, Arc<T>>, - values: Vec<DynValue>, + values: Vec<DynValue<EnvelopeKey>>, object_name: &str, key_name: &str, ) -> super::Result<Vec<MaybeDynValue<T>>> { @@ -328,7 +285,7 @@ impl IfBlock<Vec<DynValue>> { } } -impl IfBlock<Option<DynValue>> { +impl IfBlock<Option<DynValue<EnvelopeKey>>> { pub fn map_if_block<T: ?Sized>( self, map: &AHashMap<String, Arc<T>>, @@ -352,7 +309,7 @@ impl IfBlock<Option<DynValue>> { fn map_value<T: ?Sized>( map: &AHashMap<String, Arc<T>>, - value: Option<DynValue>, + value: Option<DynValue<EnvelopeKey>>, object_name: &str, key_name: &str, ) -> super::Result<Option<MaybeDynValue<T>>> { diff --git a/crates/smtp/src/config/mod.rs b/crates/smtp/src/config/mod.rs index 0b149d98..4d1136d3 100644 --- a/crates/smtp/src/config/mod.rs +++ b/crates/smtp/src/config/mod.rs @@ -231,14 +231,14 @@ pub struct Auth { pub struct Mail { pub script: IfBlock<Option<Arc<Sieve>>>, - pub rewrite: IfBlock<Option<DynValue>>, + pub rewrite: IfBlock<Option<DynValue<EnvelopeKey>>>, } pub struct Rcpt { pub script: IfBlock<Option<Arc<Sieve>>>, pub relay: IfBlock<bool>, pub directory: IfBlock<Option<MaybeDynValue<dyn Directory>>>, - pub rewrite: IfBlock<Option<DynValue>>, + pub rewrite: IfBlock<Option<DynValue<EnvelopeKey>>>, // Errors pub errors_max: IfBlock<usize>, @@ -380,7 +380,7 @@ pub enum AddressMatch { #[derive(Clone)] pub enum MaybeDynValue<T: ?Sized> { Dynamic { - eval: DynValue, + eval: DynValue<EnvelopeKey>, items: AHashMap<String, Arc<T>>, }, Static(Arc<T>), diff --git a/crates/smtp/src/config/queue.rs b/crates/smtp/src/config/queue.rs index ba9419de..b666370b 100644 --- a/crates/smtp/src/config/queue.rs +++ b/crates/smtp/src/config/queue.rs @@ -189,7 +189,11 @@ impl ConfigQueue for Config { .parse_if_block("report.dsn.from-address", ctx, &sender_envelope_keys)? .unwrap_or_else(|| IfBlock::new(format!("MAILER-DAEMON@{default_hostname}"))), sign: self - .parse_if_block::<Vec<DynValue>>("report.dsn.sign", ctx, &sender_envelope_keys)? + .parse_if_block::<Vec<DynValue<EnvelopeKey>>>( + "report.dsn.sign", + ctx, + &sender_envelope_keys, + )? .unwrap_or_default() .map_if_block(&ctx.signers, "report.dsn.sign", "signature")?, }, diff --git a/crates/smtp/src/config/report.rs b/crates/smtp/src/config/report.rs index dce1b3e6..dca3d6db 100644 --- a/crates/smtp/src/config/report.rs +++ b/crates/smtp/src/config/report.rs @@ -120,7 +120,11 @@ impl ConfigReport for Config { .parse_if_block(("report", id, "subject"), ctx, available_keys)? .unwrap_or_else(|| IfBlock::new(format!("{} Report", id.to_ascii_uppercase()))), sign: self - .parse_if_block::<Vec<DynValue>>(("report", id, "sign"), ctx, available_keys)? + .parse_if_block::<Vec<DynValue<EnvelopeKey>>>( + ("report", id, "sign"), + ctx, + available_keys, + )? .unwrap_or_default() .map_if_block(&ctx.signers, &("report", id, "sign").as_key(), "signature")?, send: self @@ -173,7 +177,7 @@ impl ConfigReport for Config { .parse_if_block(("report", id, "aggregate.send"), ctx, available_keys)? .unwrap_or_default(), sign: self - .parse_if_block::<Vec<DynValue>>( + .parse_if_block::<Vec<DynValue<EnvelopeKey>>>( ("report", id, "aggregate.sign"), ctx, &rcpt_envelope_keys, diff --git a/crates/smtp/src/config/session.rs b/crates/smtp/src/config/session.rs index 6eb60eb4..2f75ec28 100644 --- a/crates/smtp/src/config/session.rs +++ b/crates/smtp/src/config/session.rs @@ -250,7 +250,11 @@ impl ConfigSession for Config { Ok(Auth { directory: self - .parse_if_block::<Option<DynValue>>("session.auth.directory", ctx, &available_keys)? + .parse_if_block::<Option<DynValue<EnvelopeKey>>>( + "session.auth.directory", + ctx, + &available_keys, + )? .unwrap_or_default() .map_if_block( &ctx.directory.directories, @@ -297,7 +301,11 @@ impl ConfigSession for Config { .unwrap_or_default() .map_if_block(&ctx.scripts, "session.mail.script", "script")?, rewrite: self - .parse_if_block::<Option<DynValue>>("session.mail.rewrite", ctx, &available_keys)? + .parse_if_block::<Option<DynValue<EnvelopeKey>>>( + "session.mail.rewrite", + ctx, + &available_keys, + )? .unwrap_or_default(), }) } @@ -321,7 +329,11 @@ impl ConfigSession for Config { .parse_if_block("session.rcpt.relay", ctx, &available_keys)? .unwrap_or_else(|| IfBlock::new(false)), directory: self - .parse_if_block::<Option<DynValue>>("session.rcpt.directory", ctx, &available_keys)? + .parse_if_block::<Option<DynValue<EnvelopeKey>>>( + "session.rcpt.directory", + ctx, + &available_keys, + )? .unwrap_or_default() .map_if_block( &ctx.directory.directories, @@ -338,7 +350,11 @@ impl ConfigSession for Config { .parse_if_block("session.rcpt.max-recipients", ctx, &available_keys)? .unwrap_or_else(|| IfBlock::new(100)), rewrite: self - .parse_if_block::<Option<DynValue>>("session.rcpt.rewrite", ctx, &available_keys)? + .parse_if_block::<Option<DynValue<EnvelopeKey>>>( + "session.rcpt.rewrite", + ctx, + &available_keys, + )? .unwrap_or_default(), }) } diff --git a/crates/smtp/src/core/if_block.rs b/crates/smtp/src/core/if_block.rs index fbfaf03c..9d6e4b89 100644 --- a/crates/smtp/src/core/if_block.rs +++ b/crates/smtp/src/core/if_block.rs @@ -21,28 +21,22 @@ * for more details. */ -use std::{ - borrow::Cow, - net::{IpAddr, Ipv4Addr}, - sync::Arc, -}; +use std::{borrow::Cow, net::IpAddr, sync::Arc}; -use utils::config::DynValue; +use utils::config::{DynValue, KeyLookup}; use crate::config::{ Condition, ConditionMatch, Conditions, EnvelopeKey, IfBlock, IpAddrMask, MaybeDynValue, StringMatch, }; -use super::Envelope; - pub struct Captures<'x, T> { value: &'x T, captures: Vec<String>, } impl<T: Default> IfBlock<T> { - pub async fn eval(&self, envelope: &impl Envelope) -> &T { + pub async fn eval(&self, envelope: &impl KeyLookup<Key = EnvelopeKey>) -> &T { for if_then in &self.if_then { if if_then.conditions.eval(envelope).await { return &if_then.then; @@ -52,7 +46,10 @@ impl<T: Default> IfBlock<T> { &self.default } - pub async fn eval_and_capture(&self, envelope: &impl Envelope) -> Captures<'_, T> { + pub async fn eval_and_capture( + &self, + envelope: &impl KeyLookup<Key = EnvelopeKey>, + ) -> Captures<'_, T> { for if_then in &self.if_then { if let Some(captures) = if_then.conditions.eval_and_capture(envelope).await { return Captures { @@ -70,7 +67,7 @@ impl<T: Default> IfBlock<T> { } impl Conditions { - pub async fn eval(&self, envelope: &impl Envelope) -> bool { + pub async fn eval(&self, envelope: &impl KeyLookup<Key = EnvelopeKey>) -> bool { let mut conditions = self.conditions.iter(); let mut matched = false; @@ -79,48 +76,27 @@ impl Conditions { Condition::Match { key, value, not } => { matched = match value { ConditionMatch::String(value) => { - let ctx_value = envelope.key_to_string(key); + let ctx_value = envelope.key(key); match value { StringMatch::Equal(value) => value.eq(ctx_value.as_ref()), StringMatch::StartsWith(value) => ctx_value.starts_with(value), StringMatch::EndsWith(value) => ctx_value.ends_with(value), } } - ConditionMatch::IpAddrMask(value) => value.matches(&match key { - EnvelopeKey::RemoteIp => envelope.remote_ip(), - EnvelopeKey::LocalIp => envelope.local_ip(), - _ => IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), - }), - ConditionMatch::UInt(value) => { - *value - == if key == &EnvelopeKey::Listener { - envelope.listener_id() - } else { - debug_assert!(false, "Invalid value for UInt context key."); - u16::MAX - } - } - ConditionMatch::Int(value) => { - *value - == if key == &EnvelopeKey::Listener { - envelope.priority() - } else { - debug_assert!(false, "Invalid value for UInt context key."); - i16::MAX - } + ConditionMatch::IpAddrMask(value) => { + value.matches(&envelope.key_as_ip(key)) } + ConditionMatch::UInt(value) => *value == envelope.key_as_int(key) as u16, + ConditionMatch::Int(value) => *value == envelope.key_as_int(key) as i16, ConditionMatch::Lookup(lookup) => { - if let Some(result) = - lookup.contains(envelope.key_to_string(key).as_ref()).await + if let Some(result) = lookup.contains(envelope.key(key).as_ref()).await { result } else { return false; } } - ConditionMatch::Regex(value) => { - value.is_match(envelope.key_to_string(key).as_ref()) - } + ConditionMatch::Regex(value) => value.is_match(envelope.key(key).as_ref()), } ^ not; } Condition::JumpIfTrue { positions } => { @@ -145,7 +121,10 @@ impl Conditions { matched } - pub async fn eval_and_capture(&self, envelope: &impl Envelope) -> Option<Vec<String>> { + pub async fn eval_and_capture( + &self, + envelope: &impl KeyLookup<Key = EnvelopeKey>, + ) -> Option<Vec<String>> { let mut conditions = self.conditions.iter(); let mut matched = false; let mut last_capture = vec![]; @@ -154,36 +133,18 @@ impl Conditions { while let Some(rule) = conditions.next() { match rule { Condition::Match { key, value, not } => { - let ctx_value = envelope.key_to_string(key); + let ctx_value = envelope.key(key); matched = match value { ConditionMatch::String(value) => match value { StringMatch::Equal(value) => value.eq(ctx_value.as_ref()), StringMatch::StartsWith(value) => ctx_value.starts_with(value), StringMatch::EndsWith(value) => ctx_value.ends_with(value), }, - ConditionMatch::IpAddrMask(value) => value.matches(&match key { - EnvelopeKey::RemoteIp => envelope.remote_ip(), - EnvelopeKey::LocalIp => envelope.local_ip(), - _ => IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), - }), - ConditionMatch::UInt(value) => { - *value - == if key == &EnvelopeKey::Listener { - envelope.listener_id() - } else { - debug_assert!(false, "Invalid value for UInt context key."); - u16::MAX - } - } - ConditionMatch::Int(value) => { - *value - == if key == &EnvelopeKey::Listener { - envelope.priority() - } else { - debug_assert!(false, "Invalid value for UInt context key."); - i16::MAX - } + ConditionMatch::IpAddrMask(value) => { + value.matches(&envelope.key_as_ip(key)) } + ConditionMatch::UInt(value) => *value == envelope.key_as_int(key) as u16, + ConditionMatch::Int(value) => *value == envelope.key_as_int(key) as i16, ConditionMatch::Lookup(lookup) => { lookup.contains(ctx_value.as_ref()).await? } @@ -288,23 +249,23 @@ impl IpAddrMask { } } -impl<'x> Captures<'x, DynValue> { - pub fn into_value(self) -> Cow<'x, str> { - self.value.apply(self.captures) +impl<'x> Captures<'x, DynValue<EnvelopeKey>> { + pub fn into_value(self, keys: &'x impl KeyLookup<Key = EnvelopeKey>) -> Cow<'x, str> { + self.value.apply(self.captures, keys) } } -impl<'x> Captures<'x, Option<DynValue>> { - pub fn into_value(self) -> Option<Cow<'x, str>> { - self.value.as_ref().map(|v| v.apply(self.captures)) +impl<'x> Captures<'x, Option<DynValue<EnvelopeKey>>> { + pub fn into_value(self, keys: &'x impl KeyLookup<Key = EnvelopeKey>) -> Option<Cow<'x, str>> { + self.value.as_ref().map(|v| v.apply(self.captures, keys)) } } impl<'x, T: ?Sized> Captures<'x, MaybeDynValue<T>> { - pub fn into_value(self) -> Option<Arc<T>> { + pub fn into_value(self, keys: &impl KeyLookup<Key = EnvelopeKey>) -> Option<Arc<T>> { match &self.value { MaybeDynValue::Dynamic { eval, items } => { - let r = eval.apply(self.captures); + let r = eval.apply(self.captures, keys); match items.get(r.as_ref()) { Some(value) => value.clone().into(), @@ -326,12 +287,12 @@ impl<'x, T: ?Sized> Captures<'x, MaybeDynValue<T>> { } impl<'x, T: ?Sized> Captures<'x, Vec<MaybeDynValue<T>>> { - pub fn into_value(self) -> Vec<Arc<T>> { + pub fn into_value(self, keys: &impl KeyLookup<Key = EnvelopeKey>) -> Vec<Arc<T>> { let mut results = Vec::with_capacity(self.value.len()); for value in self.value.iter() { match value { MaybeDynValue::Dynamic { eval, items } => { - let r = eval.apply_borrowed(&self.captures); + let r = eval.apply_borrowed(&self.captures, keys); match items.get(r.as_ref()) { Some(value) => { results.push(value.clone()); @@ -357,10 +318,10 @@ impl<'x, T: ?Sized> Captures<'x, Vec<MaybeDynValue<T>>> { } impl<'x, T: ?Sized> Captures<'x, Option<MaybeDynValue<T>>> { - pub fn into_value(self) -> Option<Arc<T>> { + pub fn into_value(self, keys: &impl KeyLookup<Key = EnvelopeKey>) -> Option<Arc<T>> { match self.value.as_ref()? { MaybeDynValue::Dynamic { eval, items } => { - let r = eval.apply(self.captures); + let r = eval.apply(self.captures, keys); match items.get(r.as_ref()) { Some(value) => value.clone().into(), None => { diff --git a/crates/smtp/src/core/mod.rs b/crates/smtp/src/core/mod.rs index 36b959fc..7f83de93 100644 --- a/crates/smtp/src/core/mod.rs +++ b/crates/smtp/src/core/mod.rs @@ -22,7 +22,6 @@ */ use std::{ - borrow::Cow, hash::Hash, net::IpAddr, sync::{atomic::AtomicU32, Arc}, @@ -50,8 +49,7 @@ use utils::{ use crate::{ config::{ - DkimSigner, EnvelopeKey, MailAuthConfig, QueueConfig, ReportConfig, SessionConfig, - VerifyStrategy, + DkimSigner, MailAuthConfig, QueueConfig, ReportConfig, SessionConfig, VerifyStrategy, }, inbound::auth::SaslToken, outbound::{ @@ -279,37 +277,6 @@ impl Default for State { } } -pub trait Envelope { - fn local_ip(&self) -> IpAddr; - fn remote_ip(&self) -> IpAddr; - fn sender_domain(&self) -> &str; - fn sender(&self) -> &str; - fn rcpt_domain(&self) -> &str; - fn rcpt(&self) -> &str; - fn helo_domain(&self) -> &str; - fn authenticated_as(&self) -> &str; - fn mx(&self) -> &str; - fn listener_id(&self) -> u16; - fn priority(&self) -> i16; - - #[inline(always)] - fn key_to_string(&self, key: &EnvelopeKey) -> Cow<'_, str> { - match key { - EnvelopeKey::Recipient => self.rcpt().into(), - EnvelopeKey::RecipientDomain => self.rcpt_domain().into(), - EnvelopeKey::Sender => self.sender().into(), - EnvelopeKey::SenderDomain => self.sender_domain().into(), - EnvelopeKey::Mx => self.mx().into(), - EnvelopeKey::AuthenticatedAs => self.authenticated_as().into(), - EnvelopeKey::HeloDomain => self.helo_domain().into(), - EnvelopeKey::Listener => self.listener_id().to_string().into(), - EnvelopeKey::RemoteIp => self.remote_ip().to_string().into(), - EnvelopeKey::LocalIp => self.local_ip().to_string().into(), - EnvelopeKey::Priority => self.priority().to_string().into(), - } - } -} - impl VerifyStrategy { #[inline(always)] pub fn verify(&self) -> bool { diff --git a/crates/smtp/src/core/params.rs b/crates/smtp/src/core/params.rs index 2380adcc..79df57bb 100644 --- a/crates/smtp/src/core/params.rs +++ b/crates/smtp/src/core/params.rs @@ -44,7 +44,7 @@ impl<T: AsyncRead + AsyncWrite> Session<T> { // Auth parameters let ac = &self.core.session.config.auth; - self.params.auth_directory = ac.directory.eval_and_capture(self).await.into_value(); + self.params.auth_directory = ac.directory.eval_and_capture(self).await.into_value(self); self.params.auth_require = *ac.require.eval(self).await; self.params.auth_errors_max = *ac.errors_max.eval(self).await; self.params.auth_errors_wait = *ac.errors_wait.eval(self).await; diff --git a/crates/smtp/src/core/throttle.rs b/crates/smtp/src/core/throttle.rs index 49536c67..783efdb3 100644 --- a/crates/smtp/src/core/throttle.rs +++ b/crates/smtp/src/core/throttle.rs @@ -24,7 +24,7 @@ use ::utils::listener::limiter::{ConcurrencyLimiter, RateLimiter}; use dashmap::mapref::entry::Entry; use tokio::io::{AsyncRead, AsyncWrite}; -use utils::config::Rate; +use utils::config::{KeyLookup, Rate}; use std::{ hash::{BuildHasher, Hash, Hasher}, @@ -34,7 +34,7 @@ use std::{ use crate::config::*; -use super::{Envelope, Session}; +use super::Session; #[derive(Debug)] pub struct Limiter { @@ -86,24 +86,31 @@ impl BuildHasher for ThrottleKeyHasherBuilder { } impl QueueQuota { - pub fn new_key(&self, e: &impl Envelope) -> ThrottleKey { + pub fn new_key(&self, e: &impl KeyLookup<Key = EnvelopeKey>) -> ThrottleKey { let mut hasher = blake3::Hasher::new(); if (self.keys & THROTTLE_RCPT) != 0 { - hasher.update(e.rcpt().as_bytes()); + hasher.update(e.key(&EnvelopeKey::Recipient).as_bytes()); } if (self.keys & THROTTLE_RCPT_DOMAIN) != 0 { - hasher.update(e.rcpt_domain().as_bytes()); + hasher.update(e.key(&EnvelopeKey::RecipientDomain).as_bytes()); } if (self.keys & THROTTLE_SENDER) != 0 { - let sender = e.sender(); - hasher.update(if !sender.is_empty() { sender } else { "<>" }.as_bytes()); + let sender = e.key(&EnvelopeKey::Sender); + hasher.update( + if !sender.is_empty() { + sender.as_ref() + } else { + "<>" + } + .as_bytes(), + ); } if (self.keys & THROTTLE_SENDER_DOMAIN) != 0 { - let sender_domain = e.sender_domain(); + let sender_domain = e.key(&EnvelopeKey::SenderDomain); hasher.update( if !sender_domain.is_empty() { - sender_domain + sender_domain.as_ref() } else { "<>" } @@ -126,24 +133,31 @@ impl QueueQuota { } impl Throttle { - pub fn new_key(&self, e: &impl Envelope) -> ThrottleKey { + pub fn new_key(&self, e: &impl KeyLookup<Key = EnvelopeKey>) -> ThrottleKey { let mut hasher = blake3::Hasher::new(); if (self.keys & THROTTLE_RCPT) != 0 { - hasher.update(e.rcpt().as_bytes()); + hasher.update(e.key(&EnvelopeKey::Recipient).as_bytes()); } if (self.keys & THROTTLE_RCPT_DOMAIN) != 0 { - hasher.update(e.rcpt_domain().as_bytes()); + hasher.update(e.key(&EnvelopeKey::RecipientDomain).as_bytes()); } if (self.keys & THROTTLE_SENDER) != 0 { - let sender = e.sender(); - hasher.update(if !sender.is_empty() { sender } else { "<>" }.as_bytes()); + let sender = e.key(&EnvelopeKey::Sender); + hasher.update( + if !sender.is_empty() { + sender.as_ref() + } else { + "<>" + } + .as_bytes(), + ); } if (self.keys & THROTTLE_SENDER_DOMAIN) != 0 { - let sender_domain = e.sender_domain(); + let sender_domain = e.key(&EnvelopeKey::SenderDomain); hasher.update( if !sender_domain.is_empty() { - sender_domain + sender_domain.as_ref() } else { "<>" } @@ -151,19 +165,19 @@ impl Throttle { ); } if (self.keys & THROTTLE_HELO_DOMAIN) != 0 { - hasher.update(e.helo_domain().as_bytes()); + hasher.update(e.key(&EnvelopeKey::HeloDomain).as_bytes()); } if (self.keys & THROTTLE_AUTH_AS) != 0 { - hasher.update(e.authenticated_as().as_bytes()); + hasher.update(e.key(&EnvelopeKey::AuthenticatedAs).as_bytes()); } if (self.keys & THROTTLE_LISTENER) != 0 { - hasher.update(&e.listener_id().to_ne_bytes()[..]); + hasher.update(&e.key_as_int(&EnvelopeKey::Listener).to_ne_bytes()[..]); } if (self.keys & THROTTLE_MX) != 0 { - hasher.update(e.mx().as_bytes()); + hasher.update(e.key(&EnvelopeKey::Mx).as_bytes()); } if (self.keys & THROTTLE_REMOTE_IP) != 0 { - match &e.remote_ip() { + match &e.key_as_ip(&EnvelopeKey::RemoteIp) { IpAddr::V4(ip) => { hasher.update(&ip.octets()[..]); } @@ -173,7 +187,7 @@ impl Throttle { } } if (self.keys & THROTTLE_LOCAL_IP) != 0 { - match &e.local_ip() { + match &e.key_as_ip(&EnvelopeKey::LocalIp) { IpAddr::V4(ip) => { hasher.update(&ip.octets()[..]); } diff --git a/crates/smtp/src/inbound/data.rs b/crates/smtp/src/inbound/data.rs index 0d94a50a..aee2d4bc 100644 --- a/crates/smtp/src/inbound/data.rs +++ b/crates/smtp/src/inbound/data.rs @@ -145,7 +145,7 @@ impl<T: AsyncWrite + AsyncRead + IsTls + Unpin> Session<T> { // Verify ARC let arc = *ac.arc.verify.eval(self).await; - let arc_sealer = ac.arc.seal.eval_and_capture(self).await.into_value(); + let arc_sealer = ac.arc.seal.eval_and_capture(self).await.into_value(self); let arc_output = if arc.verify() || arc_sealer.is_some() { let arc_output = self.core.resolvers.dns.verify_arc(&auth_message).await; @@ -502,7 +502,7 @@ impl<T: AsyncWrite + AsyncRead + IsTls + Unpin> Session<T> { // DKIM sign let raw_message = edited_message.unwrap_or(raw_message); - for signer in ac.dkim.sign.eval_and_capture(self).await.into_value() { + for signer in ac.dkim.sign.eval_and_capture(self).await.into_value(self) { match signer.sign_chained(&[headers.as_ref(), &raw_message]) { Ok(signature) => { signature.write_header(&mut headers); diff --git a/crates/smtp/src/inbound/mail.rs b/crates/smtp/src/inbound/mail.rs index b0b0a5c5..db9a5063 100644 --- a/crates/smtp/src/inbound/mail.rs +++ b/crates/smtp/src/inbound/mail.rs @@ -172,13 +172,14 @@ impl<T: AsyncWrite + AsyncRead + Unpin + IsTls> Session<T> { .rewrite .eval_and_capture(self) .await - .into_value() + .into_value(self) + .map(|s| s.into_owned()) { let mut mail_from = self.data.mail_from.as_mut().unwrap(); 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.into_owned(); + mail_from.address = new_address; } else if new_address.is_empty() { mail_from.address_lcase.clear(); mail_from.domain.clear(); diff --git a/crates/smtp/src/inbound/rcpt.rs b/crates/smtp/src/inbound/rcpt.rs index 84002c57..2de78791 100644 --- a/crates/smtp/src/inbound/rcpt.rs +++ b/crates/smtp/src/inbound/rcpt.rs @@ -121,13 +121,14 @@ impl<T: AsyncWrite + AsyncRead + Unpin> Session<T> { .rewrite .eval_and_capture(self) .await - .into_value() + .into_value(self) + .map(|s| s.into_owned()) { let mut rcpt = self.data.rcpt_to.last_mut().unwrap(); if new_address.contains('@') { rcpt.address_lcase = new_address.to_lowercase(); rcpt.domain = rcpt.address_lcase.domain_part().to_string(); - rcpt.address = new_address.into_owned(); + rcpt.address = new_address; } } @@ -149,7 +150,7 @@ impl<T: AsyncWrite + AsyncRead + Unpin> Session<T> { .directory .eval_and_capture(self) .await - .into_value() + .into_value(self) { if let Ok(is_local_domain) = directory.is_local_domain(&rcpt.domain).await { if is_local_domain { diff --git a/crates/smtp/src/inbound/session.rs b/crates/smtp/src/inbound/session.rs index b233c5bf..43abbeb4 100644 --- a/crates/smtp/src/inbound/session.rs +++ b/crates/smtp/src/inbound/session.rs @@ -21,7 +21,7 @@ * for more details. */ -use std::net::IpAddr; +use std::net::{IpAddr, Ipv4Addr}; use smtp_proto::{ request::receiver::{ @@ -31,9 +31,12 @@ use smtp_proto::{ *, }; use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; -use utils::config::ServerProtocol; +use utils::config::{KeyLookup, ServerProtocol}; -use crate::core::{Envelope, Session, State}; +use crate::{ + config::EnvelopeKey, + core::{Session, State}, +}; use super::{auth::SaslToken, IsTls}; @@ -392,75 +395,62 @@ impl<T: AsyncWrite + AsyncRead + Unpin> Session<T> { } } -impl<T: AsyncRead + AsyncWrite> Envelope for Session<T> { - #[inline(always)] - fn local_ip(&self) -> IpAddr { - self.data.local_ip - } - - #[inline(always)] - fn remote_ip(&self) -> IpAddr { - self.data.remote_ip - } - - #[inline(always)] - fn sender_domain(&self) -> &str { - self.data - .mail_from - .as_ref() - .map(|a| a.domain.as_str()) - .unwrap_or_default() - } - - #[inline(always)] - fn sender(&self) -> &str { - self.data - .mail_from - .as_ref() - .map(|a| a.address_lcase.as_str()) - .unwrap_or_default() - } - - #[inline(always)] - fn rcpt_domain(&self) -> &str { - self.data - .rcpt_to - .last() - .map(|r| r.domain.as_str()) - .unwrap_or_default() - } - - #[inline(always)] - fn rcpt(&self) -> &str { - self.data - .rcpt_to - .last() - .map(|r| r.address_lcase.as_str()) - .unwrap_or_default() - } - - #[inline(always)] - fn helo_domain(&self) -> &str { - self.data.helo_domain.as_str() - } +impl<T: AsyncRead + AsyncWrite> KeyLookup for Session<T> { + type Key = EnvelopeKey; - #[inline(always)] - fn authenticated_as(&self) -> &str { - self.data.authenticated_as.as_str() - } - - #[inline(always)] - fn mx(&self) -> &str { - "" + fn key(&self, key: &Self::Key) -> std::borrow::Cow<'_, str> { + match key { + EnvelopeKey::Recipient => self + .data + .rcpt_to + .last() + .map(|r| r.address_lcase.as_str()) + .unwrap_or_default() + .into(), + EnvelopeKey::RecipientDomain => self + .data + .rcpt_to + .last() + .map(|r| r.domain.as_str()) + .unwrap_or_default() + .into(), + EnvelopeKey::Sender => self + .data + .mail_from + .as_ref() + .map(|m| m.address_lcase.as_str()) + .unwrap_or_default() + .into(), + EnvelopeKey::SenderDomain => self + .data + .mail_from + .as_ref() + .map(|m| m.domain.as_str()) + .unwrap_or_default() + .into(), + EnvelopeKey::HeloDomain => self.data.helo_domain.as_str().into(), + EnvelopeKey::AuthenticatedAs => self.data.authenticated_as.as_str().into(), + EnvelopeKey::Listener => self.instance.id.as_str().into(), + EnvelopeKey::RemoteIp => self.data.remote_ip.to_string().into(), + EnvelopeKey::LocalIp => self.data.local_ip.to_string().into(), + EnvelopeKey::Priority => self.data.priority.to_string().into(), + EnvelopeKey::Mx => "".into(), + } } - #[inline(always)] - fn listener_id(&self) -> u16 { - self.instance.listener_id + fn key_as_int(&self, key: &Self::Key) -> i32 { + match key { + EnvelopeKey::Listener => self.instance.listener_id as i32, + EnvelopeKey::Priority => self.data.priority as i32, + _ => 0, + } } - #[inline(always)] - fn priority(&self) -> i16 { - self.data.priority + fn key_as_ip(&self, key: &Self::Key) -> IpAddr { + match key { + EnvelopeKey::RemoteIp => self.data.remote_ip, + EnvelopeKey::LocalIp => self.data.local_ip, + _ => IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), + } } } diff --git a/crates/smtp/src/inbound/vrfy.rs b/crates/smtp/src/inbound/vrfy.rs index ffd171a0..e79294bf 100644 --- a/crates/smtp/src/inbound/vrfy.rs +++ b/crates/smtp/src/inbound/vrfy.rs @@ -37,7 +37,7 @@ impl<T: AsyncWrite + AsyncRead + Unpin> Session<T> { .directory .eval_and_capture(self) .await - .into_value() + .into_value(self) { Some(address_lookup) if self.params.can_vrfy => { match address_lookup.vrfy(&address.to_lowercase()).await { @@ -98,7 +98,7 @@ impl<T: AsyncWrite + AsyncRead + Unpin> Session<T> { .directory .eval_and_capture(self) .await - .into_value() + .into_value(self) { Some(address_lookup) if self.params.can_expn => { match address_lookup.expn(&address.to_lowercase()).await { diff --git a/crates/smtp/src/outbound/lookup.rs b/crates/smtp/src/outbound/lookup.rs index c5506a43..72778b8f 100644 --- a/crates/smtp/src/outbound/lookup.rs +++ b/crates/smtp/src/outbound/lookup.rs @@ -25,9 +25,11 @@ use std::net::IpAddr; use mail_auth::MX; use rand::{seq::SliceRandom, Rng}; +use utils::config::KeyLookup; use crate::{ - core::{Envelope, SMTP}, + config::EnvelopeKey, + core::SMTP, queue::{Error, ErrorDetails, Status}, }; @@ -37,7 +39,7 @@ impl SMTP { pub async fn resolve_host( &self, remote_host: &NextHop<'_>, - envelope: &impl Envelope, + envelope: &impl KeyLookup<Key = EnvelopeKey>, max_multihomed: usize, ) -> Result<(Option<IpAddr>, Vec<IpAddr>), Status<(), Error>> { let remote_ips = self @@ -100,7 +102,7 @@ impl SMTP { } else { Err(Status::TemporaryFailure(Error::DnsError(format!( "No IP addresses found for {:?}.", - envelope.mx() + envelope.key(&EnvelopeKey::Mx) )))) } } diff --git a/crates/smtp/src/queue/mod.rs b/crates/smtp/src/queue/mod.rs index d4275791..b80e87c5 100644 --- a/crates/smtp/src/queue/mod.rs +++ b/crates/smtp/src/queue/mod.rs @@ -31,9 +31,12 @@ use std::{ use serde::{Deserialize, Serialize}; use smtp_proto::Response; -use utils::listener::limiter::{ConcurrencyLimiter, InFlight}; +use utils::{ + config::KeyLookup, + listener::limiter::{ConcurrencyLimiter, InFlight}, +}; -use crate::core::{management, Envelope}; +use crate::{config::EnvelopeKey, core::management}; pub mod dsn; pub mod manager; @@ -241,49 +244,30 @@ impl<'x> SimpleEnvelope<'x> { } } -impl<'x> Envelope for SimpleEnvelope<'x> { - fn local_ip(&self) -> IpAddr { - IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)) - } - - fn remote_ip(&self) -> IpAddr { - IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)) - } +impl<'x> KeyLookup for SimpleEnvelope<'x> { + type Key = EnvelopeKey; - fn sender_domain(&self) -> &str { - &self.message.return_path_domain - } - - fn sender(&self) -> &str { - &self.message.return_path_lcase - } - - fn rcpt_domain(&self) -> &str { - self.domain - } - - fn rcpt(&self) -> &str { - self.recipient - } - - fn helo_domain(&self) -> &str { - "" - } - - fn authenticated_as(&self) -> &str { - "" - } - - fn mx(&self) -> &str { - "" + fn key(&self, key: &Self::Key) -> std::borrow::Cow<'_, str> { + match key { + EnvelopeKey::Sender => self.message.return_path_lcase.as_str().into(), + EnvelopeKey::SenderDomain => self.message.return_path_domain.as_str().into(), + EnvelopeKey::Priority => self.message.priority.to_string().into(), + EnvelopeKey::Recipient => self.recipient.into(), + EnvelopeKey::RecipientDomain => self.domain.into(), + _ => "".into(), + } } - fn listener_id(&self) -> u16 { - 0 + fn key_as_int(&self, key: &Self::Key) -> i32 { + if matches!(key, EnvelopeKey::Priority) { + self.message.priority as i32 + } else { + 0 + } } - fn priority(&self) -> i16 { - self.message.priority + fn key_as_ip(&self, _: &Self::Key) -> IpAddr { + IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)) } } @@ -295,141 +279,86 @@ pub struct QueueEnvelope<'x> { pub local_ip: IpAddr, } -impl<'x> Envelope for QueueEnvelope<'x> { - fn local_ip(&self) -> IpAddr { - self.local_ip - } - - fn remote_ip(&self) -> IpAddr { - self.remote_ip - } - - fn sender_domain(&self) -> &str { - &self.message.return_path_domain - } - - fn sender(&self) -> &str { - &self.message.return_path_lcase - } - - fn rcpt_domain(&self) -> &str { - self.domain - } +impl<'x> KeyLookup for QueueEnvelope<'x> { + type Key = EnvelopeKey; - fn rcpt(&self) -> &str { - "" - } - - fn helo_domain(&self) -> &str { - "" - } - - fn authenticated_as(&self) -> &str { - "" - } - - fn mx(&self) -> &str { - self.mx + fn key(&self, key: &Self::Key) -> std::borrow::Cow<'_, str> { + match key { + EnvelopeKey::Sender => self.message.return_path_lcase.as_str().into(), + EnvelopeKey::SenderDomain => self.message.return_path_domain.as_str().into(), + EnvelopeKey::RecipientDomain => self.domain.into(), + EnvelopeKey::Mx => self.mx.into(), + EnvelopeKey::Priority => self.message.priority.to_string().into(), + _ => "".into(), + } } - fn listener_id(&self) -> u16 { - 0 + fn key_as_int(&self, key: &Self::Key) -> i32 { + if matches!(key, EnvelopeKey::Priority) { + self.message.priority as i32 + } else { + 0 + } } - fn priority(&self) -> i16 { - self.message.priority + fn key_as_ip(&self, key: &Self::Key) -> IpAddr { + match key { + EnvelopeKey::RemoteIp => self.remote_ip, + EnvelopeKey::LocalIp => self.local_ip, + _ => IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), + } } } -impl Envelope for Message { - fn local_ip(&self) -> IpAddr { - IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)) - } - - fn remote_ip(&self) -> IpAddr { - IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)) - } +impl KeyLookup for Message { + type Key = EnvelopeKey; - fn sender_domain(&self) -> &str { - &self.return_path_domain - } - - fn sender(&self) -> &str { - &self.return_path_lcase - } - - fn rcpt_domain(&self) -> &str { - "" - } - - fn rcpt(&self) -> &str { - "" - } - - fn helo_domain(&self) -> &str { - "" - } - - fn authenticated_as(&self) -> &str { - "" - } - - fn mx(&self) -> &str { - "" - } - - fn listener_id(&self) -> u16 { - 0 + fn key(&self, key: &Self::Key) -> std::borrow::Cow<'_, str> { + match key { + EnvelopeKey::Sender => self.return_path_lcase.as_str().into(), + EnvelopeKey::SenderDomain => self.return_path_domain.as_str().into(), + EnvelopeKey::Priority => self.priority.to_string().into(), + _ => "".into(), + } } - fn priority(&self) -> i16 { - self.priority - } -} - -impl Envelope for &str { - fn local_ip(&self) -> IpAddr { - IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)) + fn key_as_int(&self, key: &Self::Key) -> i32 { + if matches!(key, EnvelopeKey::Priority) { + self.priority as i32 + } else { + 0 + } } - fn remote_ip(&self) -> IpAddr { + fn key_as_ip(&self, _: &Self::Key) -> IpAddr { IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)) } +} - fn sender_domain(&self) -> &str { - "" - } - - fn sender(&self) -> &str { - "" - } - - fn rcpt_domain(&self) -> &str { - self - } - - fn rcpt(&self) -> &str { - "" - } +pub struct RecipientDomain<'x>(&'x str); - fn helo_domain(&self) -> &str { - "" +impl<'x> RecipientDomain<'x> { + pub fn new(domain: &'x str) -> Self { + Self(domain) } +} - fn authenticated_as(&self) -> &str { - "" - } +impl<'x> KeyLookup for RecipientDomain<'x> { + type Key = EnvelopeKey; - fn mx(&self) -> &str { - "" + fn key(&self, key: &Self::Key) -> std::borrow::Cow<'_, str> { + match key { + EnvelopeKey::RecipientDomain => self.0.into(), + _ => "".into(), + } } - fn listener_id(&self) -> u16 { + fn key_as_int(&self, _: &Self::Key) -> i32 { 0 } - fn priority(&self) -> i16 { - 0 + fn key_as_ip(&self, _: &Self::Key) -> IpAddr { + IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)) } } diff --git a/crates/smtp/src/queue/quota.rs b/crates/smtp/src/queue/quota.rs index affad4a6..377a9c14 100644 --- a/crates/smtp/src/queue/quota.rs +++ b/crates/smtp/src/queue/quota.rs @@ -24,10 +24,11 @@ use std::sync::{atomic::Ordering, Arc}; use dashmap::mapref::entry::Entry; +use utils::config::KeyLookup; use crate::{ - config::QueueQuota, - core::{Envelope, QueueCore}, + config::{EnvelopeKey, QueueQuota}, + core::QueueCore, }; use super::{Message, QuotaLimiter, SimpleEnvelope, Status, UsedQuota}; @@ -93,7 +94,7 @@ impl QueueCore { async fn reserve_quota( &self, quota: &QueueQuota, - envelope: &impl Envelope, + envelope: &impl KeyLookup<Key = EnvelopeKey>, size: usize, id: u64, refs: &mut Vec<UsedQuota>, diff --git a/crates/smtp/src/queue/throttle.rs b/crates/smtp/src/queue/throttle.rs index 3fae6d2d..0699f78b 100644 --- a/crates/smtp/src/queue/throttle.rs +++ b/crates/smtp/src/queue/throttle.rs @@ -24,11 +24,14 @@ use std::time::Instant; use dashmap::mapref::entry::Entry; -use utils::listener::limiter::{ConcurrencyLimiter, InFlight, RateLimiter}; +use utils::{ + config::KeyLookup, + listener::limiter::{ConcurrencyLimiter, InFlight, RateLimiter}, +}; use crate::{ - config::Throttle, - core::{throttle::Limiter, Envelope, QueueCore}, + config::{EnvelopeKey, Throttle}, + core::{throttle::Limiter, QueueCore}, }; use super::{Domain, Status}; @@ -43,7 +46,7 @@ impl QueueCore { pub async fn is_allowed( &self, throttle: &Throttle, - envelope: &impl Envelope, + envelope: &impl KeyLookup<Key = EnvelopeKey>, in_flight: &mut Vec<InFlight>, span: &tracing::Span, ) -> Result<(), Error> { diff --git a/crates/smtp/src/reporting/dmarc.rs b/crates/smtp/src/reporting/dmarc.rs index cd0c19cb..4024c565 100644 --- a/crates/smtp/src/reporting/dmarc.rs +++ b/crates/smtp/src/reporting/dmarc.rs @@ -40,7 +40,7 @@ use tokio::{ use crate::{ config::AggregateFrequency, core::{Session, SMTP}, - queue::{DomainPart, InstantFromTimestamp, Schedule}, + queue::{DomainPart, InstantFromTimestamp, RecipientDomain, Schedule}, }; use super::{ @@ -376,25 +376,50 @@ impl GenerateDmarcReport for Arc<SMTP> { .with_date_range_begin(path.created) .with_date_range_end(deliver_at) .with_report_id(format!("{}_{}", domain.policy, path.created)) - .with_email(handle.block_on(config.address.eval(&domain.inner.as_str()))); - if let Some(org_name) = handle.block_on(config.org_name.eval(&domain.inner.as_str())) { + .with_email( + handle.block_on( + config + .address + .eval(&RecipientDomain::new(domain.inner.as_str())), + ), + ); + if let Some(org_name) = handle.block_on( + config + .org_name + .eval(&RecipientDomain::new(domain.inner.as_str())), + ) { report = report.with_org_name(org_name); } - if let Some(contact_info) = - handle.block_on(config.contact_info.eval(&domain.inner.as_str())) - { + if let Some(contact_info) = handle.block_on( + config + .contact_info + .eval(&RecipientDomain::new(domain.inner.as_str())), + ) { report = report.with_extra_contact_info(contact_info); } for (record, count) in record_map { report.add_record(record.with_count(count)); } - let from_addr = handle.block_on(config.address.eval(&domain.inner.as_str())); + let from_addr = handle.block_on( + config + .address + .eval(&RecipientDomain::new(domain.inner.as_str())), + ); let mut message = Vec::with_capacity(path.size); let _ = report.write_rfc5322( - handle.block_on(core.report.config.submitter.eval(&domain.inner.as_str())), + handle.block_on( + core.report + .config + .submitter + .eval(&RecipientDomain::new(domain.inner.as_str())), + ), ( handle - .block_on(config.name.eval(&domain.inner.as_str())) + .block_on( + config + .name + .eval(&RecipientDomain::new(domain.inner.as_str())), + ) .as_str(), from_addr.as_str(), ), @@ -432,7 +457,7 @@ impl Scheduler { .config .dmarc_aggregate .max_size - .eval(&event.domain.as_str()) + .eval(&RecipientDomain::new(event.domain.as_str())) .await; let policy = event.dmarc_record.to_hash(); diff --git a/crates/smtp/src/reporting/mod.rs b/crates/smtp/src/reporting/mod.rs index d562a43a..0c13ef9c 100644 --- a/crates/smtp/src/reporting/mod.rs +++ b/crates/smtp/src/reporting/mod.rs @@ -182,7 +182,7 @@ impl Message { bytes: &[u8], span: &tracing::Span, ) -> Option<Vec<u8>> { - let signers = config.eval_and_capture(self).await.into_value(); + let signers = config.eval_and_capture(self).await.into_value(self); if !signers.is_empty() { let mut headers = Vec::with_capacity(64); for signer in signers.iter() { diff --git a/crates/smtp/src/reporting/scheduler.rs b/crates/smtp/src/reporting/scheduler.rs index 50b8ded4..c63a34da 100644 --- a/crates/smtp/src/reporting/scheduler.rs +++ b/crates/smtp/src/reporting/scheduler.rs @@ -47,7 +47,7 @@ use tokio::{ use crate::{ config::AggregateFrequency, core::{management::ReportRequest, worker::SpawnCleanup, ReportCore, SMTP}, - queue::{InstantFromTimestamp, Schedule}, + queue::{InstantFromTimestamp, RecipientDomain, Schedule}, }; use super::{dmarc::GenerateDmarcReport, tls::GenerateTlsReport, Event}; @@ -198,8 +198,23 @@ impl SMTP { }; // Build base path - let mut path = self.report.config.path.eval(&domain).await.clone(); - path.push((policy % *self.report.config.hash.eval(&domain).await).to_string()); + let mut path = self + .report + .config + .path + .eval(&RecipientDomain::new(domain)) + .await + .clone(); + path.push( + (policy + % *self + .report + .config + .hash + .eval(&RecipientDomain::new(domain)) + .await) + .to_string(), + ); let _ = fs::create_dir(&path).await; // Build filename diff --git a/crates/smtp/src/reporting/tls.rs b/crates/smtp/src/reporting/tls.rs index 6f5e9a47..c3c578c1 100644 --- a/crates/smtp/src/reporting/tls.rs +++ b/crates/smtp/src/reporting/tls.rs @@ -42,7 +42,7 @@ use crate::{ config::AggregateFrequency, core::SMTP, outbound::mta_sts::{Mode, MxPattern}, - queue::{InstantFromTimestamp, Schedule}, + queue::{InstantFromTimestamp, RecipientDomain, Schedule}, USER_AGENT, }; @@ -93,14 +93,18 @@ impl GenerateTlsReport for Arc<SMTP> { let config = &core.report.config.tls; let mut report = TlsReport { organization_name: handle - .block_on(config.org_name.eval(&domain.as_str())) + .block_on(config.org_name.eval(&RecipientDomain::new(domain.as_str()))) .clone(), date_range: DateRange { start_datetime: DateTime::from_timestamp(path.created as i64), end_datetime: DateTime::from_timestamp(deliver_at as i64), }, contact_info: handle - .block_on(config.contact_info.eval(&domain.as_str())) + .block_on( + config + .contact_info + .eval(&RecipientDomain::new(domain.as_str())), + ) .clone(), report_id: format!( "{}_{}", @@ -241,13 +245,21 @@ impl GenerateTlsReport for Arc<SMTP> { // Deliver report over SMTP if !rcpts.is_empty() { - let from_addr = handle.block_on(config.address.eval(&domain.as_str())); + let from_addr = + handle.block_on(config.address.eval(&RecipientDomain::new(domain.as_str()))); let mut message = Vec::with_capacity(path.size); let _ = report.write_rfc5322_from_bytes( &domain, - handle.block_on(core.report.config.submitter.eval(&domain.as_str())), + handle.block_on( + core.report + .config + .submitter + .eval(&RecipientDomain::new(domain.as_str())), + ), ( - handle.block_on(config.name.eval(&domain.as_str())).as_str(), + handle + .block_on(config.name.eval(&RecipientDomain::new(domain.as_str()))) + .as_str(), from_addr.as_str(), ), rcpts.iter().copied(), @@ -283,7 +295,7 @@ impl Scheduler { .config .tls .max_size - .eval(&event.domain.as_str()) + .eval(&RecipientDomain::new(event.domain.as_str())) .await; let policy_hash = event.policy.to_hash(); diff --git a/crates/utils/src/config/dynvalue.rs b/crates/utils/src/config/dynvalue.rs index f1c473f1..ec0a03ea 100644 --- a/crates/utils/src/config/dynvalue.rs +++ b/crates/utils/src/config/dynvalue.rs @@ -25,10 +25,10 @@ use std::borrow::Cow; use super::{ utils::{AsKey, ParseValue}, - DynValue, + DynValue, KeyLookup, }; -impl ParseValue for DynValue { +impl<T: ParseValue> ParseValue for DynValue<T> { #[allow(clippy::while_let_on_iterator)] fn parse_value(key: impl AsKey, value: &str) -> super::Result<Self> { let mut items = vec![]; @@ -38,35 +38,66 @@ impl ParseValue for DynValue { while let Some(&ch) = iter.next() { if ch == b'$' && matches!(iter.peek(), Some(b'{')) { iter.next(); - if matches!(iter.peek(), Some(ch) if ch.is_ascii_digit()) { - if !buf.is_empty() { - items.push(DynValue::String(String::from_utf8(buf).unwrap())); - buf = vec![]; + match iter.peek() { + Some(ch) if **ch == b'{' => { + buf.push(b'$'); + while let Some(&ch) = iter.next() { + if ch == b'}' { + break; + } else { + buf.push(ch); + } + } } - - while let Some(&ch) = iter.next() { + Some(ch) => { + if !buf.is_empty() { + items.push(DynValue::String(String::from_utf8(buf).unwrap())); + buf = vec![]; + } if ch.is_ascii_digit() { - buf.push(ch); - } else if ch == b'}' && !buf.is_empty() { - let str_num = std::str::from_utf8(&buf).unwrap(); - items.push(DynValue::Position(str_num.parse().map_err(|_| { - format!( - "Failed to parse position {str_num:?} in value {value:?} for key {}", - key.as_key() - ) - })?)); - buf.clear(); - break; + while let Some(&ch) = iter.next() { + if ch.is_ascii_digit() { + buf.push(ch); + } else if ch == b'}' && !buf.is_empty() { + let str_num = std::str::from_utf8(&buf).unwrap(); + items.push(DynValue::Position(str_num.parse().map_err(|_| { + format!( + "Failed to parse position {str_num:?} in value {value:?} for key {}", + key.as_key() + ) + })?)); + buf.clear(); + break; + } else { + return Err(format!( + "Invalid dynamic string {value:?} for key {}", + key.as_key() + )); + } + } } else { - return Err(format!( - "Invalid dynamic string {value:?} for key {}", - key.as_key() - )); + while let Some(&ch) = iter.next() { + if ch == b'}' { + if !buf.is_empty() { + items.push(DynValue::Key(T::parse_value( + key.clone(), + std::str::from_utf8(&buf).unwrap_or_default(), + )?)); + buf.clear(); + break; + } else { + return Err(format!( + "Invalid dynamic string {value:?} for key {}", + key.as_key() + )); + } + } else { + buf.push(ch); + } + } } } - } else { - buf.push(b'$'); - buf.push(b'{'); + None => {} } } else { buf.push(ch); @@ -90,8 +121,12 @@ impl ParseValue for DynValue { } } -impl DynValue { - pub fn apply(&self, captures: Vec<String>) -> Cow<str> { +impl<T: ParseValue> DynValue<T> { + pub fn apply<'x, 'y: 'x>( + &'x self, + captures: Vec<String>, + keys: &'y impl KeyLookup<Key = T>, + ) -> Cow<'x, str> { match self { DynValue::String(value) => Cow::Borrowed(value.as_str()), DynValue::Position(pos) => captures @@ -110,16 +145,22 @@ impl DynValue { result.push_str(capture); } } + DynValue::Key(key) => result.push_str(keys.key(key).as_ref()), DynValue::List(_) => unreachable!(), } } Cow::Owned(result) } + DynValue::Key(key) => keys.key(key), } } - pub fn apply_borrowed<'x, 'y: 'x>(&'x self, captures: &'y [String]) -> Cow<'x, str> { + pub fn apply_borrowed<'x, 'y: 'x>( + &'x self, + captures: &'y [String], + keys: &'y impl KeyLookup<Key = T>, + ) -> Cow<'x, str> { match self { DynValue::String(value) => Cow::Borrowed(value.as_str()), DynValue::Position(pos) => captures @@ -137,12 +178,14 @@ impl DynValue { result.push_str(capture); } } + DynValue::Key(key) => result.push_str(keys.key(key).as_ref()), DynValue::List(_) => unreachable!(), } } Cow::Owned(result) } + DynValue::Key(key) => keys.key(key), } } } diff --git a/crates/utils/src/config/mod.rs b/crates/utils/src/config/mod.rs index d45cbc68..217354a2 100644 --- a/crates/utils/src/config/mod.rs +++ b/crates/utils/src/config/mod.rs @@ -27,13 +27,21 @@ pub mod listener; pub mod parser; pub mod utils; -use std::{collections::BTreeMap, fmt::Display, net::SocketAddr, time::Duration}; +use std::{ + borrow::Cow, + collections::BTreeMap, + fmt::Display, + net::{IpAddr, Ipv4Addr, SocketAddr}, + time::Duration, +}; use rustls::ServerConfig; use tokio::net::TcpSocket; use crate::{failed, UnwrapFailure}; +use self::utils::ParseValue; + #[derive(Debug, Clone, PartialEq, Eq)] pub struct Config { pub keys: BTreeMap<String, String>, @@ -76,10 +84,35 @@ pub enum ServerProtocol { } #[derive(Debug, Clone)] -pub enum DynValue { +pub enum DynValue<T: ParseValue> { String(String), Position(usize), - List(Vec<DynValue>), + Key(T), + List(Vec<DynValue<T>>), +} + +pub trait KeyLookup { + type Key: ParseValue; + + fn key(&self, key: &Self::Key) -> Cow<'_, str>; + fn key_as_int(&self, key: &Self::Key) -> i32; + fn key_as_ip(&self, key: &Self::Key) -> IpAddr; +} + +impl KeyLookup for () { + type Key = String; + + fn key(&self, _: &Self::Key) -> Cow<'_, str> { + "".into() + } + + fn key_as_int(&self, _: &Self::Key) -> i32 { + 0 + } + + fn key_as_ip(&self, _: &Self::Key) -> IpAddr { + IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)) + } } #[derive(Debug, Default, PartialEq, Eq, Clone)] diff --git a/tests/resources/smtp/config/rules-dynvalue.toml b/tests/resources/smtp/config/rules-dynvalue.toml index 36951bac..eafdad61 100644 --- a/tests/resources/smtp/config/rules-dynvalue.toml +++ b/tests/resources/smtp/config/rules-dynvalue.toml @@ -24,7 +24,7 @@ test = [ {if = "rcpt-domain", starts-with = "foo", then = "${0}${{0}}"}, {else = false} ] -expect = "foo.example.org${{0}}" +expect = "foo.example.org${0}" [eval."regex"] test = [ @@ -40,6 +40,13 @@ test = [ ] expect = "user@foo.example.org" +[eval."envelope-match"] +test = [ + {if = "authenticated-as", matches = "^([^.]+)@(.+)$", then = "rcpt ${rcpt} listener ${listener} ip ${local-ip} priority ${priority}"}, + {else = false} +] +expect = "rcpt user@foo.example.org listener 123 ip 192.168.9.3 priority -4" + [eval."static-match"] test = [ {if = "authenticated-as", matches = "^([^.]+)@(.+)$", then = "hello world"}, @@ -64,6 +71,11 @@ type = "memory" [directory."list_foo".lookup] domains = ["foo"] +[directory."list_123"] +type = "memory" +[directory."list_123".lookup] +domains = ["123"] + [maybe-eval."dyn_mx"] test = [ {if = "mx", matches = "([^.]+)\.(.+)$", then = "list_${1}"}, @@ -86,3 +98,7 @@ expect = "mx" test = "list_foo" expect = "foo" +[maybe-eval."dyn_123"] +test = "list_${listener}" +expect = "123" + diff --git a/tests/src/smtp/config.rs b/tests/src/smtp/config.rs index a1b0b13d..b441b994 100644 --- a/tests/src/smtp/config.rs +++ b/tests/src/smtp/config.rs @@ -21,22 +21,26 @@ * for more details. */ -use std::{borrow::Cow, fs, net::IpAddr, path::PathBuf, sync::Arc, time::Duration}; +use std::{ + borrow::Cow, + fs, + net::{IpAddr, Ipv4Addr}, + path::PathBuf, + sync::Arc, + time::Duration, +}; use tokio::net::TcpSocket; -use utils::config::{Config, DynValue, Listener, Rate, Server, ServerProtocol}; +use utils::config::{Config, DynValue, KeyLookup, Listener, Rate, Server, ServerProtocol}; use ahash::{AHashMap, AHashSet}; use directory::{config::ConfigDirectory, Lookup}; -use smtp::{ - config::{ - condition::ConfigCondition, if_block::ConfigIf, throttle::ConfigThrottle, Condition, - ConditionMatch, Conditions, ConfigContext, EnvelopeKey, IfBlock, IfThen, IpAddrMask, - StringMatch, Throttle, THROTTLE_AUTH_AS, THROTTLE_REMOTE_IP, THROTTLE_SENDER_DOMAIN, - }, - core::Envelope, +use smtp::config::{ + condition::ConfigCondition, if_block::ConfigIf, throttle::ConfigThrottle, Condition, + ConditionMatch, Conditions, ConfigContext, EnvelopeKey, IfBlock, IfThen, IpAddrMask, + StringMatch, Throttle, THROTTLE_AUTH_AS, THROTTLE_REMOTE_IP, THROTTLE_SENDER_DOMAIN, }; use super::add_test_certs; @@ -597,7 +601,7 @@ async fn eval_dynvalue() { for test_name in config.sub_keys("eval") { //println!("============= Testing {:?} ==================", key); let if_block = config - .parse_if_block::<Option<DynValue>>( + .parse_if_block::<Option<DynValue<EnvelopeKey>>>( ("eval", test_name, "test"), &context, &[ @@ -621,7 +625,10 @@ async fn eval_dynvalue() { .map(Cow::Owned); assert_eq!( - if_block.eval_and_capture(&envelope).await.into_value(), + if_block + .eval_and_capture(&envelope) + .await + .into_value(&envelope), expected, "failed for test {test_name:?}" ); @@ -630,7 +637,7 @@ async fn eval_dynvalue() { for test_name in config.sub_keys("maybe-eval") { //println!("============= Testing {:?} ==================", key); let if_block = config - .parse_if_block::<Option<DynValue>>( + .parse_if_block::<Option<DynValue<EnvelopeKey>>>( ("maybe-eval", test_name, "test"), &context, &[ @@ -661,7 +668,7 @@ async fn eval_dynvalue() { assert!(if_block .eval_and_capture(&envelope) .await - .into_value() + .into_value(&envelope) .unwrap() .is_local_domain(expected) .await @@ -669,49 +676,39 @@ async fn eval_dynvalue() { } } -impl Envelope for TestEnvelope { - fn local_ip(&self) -> IpAddr { - self.local_ip - } - - fn remote_ip(&self) -> IpAddr { - self.remote_ip - } - - fn sender_domain(&self) -> &str { - self.sender_domain.as_str() - } - - fn sender(&self) -> &str { - self.sender.as_str() - } - - fn rcpt_domain(&self) -> &str { - self.rcpt_domain.as_str() - } - - fn rcpt(&self) -> &str { - self.rcpt.as_str() - } - - fn helo_domain(&self) -> &str { - self.helo_domain.as_str() - } - - fn authenticated_as(&self) -> &str { - self.authenticated_as.as_str() - } - - fn mx(&self) -> &str { - self.mx.as_str() +impl KeyLookup for TestEnvelope { + type Key = EnvelopeKey; + + fn key(&self, key: &Self::Key) -> std::borrow::Cow<'_, str> { + match key { + EnvelopeKey::Recipient => self.rcpt.as_str().into(), + EnvelopeKey::RecipientDomain => self.rcpt_domain.as_str().into(), + EnvelopeKey::Sender => self.sender.as_str().into(), + EnvelopeKey::SenderDomain => self.sender_domain.as_str().into(), + EnvelopeKey::AuthenticatedAs => self.authenticated_as.as_str().into(), + EnvelopeKey::Listener => self.listener_id.to_string().into(), + EnvelopeKey::RemoteIp => self.remote_ip.to_string().into(), + EnvelopeKey::LocalIp => self.local_ip.to_string().into(), + EnvelopeKey::Priority => self.priority.to_string().into(), + EnvelopeKey::Mx => self.mx.as_str().into(), + EnvelopeKey::HeloDomain => self.helo_domain.as_str().into(), + } } - fn listener_id(&self) -> u16 { - self.listener_id + fn key_as_int(&self, key: &Self::Key) -> i32 { + match key { + EnvelopeKey::Priority => self.priority as i32, + EnvelopeKey::Listener => self.listener_id as i32, + _ => todo!(), + } } - fn priority(&self) -> i16 { - self.priority + fn key_as_ip(&self, key: &Self::Key) -> IpAddr { + match key { + EnvelopeKey::RemoteIp => self.remote_ip, + EnvelopeKey::LocalIp => self.local_ip, + _ => IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), + } } } diff --git a/tests/src/smtp/inbound/auth.rs b/tests/src/smtp/inbound/auth.rs index 013cccd8..53c8a37c 100644 --- a/tests/src/smtp/inbound/auth.rs +++ b/tests/src/smtp/inbound/auth.rs @@ -30,7 +30,7 @@ use crate::smtp::{ ParseTestConfig, TestConfig, }; use smtp::{ - config::ConfigContext, + config::{ConfigContext, EnvelopeKey}, core::{Session, State, SMTP}, }; @@ -68,7 +68,7 @@ async fn auth() { .parse_if(&ctx); config.directory = r"[{if = 'remote-ip', eq = '10.0.0.1', then = 'local'}, {else = false}]" - .parse_if::<Option<DynValue>>(&ctx) + .parse_if::<Option<DynValue<EnvelopeKey>>>(&ctx) .map_if_block(&ctx.directory.directories, "", "") .unwrap(); config.errors_max = r"[{if = 'remote-ip', eq = '10.0.0.1', then = 2}, diff --git a/tests/src/smtp/inbound/dmarc.rs b/tests/src/smtp/inbound/dmarc.rs index eadc1eac..dbc22277 100644 --- a/tests/src/smtp/inbound/dmarc.rs +++ b/tests/src/smtp/inbound/dmarc.rs @@ -42,7 +42,9 @@ use crate::smtp::{ ParseTestConfig, TestConfig, TestSMTP, }; use smtp::{ - config::{AggregateFrequency, ConfigContext, IfBlock, MaybeDynValue, VerifyStrategy}, + config::{ + AggregateFrequency, ConfigContext, EnvelopeKey, IfBlock, MaybeDynValue, VerifyStrategy, + }, core::{Session, SMTP}, }; @@ -168,15 +170,15 @@ async fn dmarc() { let mut config = &mut core.report.config; config.spf.sign = "['rsa']" - .parse_if::<Vec<DynValue>>(&ctx) + .parse_if::<Vec<DynValue<EnvelopeKey>>>(&ctx) .map_if_block(&ctx.signers, "", "") .unwrap(); config.dmarc.sign = "['rsa']" - .parse_if::<Vec<DynValue>>(&ctx) + .parse_if::<Vec<DynValue<EnvelopeKey>>>(&ctx) .map_if_block(&ctx.signers, "", "") .unwrap(); config.dkim.sign = "['rsa']" - .parse_if::<Vec<DynValue>>(&ctx) + .parse_if::<Vec<DynValue<EnvelopeKey>>>(&ctx) .map_if_block(&ctx.signers, "", "") .unwrap(); diff --git a/tests/src/smtp/inbound/rewrite.rs b/tests/src/smtp/inbound/rewrite.rs index eb895f58..57133a3b 100644 --- a/tests/src/smtp/inbound/rewrite.rs +++ b/tests/src/smtp/inbound/rewrite.rs @@ -113,7 +113,11 @@ async fn address_rewrite() { .map_if_block(&ctx.scripts, "session.mail.script", "script") .unwrap(); config.mail.rewrite = settings - .parse_if_block::<Option<DynValue>>("session.mail.rewrite", &ctx, &available_keys) + .parse_if_block::<Option<DynValue<EnvelopeKey>>>( + "session.mail.rewrite", + &ctx, + &available_keys, + ) .unwrap() .unwrap_or_default(); config.rcpt.script = settings @@ -123,7 +127,11 @@ async fn address_rewrite() { .map_if_block(&ctx.scripts, "session.rcpt.script", "script") .unwrap(); config.rcpt.rewrite = settings - .parse_if_block::<Option<DynValue>>("session.rcpt.rewrite", &ctx, &available_keys) + .parse_if_block::<Option<DynValue<EnvelopeKey>>>( + "session.rcpt.rewrite", + &ctx, + &available_keys, + ) .unwrap() .unwrap_or_default(); config.rcpt.relay = IfBlock::new(true); diff --git a/tests/src/smtp/inbound/sign.rs b/tests/src/smtp/inbound/sign.rs index 6da52cc4..3a0f6aab 100644 --- a/tests/src/smtp/inbound/sign.rs +++ b/tests/src/smtp/inbound/sign.rs @@ -36,7 +36,9 @@ use crate::smtp::{ ParseTestConfig, TestConfig, TestSMTP, }; use smtp::{ - config::{auth::ConfigAuth, ConfigContext, IfBlock, MaybeDynValue, VerifyStrategy}, + config::{ + auth::ConfigAuth, ConfigContext, EnvelopeKey, IfBlock, MaybeDynValue, VerifyStrategy, + }, core::{Session, SMTP}, }; @@ -174,11 +176,11 @@ async fn sign_and_seal() { config.arc.verify = config.spf.verify_ehlo.clone(); config.dmarc.verify = config.spf.verify_ehlo.clone(); config.dkim.sign = "['rsa']" - .parse_if::<Vec<DynValue>>(&ctx) + .parse_if::<Vec<DynValue<EnvelopeKey>>>(&ctx) .map_if_block(&ctx.signers, "", "") .unwrap(); config.arc.seal = "'ed'" - .parse_if::<Option<DynValue>>(&ctx) + .parse_if::<Option<DynValue<EnvelopeKey>>>(&ctx) .map_if_block(&ctx.sealers, "", "") .unwrap(); diff --git a/tests/src/smtp/lookup/sql.rs b/tests/src/smtp/lookup/sql.rs index 8a672074..ed1fe51b 100644 --- a/tests/src/smtp/lookup/sql.rs +++ b/tests/src/smtp/lookup/sql.rs @@ -35,7 +35,7 @@ use crate::{ }, }; use smtp::{ - config::{ConfigContext, IfBlock}, + config::{ConfigContext, EnvelopeKey, IfBlock}, core::{Session, SMTP}, }; @@ -115,7 +115,7 @@ async fn lookup_sql() { // Enable AUTH let mut config = &mut core.session.config.auth; config.directory = r"'sql'" - .parse_if::<Option<DynValue>>(&ctx) + .parse_if::<Option<DynValue<EnvelopeKey>>>(&ctx) .map_if_block(&ctx.directory.directories, "", "") .unwrap(); config.mechanisms = IfBlock::new(AUTH_PLAIN | AUTH_LOGIN); @@ -124,7 +124,7 @@ async fn lookup_sql() { // Enable VRFY/EXPN/RCPT let mut config = &mut core.session.config.rcpt; config.directory = r"'sql'" - .parse_if::<Option<DynValue>>(&ctx) + .parse_if::<Option<DynValue<EnvelopeKey>>>(&ctx) .map_if_block(&ctx.directory.directories, "", "") .unwrap(); config.relay = IfBlock::new(false); diff --git a/tests/src/smtp/lookup/utils.rs b/tests/src/smtp/lookup/utils.rs index 5bd76478..576380c4 100644 --- a/tests/src/smtp/lookup/utils.rs +++ b/tests/src/smtp/lookup/utils.rs @@ -37,6 +37,7 @@ use smtp::{ lookup::ToNextHop, mta_sts::{Mode, MxPattern, Policy}, }, + queue::RecipientDomain, }; use crate::smtp::TestConfig; @@ -75,7 +76,11 @@ async fn lookup_ip() { // Ipv4 strategy core.queue.config.ip_strategy = IfBlock::new(IpLookupStrategy::Ipv4thenIpv6); let (source_ips, remote_ips) = core - .resolve_host(&NextHop::MX("mx.foobar.org"), &"envelope", 2) + .resolve_host( + &NextHop::MX("mx.foobar.org"), + &RecipientDomain::new("envelope"), + 2, + ) .await .unwrap(); assert!(ipv4.contains(&match source_ips.unwrap() { @@ -87,7 +92,11 @@ async fn lookup_ip() { // Ipv6 strategy core.queue.config.ip_strategy = IfBlock::new(IpLookupStrategy::Ipv6thenIpv4); let (source_ips, remote_ips) = core - .resolve_host(&NextHop::MX("mx.foobar.org"), &"envelope", 2) + .resolve_host( + &NextHop::MX("mx.foobar.org"), + &RecipientDomain::new("envelope"), + 2, + ) .await .unwrap(); assert!(ipv6.contains(&match source_ips.unwrap() { diff --git a/tests/src/smtp/queue/dsn.rs b/tests/src/smtp/queue/dsn.rs index cdc8c328..93adb8fb 100644 --- a/tests/src/smtp/queue/dsn.rs +++ b/tests/src/smtp/queue/dsn.rs @@ -36,7 +36,7 @@ use crate::smtp::{ ParseTestConfig, TestConfig, TestSMTP, }; use smtp::{ - config::ConfigContext, + config::{ConfigContext, EnvelopeKey}, core::SMTP, queue::{ DeliveryAttempt, Domain, Error, ErrorDetails, HostResponse, Message, Recipient, Schedule, @@ -110,7 +110,7 @@ async fn generate_dsn() { let ctx = ConfigContext::new(&[]).parse_signatures(); let mut config = &mut core.queue.config.dsn; config.sign = "['rsa']" - .parse_if::<Vec<DynValue>>(&ctx) + .parse_if::<Vec<DynValue<EnvelopeKey>>>(&ctx) .map_if_block(&ctx.signers, "", "") .unwrap(); @@ -193,7 +193,7 @@ async fn compare_dsn(message: Box<Message>, test: &str) { failed.set_extension("failed"); fs::write(&failed, dsn.as_bytes()).unwrap(); panic!( - "Failed for {}, ouput saved to {}", + "Failed for {}, output saved to {}", path.display(), failed.display() ); diff --git a/tests/src/smtp/reporting/dmarc.rs b/tests/src/smtp/reporting/dmarc.rs index 7f929476..f3cde714 100644 --- a/tests/src/smtp/reporting/dmarc.rs +++ b/tests/src/smtp/reporting/dmarc.rs @@ -41,7 +41,7 @@ use crate::smtp::{ ParseTestConfig, TestConfig, TestSMTP, }; use smtp::{ - config::{AggregateFrequency, ConfigContext, IfBlock}, + config::{AggregateFrequency, ConfigContext, EnvelopeKey, IfBlock}, core::SMTP, reporting::{ dmarc::GenerateDmarcReport, @@ -67,7 +67,7 @@ async fn report_dmarc() { config.path = IfBlock::new(temp_dir.temp_dir.clone()); config.hash = IfBlock::new(16); config.dmarc_aggregate.sign = "['rsa']" - .parse_if::<Vec<DynValue>>(&ctx) + .parse_if::<Vec<DynValue<EnvelopeKey>>>(&ctx) .map_if_block(&ctx.signers, "", "") .unwrap(); config.dmarc_aggregate.max_size = IfBlock::new(4096); diff --git a/tests/src/smtp/reporting/tls.rs b/tests/src/smtp/reporting/tls.rs index 2e213009..627a3a89 100644 --- a/tests/src/smtp/reporting/tls.rs +++ b/tests/src/smtp/reporting/tls.rs @@ -38,7 +38,7 @@ use crate::smtp::{ ParseTestConfig, TestConfig, TestSMTP, }; use smtp::{ - config::{AggregateFrequency, ConfigContext, IfBlock}, + config::{AggregateFrequency, ConfigContext, EnvelopeKey, IfBlock}, core::SMTP, reporting::{ scheduler::{ReportType, Scheduler}, @@ -64,7 +64,7 @@ async fn report_tls() { config.path = IfBlock::new(temp_dir.temp_dir.clone()); config.hash = IfBlock::new(16); config.tls.sign = "['rsa']" - .parse_if::<Vec<DynValue>>(&ctx) + .parse_if::<Vec<DynValue<EnvelopeKey>>>(&ctx) .map_if_block(&ctx.signers, "", "") .unwrap(); config.tls.max_size = IfBlock::new(4096); |