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:
@@ -0,0 +1,111 @@
|
||||
package inbound
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/mixeme/selfpost/internal/postfix"
|
||||
"github.com/mixeme/selfpost/internal/store"
|
||||
)
|
||||
|
||||
type fakeMaps struct {
|
||||
routes []postfix.InboundRoute
|
||||
err error
|
||||
rebuild int
|
||||
}
|
||||
|
||||
func (f *fakeMaps) RebuildInboundMaps(routes []postfix.InboundRoute) error {
|
||||
f.rebuild++
|
||||
f.routes = append([]postfix.InboundRoute(nil), routes...)
|
||||
return f.err
|
||||
}
|
||||
|
||||
func testService(t *testing.T) (*Service, *store.Store, *fakeMaps) {
|
||||
t.Helper()
|
||||
st, err := store.Open(filepath.Join(t.TempDir(), "test.db"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Cleanup(func() { st.Close() })
|
||||
m := &fakeMaps{}
|
||||
return NewService(st, m), st, m
|
||||
}
|
||||
|
||||
func TestAddAndSetTransport(t *testing.T) {
|
||||
s, _, m := testService(t)
|
||||
d, err := s.Add("Lists.Example.COM")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if d.Name != "lists.example.com" {
|
||||
t.Fatalf("name = %q", d.Name)
|
||||
}
|
||||
if m.rebuild != 1 {
|
||||
t.Fatalf("rebuild after add = %d", m.rebuild)
|
||||
}
|
||||
// Empty host is omitted from maps.
|
||||
if len(m.routes) != 1 || m.routes[0].Host != "" {
|
||||
t.Fatalf("routes after add: %+v", m.routes)
|
||||
}
|
||||
|
||||
if err := s.SetTransport(d.ID, "10.0.0.8", "25", store.TLSModeEncrypt); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if m.routes[0].Host != "10.0.0.8" || m.routes[0].TLSMode != store.TLSModeEncrypt {
|
||||
t.Fatalf("routes after transport: %+v", m.routes[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetRecipientsValidatesDomain(t *testing.T) {
|
||||
s, _, _ := testService(t)
|
||||
d, err := s.Add("lists.example.com")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = s.SetRecipients(d.ID, store.RecipientModeList, []string{"staff@other.com"})
|
||||
if err == nil {
|
||||
t.Fatal("expected foreign-domain error")
|
||||
}
|
||||
err = s.SetRecipients(d.ID, store.RecipientModeList, nil)
|
||||
if err == nil {
|
||||
t.Fatal("expected empty-list error")
|
||||
}
|
||||
if err := s.SetRecipients(d.ID, store.RecipientModeList, []string{"staff@lists.example.com"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRejectsUnsafeHost(t *testing.T) {
|
||||
s, _, _ := testService(t)
|
||||
d, err := s.Add("lists.example.com")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := s.SetTransport(d.ID, "10.0.0.8; rm", "25", store.TLSModeMay); err == nil {
|
||||
t.Fatal("expected unsafe host to be rejected")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteResyncs(t *testing.T) {
|
||||
s, _, m := testService(t)
|
||||
d, err := s.Add("lists.example.com")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := s.Delete(d.ID); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !errors.Is(mustGet(t, s, d.ID), store.ErrInboundDomainNotFound) {
|
||||
t.Fatal("domain still present")
|
||||
}
|
||||
if len(m.routes) != 0 {
|
||||
t.Fatalf("maps after delete: %+v", m.routes)
|
||||
}
|
||||
}
|
||||
|
||||
func mustGet(t *testing.T, s *Service, id int64) error {
|
||||
t.Helper()
|
||||
_, err := s.Get(id)
|
||||
return err
|
||||
}
|
||||
Reference in New Issue
Block a user