author | Alberto Bertogli
<albertito@blitiri.com.ar> 2022-01-14 00:58:26 UTC |
committer | Alberto Bertogli
<albertito@blitiri.com.ar> 2022-03-11 20:51:06 UTC |
parent | 3255ff68018bc51908ba2ad864fd8e3d7b876c31 |
cmd/chasquid-util/chasquid-util.go | +4 | -2 |
docs/aliases.md | +11 | -1 |
internal/aliases/aliases.go | +66 | -6 |
internal/aliases/aliases_test.go | +182 | -47 |
internal/aliases/testdata/erroring-hook.sh | +5 | -0 |
internal/queue/queue_test.go | +16 | -7 |
internal/smtpsrv/server.go | +4 | -2 |
diff --git a/cmd/chasquid-util/chasquid-util.go b/cmd/chasquid-util/chasquid-util.go index e932ba8..ff9dcbe 100644 --- a/cmd/chasquid-util/chasquid-util.go +++ b/cmd/chasquid-util/chasquid-util.go @@ -224,7 +224,7 @@ func aliasesResolve() { } _ = os.Chdir(configDir) - r := aliases.NewResolver() + r := aliases.NewResolver(allUsersExist) r.SuffixSep = *conf.SuffixSeparators r.DropChars = *conf.DropCharacters @@ -289,6 +289,8 @@ func domaininfoRemove() { } } +func allUsersExist(user, domain string) (bool, error) { return true, nil } + // chasquid-util aliases-add <source> <target> func aliasesAdd() { source := args["$2"] @@ -315,7 +317,7 @@ func aliasesAdd() { _ = os.Chdir(configDir) // Setup alias resolver. - r := aliases.NewResolver() + r := aliases.NewResolver(allUsersExist) r.SuffixSep = *conf.SuffixSeparators r.DropChars = *conf.DropCharacters diff --git a/docs/aliases.md b/docs/aliases.md index f4ad31a..1a957d0 100644 --- a/docs/aliases.md +++ b/docs/aliases.md @@ -65,6 +65,16 @@ user: | /usr/bin/email-handler --work null: | cat ``` +### Catch-all + +If the aliased user is `*`, then mail sent to an unknown user will not be +rejected, but redirected to the indicated destination instead. + +``` +pepe: jose + +*: pepe, rose@backgarden +``` ## Processing @@ -80,7 +90,7 @@ will fail. If the command exits with an error (non-0 exit code), the delivery will be considered failed. The `chasquid-util` command-line tool can be used to check and resolve -aliases. +aliases. Note that it doesn't run aliases hooks, or handle catch-all aliases. ## Hooks diff --git a/internal/aliases/aliases.go b/internal/aliases/aliases.go index 02079cd..4e62e50 100644 --- a/internal/aliases/aliases.go +++ b/internal/aliases/aliases.go @@ -24,6 +24,9 @@ // Usually there will be one database per domain, and there's no need to // include the "@" in the user (in this case, "@" will be forbidden). // +// If the user is the string "*", then it is considered a "catch-all alias": +// emails that don't match any known users or other aliases will be sent here. +// // // Recipients // @@ -104,6 +107,9 @@ var ( recursionLimit = 10 ) +// Type of the "does this user exist" function", for convenience. +type existsFn func(user, domain string) (bool, error) + // Resolver represents the aliases resolver. type Resolver struct { // Suffix separator, to perform suffix removal. @@ -115,6 +121,9 @@ type Resolver struct { // Path to the resolve hook. ResolveHook string + // Function to check if a user exists in the userdb. + userExistsInDB existsFn + // Map of domain -> alias files for that domain. // We keep track of them for reloading purposes. files map[string][]string @@ -128,11 +137,13 @@ type Resolver struct { } // NewResolver returns a new, empty Resolver. -func NewResolver() *Resolver { +func NewResolver(userExists existsFn) *Resolver { return &Resolver{ files: map[string][]string{}, domains: map[string]bool{}, aliases: map[string][]Recipient{}, + + userExistsInDB: userExists, } } @@ -155,7 +166,17 @@ func (v *Resolver) Exists(addr string) (string, bool) { addr = v.cleanIfLocal(addr) rcpts, _ := v.lookup(addr, tr) - return addr, len(rcpts) > 0 + if len(rcpts) > 0 { + return addr, true + } + + domain := envelope.DomainOf(addr) + catchAll, _ := v.lookup("*@"+domain, tr) + if len(catchAll) > 0 { + return addr, true + } + + return addr, false } func (v *Resolver) lookup(addr string, tr *trace.Trace) ([]Recipient, error) { @@ -183,7 +204,8 @@ func (v *Resolver) resolve(rcount int, addr string, tr *trace.Trace) ([]Recipien // If the address is not local, we return it as-is, so delivery is // attempted against it. // Example: an alias that resolves to a non-local address. - if _, ok := v.domains[envelope.DomainOf(addr)]; !ok { + user, domain := envelope.Split(addr) + if _, ok := v.domains[domain]; !ok { tr.Debugf("%d| non-local domain, returning %q", rcount, addr) return []Recipient{{addr, EMAIL}}, nil } @@ -200,9 +222,43 @@ func (v *Resolver) resolve(rcount int, addr string, tr *trace.Trace) ([]Recipien return nil, err } + // No alias for this local address. if len(rcpts) == 0 { - tr.Debugf("%d| no aliases found, returning %q", rcount, addr) - return []Recipient{{addr, EMAIL}}, nil + tr.Debugf("%d| no alias found", rcount) + // If the user exists, then use it as-is, no need to recurse further. + ok, err := v.userExistsInDB(user, domain) + if err != nil { + tr.Debugf("%d| error checking if user exists: %v", rcount, err) + return nil, err + } + if ok { + tr.Debugf("%d| user exists, returning %q", rcount, addr) + return []Recipient{{addr, EMAIL}}, nil + } + + catchAll, err := v.lookup("*@"+domain, tr) + if err != nil { + tr.Debugf("%d| error in catchall lookup: %v", rcount, err) + return nil, err + } + if len(catchAll) > 0 { + // If there's a catch-all, then use it and keep resolving + // recursively (since the catch-all destination could be an + // alias). + tr.Debugf("%d| using catch-all: %v", rcount, catchAll) + rcpts = catchAll + } else { + // Otherwise, return the original address unchanged. + // The caller will handle that situation, and we don't need to + // invalidate the whole resolution (there could be other valid + // aliases). + // The queue will attempt delivery against this local (but + // evidently non-existing) address, and the courier will emit a + // clearer failure, re-using the existing codepaths and + // simplifying the logic. + tr.Debugf("%d| no catch-all, returning %q", rcount, addr) + return []Recipient{{addr, EMAIL}}, nil + } } ret := []Recipient{} @@ -229,7 +285,11 @@ func (v *Resolver) resolve(rcount int, addr string, tr *trace.Trace) ([]Recipien func (v *Resolver) cleanIfLocal(addr string) string { user, domain := envelope.Split(addr) - if !v.domains[domain] { + v.mu.Lock() + isLocal := v.domains[domain] + v.mu.Unlock() + + if !isLocal { return addr } diff --git a/internal/aliases/aliases_test.go b/internal/aliases/aliases_test.go index 49118de..a0e091b 100644 --- a/internal/aliases/aliases_test.go +++ b/internal/aliases/aliases_test.go @@ -1,27 +1,32 @@ package aliases import ( + "errors" "io/ioutil" "os" + "os/exec" "reflect" + "strings" "testing" ) type Cases []struct { addr string expect []Recipient + err error } func (cases Cases) check(t *testing.T, r *Resolver) { t.Helper() for _, c := range cases { got, err := r.Resolve(c.addr) - if err != nil { - t.Errorf("case %q, got error: %v", c.addr, err) - continue + if err != c.err { + t.Errorf("case %q: expected error %v, got %v", + c.addr, c.err, err) } if !reflect.DeepEqual(got, c.expect) { - t.Errorf("case %q, got %+v, expected %+v", c.addr, got, c.expect) + t.Errorf("case %q: got %+v, expected %+v", + c.addr, got, c.expect) } } } @@ -44,8 +49,31 @@ func mustNotExist(t *testing.T, r *Resolver, addrs ...string) { } } +func allUsersExist(user, domain string) (bool, error) { + return true, nil +} + +func usersWithXDontExist(user, domain string) (bool, error) { + if strings.HasPrefix(user, "x") { + return false, nil + } + return true, nil +} + +var userLookupError = errors.New("test error userLookupError") + +func usersWithXErrorYDontExist(user, domain string) (bool, error) { + if strings.HasPrefix(user, "x") { + return false, userLookupError + } + if strings.HasPrefix(user, "y") { + return false, nil + } + return true, nil +} + func TestBasic(t *testing.T) { - resolver := NewResolver() + resolver := NewResolver(allUsersExist) resolver.AddDomain("localA") resolver.AddDomain("localB") resolver.aliases = map[string][]Recipient{ @@ -55,9 +83,9 @@ func TestBasic(t *testing.T) { } cases := Cases{ - {"a@localA", []Recipient{{"c@d", EMAIL}, {"cmd", PIPE}}}, - {"e@localB", []Recipient{{"cmd", PIPE}}}, - {"x@y", []Recipient{{"x@y", EMAIL}}}, + {"a@localA", []Recipient{{"c@d", EMAIL}, {"cmd", PIPE}}, nil}, + {"e@localB", []Recipient{{"cmd", PIPE}}, nil}, + {"x@y", []Recipient{{"x@y", EMAIL}}, nil}, } cases.check(t, resolver) @@ -65,8 +93,59 @@ func TestBasic(t *testing.T) { mustNotExist(t, resolver, "x@y") } +func TestCatchAll(t *testing.T) { + resolver := NewResolver(usersWithXDontExist) + resolver.AddDomain("dom") + resolver.aliases = map[string][]Recipient{ + "a@dom": {{"a@remote", EMAIL}}, + "b@dom": {{"c@dom", EMAIL}}, + "c@dom": {{"cmd", PIPE}}, + "*@dom": {{"c@dom", EMAIL}}, + } + + cases := Cases{ + {"a@dom", []Recipient{{"a@remote", EMAIL}}, nil}, + {"b@dom", []Recipient{{"cmd", PIPE}}, nil}, + {"c@dom", []Recipient{{"cmd", PIPE}}, nil}, + {"x@dom", []Recipient{{"cmd", PIPE}}, nil}, + + // Remote should be returned as-is regardless. + {"a@remote", []Recipient{{"a@remote", EMAIL}}, nil}, + {"x@remote", []Recipient{{"x@remote", EMAIL}}, nil}, + } + cases.check(t, resolver) + + mustExist(t, resolver, + // Exist as users. + "a@dom", "b@dom", "c@dom", + + // Do not exist as users, but catch-all saves them. + "x@dom", "x1@dom") +} + +func TestUserLookupErrors(t *testing.T) { + resolver := NewResolver(usersWithXErrorYDontExist) + resolver.AddDomain("dom") + resolver.aliases = map[string][]Recipient{ + "a@dom": {{"a@remote", EMAIL}}, + "b@dom": {{"x@dom", EMAIL}}, + "*@dom": {{"x@dom", EMAIL}}, + } + + cases := Cases{ + {"a@dom", []Recipient{{"a@remote", EMAIL}}, nil}, + {"b@dom", nil, userLookupError}, + {"c@dom", []Recipient{{"c@dom", EMAIL}}, nil}, + {"x@dom", nil, userLookupError}, + + // This one goes through the catch-all. + {"y@dom", nil, userLookupError}, + } + cases.check(t, resolver) +} + func TestAddrRewrite(t *testing.T) { - resolver := NewResolver() + resolver := NewResolver(allUsersExist) resolver.AddDomain("def") resolver.AddDomain("p-q.com") resolver.aliases = map[string][]Recipient{ @@ -79,36 +158,36 @@ func TestAddrRewrite(t *testing.T) { resolver.SuffixSep = "-+" cases := Cases{ - {"abc@def", []Recipient{{"x@y", EMAIL}}}, - {"a.b.c@def", []Recipient{{"x@y", EMAIL}}}, - {"a~b~c@def", []Recipient{{"x@y", EMAIL}}}, - {"a.b~c@def", []Recipient{{"x@y", EMAIL}}}, - {"abc-ñaca@def", []Recipient{{"x@y", EMAIL}}}, - {"abc-ñaca@def", []Recipient{{"x@y", EMAIL}}}, - {"abc-xyz@def", []Recipient{{"x@y", EMAIL}}}, - {"abc+xyz@def", []Recipient{{"x@y", EMAIL}}}, - {"abc-x.y+z@def", []Recipient{{"x@y", EMAIL}}}, - - {"ñ.o~ño-ñaca@def", []Recipient{{"x@y", EMAIL}}}, + {"abc@def", []Recipient{{"x@y", EMAIL}}, nil}, + {"a.b.c@def", []Recipient{{"x@y", EMAIL}}, nil}, + {"a~b~c@def", []Recipient{{"x@y", EMAIL}}, nil}, + {"a.b~c@def", []Recipient{{"x@y", EMAIL}}, nil}, + {"abc-ñaca@def", []Recipient{{"x@y", EMAIL}}, nil}, + {"abc-ñaca@def", []Recipient{{"x@y", EMAIL}}, nil}, + {"abc-xyz@def", []Recipient{{"x@y", EMAIL}}, nil}, + {"abc+xyz@def", []Recipient{{"x@y", EMAIL}}, nil}, + {"abc-x.y+z@def", []Recipient{{"x@y", EMAIL}}, nil}, + + {"ñ.o~ño-ñaca@def", []Recipient{{"x@y", EMAIL}}, nil}, // Don't mess with the domain, even if it's known. - {"a.bc-ñaca@p-q.com", []Recipient{{"abc@p-q.com", EMAIL}}}, + {"a.bc-ñaca@p-q.com", []Recipient{{"abc@p-q.com", EMAIL}}, nil}, // Clean the right hand side too (if it's a local domain). - {"recu+blah@def", []Recipient{{"ab@p-q.com", EMAIL}}}, + {"recu+blah@def", []Recipient{{"ab@p-q.com", EMAIL}}, nil}, // We should not mess with emails for domains we don't know. - {"xy@z.com", []Recipient{{"xy@z.com", EMAIL}}}, - {"x.y@z.com", []Recipient{{"x.y@z.com", EMAIL}}}, - {"x-@y-z.com", []Recipient{{"x-@y-z.com", EMAIL}}}, - {"x+blah@y", []Recipient{{"x+blah@y", EMAIL}}}, - {"remo@def", []Recipient{{"x-@y-z.com", EMAIL}}}, + {"xy@z.com", []Recipient{{"xy@z.com", EMAIL}}, nil}, + {"x.y@z.com", []Recipient{{"x.y@z.com", EMAIL}}, nil}, + {"x-@y-z.com", []Recipient{{"x-@y-z.com", EMAIL}}, nil}, + {"x+blah@y", []Recipient{{"x+blah@y", EMAIL}}, nil}, + {"remo@def", []Recipient{{"x-@y-z.com", EMAIL}}, nil}, } cases.check(t, resolver) } func TestExistsRewrite(t *testing.T) { - resolver := NewResolver() + resolver := NewResolver(allUsersExist) resolver.AddDomain("def") resolver.AddDomain("p-q.com") resolver.aliases = map[string][]Recipient{ @@ -150,7 +229,7 @@ func TestExistsRewrite(t *testing.T) { } func TestTooMuchRecursion(t *testing.T) { - resolver := NewResolver() + resolver := NewResolver(allUsersExist) resolver.AddDomain("b") resolver.AddDomain("d") resolver.aliases = map[string][]Recipient{ @@ -168,6 +247,34 @@ func TestTooMuchRecursion(t *testing.T) { } } +func TestTooMuchRecursionOnCatchAll(t *testing.T) { + resolver := NewResolver(usersWithXDontExist) + resolver.AddDomain("dom") + resolver.aliases = map[string][]Recipient{ + "a@dom": {{"x@dom", EMAIL}}, + "*@dom": {{"a@dom", EMAIL}}, + } + + cases := Cases{ + // b@dom is local and exists. + {"b@dom", []Recipient{{"b@dom", EMAIL}}, nil}, + + // a@remote is remote. + {"a@remote", []Recipient{{"a@remote", EMAIL}}, nil}, + } + cases.check(t, resolver) + + for _, addr := range []string{"a@dom", "x@dom", "xx@dom"} { + rs, err := resolver.Resolve(addr) + if err != ErrRecursionLimitExceeded { + t.Errorf("%s: expected ErrRecursionLimitExceeded, got %v", addr, err) + } + if rs != nil { + t.Errorf("%s: expected nil recipients, got %+v", addr, rs) + } + } +} + func mustWriteFile(t *testing.T, content string) string { f, err := ioutil.TempFile("", "aliases_test") if err != nil { @@ -217,7 +324,7 @@ func TestAddFile(t *testing.T) { fname := mustWriteFile(t, c.contents) defer os.Remove(fname) - resolver := NewResolver() + resolver := NewResolver(allUsersExist) err := resolver.AddAliasesFile("dom", fname) if err != nil { t.Fatalf("error adding file: %v", err) @@ -260,20 +367,20 @@ func TestRichFile(t *testing.T) { fname := mustWriteFile(t, richFileContents) defer os.Remove(fname) - resolver := NewResolver() + resolver := NewResolver(allUsersExist) err := resolver.AddAliasesFile("dom", fname) if err != nil { t.Fatalf("failed to add file: %v", err) } cases := Cases{ - {"a@dom", []Recipient{{"b@dom", EMAIL}}}, - {"c@dom", []Recipient{{"d@e", EMAIL}, {"f@dom", EMAIL}}}, - {"x@dom", []Recipient{{"command", PIPE}}}, - {"o1@dom", []Recipient{{"b@dom", EMAIL}}}, - {"aA@dom", []Recipient{{"bb@dom-b", EMAIL}}}, - {"aa@dom", []Recipient{{"bb@dom-b", EMAIL}}}, - {"y@dom", []Recipient{{"z@dom", EMAIL}}}, + {"a@dom", []Recipient{{"b@dom", EMAIL}}, nil}, + {"c@dom", []Recipient{{"d@e", EMAIL}, {"f@dom", EMAIL}}, nil}, + {"x@dom", []Recipient{{"command", PIPE}}, nil}, + {"o1@dom", []Recipient{{"b@dom", EMAIL}}, nil}, + {"aA@dom", []Recipient{{"bb@dom-b", EMAIL}}, nil}, + {"aa@dom", []Recipient{{"bb@dom-b", EMAIL}}, nil}, + {"y@dom", []Recipient{{"z@dom", EMAIL}}, nil}, } cases.check(t, resolver) } @@ -293,7 +400,7 @@ func TestManyFiles(t *testing.T) { defer os.Remove(fname) } - resolver := NewResolver() + resolver := NewResolver(allUsersExist) for domain, fname := range files { err := resolver.AddAliasesFile(domain, fname) if err != nil { @@ -303,14 +410,14 @@ func TestManyFiles(t *testing.T) { check := func() { cases := Cases{ - {"a@d1", []Recipient{{"b@d1", EMAIL}}}, - {"c@d1", []Recipient{{"d@e", EMAIL}}}, - {"x@d1", []Recipient{{"x@d1", EMAIL}}}, - {"a@domain2", []Recipient{{"b@domain2", EMAIL}}}, - {"c@domain2", []Recipient{{"d@e", EMAIL}}}, - {"x@dom3", []Recipient{{"y@dom3", EMAIL}, {"z@dom3", EMAIL}}}, - {"a@dom4", []Recipient{{"cmd", PIPE}}}, - {"a@xd1", []Recipient{{"cmd", PIPE}}}, + {"a@d1", []Recipient{{"b@d1", EMAIL}}, nil}, + {"c@d1", []Recipient{{"d@e", EMAIL}}, nil}, + {"x@d1", []Recipient{{"x@d1", EMAIL}}, nil}, + {"a@domain2", []Recipient{{"b@domain2", EMAIL}}, nil}, + {"c@domain2", []Recipient{{"d@e", EMAIL}}, nil}, + {"x@dom3", []Recipient{{"y@dom3", EMAIL}, {"z@dom3", EMAIL}}, nil}, + {"a@dom4", []Recipient{{"cmd", PIPE}}, nil}, + {"a@xd1", []Recipient{{"cmd", PIPE}}, nil}, } cases.check(t, resolver) } @@ -324,3 +431,31 @@ func TestManyFiles(t *testing.T) { check() } + +func TestHookError(t *testing.T) { + resolver := NewResolver(allUsersExist) + resolver.AddDomain("localA") + resolver.aliases = map[string][]Recipient{ + "a@localA": {{"c@d", EMAIL}}, + } + + // First check that the test is set up reasonably. + mustExist(t, resolver, "a@localA") + Cases{ + {"a@localA", []Recipient{{"c@d", EMAIL}}, nil}, + }.check(t, resolver) + + // Now use a resolver that exits with an error. + resolver.ResolveHook = "testdata/erroring-hook.sh" + + // Check that the hook is run and the error is propagated. + mustNotExist(t, resolver, "a@localA") + rcpts, err := resolver.Resolve("a@localA") + if len(rcpts) != 0 { + t.Errorf("expected no recipients, got %v", rcpts) + } + execErr := &exec.ExitError{} + if !errors.As(err, &execErr) { + t.Errorf("expected *exec.ExitError, got %T - %v", err, err) + } +} diff --git a/internal/aliases/testdata/erroring-hook.sh b/internal/aliases/testdata/erroring-hook.sh new file mode 100755 index 0000000..551a5b6 --- /dev/null +++ b/internal/aliases/testdata/erroring-hook.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +# Hook that always returns error. +# This could be replaced by /bin/false, but that doesn't work on freebsd. +exit 1 diff --git a/internal/queue/queue_test.go b/internal/queue/queue_test.go index 9782c68..acef9cd 100644 --- a/internal/queue/queue_test.go +++ b/internal/queue/queue_test.go @@ -12,12 +12,15 @@ import ( "blitiri.com.ar/go/chasquid/internal/testlib" ) +func allUsersExist(user, domain string) (bool, error) { return true, nil } + func TestBasic(t *testing.T) { dir := testlib.MustTempDir(t) defer testlib.RemoveIfOk(t, dir) localC := testlib.NewTestCourier() remoteC := testlib.NewTestCourier() - q, _ := New(dir, set.NewString("loco"), aliases.NewResolver(), + q, _ := New(dir, set.NewString("loco"), + aliases.NewResolver(allUsersExist), localC, remoteC) localC.Expect(2) @@ -67,7 +70,8 @@ func TestDSNOnTimeout(t *testing.T) { remoteC := testlib.NewTestCourier() dir := testlib.MustTempDir(t) defer testlib.RemoveIfOk(t, dir) - q, _ := New(dir, set.NewString("loco"), aliases.NewResolver(), + q, _ := New(dir, set.NewString("loco"), + aliases.NewResolver(allUsersExist), localC, remoteC) // Insert an expired item in the queue. @@ -111,7 +115,8 @@ func TestAliases(t *testing.T) { remoteC := testlib.NewTestCourier() dir := testlib.MustTempDir(t) defer testlib.RemoveIfOk(t, dir) - q, _ := New(dir, set.NewString("loco"), aliases.NewResolver(), + q, _ := New(dir, set.NewString("loco"), + aliases.NewResolver(allUsersExist), localC, remoteC) q.aliases.AddDomain("loco") @@ -155,7 +160,8 @@ func TestAliases(t *testing.T) { func TestFullQueue(t *testing.T) { dir := testlib.MustTempDir(t) defer testlib.RemoveIfOk(t, dir) - q, _ := New(dir, set.NewString(), aliases.NewResolver(), + q, _ := New(dir, set.NewString(), + aliases.NewResolver(allUsersExist), testlib.DumbCourier, testlib.DumbCourier) // Force-insert maxQueueSize items in the queue. @@ -197,7 +203,8 @@ func TestFullQueue(t *testing.T) { func TestPipes(t *testing.T) { dir := testlib.MustTempDir(t) defer testlib.RemoveIfOk(t, dir) - q, _ := New(dir, set.NewString("loco"), aliases.NewResolver(), + q, _ := New(dir, set.NewString("loco"), + aliases.NewResolver(allUsersExist), testlib.DumbCourier, testlib.DumbCourier) item := &Item{ Message: Message{ @@ -219,7 +226,8 @@ func TestBadPath(t *testing.T) { // A new queue will attempt to os.MkdirAll the path. // We expect this path to fail. _, err := New("/proc/doesnotexist", set.NewString("loco"), - aliases.NewResolver(), testlib.DumbCourier, testlib.DumbCourier) + aliases.NewResolver(allUsersExist), + testlib.DumbCourier, testlib.DumbCourier) if err == nil { t.Errorf("could create queue, expected permission denied") } @@ -270,7 +278,8 @@ func TestSerialization(t *testing.T) { // Create the queue; should load the remoteC := testlib.NewTestCourier() remoteC.Expect(1) - q, _ := New(dir, set.NewString("loco"), aliases.NewResolver(), + q, _ := New(dir, set.NewString("loco"), + aliases.NewResolver(allUsersExist), testlib.DumbCourier, remoteC) q.Load() diff --git a/internal/smtpsrv/server.go b/internal/smtpsrv/server.go index b4a5509..80484a4 100644 --- a/internal/smtpsrv/server.go +++ b/internal/smtpsrv/server.go @@ -76,6 +76,8 @@ type Server struct { // NewServer returns a new empty Server. func NewServer() *Server { + authr := auth.NewAuthenticator() + aliasesR := aliases.NewResolver(authr.Exists) return &Server{ addrs: map[SocketMode][]string{}, listeners: map[SocketMode][]net.Listener{}, @@ -83,8 +85,8 @@ func NewServer() *Server { connTimeout: 20 * time.Minute, commandTimeout: 1 * time.Minute, localDomains: &set.String{}, - authr: auth.NewAuthenticator(), - aliasesR: aliases.NewResolver(), + authr: authr, + aliasesR: aliasesR, } }