diff --git a/crates/services/src/inbuxa_scheduled_reports.rs b/crates/services/src/inbuxa_scheduled_reports.rs index fd4afb1c..a9bff86c 100644 --- a/crates/services/src/inbuxa_scheduled_reports.rs +++ b/crates/services/src/inbuxa_scheduled_reports.rs @@ -819,6 +819,44 @@ fn failing_facts(reports: &[dlv::Report]) -> BTreeMap { out } +/// RP-5: a finding that stopped failing, in words. Only the keys are kept +/// between runs, and each key carries what its sentence needs. Past tense, +/// since a finding also goes when its address or domain does. +fn fixed_text(key: &str) -> String { + if let Some(rest) = key.strip_prefix("ip:") { + if let Some((ip, list)) = rest.split_once(":list:") { + return format!("{ip} was listed on {list}"); + } + if let Some(ip) = rest.strip_suffix(":ptr") { + return format!("{ip} had no reverse DNS"); + } + if let Some(ip) = rest.strip_suffix(":fcrdns") { + return format!("{ip}'s reverse DNS didn't point back to it"); + } + } else if let Some((domain, what)) = key + .strip_prefix("domain:") + .and_then(|rest| rest.split_once(':')) + { + if let Some(list) = what.strip_prefix("list:") { + return format!("{domain} was listed on {list}"); + } + if let Some(ip) = what.strip_prefix("spf:") { + return format!("SPF for {domain} didn't let {ip} send"); + } + if let Some(selector) = what.strip_prefix("dkim:") { + return format!( + "{domain}'s DKIM key {selector} was missing from DNS or wasn't the one signing" + ); + } + if what == "mta-sts" { + return format!("{domain}'s MTA-STS policy couldn't be fetched"); + } + } else if let Some(name) = key.strip_prefix("cert:") { + return format!("No certificate covered {name}"); + } + key.to_string() +} + async fn deliverability( server: &Server, tenant: Option, @@ -846,8 +884,9 @@ async fn deliverability( ]); } for key in before.iter().filter(|k| !now.contains_key(**k)) { - lines.push(format!("Fixed: {key}")); - rows.push(vec![key.to_string(), String::new(), "fixed".into()]); + let text = fixed_text(key); + lines.push(format!("Fixed: {text}")); + rows.push(vec![key.to_string(), text, "fixed".into()]); } let failing = now.keys().cloned().collect(); if lines.is_empty() { @@ -1280,6 +1319,44 @@ mod tests { ); } + #[test] + fn fixed_findings_read_as_words() { + for (key, text) in [ + ( + "ip:192.0.2.1:list:Spamhaus ZEN", + "192.0.2.1 was listed on Spamhaus ZEN", + ), + ("ip:2001:db8::1:ptr", "2001:db8::1 had no reverse DNS"), + ( + "ip:2001:db8::1:fcrdns", + "2001:db8::1's reverse DNS didn't point back to it", + ), + ( + "domain:example.org:list:Spamhaus DBL", + "example.org was listed on Spamhaus DBL", + ), + ( + "domain:example.org:spf:2001:db8::1", + "SPF for example.org didn't let 2001:db8::1 send", + ), + ( + "domain:example.org:dkim:v1-ed25519", + "example.org's DKIM key v1-ed25519 was missing from DNS or wasn't the one signing", + ), + ( + "domain:example.org:mta-sts", + "example.org's MTA-STS policy couldn't be fetched", + ), + ( + "cert:mail.example.org", + "No certificate covered mail.example.org", + ), + ("something:new", "something:new"), + ] { + assert_eq!(fixed_text(key), text, "{key}"); + } + } + #[test] fn quiet_period_mail_says_so() { let report = model::Report::digest(0);