diff options
Diffstat (limited to 'pkgs/by-name/st/stalwart-mail-patched/patches/use-platform-ca-roots.patch')
-rw-r--r-- | pkgs/by-name/st/stalwart-mail-patched/patches/use-platform-ca-roots.patch | 747 |
1 files changed, 0 insertions, 747 deletions
diff --git a/pkgs/by-name/st/stalwart-mail-patched/patches/use-platform-ca-roots.patch b/pkgs/by-name/st/stalwart-mail-patched/patches/use-platform-ca-roots.patch deleted file mode 100644 index 392fbde..0000000 --- a/pkgs/by-name/st/stalwart-mail-patched/patches/use-platform-ca-roots.patch +++ /dev/null @@ -1,747 +0,0 @@ -From 66227b07c6cb4781a38fe603c2e856c5696e0f94 Mon Sep 17 00:00:00 2001 -From: aszlig <aszlig@nix.build> -Date: Wed, 21 May 2025 19:03:55 +0200 -Subject: [PATCH] Allow to switch to operating system CA root store - -So far, we only used the root CA certificates from the "webpki-roots" -crate, which makes it very difficult if you want to run a custom CA. - -In my case I'm running automated tests of full production systems, which -also includes an ACME setup that injects a Pebble instance into the -system's CA certificates. - -Since Stalwart doesn't use the system's root CA certificate store, it's -very difficult to inject the certificate for the Pebble instance. - -Given that there's also some interest (issue #247) for doing this in an -enterprise environment with intranet CAs, I decided to generalise this -far enough that it can not only be used in my downstream setup. - -Currently, this doesn't fully address the issue, since in the long term -this might be something we'd want to configure at runtime, as per -@mdecimus's comment[1]: - -> A slightly more complex approach is required that allows the user to -> select at runtime which CA store to use. - -However, this makes it at least easier to switch to native root CA store -by simply recompiling with the "tls-native-roots" feature. - -[1]: https://github.com/stalwartlabs/stalwart/issues/247#issuecomment-2437039500 - -Signed-off-by: aszlig <aszlig@nix.build> -Issue: https://github.com/stalwartlabs/stalwart/issues/247 ---- - Cargo.lock | 4 ++ - crates/cli/Cargo.toml | 10 +++-- - crates/cli/src/main.rs | 8 ++-- - crates/common/Cargo.toml | 2 +- - crates/common/src/enterprise/llm.rs | 2 +- - crates/common/src/enterprise/mod.rs | 18 +++++--- - crates/common/src/lib.rs | 3 +- - crates/common/src/listener/acme/directory.rs | 6 +-- - crates/common/src/manager/mod.rs | 2 +- - crates/common/src/scripts/plugins/http.rs | 2 +- - crates/common/src/telemetry/webhooks/mod.rs | 2 +- - crates/directory/Cargo.toml | 2 +- - crates/directory/src/backend/oidc/lookup.rs | 4 +- - crates/jmap/Cargo.toml | 2 +- - crates/main/Cargo.toml | 1 + - crates/services/Cargo.toml | 2 +- - crates/services/src/state_manager/http.rs | 2 +- - crates/smtp/Cargo.toml | 2 +- - crates/smtp/src/inbound/hooks/client.rs | 2 +- - crates/smtp/src/outbound/mta_sts/lookup.rs | 3 +- - crates/smtp/src/reporting/tls.rs | 5 +-- - crates/spam-filter/Cargo.toml | 2 +- - crates/spam-filter/src/analysis/url.rs | 2 +- - crates/store/Cargo.toml | 2 +- - crates/store/src/backend/azure/mod.rs | 2 +- - crates/store/src/backend/http/lookup.rs | 2 +- - crates/trc/Cargo.toml | 2 +- - crates/utils/Cargo.toml | 2 + - crates/utils/src/lib.rs | 46 ++++++++++++++------ - crates/utils/src/suffixlist.rs | 7 ++- - tests/Cargo.toml | 2 +- - tests/src/jmap/auth_oauth.rs | 6 +-- - tests/src/jmap/mod.rs | 4 +- - tests/src/smtp/management/queue.rs | 2 +- - tests/src/webdav/mod.rs | 2 +- - 35 files changed, 103 insertions(+), 64 deletions(-) - -diff --git a/Cargo.lock b/Cargo.lock -index 0eeb42510..6dd394dc3 100644 ---- a/Cargo.lock -+++ b/Cargo.lock -@@ -3372,6 +3372,7 @@ dependencies = [ - "hyper 1.6.0", - "hyper-util", - "rustls 0.23.27", -+ "rustls-native-certs 0.8.1", - "rustls-pki-types", - "tokio", - "tokio-rustls 0.26.2", -@@ -6318,6 +6319,7 @@ dependencies = [ - "pin-project-lite", - "quinn", - "rustls 0.23.27", -+ "rustls-native-certs 0.8.1", - "rustls-pemfile 2.2.0", - "rustls-pki-types", - "serde", -@@ -7694,6 +7696,7 @@ dependencies = [ - "serde", - "serde_json", - "tokio", -+ "utils", - ] - - [[package]] -@@ -8795,6 +8798,7 @@ dependencies = [ - "rustls 0.23.27", - "rustls-pemfile 2.2.0", - "rustls-pki-types", -+ "rustls-platform-verifier", - "serde", - "serde_json", - "smtp-proto", -diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml -index 5573df819..719441a0c 100644 ---- a/crates/cli/Cargo.toml -+++ b/crates/cli/Cargo.toml -@@ -11,9 +11,9 @@ readme = "README.md" - resolver = "2" - - [dependencies] --jmap-client = { version = "0.3", features = ["async"] } --mail-parser = { version = "0.11", features = ["full_encoding", "serde"] } --reqwest = { version = "0.12", default-features = false, features = ["rustls-tls-webpki-roots", "http2"]} -+jmap-client = { version = "0.3", features = ["async"] } -+mail-parser = { version = "0.11", features = ["full_encoding", "serde"] } -+reqwest = { version = "0.12", default-features = false, features = ["http2"]} - tokio = { version = "1.45", features = ["full"] } - num_cpus = "1.13.1" - clap = { version = "4.1.6", features = ["derive"] } -@@ -30,3 +30,7 @@ futures = "0.3.28" - pwhash = "1.0.0" - rand = "0.9.0" - mail-auth = { version = "0.7" } -+utils.path = "../utils" -+ -+[features] -+tls-native-roots = ["utils/tls-native-roots"] -diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs -index f295c217f..f62d528c5 100644 ---- a/crates/cli/src/main.rs -+++ b/crates/cli/src/main.rs -@@ -86,7 +86,7 @@ fn parse_credentials(credentials: &str) -> Credentials { - - async fn oauth(url: &str) -> Credentials { - let metadata: HashMap<String, serde_json::Value> = serde_json::from_slice( -- &reqwest::Client::builder() -+ &utils::reqwest_client_builder() - .danger_accept_invalid_certs(is_localhost(url)) - .build() - .unwrap_or_default() -@@ -104,7 +104,7 @@ async fn oauth(url: &str) -> Credentials { - let mut params: HashMap<String, String> = - HashMap::from_iter([("client_id".to_string(), "Stalwart_CLI".to_string())]); - let response: HashMap<String, serde_json::Value> = serde_json::from_slice( -- &reqwest::Client::builder() -+ &utils::reqwest_client_builder() - .danger_accept_invalid_certs(is_localhost(url)) - .build() - .unwrap_or_default() -@@ -138,7 +138,7 @@ async fn oauth(url: &str) -> Credentials { - std::io::stdin().lock().lines().next(); - - let mut response: HashMap<String, serde_json::Value> = serde_json::from_slice( -- &reqwest::Client::builder() -+ &utils::reqwest_client_builder() - .danger_accept_invalid_certs(is_localhost(url)) - .build() - .unwrap_or_default() -@@ -230,7 +230,7 @@ impl Client { - }, - url - ); -- let mut request = reqwest::Client::builder() -+ let mut request = utils::reqwest_client_builder() - .danger_accept_invalid_certs(is_localhost(&url)) - .timeout(Duration::from_secs(self.timeout.unwrap_or(60))) - .build() -diff --git a/crates/common/Cargo.toml b/crates/common/Cargo.toml -index 58028bad3..5add0a5dc 100644 ---- a/crates/common/Cargo.toml -+++ b/crates/common/Cargo.toml -@@ -33,7 +33,7 @@ tokio = { version = "1.45", features = ["net", "macros"] } - tokio-rustls = { version = "0.26", default-features = false, features = ["ring", "tls12"] } - futures = "0.3" - rcgen = "0.12" --reqwest = { version = "0.12", default-features = false, features = ["rustls-tls-webpki-roots", "http2", "stream"]} -+reqwest = { version = "0.12", default-features = false, features = ["http2", "stream"]} - serde = { version = "1.0", features = ["derive"]} - serde_json = "1.0" - base64 = "0.22" -diff --git a/crates/common/src/enterprise/llm.rs b/crates/common/src/enterprise/llm.rs -index 8338f39d3..fe013c0f0 100644 ---- a/crates/common/src/enterprise/llm.rs -+++ b/crates/common/src/enterprise/llm.rs -@@ -125,7 +125,7 @@ impl AiApiConfig { - }; - - // Send request -- let response = reqwest::Client::builder() -+ let response = utils::reqwest_client_builder() - .timeout(self.timeout) - .danger_accept_invalid_certs(self.tls_allow_invalid_certs) - .build() -diff --git a/crates/common/src/enterprise/mod.rs b/crates/common/src/enterprise/mod.rs -index 9fc8de495..ddaf27880 100644 ---- a/crates/common/src/enterprise/mod.rs -+++ b/crates/common/src/enterprise/mod.rs -@@ -188,12 +188,18 @@ impl Server { - - let mut logo = None; - if let Some(logo_url) = logo_url { -- let response = reqwest::get(logo_url.as_str()).await.map_err(|err| { -- trc::ResourceEvent::DownloadExternal -- .into_err() -- .details("Failed to download logo") -- .reason(err) -- })?; -+ let response = utils::reqwest_client_builder() -+ .build() -+ .unwrap_or_default() -+ .get(logo_url.as_str()) -+ .send() -+ .await -+ .map_err(|err| { -+ trc::ResourceEvent::DownloadExternal -+ .into_err() -+ .details("Failed to download logo") -+ .reason(err) -+ })?; - - let content_type = response - .headers() -diff --git a/crates/common/src/lib.rs b/crates/common/src/lib.rs -index ce4b41b89..185508d1f 100644 ---- a/crates/common/src/lib.rs -+++ b/crates/common/src/lib.rs -@@ -46,6 +46,8 @@ use utils::{ - snowflake::SnowflakeIdGenerator, - }; - -+pub use utils::USER_AGENT; -+ - pub mod addresses; - pub mod auth; - pub mod config; -@@ -67,7 +69,6 @@ pub use psl; - pub static VERSION_PRIVATE: &str = env!("CARGO_PKG_VERSION"); - pub static VERSION_PUBLIC: &str = "1.0.0"; - --pub static USER_AGENT: &str = "Stalwart/1.0.0"; - pub static DAEMON_NAME: &str = concat!("Stalwart v", env!("CARGO_PKG_VERSION"),); - pub static PROD_ID: &str = "-//Stalwart Labs Ltd.//Stalwart Server//EN"; - -diff --git a/crates/common/src/listener/acme/directory.rs b/crates/common/src/listener/acme/directory.rs -index f095e1969..6f9cfa0e0 100644 ---- a/crates/common/src/listener/acme/directory.rs -+++ b/crates/common/src/listener/acme/directory.rs -@@ -7,7 +7,6 @@ use super::jose::{ - }; - use base64::Engine; - use base64::engine::general_purpose::URL_SAFE_NO_PAD; --use hyper::header::USER_AGENT; - use rcgen::{Certificate, CustomExtension, PKCS_ECDSA_P256_SHA256}; - use reqwest::header::CONTENT_TYPE; - use reqwest::{Method, Response}; -@@ -316,7 +315,7 @@ async fn https( - body: Option<String>, - ) -> trc::Result<Response> { - let url = url.as_ref(); -- let mut builder = reqwest::Client::builder() -+ let mut builder = utils::reqwest_client_builder() - .timeout(Duration::from_secs(30)) - .http1_only(); - -@@ -330,8 +329,7 @@ async fn https( - let mut request = builder - .build() - .map_err(|err| trc::EventType::Acme(trc::AcmeEvent::Error).from_http_error(err))? -- .request(method, url) -- .header(USER_AGENT, crate::USER_AGENT); -+ .request(method, url); - - if let Some(body) = body { - request = request -diff --git a/crates/common/src/manager/mod.rs b/crates/common/src/manager/mod.rs -index 51861a82c..a59e74b56 100644 ---- a/crates/common/src/manager/mod.rs -+++ b/crates/common/src/manager/mod.rs -@@ -76,7 +76,7 @@ pub async fn fetch_resource( - .await - .map_err(|err| format!("Failed to read {path}: {err}")) - } else { -- let response = reqwest::Client::builder() -+ let response = utils::reqwest_client_builder() - .timeout(timeout) - .danger_accept_invalid_certs(is_localhost_url(url)) - .user_agent(USER_AGENT) -diff --git a/crates/common/src/scripts/plugins/http.rs b/crates/common/src/scripts/plugins/http.rs -index 42e2af553..54d906e17 100644 ---- a/crates/common/src/scripts/plugins/http.rs -+++ b/crates/common/src/scripts/plugins/http.rs -@@ -26,7 +26,7 @@ pub async fn exec_header(ctx: PluginContext<'_>) -> trc::Result<Variable> { - return Ok(Variable::from(url.split_once("/?").unwrap().1.to_string())); - } - -- reqwest::Client::builder() -+ utils::reqwest_client_builder() - .user_agent(agent.as_ref()) - .timeout(Duration::from_millis(timeout)) - .redirect(Policy::none()) -diff --git a/crates/common/src/telemetry/webhooks/mod.rs b/crates/common/src/telemetry/webhooks/mod.rs -index a70005399..2cebfd81f 100644 ---- a/crates/common/src/telemetry/webhooks/mod.rs -+++ b/crates/common/src/telemetry/webhooks/mod.rs -@@ -148,7 +148,7 @@ async fn post_webhook_events( - } - - // Send request -- let response = reqwest::Client::builder() -+ let response = utils::reqwest_client_builder() - .timeout(settings.timeout) - .danger_accept_invalid_certs(settings.tls_allow_invalid_certs) - .build() -diff --git a/crates/directory/Cargo.toml b/crates/directory/Cargo.toml -index ae8eca025..4a033834b 100644 ---- a/crates/directory/Cargo.toml -+++ b/crates/directory/Cargo.toml -@@ -35,7 +35,7 @@ futures = "0.3" - regex = "1.7.0" - serde = { version = "1.0", features = ["derive"]} - totp-rs = { version = "5.5.1", features = ["otpauth"] } --reqwest = { version = "0.12", default-features = false, features = ["rustls-tls-webpki-roots", "http2"] } -+reqwest = { version = "0.12", default-features = false, features = ["http2"] } - serde_json = "1.0" - base64 = "0.22" - rkyv = { version = "0.8.10", features = ["little_endian"] } -diff --git a/crates/directory/src/backend/oidc/lookup.rs b/crates/directory/src/backend/oidc/lookup.rs -index 755064115..907831a46 100644 ---- a/crates/directory/src/backend/oidc/lookup.rs -+++ b/crates/directory/src/backend/oidc/lookup.rs -@@ -36,10 +36,10 @@ impl OpenIdDirectory { - QueryBy::Credentials(Credentials::OAuthBearer { token }) => { - // Send request - #[cfg(feature = "test_mode")] -- let client = reqwest::Client::builder().danger_accept_invalid_certs(true); -+ let client = utils::reqwest_client_builder().danger_accept_invalid_certs(true); - - #[cfg(not(feature = "test_mode"))] -- let client = reqwest::Client::builder(); -+ let client = utils::reqwest_client_builder(); - - let client = client - .timeout(self.config.endpoint_timeout) -diff --git a/crates/jmap/Cargo.toml b/crates/jmap/Cargo.toml -index 9d9cfa7d7..a3a7b5003 100644 ---- a/crates/jmap/Cargo.toml -+++ b/crates/jmap/Cargo.toml -@@ -36,7 +36,7 @@ p256 = { version = "0.13", features = ["ecdh"] } - hkdf = "0.12.3" - sha1 = "0.10" - sha2 = "0.10" --reqwest = { version = "0.12", default-features = false, features = ["rustls-tls-webpki-roots", "http2"]} -+reqwest = { version = "0.12", default-features = false, features = ["http2"]} - tokio-tungstenite = "0.26" - tungstenite = "0.26" - chrono = "0.4" -diff --git a/crates/main/Cargo.toml b/crates/main/Cargo.toml -index 1023b73bf..4b39dae29 100644 ---- a/crates/main/Cargo.toml -+++ b/crates/main/Cargo.toml -@@ -64,3 +64,4 @@ enterprise = [ "jmap/enterprise", - "dav/enterprise", - "groupware/enterprise", - "services/enterprise" ] -+tls-native-roots = ["utils/tls-native-roots"] -diff --git a/crates/services/Cargo.toml b/crates/services/Cargo.toml -index 11bb76e5a..35aa0b6cb 100644 ---- a/crates/services/Cargo.toml -+++ b/crates/services/Cargo.toml -@@ -24,7 +24,7 @@ rsa = "0.9.2" - p256 = { version = "0.13", features = ["ecdh"] } - hkdf = "0.12.3" - sha2 = "0.10" --reqwest = { version = "0.12", default-features = false, features = ["rustls-tls-webpki-roots", "http2"]} -+reqwest = { version = "0.12", default-features = false, features = ["http2"]} - base64 = "0.22" - compact_str = "0.9.0" - -diff --git a/crates/services/src/state_manager/http.rs b/crates/services/src/state_manager/http.rs -index edd01865f..ee26b8482 100644 ---- a/crates/services/src/state_manager/http.rs -+++ b/crates/services/src/state_manager/http.rs -@@ -63,7 +63,7 @@ pub(crate) async fn http_request( - keys: Option<EncryptionKeys>, - push_timeout: Duration, - ) -> bool { -- let client_builder = reqwest::Client::builder().timeout(push_timeout); -+ let client_builder = utils::reqwest_client_builder().timeout(push_timeout); - - #[cfg(feature = "test_mode")] - let client_builder = client_builder.danger_accept_invalid_certs(true); -diff --git a/crates/smtp/Cargo.toml b/crates/smtp/Cargo.toml -index e4a781796..d21665c92 100644 ---- a/crates/smtp/Cargo.toml -+++ b/crates/smtp/Cargo.toml -@@ -47,7 +47,7 @@ blake3 = "1.3" - lru-cache = "0.1.2" - rand = "0.9.0" - x509-parser = "0.17.0" --reqwest = { version = "0.12", default-features = false, features = ["rustls-tls-webpki-roots", "http2"] } -+reqwest = { version = "0.12", default-features = false, features = ["http2"] } - serde = { version = "1.0", features = ["derive", "rc"] } - serde_json = "1.0" - num_cpus = "1.15.0" -diff --git a/crates/smtp/src/inbound/hooks/client.rs b/crates/smtp/src/inbound/hooks/client.rs -index 2e3a9e17c..6bfb40a63 100644 ---- a/crates/smtp/src/inbound/hooks/client.rs -+++ b/crates/smtp/src/inbound/hooks/client.rs -@@ -13,7 +13,7 @@ pub(super) async fn send_mta_hook_request( - mta_hook: &MTAHook, - request: Request, - ) -> Result<Response, String> { -- let response = reqwest::Client::builder() -+ let response = utils::reqwest_client_builder() - .timeout(mta_hook.timeout) - .danger_accept_invalid_certs(mta_hook.tls_allow_invalid_certs) - .build() -diff --git a/crates/smtp/src/outbound/mta_sts/lookup.rs b/crates/smtp/src/outbound/mta_sts/lookup.rs -index c8b279c2f..10f2aafeb 100644 ---- a/crates/smtp/src/outbound/mta_sts/lookup.rs -+++ b/crates/smtp/src/outbound/mta_sts/lookup.rs -@@ -67,8 +67,7 @@ impl MtaStsLookup for Server { - - // Fetch policy - #[cfg(not(feature = "test_mode"))] -- let bytes = reqwest::Client::builder() -- .user_agent(common::USER_AGENT) -+ let bytes = utils::reqwest_client_builder() - .timeout(timeout) - .redirect(reqwest::redirect::Policy::none()) - .build()? -diff --git a/crates/smtp/src/reporting/tls.rs b/crates/smtp/src/reporting/tls.rs -index 28e9fa47b..875f395c8 100644 ---- a/crates/smtp/src/reporting/tls.rs -+++ b/crates/smtp/src/reporting/tls.rs -@@ -8,7 +8,7 @@ use super::{AggregateTimestamp, SerializedSize}; - use crate::{queue::RecipientDomain, reporting::SmtpReporting}; - use ahash::AHashMap; - use common::{ -- Server, USER_AGENT, -+ Server, - config::smtp::{ - report::AggregateFrequency, - resolver::{Mode, MxPattern}, -@@ -142,8 +142,7 @@ impl TlsReporting for Server { - for uri in &rua { - match uri { - ReportUri::Http(uri) => { -- if let Ok(client) = reqwest::Client::builder() -- .user_agent(USER_AGENT) -+ if let Ok(client) = utils::reqwest_client_builder() - .timeout(Duration::from_secs(2 * 60)) - .build() - { -diff --git a/crates/spam-filter/Cargo.toml b/crates/spam-filter/Cargo.toml -index f6ec739a8..5a3df8453 100644 ---- a/crates/spam-filter/Cargo.toml -+++ b/crates/spam-filter/Cargo.toml -@@ -19,7 +19,7 @@ tokio = { version = "1.45", features = ["net", "macros"] } - psl = "2" - hyper = { version = "1.0.1", features = ["server", "http1", "http2"] } - idna = "1.0" --reqwest = { version = "0.12", default-features = false, features = ["rustls-tls-webpki-roots", "http2", "stream"]} -+reqwest = { version = "0.12", default-features = false, features = ["http2", "stream"]} - decancer = "3.0.1" - unicode-security = "0.1.0" - infer = "0.19" -diff --git a/crates/spam-filter/src/analysis/url.rs b/crates/spam-filter/src/analysis/url.rs -index a0d663917..e6377f13c 100644 ---- a/crates/spam-filter/src/analysis/url.rs -+++ b/crates/spam-filter/src/analysis/url.rs -@@ -290,7 +290,7 @@ async fn http_get_header( - Ok(None) - }; - } -- reqwest::Client::builder() -+ utils::reqwest_client_builder() - .user_agent("Mozilla/5.0 (X11; Linux i686; rv:109.0) Gecko/20100101 Firefox/118.0") - .timeout(timeout) - .redirect(Policy::none()) -diff --git a/crates/store/Cargo.toml b/crates/store/Cargo.toml -index 1f8965049..3bd1a228c 100644 ---- a/crates/store/Cargo.toml -+++ b/crates/store/Cargo.toml -@@ -16,7 +16,7 @@ async-nats = { version = "0.40", default-features = false, features = ["server_2 - azure_core = { version = "0.21.0", optional = true } - azure_storage = { version = "0.21.0", default-features = false, features = ["enable_reqwest_rustls", "hmac_rust"], optional = true } - azure_storage_blobs = { version = "0.21.0", default-features = false, features = ["enable_reqwest_rustls", "hmac_rust"], optional = true } --reqwest = { version = "0.12", default-features = false, features = ["rustls-tls-webpki-roots", "http2", "stream"]} -+reqwest = { version = "0.12", default-features = false, features = ["http2", "stream"]} - tokio = { version = "1.45", features = ["sync", "fs", "io-util"] } - r2d2 = { version = "0.8.10", optional = true } - futures = { version = "0.3", optional = true } -diff --git a/crates/store/src/backend/azure/mod.rs b/crates/store/src/backend/azure/mod.rs -index 8bbaea073..fbdb03eb7 100644 ---- a/crates/store/src/backend/azure/mod.rs -+++ b/crates/store/src/backend/azure/mod.rs -@@ -63,7 +63,7 @@ impl AzureStore { - let timeout = config - .property_or_default::<Duration>((&prefix, "timeout"), "30s") - .unwrap_or_else(|| Duration::from_secs(30)); -- let transport = match reqwest::Client::builder().timeout(timeout).build() { -+ let transport = match utils::reqwest_client_builder().timeout(timeout).build() { - Ok(client) => Arc::new(client), - Err(err) => { - config.new_build_error( -diff --git a/crates/store/src/backend/http/lookup.rs b/crates/store/src/backend/http/lookup.rs -index dbaa932ed..ff7cca2d8 100644 ---- a/crates/store/src/backend/http/lookup.rs -+++ b/crates/store/src/backend/http/lookup.rs -@@ -91,7 +91,7 @@ impl HttpStore { - async fn try_refresh(&self) -> trc::Result<AHashMap<String, Value<'static>>> { - let time = Instant::now(); - let agent = BROWSER_USER_AGENTS.choose(&mut rand::rng()).unwrap(); -- let response = reqwest::Client::builder() -+ let response = utils::reqwest_client_builder() - .timeout(self.config.timeout) - .user_agent(*agent) - .build() -diff --git a/crates/trc/Cargo.toml b/crates/trc/Cargo.toml -index 61d1cd69a..22d8e6599 100644 ---- a/crates/trc/Cargo.toml -+++ b/crates/trc/Cargo.toml -@@ -11,7 +11,7 @@ mail-parser = { version = "0.11", features = ["full_encoding"] } - base64 = "0.22.1" - serde = "1.0" - serde_json = "1.0.120" --reqwest = { version = "0.12", default-features = false, features = ["rustls-tls-webpki-roots", "http2"]} -+reqwest = { version = "0.12", default-features = false, features = ["http2"]} - rtrb = "0.3.1" - parking_lot = "0.12.3" - tokio = { version = "1.45", features = ["net", "macros"] } -diff --git a/crates/utils/Cargo.toml b/crates/utils/Cargo.toml -index 4766c55b5..f45780d24 100644 ---- a/crates/utils/Cargo.toml -+++ b/crates/utils/Cargo.toml -@@ -9,6 +9,7 @@ trc = { path = "../trc" } - rustls = { version = "0.23.5", default-features = false, features = ["std", "ring", "tls12"] } - rustls-pemfile = "2.0" - rustls-pki-types = { version = "1" } -+rustls-platform-verifier = { version = "0.5.3", optional = true } - tokio = { version = "1.45", features = ["net", "macros", "signal"] } - tokio-rustls = { version = "0.26", default-features = false, features = ["ring", "tls12"] } - serde = { version = "1.0", features = ["derive"]} -@@ -45,6 +46,7 @@ privdrop = "0.5.3" - - [features] - test_mode = [] -+tls-native-roots = ["dep:rustls-platform-verifier", "reqwest/rustls-tls-native-roots"] - - [dev-dependencies] - tokio = { version = "1.45", features = ["full"] } -diff --git a/crates/utils/src/lib.rs b/crates/utils/src/lib.rs -index 2b73df148..2efcc96d7 100644 ---- a/crates/utils/src/lib.rs -+++ b/crates/utils/src/lib.rs -@@ -21,14 +21,14 @@ use compact_str::ToCompactString; - use futures::StreamExt; - use reqwest::Response; - use rustls::{ -- ClientConfig, RootCertStore, SignatureScheme, -+ ClientConfig, SignatureScheme, - client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier}, - }; --use rustls_pki_types::TrustAnchor; - - pub use downcast_rs; - pub use erased_serde; - -+pub static USER_AGENT: &str = "Stalwart/1.0.0"; - pub const BLOB_HASH_LEN: usize = 32; - - #[derive( -@@ -294,20 +294,28 @@ pub async fn wait_for_shutdown() { - } - - pub fn rustls_client_config(allow_invalid_certs: bool) -> ClientConfig { -+ #[cfg(feature = "tls-native-roots")] -+ use rustls_platform_verifier::BuilderVerifierExt; -+ - let config = ClientConfig::builder(); - - if !allow_invalid_certs { -- let mut root_cert_store = RootCertStore::empty(); -- -- root_cert_store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().map(|ta| TrustAnchor { -- subject: ta.subject.clone(), -- subject_public_key_info: ta.subject_public_key_info.clone(), -- name_constraints: ta.name_constraints.clone(), -- })); -- -- config -- .with_root_certificates(root_cert_store) -- .with_no_client_auth() -+ #[cfg(feature = "tls-native-roots")] -+ let config = config.with_platform_verifier(); -+ -+ #[cfg(not(feature = "tls-native-roots"))] -+ let config = config.with_root_certificates( -+ webpki_roots::TLS_SERVER_ROOTS -+ .iter() -+ .map(|ta| rustls_pki_types::TrustAnchor { -+ subject: ta.subject.clone(), -+ subject_public_key_info: ta.subject_public_key_info.clone(), -+ name_constraints: ta.name_constraints.clone(), -+ }) -+ .collect::<rustls::RootCertStore>(), -+ ); -+ -+ config.with_no_client_auth() - } else { - config - .dangerous() -@@ -316,6 +324,18 @@ pub fn rustls_client_config(allow_invalid_certs: bool) -> ClientConfig { - } - } - -+pub fn reqwest_client_builder() -> reqwest::ClientBuilder { -+ let builder = reqwest::Client::builder(); -+ -+ #[cfg(feature = "tls-native-roots")] -+ let builder = builder.tls_built_in_native_certs(true); -+ -+ #[cfg(not(feature = "tls-native-roots"))] -+ let builder = builder.tls_built_in_webpki_certs(true); -+ -+ builder.user_agent(USER_AGENT) -+} -+ - #[derive(Debug)] - struct DummyVerifier; - -diff --git a/crates/utils/src/suffixlist.rs b/crates/utils/src/suffixlist.rs -index e0921abe3..b53126ae9 100644 ---- a/crates/utils/src/suffixlist.rs -+++ b/crates/utils/src/suffixlist.rs -@@ -111,7 +111,12 @@ impl PublicSuffix { - - for (idx, value) in values.into_iter().enumerate() { - let bytes = if value.starts_with("https://") || value.starts_with("http://") { -- let result = match reqwest::get(&value).await { -+ let result = match crate::reqwest_client_builder() -+ .build() -+ .unwrap_or_default() -+ .get(&value) -+ .await -+ { - Ok(r) => { - if r.status().is_success() { - r.bytes().await -diff --git a/tests/Cargo.toml b/tests/Cargo.toml -index 386b8255a..68692e939 100644 ---- a/tests/Cargo.toml -+++ b/tests/Cargo.toml -@@ -59,7 +59,7 @@ rayon = { version = "1.5.1" } - flate2 = { version = "1.0.17", features = ["zlib"], default-features = false } - serde = { version = "1.0", features = ["derive"]} - serde_json = "1.0" --reqwest = { version = "0.12", default-features = false, features = ["rustls-tls-webpki-roots", "multipart", "http2"]} -+reqwest = { version = "0.12", default-features = false, features = ["multipart", "http2"]} - bytes = "1.4.0" - futures = "0.3" - ece = "2.2" -diff --git a/tests/src/jmap/auth_oauth.rs b/tests/src/jmap/auth_oauth.rs -index 0d05d3a77..fff811fcf 100644 ---- a/tests/src/jmap/auth_oauth.rs -+++ b/tests/src/jmap/auth_oauth.rs -@@ -411,7 +411,7 @@ async fn post_bytes( - auth_token: Option<&str>, - params: &AHashMap<String, String>, - ) -> Bytes { -- let mut client = reqwest::Client::builder() -+ let mut client = utils::reqwest_client_builder() - .timeout(Duration::from_millis(500)) - .danger_accept_invalid_certs(true) - .build() -@@ -437,7 +437,7 @@ async fn post_json<D: DeserializeOwned>( - auth_token: Option<&str>, - body: &impl Serialize, - ) -> D { -- let mut client = reqwest::Client::builder() -+ let mut client = utils::reqwest_client_builder() - .timeout(Duration::from_millis(500)) - .danger_accept_invalid_certs(true) - .build() -@@ -473,7 +473,7 @@ async fn post_with_auth<T: DeserializeOwned>( - } - - async fn get_bytes(url: &str) -> Bytes { -- reqwest::Client::builder() -+ utils::reqwest_client_builder() - .timeout(Duration::from_millis(500)) - .danger_accept_invalid_certs(true) - .build() -diff --git a/tests/src/jmap/mod.rs b/tests/src/jmap/mod.rs -index 554026a00..ffd8d292a 100644 ---- a/tests/src/jmap/mod.rs -+++ b/tests/src/jmap/mod.rs -@@ -419,7 +419,7 @@ pub async fn jmap_raw_request(body: impl AsRef<str>, username: &str, secret: &st - }"#; - - String::from_utf8( -- reqwest::Client::builder() -+ utils::reqwest_client_builder() - .danger_accept_invalid_certs(true) - .timeout(Duration::from_millis(1000)) - .default_headers(headers) -@@ -620,7 +620,7 @@ impl ManagementApi { - query: &str, - body: Option<String>, - ) -> Result<String, String> { -- let mut request = reqwest::Client::builder() -+ let mut request = utils::reqwest_client_builder() - .timeout(Duration::from_millis(500)) - .danger_accept_invalid_certs(true) - .build() -diff --git a/tests/src/smtp/management/queue.rs b/tests/src/smtp/management/queue.rs -index e86f55169..a0d144127 100644 ---- a/tests/src/smtp/management/queue.rs -+++ b/tests/src/smtp/management/queue.rs -@@ -493,7 +493,7 @@ async fn manage_queue() { - - // Test authentication error - assert_eq!( -- reqwest::Client::builder() -+ utils::reqwest_client_builder() - .timeout(Duration::from_millis(500)) - .danger_accept_invalid_certs(true) - .build() -diff --git a/tests/src/webdav/mod.rs b/tests/src/webdav/mod.rs -index 0ed8b1888..25aab71a4 100644 ---- a/tests/src/webdav/mod.rs -+++ b/tests/src/webdav/mod.rs -@@ -302,7 +302,7 @@ impl DummyWebDavClient { - headers: impl IntoIterator<Item = (&'static str, &str)>, - body: impl Into<String>, - ) -> DavResponse { -- let mut request = reqwest::Client::builder() -+ let mut request = utils::reqwest_client_builder() - .timeout(Duration::from_millis(500)) - .danger_accept_invalid_certs(true) - .build() |