diff --git a/Cargo.lock b/Cargo.lock index e4cb09dd..309fef47 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2951,6 +2951,7 @@ dependencies = [ "tracing", "tungstenite", "utils", + "x509-parser 0.16.0", ] [[package]] diff --git a/crates/common/src/config/smtp/queue.rs b/crates/common/src/config/smtp/queue.rs index 890f5247..d543d0e9 100644 --- a/crates/common/src/config/smtp/queue.rs +++ b/crates/common/src/config/smtp/queue.rs @@ -167,6 +167,9 @@ impl Default for QueueConfig { ), invalid_certs: IfBlock::new::<()>( "queue.outbound.tls.allow-invalid-certs", + #[cfg(not(feature = "test_mode"))] + [("retry_num > 0 && last_error == 'tls'", "true")], + #[cfg(feature = "test_mode")] [], "false", ), diff --git a/crates/common/src/expr/tokenizer.rs b/crates/common/src/expr/tokenizer.rs index 9dfcac3f..c0930eff 100644 --- a/crates/common/src/expr/tokenizer.rs +++ b/crates/common/src/expr/tokenizer.rs @@ -365,6 +365,11 @@ impl TokenMap { V_PRIORITY, V_PROTOCOL, V_TLS, + V_QUEUE_RETRY_NUM, + V_QUEUE_NOTIFY_NUM, + V_QUEUE_EXPIRES_IN, + V_QUEUE_LAST_STATUS, + V_QUEUE_LAST_ERROR, ]) } diff --git a/crates/jmap/Cargo.toml b/crates/jmap/Cargo.toml index 696a5304..f92efc2d 100644 --- a/crates/jmap/Cargo.toml +++ b/crates/jmap/Cargo.toml @@ -55,6 +55,7 @@ rsa = "0.9.2" async-trait = "0.1.68" lz4_flex = { version = "0.11", default-features = false } rev_lines = "0.3.0" +x509-parser = "0.16.0" [dev-dependencies] ece = "2.2" diff --git a/crates/jmap/src/api/management/domain.rs b/crates/jmap/src/api/management/domain.rs index 1b3f40ca..233c8221 100644 --- a/crates/jmap/src/api/management/domain.rs +++ b/crates/jmap/src/api/management/domain.rs @@ -27,8 +27,10 @@ use hyper::Method; use jmap_proto::error::request::RequestError; use serde::{Deserialize, Serialize}; use serde_json::json; +use sha1::Digest; use store::ahash::AHashMap; use utils::url_params::UrlParams; +use x509_parser::parse_x509_certificate; use crate::{ api::{ @@ -229,6 +231,48 @@ impl JMAP { content: format!("v=DMARC1; p=reject; rua=mailto:postmaster@{domain_name}; ruf=mailto:postmaster@{domain_name}",), }); + // Add TLSA records + for (name, key) in self.core.tls.certificates.load().iter() { + if !name.ends_with(domain_name) { + continue; + } + let cert = if let Some(cert) = key.cert.first().map(|cert| cert.as_ref()) { + cert + } else { + tracing::debug!("No certificate found for domain: {}", domain_name); + continue; + }; + let parsed_cert = match parse_x509_certificate(cert) { + Ok((_, parsed_cert)) => parsed_cert, + Err(err) => { + tracing::debug!("Failed to parse certificate: {}", err); + continue; + } + }; + + let name = if !name.starts_with('.') { + format!("_25._tcp.{name}.") + } else { + format!("_25._tcp.mail.{name}.") + }; + + for (s, cert) in [cert, parsed_cert.subject_pki.raw].into_iter().enumerate() { + for (m, hash) in [ + format!("{:x}", sha2::Sha256::digest(cert)), + format!("{:x}", sha2::Sha512::digest(cert)), + ] + .into_iter() + .enumerate() + { + records.push(DnsRecord { + typ: "TLSA".to_string(), + name: name.clone(), + content: format!("3 {} {} {}", s, m + 1, hash), + }); + } + } + } + Ok(records) } }