1
0
mirror of https://blitiri.com.ar/repos/chasquid synced 2025-12-17 14:37:02 +00:00
Files
go-chasquid-smtp/internal/dovecot/dovecot_test.go
Alberto Bertogli fa651e74e3 dovecot: Retry auto-detect until we find a usable socket pair
Currently, chasquid attempts to auto-detect dovecot sockets when
starting up (if needed). If autodetection fails, chasquid emits an
error, continues serving, and never tries again.

This can be problematic if chasquid starts up before dovecot, and at the
time the dovecot sockets are not present (e.g. after a reboot). In that
case, chasquid will not use dovecot for authentication even after
dovecot has started.

This patch changes the autodetect logic, by doing autodetection at
startup and on each request, until we find a working pair of sockets.
Once we do, they're used consistently.

That way, if dovecot is not ready when chasquid starts, it's not a
problem and chasquid will start using dovecot once it becomes available.

Thanks to Thor77 (thor77@thor77.org) for reporting and helping
troubleshoot this issue.
2021-05-24 10:21:33 +01:00

143 lines
4.0 KiB
Go

package dovecot
// The dovecot package is mainly tested via integration/external tests using
// the dovecot-auth-cli tool. See cmd/dovecot-auth-cli for more details.
// The tests here are more narrow and only test specific functionality that is
// easier to cover from Go.
import (
"net"
"testing"
"blitiri.com.ar/go/chasquid/internal/testlib"
)
func TestUsernameNotSafe(t *testing.T) {
a := NewAuth("/tmp/nothing", "/tmp/nothing")
cases := []string{
"a b", " ab", "ab ", "a\tb", "a\t", " ", "\t", "\t "}
for _, c := range cases {
ok, err := a.Authenticate(c, "passwd")
if ok || err != errUsernameNotSafe {
t.Errorf("Authenticate(%q, _): got %v, %v", c, ok, err)
}
ok, err = a.Exists(c)
if ok || err != errUsernameNotSafe {
t.Errorf("Exists(%q): got %v, %v", c, ok, err)
}
}
}
func TestAutodetect(t *testing.T) {
// Check on a pair that does not exist.
a := NewAuth("uDoesNotExist", "cDoesNotExist")
err := a.Check()
if err != errFailedToConnect {
t.Errorf("Expected failure to connect, got %v", err)
}
// We override the default paths, so we can point the "defaults" to our
// test environment as needed.
defaultUserdbPaths = []string{"/dev/null"}
defaultClientPaths = []string{"/dev/null"}
// Autodetect failure: no valid sockets on the list.
a = NewAuth("", "")
err = a.Check()
if err != errNoUserdbSocket {
t.Errorf("Expected failure to find userdb socket, got %v", err)
}
ok, err := a.Exists("user")
if ok != false || err != errNoUserdbSocket {
t.Errorf("Expected {false, no userdb socket}, got {%v, %v}", ok, err)
}
ok, err = a.Authenticate("user", "password")
if ok != false || err != errNoUserdbSocket {
t.Errorf("Expected {false, no userdb socket}, got {%v, %v}", ok, err)
}
// Create a temporary directory, and two sockets on it.
dir := testlib.MustTempDir(t)
defer testlib.RemoveIfOk(t, dir)
userdb := dir + "/userdb"
client := dir + "/client"
uL := mustListen(t, userdb)
cL := mustListen(t, client)
// Autodetect finds the user, but fails to find the client.
defaultUserdbPaths = []string{"/dev/null", userdb}
defaultClientPaths = []string{"/dev/null"}
a = NewAuth("", "")
err = a.Check()
if err != errNoClientSocket {
t.Errorf("Expected failure to find userdb socket, got %v", err)
}
// Autodetect should pick the suggestions passed as parameters (if
// possible).
defaultUserdbPaths = []string{"/dev/null"}
defaultClientPaths = []string{"/dev/null", client}
a = NewAuth(userdb, "")
err = a.Check()
if err != nil {
t.Errorf("Expected successful check, got %v", err)
}
if a.addr.userdb != userdb || a.addr.client != client {
t.Errorf("Expected autodetect to pick {%q, %q}, but got {%q, %q}",
userdb, client, a.addr.userdb, a.addr.client)
}
// Successful autodetection against open sockets.
defaultUserdbPaths = append(defaultUserdbPaths, userdb)
defaultClientPaths = append(defaultClientPaths, client)
a = NewAuth("", "")
err = a.Check()
if err != nil {
t.Errorf("Expected successful check, got %v", err)
}
// Close the two sockets, and re-do the check: now we have pinned the
// paths, and check should fail to connect.
// We need to tell Go to keep the socket files around explicitly, as the
// default is to delete them since they were created by the net library.
uL.SetUnlinkOnClose(false)
uL.Close()
err = a.Check()
if err != errFailedToConnect {
t.Errorf("Expected failed to connect, got %v", err)
}
cL.SetUnlinkOnClose(false)
cL.Close()
err = a.Check()
if err != errFailedToConnect {
t.Errorf("Expected failed to connect, got %v", err)
}
}
func TestReload(t *testing.T) {
// Make sure Reload does not fail.
a := Auth{}
if err := a.Reload(); err != nil {
t.Errorf("Reload failed")
}
}
func mustListen(t *testing.T, path string) *net.UnixListener {
addr, err := net.ResolveUnixAddr("unix", path)
if err != nil {
t.Fatalf("failed to resolve unix addr %q: %v", path, err)
}
l, err := net.ListenUnix("unix", addr)
if err != nil {
t.Fatalf("failed to listen on %q: %v", path, err)
}
return l
}