Import upstream v0.16.25, stripped

Upstream commit: 3f657330c0f49a015a3a372fb59669b5cccbca6d
Enterprise-only files removed or emptied: 63
Enterprise-only snippets removed: 118 in 50 files
Dangling module declarations removed: 5
Edits turning enterprise off: 25
Third-party code: 14 files, 0 not in THIRD-PARTY.md
Renamed identifiers: 67 in 19 files
Verification: clean

The same Enterprise footprint as v0.16.24. The build check is clean
apart from the expected errors in the rebuilt-feature tests. A
bug-fix release: DKIM rotation, IMAP failed-login answers, DNSBL
multi-code scoring and negative TTLs, Pyzor on short messages, queue
quotas with an empty match, RocksDB info-log rotation, and
Autodiscover schema handling.
This commit is contained in:
jcoffey-dev committed 2026-10-05 21:15:02 -07:00
1 parent f59b084ce5
commit 72f8ddd5e7
67 files changed
+1643 -492

No files matched your search

+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "common"
version = "0.16.24"
version = "0.16.25"
edition = "2024"
build = "build.rs"
+1 -1
View File
@@ -194,7 +194,7 @@ pub struct Caches {
pub dns_ipv6: CacheWithTtl<Box<str>, RecordSet<Ipv6Addr>>,
pub dns_tlsa: CacheWithTtl<Box<str>, Arc<Tlsa>>,
pub dns_mta_sts: CacheWithTtl<Box<str>, Arc<Policy>>,
pub dns_rbl: CacheWithTtl<Box<str>, Option<Arc<IpResolver>>>,
pub dns_rbl: CacheWithTtl<Box<str>, Option<Arc<[IpResolver]>>>,
pub negative_cache_ttl: Duration,
}
+14 -2
View File
@@ -225,10 +225,22 @@ impl WebApplicationManager {
let cached = if force_refresh {
None
} else {
server
match server
.blob_store()
.get_blob(self.blob_key.as_slice(), 0..usize::MAX)
.await?
.await
{
Ok(cached) => cached,
Err(err) => {
trc::event!(
Resource(trc::ResourceEvent::Error),
Reason = err,
Url = self.url.clone(),
Details = "Failed to read cached application bundle, downloading it again"
);
None
}
}
};
let is_cached = cached.is_some();
let bundle = match cached {
+1 -1
View File
@@ -529,7 +529,7 @@ async fn insert_safe_defaults(bp: &mut Bootstrap) -> trc::Result<()> {
ansi: false,
prefix: "stalwart.log".into(),
rotate: LogRotateFrequency::Daily,
path: "/var/log/stalwart".into(),
path: "/var/log/inbuxa".into(),
..Default::default()
})
.into(),
@@ -9,7 +9,7 @@ use quick_xml::Reader;
use quick_xml::XmlVersion;
use quick_xml::events::Event;
use registry::schema::{enums::ServiceProtocol, structs::Service};
use std::fmt::Write;
use std::{borrow::Cow, fmt::Write};
use utils::map::vec_map::VecMap;
impl Server {
@@ -18,26 +18,72 @@ impl Server {
body: Option<Vec<u8>>,
) -> trc::Result<Resource<Vec<u8>>> {
// Obtain parameters
let emailaddress = parse_autodiscover_request(body.as_deref().unwrap_or_default())
.map_err(|err| {
let request =
parse_autodiscover_request(body.as_deref().unwrap_or_default()).map_err(|err| {
trc::ResourceEvent::BadParameters
.into_err()
.details("Failed to parse autodiscover request")
.ctx(trc::Key::Reason, err)
})?;
Ok(Resource::new(
"application/xml; charset=utf-8",
build_autodiscover_response(
&emailaddress,
let response = match request.response_schema {
ResponseSchema::Outlook => build_autodiscover_response(
&request.email,
&self.core.network.server_name,
&self.core.network.info.services,
)
.into_bytes(),
))
ResponseSchema::Unsupported => PROVIDER_NOT_AVAILABLE_RESPONSE.as_bytes().to_vec(),
};
Ok(Resource::new("application/xml; charset=utf-8", response))
}
}
const OUTLOOK_RESPONSE_SCHEMA: &str =
"http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a";
const PROVIDER_NOT_AVAILABLE_RESPONSE: &str = concat!(
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n",
"<Autodiscover xmlns=\"http://schemas.microsoft.com/exchange/autodiscover/responseschema/2006\">\n",
"\t<Response>\n",
"\t\t<Error>\n",
"\t\t\t<ErrorCode>601</ErrorCode>\n",
"\t\t\t<Message>Provider is not available</Message>\n",
"\t\t\t<DebugData />\n",
"\t\t</Error>\n",
"\t</Response>\n",
"</Autodiscover>\n",
);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum ResponseSchema {
Outlook,
Unsupported,
}
impl ResponseSchema {
fn parse(value: &str) -> Self {
if value.trim().eq_ignore_ascii_case(OUTLOOK_RESPONSE_SCHEMA) {
ResponseSchema::Outlook
} else {
ResponseSchema::Unsupported
}
}
}
#[derive(Debug, PartialEq, Eq)]
struct AutodiscoverRequest {
email: String,
response_schema: ResponseSchema,
}
#[derive(Clone, Copy)]
enum RequestField {
EmailAddress,
ResponseSchema,
}
fn build_autodiscover_response(
emailaddress: &str,
default_host: &str,
@@ -112,7 +158,7 @@ fn build_autodiscover_response(
config
}
fn parse_autodiscover_request(bytes: &[u8]) -> Result<String, String> {
fn parse_autodiscover_request(bytes: &[u8]) -> Result<AutodiscoverRequest, String> {
if bytes.is_empty() {
return Err("Empty request body".to_string());
}
@@ -120,8 +166,9 @@ fn parse_autodiscover_request(bytes: &[u8]) -> Result<String, String> {
let mut reader = Reader::from_reader(bytes);
reader.config_mut().trim_text(true);
let mut buf = Vec::with_capacity(128);
let mut value_buf = Vec::with_capacity(128);
'outer: for tag_name in ["Autodiscover", "Request", "EMailAddress"] {
'outer: for tag_name in ["Autodiscover", "Request"] {
loop {
match reader.read_event_into(&mut buf) {
Ok(Event::Start(e)) => {
@@ -131,30 +178,6 @@ fn parse_autodiscover_request(bytes: &[u8]) -> Result<String, String> {
.eq_ignore_ascii_case(found_tag_name.as_ref())
{
continue 'outer;
} else if tag_name == "EMailAddress" {
// Skip unsupported tags under Request, such as AcceptableResponseSchema
let mut tag_count = 0;
loop {
match reader.read_event_into(&mut buf) {
Ok(Event::End(_)) => {
if tag_count == 0 {
break;
} else {
tag_count -= 1;
}
}
Ok(Event::Start(_)) => {
tag_count += 1;
}
Ok(Event::Eof) => {
return Err(format!(
"Expected value, found unexpected EOF at position {}.",
reader.buffer_position()
));
}
_ => (),
}
}
} else {
return Err(format!(
"Expected tag {}, found unexpected tag {} at position {}.",
@@ -183,36 +206,170 @@ fn parse_autodiscover_request(bytes: &[u8]) -> Result<String, String> {
}
}
if let Ok(Event::Text(text)) = reader.read_event_into(&mut buf)
&& let Ok(text) = text.xml_content(XmlVersion::Implicit1_0)
&& text.contains('@')
{
return Ok(text.trim().to_lowercase());
let mut email = None;
let mut response_schema = ResponseSchema::Outlook;
loop {
match reader.read_event_into(&mut buf) {
Ok(Event::Start(e)) => {
let local_name = e.local_name();
let field = hashify::tiny_map_ignore_case!(local_name.as_ref(),
b"EMailAddress" => RequestField::EmailAddress,
b"AcceptableResponseSchema" => RequestField::ResponseSchema,
);
let value = match reader.read_event_into(&mut value_buf) {
Ok(Event::End(_)) => None,
Ok(event) => {
let value = match event {
Event::Text(text) => text
.xml_content(XmlVersion::Implicit1_0)
.ok()
.map(Cow::into_owned),
_ => None,
};
reader
.read_to_end_into(e.name(), &mut value_buf)
.map_err(|err| {
format!("Error at position {}: {:?}", reader.buffer_position(), err)
})?;
value
}
Err(err) => {
return Err(format!(
"Error at position {}: {:?}",
reader.buffer_position(),
err
));
}
};
match (field, value) {
(Some(RequestField::EmailAddress), Some(value)) => {
email = Some(value);
}
(Some(RequestField::ResponseSchema), Some(value)) => {
response_schema = ResponseSchema::parse(&value);
}
_ => (),
}
}
Ok(Event::End(_) | Event::Eof) => break,
Ok(_) => (),
Err(e) => {
return Err(format!(
"Error at position {}: {:?}",
reader.buffer_position(),
e
));
}
}
}
Err(format!(
"Expected email address, found unexpected value at position {}.",
reader.buffer_position()
))
match email {
Some(email) if email.contains('@') => Ok(AutodiscoverRequest {
email: email.trim().to_lowercase(),
response_schema,
}),
_ => Err(format!(
"Expected email address, found unexpected value at position {}.",
reader.buffer_position()
)),
}
}
#[cfg(test)]
mod tests {
use super::{AutodiscoverRequest, ResponseSchema, parse_autodiscover_request};
#[test]
fn parse_autodiscover() {
let r = r#"<?xml version="1.0" encoding="utf-8"?>
const OUTLOOK: &str =
"http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a";
const MOBILESYNC: &str =
"http://schemas.microsoft.com/exchange/autodiscover/mobilesync/responseschema/2006";
for (request, expected) in [
(
format!(
r#"<?xml version="1.0" encoding="utf-8"?>
<Autodiscover xmlns="http://schemas.microsoft.com/exchange/autodiscover/outlook/requestschema/2006">
<Request>
<EMailAddress>email@example.com</EMailAddress>
<AcceptableResponseSchema>http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a</AcceptableResponseSchema>
<EMailAddress>Email@Example.com</EMailAddress>
<AcceptableResponseSchema>{OUTLOOK}</AcceptableResponseSchema>
</Request>
</Autodiscover>"#;
</Autodiscover>"#
),
ResponseSchema::Outlook,
),
(
format!(
r#"<Autodiscover xmlns="http://schemas.microsoft.com/exchange/autodiscover/outlook/requestschema/2006">
<Request>
<AcceptableResponseSchema>{OUTLOOK}</AcceptableResponseSchema>
<EMailAddress>[email protected]</EMailAddress>
</Request>
</Autodiscover>"#
),
ResponseSchema::Outlook,
),
(
r#"<Autodiscover>
<Request>
<EMailAddress>[email protected]</EMailAddress>
</Request>
</Autodiscover>"#
.to_string(),
ResponseSchema::Outlook,
),
(
format!(
r#"<?xml version="1.0" encoding="utf-8"?>
<Autodiscover xmlns="http://schemas.microsoft.com/exchange/autodiscover/mobilesync/requestschema/2006">
<Request>
<EMailAddress>[email protected]</EMailAddress>
<AcceptableResponseSchema>{MOBILESYNC}</AcceptableResponseSchema>
</Request>
</Autodiscover>"#
),
ResponseSchema::Unsupported,
),
(
format!(
r#"<Autodiscover>
<Request>
<LegacyDN>/o=Example/ou=Users/cn=email</LegacyDN>
<Unknown><Nested>value</Nested><Empty/></Unknown>
<AcceptableResponseSchema>{MOBILESYNC}</AcceptableResponseSchema>
<EMailAddress>[email protected]</EMailAddress>
</Request>
</Autodiscover>"#
),
ResponseSchema::Unsupported,
),
] {
assert_eq!(
parse_autodiscover_request(request.as_bytes()).expect("valid request"),
AutodiscoverRequest {
email: "[email protected]".to_string(),
response_schema: expected,
},
"{request}"
);
}
assert_eq!(
super::parse_autodiscover_request(r.as_bytes()).unwrap(),
"[email protected]"
);
for request in [
"",
"<Autodiscover><Request></Request></Autodiscover>",
"<Autodiscover><Request><EMailAddress>no-domain</EMailAddress></Request></Autodiscover>",
"<Autodiscover><Request><EMailAddress>[email protected]</Request></Autodiscover>",
"<Request><EMailAddress>[email protected]</EMailAddress></Request>",
] {
assert!(
parse_autodiscover_request(request.as_bytes()).is_err(),
"{request}"
);
}
}
#[test]