Merge upstream v0.16.24
Eight conflicted files resolved, plus the lock file and the schema:
- crates/services/src/task_manager/spam_classifier.rs: upstream's rules
update now replaces existing rules, DNSBL servers, lookups and file
extensions, keeping only whether each is on. Taken, with one difference:
an object an admin edited is kept as it is. Every object an update writes
is fingerprinted (content without `enable`, SHA-256, stored under
SUBSPACE_INBUXA "Sf"), and only one that still matches is replaced.
Scores are never replaced, as upstream has it. The AU-1.10 summary record
now names what was added, replaced and kept, and the bundled rules are
marked applied only when the update fully succeeded, so a failure runs
again on the next start. The marker becomes "3.0.2+2", which runs the
update once on upgrade to fingerprint every rule still as bundled.
- crates/common/src/network/autoconfig/autodiscover.rs: upstream's rewrite
(implicit TLS first, labeled SSL), with the per-protocol switches (LP-7,
LP-14a) passed in as a filter.
- crates/store/src/backend/mysql/{search,write}.rs: upstream's chunked
deletes (no unbounded first DELETE, stop on a short chunk, halve the
chunk on the new chunk-too-large errors) inside the fork's query timeout.
- crates/smtp/src/lib.rs: the fork's queue spawn kept. It already fixed the
stall upstream fixes here (a node without outboundMta stops accepting
mail at about 1024 queued messages), and follows role changes live.
- crates/jmap/src/registry/mapping/bootstrap.rs: the log path stays
/var/log/inbuxa/; upstream's PowerDNS mapping taken.
- crates/main/Cargo.toml: the AGPL-only license kept, version 0.16.24.
- tests/src/jmap/principal/get.rs: the fork's capabilities kept.
- resources/schema/schema.json.gz: merged as JSON; upstream relabeled the
vendor Sieve extensions "(Stalwart)", kept as "(vnd.inbuxa)".
- Cargo.lock: upstream's, with the fork's crates added by Cargo.
Also:
- tests/src/smtp/inbound/spam_rules_kept.rs: an edited rule survives an
update, an unedited one is updated, rules from before fingerprints are
handled, and the audit summary says so. Upstream's own spam_rules test
passes unchanged.
- tests/src/smtp/reporting/reschedule.rs moves to port 19058; upstream's
new spam_rules test took 19057.
- tools/fork/renames.py renames the "(Stalwart)" labels and the default
log path, so neither conflicts again.
- tools/fork/notice-check.py compares against the newest snapshot in the
checked-out history instead of the upstream branch head, so moving the
branch no longer fails other open pull requests.
- tests/src/directory/issuer.rs (since v0.16.23) stays out, and is on the
build check's known list: it tests issuer-based directory routing, which
the fork doesn't have (DIR-2).
- Strip report: docs/fork/strip-reports/v0.16.24.{md,json}.
This commit is contained in:
@@ -10,8 +10,9 @@ use crate::{Server, manager::application::Resource};
|
||||
use quick_xml::Reader;
|
||||
use quick_xml::XmlVersion;
|
||||
use quick_xml::events::Event;
|
||||
use registry::schema::enums::ServiceProtocol;
|
||||
use registry::schema::{enums::ServiceProtocol, structs::Service};
|
||||
use std::fmt::Write;
|
||||
use utils::map::vec_map::VecMap;
|
||||
|
||||
impl Server {
|
||||
pub async fn handle_autodiscover_request(
|
||||
@@ -26,89 +27,103 @@ impl Server {
|
||||
.details("Failed to parse autodiscover request")
|
||||
.ctx(trc::Key::Reason, err)
|
||||
})?;
|
||||
let default_host = &self.core.network.server_name;
|
||||
|
||||
// Build XML response
|
||||
let mut config = String::with_capacity(1024);
|
||||
let _ = writeln!(&mut config, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
|
||||
let _ = writeln!(
|
||||
&mut config,
|
||||
"<Autodiscover xmlns=\"http://schemas.microsoft.com/exchange/autodiscover/responseschema/2006\">"
|
||||
);
|
||||
let _ = writeln!(
|
||||
&mut config,
|
||||
"\t<Response xmlns=\"http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a\">"
|
||||
);
|
||||
let _ = writeln!(&mut config, "\t\t<User>");
|
||||
let _ = writeln!(
|
||||
&mut config,
|
||||
"\t\t\t<DisplayName>{emailaddress}</DisplayName>"
|
||||
);
|
||||
let _ = writeln!(
|
||||
&mut config,
|
||||
"\t\t\t<AutoDiscoverSMTPAddress>{emailaddress}</AutoDiscoverSMTPAddress>"
|
||||
);
|
||||
// DeploymentId is a required field of User but we are not a MS Exchange server so use a random value
|
||||
let _ = writeln!(
|
||||
&mut config,
|
||||
"\t\t\t<DeploymentId>644560b8-a1ce-429c-8ace-23395843f701</DeploymentId>"
|
||||
);
|
||||
let _ = writeln!(&mut config, "\t\t</User>");
|
||||
let _ = writeln!(&mut config, "\t\t<Account>");
|
||||
let _ = writeln!(&mut config, "\t\t\t<AccountType>email</AccountType>");
|
||||
let _ = writeln!(&mut config, "\t\t\t<Action>settings</Action>");
|
||||
// inbuxa: legacy-protocols LP-7, LP-14a
|
||||
let legacy_off = match emailaddress.rsplit_once('@') {
|
||||
Some((_, domain)) => self.legacy_off_for(domain).await?,
|
||||
None => self.legacy_off_for("").await?,
|
||||
};
|
||||
for (protocol, service) in &self.core.network.info.services {
|
||||
if legacy_off.service(protocol) {
|
||||
continue;
|
||||
}
|
||||
let (protocol, ports) = match protocol {
|
||||
ServiceProtocol::Imap => ("IMAP", [143, 993]),
|
||||
ServiceProtocol::Pop3 => ("POP3", [110, 995]),
|
||||
ServiceProtocol::Smtp => ("SMTP", [587, 465]),
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
for (is_tls, port) in ports.into_iter().enumerate() {
|
||||
if is_tls == 1 || service.cleartext {
|
||||
let server_name = service.hostname.as_deref().unwrap_or(default_host);
|
||||
let _ = writeln!(&mut config, "\t\t\t<Protocol>");
|
||||
let _ = writeln!(&mut config, "\t\t\t\t<Type>{protocol}</Type>",);
|
||||
let _ = writeln!(&mut config, "\t\t\t\t<Server>{server_name}</Server>");
|
||||
let _ = writeln!(&mut config, "\t\t\t\t<Port>{port}</Port>");
|
||||
let _ = writeln!(&mut config, "\t\t\t\t<LoginName>{emailaddress}</LoginName>");
|
||||
let _ = writeln!(&mut config, "\t\t\t\t<AuthRequired>on</AuthRequired>");
|
||||
let _ = writeln!(&mut config, "\t\t\t\t<DirectoryPort>0</DirectoryPort>");
|
||||
let _ = writeln!(&mut config, "\t\t\t\t<ReferralPort>0</ReferralPort>");
|
||||
let _ = writeln!(
|
||||
&mut config,
|
||||
"\t\t\t\t<SSL>{}</SSL>",
|
||||
if is_tls == 1 { "on" } else { "off" }
|
||||
);
|
||||
if is_tls == 1 {
|
||||
let _ = writeln!(&mut config, "\t\t\t\t<Encryption>TLS</Encryption>");
|
||||
}
|
||||
let _ = writeln!(&mut config, "\t\t\t\t<SPA>off</SPA>");
|
||||
let _ = writeln!(&mut config, "\t\t\t</Protocol>");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let _ = writeln!(&mut config, "\t\t</Account>");
|
||||
let _ = writeln!(&mut config, "\t</Response>");
|
||||
let _ = writeln!(&mut config, "</Autodiscover>");
|
||||
|
||||
Ok(Resource::new(
|
||||
"application/xml; charset=utf-8",
|
||||
config.into_bytes(),
|
||||
build_autodiscover_response(
|
||||
&emailaddress,
|
||||
&self.core.network.server_name,
|
||||
&self.core.network.info.services,
|
||||
|protocol| legacy_off.service(protocol),
|
||||
)
|
||||
.into_bytes(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn build_autodiscover_response(
|
||||
emailaddress: &str,
|
||||
default_host: &str,
|
||||
services: &VecMap<ServiceProtocol, Service>,
|
||||
switched_off: impl Fn(&ServiceProtocol) -> bool,
|
||||
) -> String {
|
||||
// Build XML response
|
||||
let mut config = String::with_capacity(1024);
|
||||
let _ = writeln!(&mut config, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
|
||||
let _ = writeln!(
|
||||
&mut config,
|
||||
"<Autodiscover xmlns=\"http://schemas.microsoft.com/exchange/autodiscover/responseschema/2006\">"
|
||||
);
|
||||
let _ = writeln!(
|
||||
&mut config,
|
||||
"\t<Response xmlns=\"http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a\">"
|
||||
);
|
||||
let _ = writeln!(&mut config, "\t\t<User>");
|
||||
let _ = writeln!(
|
||||
&mut config,
|
||||
"\t\t\t<DisplayName>{emailaddress}</DisplayName>"
|
||||
);
|
||||
let _ = writeln!(
|
||||
&mut config,
|
||||
"\t\t\t<AutoDiscoverSMTPAddress>{emailaddress}</AutoDiscoverSMTPAddress>"
|
||||
);
|
||||
// DeploymentId is a required field of User but we are not a MS Exchange server so use a random value
|
||||
let _ = writeln!(
|
||||
&mut config,
|
||||
"\t\t\t<DeploymentId>644560b8-a1ce-429c-8ace-23395843f701</DeploymentId>"
|
||||
);
|
||||
let _ = writeln!(&mut config, "\t\t</User>");
|
||||
let _ = writeln!(&mut config, "\t\t<Account>");
|
||||
let _ = writeln!(&mut config, "\t\t\t<AccountType>email</AccountType>");
|
||||
let _ = writeln!(&mut config, "\t\t\t<Action>settings</Action>");
|
||||
for (protocol, service) in services {
|
||||
if switched_off(protocol) {
|
||||
continue;
|
||||
}
|
||||
let (protocol, ports) = match protocol {
|
||||
ServiceProtocol::Imap => ("IMAP", [(993, true), (143, false)]),
|
||||
ServiceProtocol::Pop3 => ("POP3", [(995, true), (110, false)]),
|
||||
ServiceProtocol::Smtp => ("SMTP", [(465, true), (587, false)]),
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
// Implicit TLS is listed first so that it is preferred (RFC 8314)
|
||||
for (port, is_tls) in ports {
|
||||
if is_tls || service.cleartext {
|
||||
let server_name = service.hostname.as_deref().unwrap_or(default_host);
|
||||
let _ = writeln!(&mut config, "\t\t\t<Protocol>");
|
||||
let _ = writeln!(&mut config, "\t\t\t\t<Type>{protocol}</Type>",);
|
||||
let _ = writeln!(&mut config, "\t\t\t\t<Server>{server_name}</Server>");
|
||||
let _ = writeln!(&mut config, "\t\t\t\t<Port>{port}</Port>");
|
||||
let _ = writeln!(&mut config, "\t\t\t\t<LoginName>{emailaddress}</LoginName>");
|
||||
let _ = writeln!(&mut config, "\t\t\t\t<AuthRequired>on</AuthRequired>");
|
||||
let _ = writeln!(&mut config, "\t\t\t\t<DirectoryPort>0</DirectoryPort>");
|
||||
let _ = writeln!(&mut config, "\t\t\t\t<ReferralPort>0</ReferralPort>");
|
||||
let (ssl, encryption) = if is_tls {
|
||||
("on", "SSL")
|
||||
} else {
|
||||
("off", "TLS")
|
||||
};
|
||||
let _ = writeln!(&mut config, "\t\t\t\t<SSL>{ssl}</SSL>");
|
||||
let _ = writeln!(&mut config, "\t\t\t\t<Encryption>{encryption}</Encryption>");
|
||||
let _ = writeln!(&mut config, "\t\t\t\t<SPA>off</SPA>");
|
||||
let _ = writeln!(&mut config, "\t\t\t</Protocol>");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let _ = writeln!(&mut config, "\t\t</Account>");
|
||||
let _ = writeln!(&mut config, "\t</Response>");
|
||||
let _ = writeln!(&mut config, "</Autodiscover>");
|
||||
|
||||
config
|
||||
}
|
||||
|
||||
fn parse_autodiscover_request(bytes: &[u8]) -> Result<String, String> {
|
||||
if bytes.is_empty() {
|
||||
return Err("Empty request body".to_string());
|
||||
@@ -211,4 +226,79 @@ mod tests {
|
||||
"[email protected]"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn autodiscover_encryption() {
|
||||
use registry::schema::{enums::ServiceProtocol, structs::Service};
|
||||
use utils::map::vec_map::VecMap;
|
||||
|
||||
fn tag<'x>(block: &'x str, name: &str) -> &'x str {
|
||||
block
|
||||
.split_once(&format!("<{name}>"))
|
||||
.and_then(|(_, rest)| rest.split_once(&format!("</{name}>")))
|
||||
.map(|(value, _)| value)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
for (cleartext, expected) in [
|
||||
(
|
||||
false,
|
||||
vec![
|
||||
("IMAP", "993", "on", "SSL"),
|
||||
("POP3", "995", "on", "SSL"),
|
||||
("SMTP", "465", "on", "SSL"),
|
||||
],
|
||||
),
|
||||
(
|
||||
true,
|
||||
vec![
|
||||
("IMAP", "993", "on", "SSL"),
|
||||
("IMAP", "143", "off", "TLS"),
|
||||
("POP3", "995", "on", "SSL"),
|
||||
("POP3", "110", "off", "TLS"),
|
||||
("SMTP", "465", "on", "SSL"),
|
||||
("SMTP", "587", "off", "TLS"),
|
||||
],
|
||||
),
|
||||
] {
|
||||
let services: VecMap<ServiceProtocol, Service> = [
|
||||
ServiceProtocol::Imap,
|
||||
ServiceProtocol::Pop3,
|
||||
ServiceProtocol::Smtp,
|
||||
ServiceProtocol::Jmap,
|
||||
]
|
||||
.into_iter()
|
||||
.map(|protocol| {
|
||||
(
|
||||
protocol,
|
||||
Service {
|
||||
hostname: None,
|
||||
cleartext,
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
let response = super::build_autodiscover_response(
|
||||
"[email protected]",
|
||||
"mail.example.com",
|
||||
&services,
|
||||
|_| false,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
response
|
||||
.split("<Protocol>")
|
||||
.skip(1)
|
||||
.map(|block| (
|
||||
tag(block, "Type"),
|
||||
tag(block, "Port"),
|
||||
tag(block, "SSL"),
|
||||
tag(block, "Encryption"),
|
||||
))
|
||||
.collect::<Vec<_>>(),
|
||||
expected,
|
||||
"cleartext: {cleartext}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user