diff --git a/crates/kumo-spf/src/lib.rs b/crates/kumo-spf/src/lib.rs index 333c93d6..2ae4d485 100644 --- a/crates/kumo-spf/src/lib.rs +++ b/crates/kumo-spf/src/lib.rs @@ -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, + + /// The host name of this host, the one doing the check + pub relaying_host_name: Option, } 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 } } diff --git a/crates/kumo-spf/src/spec.rs b/crates/kumo-spf/src/spec.rs index f1157b89..3de1bae0 100644 --- a/crates/kumo-spf/src/spec.rs +++ b/crates/kumo-spf/src/spec.rs @@ -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(); diff --git a/crates/kumod/src/spf.rs b/crates/kumod/src/spf.rs index aa16990e..dc1f5781 100644 --- a/crates/kumod/src/spf.rs +++ b/crates/kumod/src/spf.rs @@ -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;