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:
1 parent
f59b084ce5
commit
72f8ddd5e7
67 files changed
+1643
-492
No files matched your search
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "common"
|
||||
version = "0.16.24"
|
||||
version = "0.16.25"
|
||||
edition = "2024"
|
||||
build = "build.rs"
|
||||
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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]
|
||||
|
||||
Reference in new issue
Block a user