release: 1.4.0: Optional inbound relay (backup-MX) on port 25

Optional inbound relay (backup-MX / forwarder) on port 25, off by default. Mail is accepted only for domains configured in the panel and forwarded to an upstream; there are still no mailboxes. A dedicated security review of the inbound path is still open on the plan (Fable).
Added:
  - `INBOUND_RELAY_ENABLE` (default `false`): when true, Postfix listens on
  - Panel **Inbound** pages (global administrator): domains, upstream host/port
  - Optional `INBOUND_ANTISPAM_MILTER` on the inbound listener only, plus
  - Inbound rate limit (`INBOUND_RATE_LIMIT_MESSAGES_PER_IP`, default 20) and
Changed:
  - Compose publishes host port 25 even when the flag is off (nothing listens
  - Full-backup restore Resync also rebuilds inbound maps when the flag is on.

Full detail: CHANGELOG.md (1.4.0).
This commit is contained in:
mix
2026-09-22 15:24:52 +03:00
parent 0730b2090e
commit 656c4d4d7f
50 changed files with 2512 additions and 125 deletions
+34
View File
@@ -374,3 +374,37 @@ func TestReportAuth(t *testing.T) {
t.Errorf("advice %q should cite %q", got.Detail, ReportAuthExample())
}
}
func TestInboundMXPointsAtServer(t *testing.T) {
f := &fakeResolver{
mx: map[string][]*net.MX{
"lists.example.com": {
{Host: "mail.primary.example.net.", Pref: 10},
{Host: "mail.example.org.", Pref: 20},
},
},
}
got := newTestChecker(f).InboundMX("lists.example.com", "mail.example.org", false)
if got.Status != health.StatusOK {
t.Fatalf("status = %q (%s)", got.Status, got.Detail)
}
}
func TestInboundMXMissingThisServer(t *testing.T) {
f := &fakeResolver{
mx: map[string][]*net.MX{
"backup.example.net": {{Host: "mail.primary.example.net.", Pref: 10}},
},
}
got := newTestChecker(f).InboundMX("backup.example.net", "mail.example.org", false)
if got.Status != health.StatusError {
t.Fatalf("status = %q, want error", got.Status)
}
}
func TestInboundMXAbsent(t *testing.T) {
got := newTestChecker(&fakeResolver{}).InboundMX("none.example", "mail.example.org", false)
if got.Status != health.StatusError {
t.Fatalf("status = %q, want error", got.Status)
}
}