Fix MTA: Relay routes are rejected with host resolves loopback address, which prevents relaying through a local proxy or tunnel

This commit is contained in:
Maurus Decimus
2026-08-23 16:28:07 +02:00
parent 54f72da768
commit 237d90f164
3 changed files with 10 additions and 3 deletions
+1
View File
@@ -34,6 +34,7 @@ If you are upgrading from v0.16.x, replace the binary (or run `docker pull`). If
- MTA:
- A domain `catchAllAddress` pointing to a mailing list or a sub-addressed mailbox is accepted at `RCPT TO` and then rejected at local delivery with `550 5.5.0 Mailbox not found`.
- `is_local_address()` and `is_local_domain()` expression functions do not match an address or domain spelled with uppercase characters.
- Relay routes are rejected with `host resolves loopback address`, which prevents relaying through a local proxy or tunnel.
- MySQL, MariaDB & PostgreSQL: Range scans, range deletions and store purges run as a single unbounded statement, so on servers that enforce a statement timeout they abort on large accounts and tasks such as account deletion can never complete. Scans now resume from the last key read and deletions fall back to bounded chunks when the server aborts a statement.
- Network: `local_port` and `local_ip` report the address Stalwart is bound to rather than the address the client connected to when the connection arrives through a trusted proxy.
- Search index:
+1 -3
View File
@@ -101,7 +101,6 @@ impl DnsLookup for Server {
}
}
#[allow(unused_mut)]
async fn resolve_host(
&self,
remote_host: &NextHop<'_>,
@@ -147,8 +146,7 @@ impl DnsLookup for Server {
};
if !remote_ips.is_empty() {
#[cfg(not(feature = "test_mode"))]
if remote_ips.iter().any(|ip| ip.is_loopback()) {
if !remote_host.allow_loopback() && remote_ips.iter().any(|ip| ip.is_loopback()) {
remote_ips.retain(|ip| !ip.is_loopback());
if remote_ips.is_empty() {
return Err(Status::PermanentFailure(ErrorDetails {
+8
View File
@@ -305,6 +305,14 @@ impl NextHop<'_> {
}
}
#[inline(always)]
fn allow_loopback(&self) -> bool {
match self {
NextHop::MX { .. } => cfg!(feature = "test_mode"),
NextHop::Relay(_) => true,
}
}
#[inline(always)]
fn credentials(&self) -> Option<&Credentials> {
match self {