spf: implement h and r macros

refs: https://github.com/KumoCorp/kumomta/issues/423
This commit is contained in:
Wez Furlong
2025-10-05 08:14:55 +01:00
parent 34622edbd2
commit 2eb21bea96
3 changed files with 52 additions and 5 deletions
+33 -1
View File
@@ -115,6 +115,13 @@ pub struct CheckHostParams {
/// IP address of the SMTP client that is emitting the mail (v4 or v6).
pub client_ip: IpAddr,
/// Explicitly the domain name passed to HELO/EHLO,
/// regardless of the `domain` value.
pub ehlo_domain: Option<String>,
/// The host name of this host, the one doing the check
pub relaying_host_name: Option<String>,
}
impl CheckHostParams {
@@ -123,6 +130,8 @@ impl CheckHostParams {
domain,
sender,
client_ip,
ehlo_domain,
relaying_host_name,
} = self;
let sender = match sender {
@@ -131,7 +140,12 @@ impl CheckHostParams {
};
match SpfContext::new(&sender, &domain, client_ip) {
Ok(cx) => cx.check(resolver, true).await,
Ok(cx) => {
cx.with_ehlo_domain(ehlo_domain.as_deref())
.with_relaying_host_name(relaying_host_name.as_deref())
.check(resolver, true)
.await
}
Err(result) => result,
}
}
@@ -144,6 +158,8 @@ struct SpfContext<'a> {
pub(crate) domain: &'a str,
pub(crate) client_ip: IpAddr,
pub(crate) now: SystemTime,
pub(crate) ehlo_domain: Option<&'a str>,
pub(crate) relaying_host_name: &'a str,
}
impl<'a> SpfContext<'a> {
@@ -169,9 +185,25 @@ impl<'a> SpfContext<'a> {
domain,
client_ip,
now: SystemTime::now(),
ehlo_domain: None,
relaying_host_name: "localhost",
})
}
pub fn with_ehlo_domain(&self, ehlo_domain: Option<&'a str>) -> Self {
Self {
ehlo_domain,
..*self
}
}
pub fn with_relaying_host_name(&self, relaying_host_name: Option<&'a str>) -> Self {
Self {
relaying_host_name: relaying_host_name.unwrap_or(self.relaying_host_name),
..*self
}
}
pub(crate) fn with_domain(&self, domain: &'a str) -> Self {
Self { domain, ..*self }
}
+14 -4
View File
@@ -155,9 +155,13 @@ impl MacroSpec {
.unwrap_or(0)
))
.unwrap(),
MacroName::RelayingHostName
| MacroName::HeloDomain
| MacroName::ValidatedDomainName => {
MacroName::HeloDomain => {
buf.push_str(cx.ehlo_domain.unwrap_or(""));
}
MacroName::RelayingHostName => {
buf.push_str(cx.relaying_host_name);
}
MacroName::ValidatedDomainName => {
return Err(format!("{:?} has not been implemented", m.name))
}
};
@@ -345,7 +349,9 @@ mod test {
"email.example.com",
IpAddr::from([192, 0, 2, 3]),
)
.unwrap();
.unwrap()
.with_ehlo_domain(Some("mx1.example.com"))
.with_relaying_host_name(Some("mx.mbp.com"));
for (input, expect) in &[
("%{s}", "strong-bad@email.example.com"),
@@ -362,6 +368,10 @@ mod test {
("%{lr}", "strong-bad"),
("%{lr-}", "bad.strong"),
("%{l1r-}", "strong"),
("%{h}", "mx1.example.com"),
("%{h2}", "example.com"),
("%{r}", "mx.mbp.com"),
("%{rr}", "com.mbp.mx"),
] {
let spec = MacroSpec::parse(input).unwrap();
let output = spec.expand(&ctx).unwrap();
+5
View File
@@ -26,11 +26,16 @@ pub fn register<'lua>(lua: &'lua Lua) -> anyhow::Result<()> {
.and_then(|v| SocketAddr::from_str(v.as_str()?).ok())
.expect("`received_from` is always set, and always to a value representing a `SocketAddr`");
let ehlo_domain = meta.get_meta("ehlo_domain").map(|s| s.to_string());
let relaying_host_name = meta.get_meta("hostname").map(|s| s.to_string());
let resolver = dns_resolver::get_resolver();
let result = CheckHostParams {
domain,
sender,
client_ip: addr.ip(),
ehlo_domain,
relaying_host_name,
}
.check(&**resolver)
.await;