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:
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "http"
|
||||
version = "0.16.23"
|
||||
version = "0.16.24"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
|
||||
+103
-70
@@ -12,7 +12,7 @@ use common::{
|
||||
},
|
||||
};
|
||||
use hyper::body::{Bytes, Frame};
|
||||
use mail_auth::{IpLookupStrategy, mta_sts::TlsRpt};
|
||||
use mail_auth::{DnssecStatus, IpLookupStrategy, mta_sts::TlsRpt};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use smtp::outbound::{
|
||||
client::{SmtpClient, StartTlsResult},
|
||||
@@ -382,81 +382,25 @@ async fn delivery_diagnose(
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch TLSA record
|
||||
tx.send(DeliveryStage::TlsaLookupStart).await?;
|
||||
|
||||
let now = Instant::now();
|
||||
let dane_policy = match server.tlsa_lookup(format!("_25._tcp.{hostname}.")).await {
|
||||
Ok(TlsaResult::Secure(tlsa)) if tlsa.has_end_entities => {
|
||||
tx.send(DeliveryStage::TlsaLookupSuccess {
|
||||
record: tlsa.as_ref().clone(),
|
||||
elapsed: now.elapsed_ms(),
|
||||
})
|
||||
.await?;
|
||||
|
||||
Some(tlsa)
|
||||
}
|
||||
Ok(TlsaResult::Secure(_)) => {
|
||||
tx.send(DeliveryStage::TlsaLookupError {
|
||||
elapsed: now.elapsed_ms(),
|
||||
reason: "TLSA record does not have end entities".to_string(),
|
||||
})
|
||||
.await?;
|
||||
|
||||
None
|
||||
}
|
||||
Ok(TlsaResult::Bogus) => {
|
||||
tx.send(DeliveryStage::TlsaLookupError {
|
||||
elapsed: now.elapsed_ms(),
|
||||
reason: "Bogus TLSA record".to_string(),
|
||||
})
|
||||
.await?;
|
||||
|
||||
continue 'outer;
|
||||
}
|
||||
Ok(TlsaResult::Missing) => {
|
||||
tx.send(DeliveryStage::TlsaNotFound {
|
||||
elapsed: now.elapsed_ms(),
|
||||
reason: "No TLSA DNSSEC records found".to_string(),
|
||||
})
|
||||
.await?;
|
||||
|
||||
None
|
||||
}
|
||||
Err(err) => {
|
||||
if matches!(
|
||||
&err,
|
||||
mail_auth::Error::Dns(mail_auth::DnsError::RecordNotFound(_))
|
||||
) {
|
||||
tx.send(DeliveryStage::TlsaNotFound {
|
||||
elapsed: now.elapsed_ms(),
|
||||
reason: "No TLSA records found for MX".to_string(),
|
||||
})
|
||||
.await?;
|
||||
|
||||
None
|
||||
} else {
|
||||
tx.send(DeliveryStage::TlsaLookupError {
|
||||
elapsed: now.elapsed_ms(),
|
||||
reason: err.to_string(),
|
||||
})
|
||||
.await?;
|
||||
|
||||
continue 'outer;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
tx.send(DeliveryStage::IpLookupStart).await?;
|
||||
|
||||
let now = Instant::now();
|
||||
let remote_ips = match host.fqdn_hostname() {
|
||||
let validate_addresses = server.core.smtp.resolvers.dnssec_available
|
||||
&& host.dnssec_status() == DnssecStatus::Secure;
|
||||
let (remote_ips, addresses_dnssec_status) = match host.fqdn_hostname() {
|
||||
HostOrIp::Host(hostname) => {
|
||||
match server
|
||||
.ip_lookup(&hostname, IpLookupStrategy::Ipv4thenIpv6, usize::MAX, false)
|
||||
.ip_lookup(
|
||||
&hostname,
|
||||
IpLookupStrategy::Ipv4thenIpv6,
|
||||
usize::MAX,
|
||||
validate_addresses,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok((remote_ips, _)) if !remote_ips.is_empty() => remote_ips,
|
||||
Ok((remote_ips, dnssec_status)) if !remote_ips.is_empty() => {
|
||||
(remote_ips, dnssec_status)
|
||||
}
|
||||
Ok(_) => {
|
||||
tx.send(DeliveryStage::IpLookupError {
|
||||
reason: "No IP addresses found for host".to_string(),
|
||||
@@ -475,7 +419,7 @@ async fn delivery_diagnose(
|
||||
}
|
||||
}
|
||||
}
|
||||
HostOrIp::Ip(ip) => vec![ip],
|
||||
HostOrIp::Ip(ip) => (vec![ip], DnssecStatus::Indeterminate),
|
||||
};
|
||||
|
||||
tx.send(DeliveryStage::IpLookupSuccess {
|
||||
@@ -484,6 +428,95 @@ async fn delivery_diagnose(
|
||||
})
|
||||
.await?;
|
||||
|
||||
// Fetch TLSA record
|
||||
tx.send(DeliveryStage::TlsaLookupStart).await?;
|
||||
|
||||
let now = Instant::now();
|
||||
let dane_policy = match host.dane_status(addresses_dnssec_status) {
|
||||
(DnssecStatus::Secure, _) => {
|
||||
match server.tlsa_lookup(format!("_25._tcp.{hostname}.")).await {
|
||||
Ok(TlsaResult::Secure(tlsa)) if tlsa.has_end_entities => {
|
||||
tx.send(DeliveryStage::TlsaLookupSuccess {
|
||||
record: tlsa.as_ref().clone(),
|
||||
elapsed: now.elapsed_ms(),
|
||||
})
|
||||
.await?;
|
||||
|
||||
Some(tlsa)
|
||||
}
|
||||
Ok(TlsaResult::Secure(_)) => {
|
||||
tx.send(DeliveryStage::TlsaLookupError {
|
||||
elapsed: now.elapsed_ms(),
|
||||
reason: "TLSA record does not have end entities".to_string(),
|
||||
})
|
||||
.await?;
|
||||
|
||||
None
|
||||
}
|
||||
Ok(TlsaResult::Bogus) => {
|
||||
tx.send(DeliveryStage::TlsaLookupError {
|
||||
elapsed: now.elapsed_ms(),
|
||||
reason: "Bogus TLSA record".to_string(),
|
||||
})
|
||||
.await?;
|
||||
|
||||
continue 'outer;
|
||||
}
|
||||
Ok(TlsaResult::Missing) => {
|
||||
tx.send(DeliveryStage::TlsaNotFound {
|
||||
elapsed: now.elapsed_ms(),
|
||||
reason: "No TLSA DNSSEC records found".to_string(),
|
||||
})
|
||||
.await?;
|
||||
|
||||
None
|
||||
}
|
||||
Err(err) => {
|
||||
if matches!(
|
||||
&err,
|
||||
mail_auth::Error::Dns(mail_auth::DnsError::RecordNotFound(_))
|
||||
) {
|
||||
tx.send(DeliveryStage::TlsaNotFound {
|
||||
elapsed: now.elapsed_ms(),
|
||||
reason: "No TLSA records found for MX".to_string(),
|
||||
})
|
||||
.await?;
|
||||
|
||||
None
|
||||
} else {
|
||||
tx.send(DeliveryStage::TlsaLookupError {
|
||||
elapsed: now.elapsed_ms(),
|
||||
reason: err.to_string(),
|
||||
})
|
||||
.await?;
|
||||
|
||||
continue 'outer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
(DnssecStatus::Bogus, dnssec_entity) => {
|
||||
tx.send(DeliveryStage::TlsaLookupError {
|
||||
elapsed: now.elapsed_ms(),
|
||||
reason: format!("Bogus {dnssec_entity} records were found"),
|
||||
})
|
||||
.await?;
|
||||
|
||||
continue 'outer;
|
||||
}
|
||||
(_, dnssec_entity) => {
|
||||
tx.send(DeliveryStage::TlsaNotFound {
|
||||
elapsed: now.elapsed_ms(),
|
||||
reason: format!(
|
||||
"{dnssec_entity} records are not DNSSEC signed, DANE does not apply"
|
||||
),
|
||||
})
|
||||
.await?;
|
||||
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
for remote_ip in remote_ips {
|
||||
// Start connection
|
||||
tx.send(DeliveryStage::ConnectionStart { remote_ip })
|
||||
|
||||
@@ -36,7 +36,7 @@ use hyper::{
|
||||
server::conn::http1,
|
||||
service::service_fn,
|
||||
};
|
||||
use hyper_util::rt::TokioIo;
|
||||
use hyper_util::rt::{TokioIo, TokioTimer};
|
||||
use jmap::{
|
||||
api::{
|
||||
ToJmapHttpResponse, event_source::EventSourceHandler, request::RequestHandler,
|
||||
@@ -690,6 +690,7 @@ async fn handle_session<T: SessionStream>(inner: Arc<Inner>, session: SessionDat
|
||||
let is_tls = session.stream.is_tls();
|
||||
|
||||
if let Err(http_err) = http1::Builder::new()
|
||||
.timer(TokioTimer::new())
|
||||
.keep_alive(true)
|
||||
.serve_connection(
|
||||
TokioIo::new(session.stream),
|
||||
@@ -875,6 +876,7 @@ async fn handle_session<T: SessionStream>(inner: Arc<Inner>, session: SessionDat
|
||||
)
|
||||
.with_upgrades()
|
||||
.await
|
||||
&& !http_err.is_timeout()
|
||||
{
|
||||
if http_err.is_parse() {
|
||||
let server = inner.build_server();
|
||||
|
||||
Reference in New Issue
Block a user