mirror of
https://blitiri.com.ar/repos/chasquid
synced 2025-12-17 14:37:02 +00:00
dovecot: Dovecot authentication package
This patch adds a new package which implements two basic primitives for
authenticating against dovecot ("user exists", and "check password").
It is still experimental/work in progress.
This commit is contained in:
9
Makefile
9
Makefile
@@ -11,7 +11,7 @@ endif
|
||||
|
||||
default: chasquid
|
||||
|
||||
all: chasquid chasquid-util smtp-check spf-check mda-lmtp
|
||||
all: chasquid chasquid-util smtp-check spf-check mda-lmtp dovecot-auth-cli
|
||||
|
||||
|
||||
chasquid:
|
||||
@@ -33,11 +33,15 @@ spf-check:
|
||||
mda-lmtp:
|
||||
go build ${GOFLAGS} ./cmd/mda-lmtp/
|
||||
|
||||
dovecot-auth-cli:
|
||||
go build ${GOFLAGS} ./cmd/dovecot-auth-cli/
|
||||
|
||||
test:
|
||||
go test ${GOFLAGS} ./...
|
||||
setsid -w ./test/run.sh
|
||||
setsid -w ./cmd/chasquid-util/test.sh
|
||||
setsid -w ./cmd/mda-lmtp/test.sh
|
||||
setsid -w ./cmd/dovecot-auth-cli/test.sh
|
||||
|
||||
|
||||
install-binaries: chasquid chasquid-util smtp-check mda-lmtp
|
||||
@@ -54,4 +58,5 @@ install-config-skeleton:
|
||||
fi
|
||||
|
||||
|
||||
.PHONY: chasquid chasquid-util smtp-check spf-check mda-lmtp test
|
||||
.PHONY: chasquid test \
|
||||
chasquid-util smtp-check spf-check mda-lmtp dovecot-auth-cli
|
||||
|
||||
2
cmd/dovecot-auth-cli/.gitignore
vendored
Normal file
2
cmd/dovecot-auth-cli/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
*.log
|
||||
dovecot-auth-cli
|
||||
36
cmd/dovecot-auth-cli/dovecot-auth-cli.go
Normal file
36
cmd/dovecot-auth-cli/dovecot-auth-cli.go
Normal file
@@ -0,0 +1,36 @@
|
||||
// CLI used for testing the dovecot authentication package.
|
||||
//
|
||||
// NOT for production use.
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"blitiri.com.ar/go/chasquid/internal/dovecot"
|
||||
)
|
||||
|
||||
func main() {
|
||||
a := dovecot.NewAuth(os.Args[1]+"-userdb", os.Args[1]+"-client")
|
||||
|
||||
var ok bool
|
||||
var err error
|
||||
|
||||
switch os.Args[2] {
|
||||
case "exists":
|
||||
ok, err = a.Exists(os.Args[3])
|
||||
case "auth":
|
||||
ok, err = a.Authenticate(os.Args[3], os.Args[4])
|
||||
default:
|
||||
fmt.Printf("unknown subcommand\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if ok {
|
||||
fmt.Printf("yes\n")
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("no: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
21
cmd/dovecot-auth-cli/test.sh
Executable file
21
cmd/dovecot-auth-cli/test.sh
Executable file
@@ -0,0 +1,21 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
. $(dirname ${0})/../../test/util/lib.sh
|
||||
|
||||
init
|
||||
|
||||
# Build the binary once, so we can use it and launch it in chamuyero scripts.
|
||||
# Otherwise, we not only spend time rebuilding it over and over, but also "go
|
||||
# run" masks the exit code, which is something we care about.
|
||||
go build dovecot-auth-cli.go
|
||||
|
||||
for i in *.cmy; do
|
||||
if ! chamuyero $i > $i.log 2>&1 ; then
|
||||
echo "# Test $i failed, log follows"
|
||||
cat $i.log
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
success
|
||||
21
cmd/dovecot-auth-cli/test_auth_error.cmy
Normal file
21
cmd/dovecot-auth-cli/test_auth_error.cmy
Normal file
@@ -0,0 +1,21 @@
|
||||
|
||||
client unix_listen .dovecot-client
|
||||
|
||||
c = ./dovecot-auth-cli .dovecot auth username password
|
||||
|
||||
client -> VERSION 1 1
|
||||
client -> SPID 12345
|
||||
client -> CUID 12345
|
||||
client -> COOKIE lovelycookie
|
||||
client -> MECH PLAIN
|
||||
client -> MECH LOGIN
|
||||
client -> DONE
|
||||
|
||||
client <- VERSION 1 1
|
||||
client <~ CPID
|
||||
|
||||
client <- AUTH 1 PLAIN service=smtp secured no-penalty nologin resp=dXNlcm5hbWUAdXNlcm5hbWUAcGFzc3dvcmQ=
|
||||
client -> OTHER
|
||||
|
||||
c <~ no: invalid response
|
||||
c wait 1
|
||||
21
cmd/dovecot-auth-cli/test_auth_no.cmy
Normal file
21
cmd/dovecot-auth-cli/test_auth_no.cmy
Normal file
@@ -0,0 +1,21 @@
|
||||
|
||||
client unix_listen .dovecot-client
|
||||
|
||||
c = ./dovecot-auth-cli .dovecot auth username password
|
||||
|
||||
client -> VERSION 1 1
|
||||
client -> SPID 12345
|
||||
client -> CUID 12345
|
||||
client -> COOKIE lovelycookie
|
||||
client -> MECH PLAIN
|
||||
client -> MECH LOGIN
|
||||
client -> DONE
|
||||
|
||||
client <- VERSION 1 1
|
||||
client <~ CPID
|
||||
|
||||
client <- AUTH 1 PLAIN service=smtp secured no-penalty nologin resp=dXNlcm5hbWUAdXNlcm5hbWUAcGFzc3dvcmQ=
|
||||
client -> FAIL 1
|
||||
|
||||
c <- no: <nil>
|
||||
c wait 1
|
||||
21
cmd/dovecot-auth-cli/test_auth_yes.cmy
Normal file
21
cmd/dovecot-auth-cli/test_auth_yes.cmy
Normal file
@@ -0,0 +1,21 @@
|
||||
|
||||
client unix_listen .dovecot-client
|
||||
|
||||
c = ./dovecot-auth-cli .dovecot auth username password
|
||||
|
||||
client -> VERSION 1 1
|
||||
client -> SPID 12345
|
||||
client -> CUID 12345
|
||||
client -> COOKIE lovelycookie
|
||||
client -> MECH PLAIN
|
||||
client -> MECH LOGIN
|
||||
client -> DONE
|
||||
|
||||
client <- VERSION 1 1
|
||||
client <~ CPID
|
||||
|
||||
client <- AUTH 1 PLAIN service=smtp secured no-penalty nologin resp=dXNlcm5hbWUAdXNlcm5hbWUAcGFzc3dvcmQ=
|
||||
client -> OK 1
|
||||
|
||||
c <- yes
|
||||
c wait 0
|
||||
16
cmd/dovecot-auth-cli/test_exists_notfound.cmy
Normal file
16
cmd/dovecot-auth-cli/test_exists_notfound.cmy
Normal file
@@ -0,0 +1,16 @@
|
||||
|
||||
userdb unix_listen .dovecot-userdb
|
||||
|
||||
c = ./dovecot-auth-cli .dovecot exists username
|
||||
|
||||
userdb -> VERSION 1 1
|
||||
userdb -> SPID 12345
|
||||
|
||||
userdb <- VERSION 1 1
|
||||
userdb <- USER 1 username service=smtp
|
||||
|
||||
userdb -> NOTFOUND 1
|
||||
|
||||
c wait 1
|
||||
|
||||
c <- no: <nil>
|
||||
15
cmd/dovecot-auth-cli/test_exists_yes.cmy
Normal file
15
cmd/dovecot-auth-cli/test_exists_yes.cmy
Normal file
@@ -0,0 +1,15 @@
|
||||
|
||||
userdb unix_listen .dovecot-userdb
|
||||
|
||||
c = ./dovecot-auth-cli .dovecot exists username
|
||||
|
||||
userdb -> VERSION 1 1
|
||||
userdb -> SPID 12345
|
||||
|
||||
userdb <- VERSION 1 1
|
||||
userdb <- USER 1 username service=smtp
|
||||
|
||||
userdb -> USER 1 username system_groups_user=blah uid=10 gid=10
|
||||
|
||||
c <- yes
|
||||
c wait 0
|
||||
8
cmd/dovecot-auth-cli/test_missing_socket.cmy
Normal file
8
cmd/dovecot-auth-cli/test_missing_socket.cmy
Normal file
@@ -0,0 +1,8 @@
|
||||
|
||||
c = ./dovecot-auth-cli .missingsocket exists username
|
||||
c <~ no: dial unix .missingsocket-userdb
|
||||
c wait 1
|
||||
|
||||
c = ./dovecot-auth-cli .missingsocket auth username password
|
||||
c <~ no: dial unix .missingsocket-client
|
||||
c wait 1
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"blitiri.com.ar/go/chasquid/internal/dovecot"
|
||||
"blitiri.com.ar/go/chasquid/internal/userdb"
|
||||
)
|
||||
|
||||
@@ -119,6 +120,7 @@ func check(t *testing.T, a *Authenticator, user, domain, passwd string, expect b
|
||||
|
||||
func TestInterfaces(t *testing.T) {
|
||||
var _ NoErrorBackend = userdb.New("/dev/null")
|
||||
var _ Backend = dovecot.NewAuth("/dev/null", "/dev/null")
|
||||
}
|
||||
|
||||
// Backend implementation for testing.
|
||||
|
||||
276
internal/dovecot/dovecot.go
Normal file
276
internal/dovecot/dovecot.go
Normal file
@@ -0,0 +1,276 @@
|
||||
// Package dovecot implements functions to interact with Dovecot's
|
||||
// authentication service.
|
||||
//
|
||||
// In particular, it supports doing user authorization, and checking if a user
|
||||
// exists. It is a very basic implementation, with only the minimum needed to
|
||||
// cover chasquid's needs.
|
||||
//
|
||||
// https://wiki.dovecot.org/Design/AuthProtocol
|
||||
// https://wiki.dovecot.org/Services#auth
|
||||
package dovecot
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/textproto"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// Default timeout to use. We expect Dovecot to be quite fast, but don't want
|
||||
// to hang forever if something gets stuck.
|
||||
const DefaultTimeout = 5 * time.Second
|
||||
|
||||
var (
|
||||
ErrUsernameNotSafe = errors.New("username not safe (contains spaces)")
|
||||
)
|
||||
|
||||
var defaultUserdbPaths = []string{
|
||||
"/var/run/dovecot/auth-chasquid-userdb",
|
||||
"/var/run/dovecot/auth-userdb",
|
||||
}
|
||||
|
||||
var defaultClientPaths = []string{
|
||||
"/var/run/dovecot/auth-chasquid-client",
|
||||
"/var/run/dovecot/auth-client",
|
||||
}
|
||||
|
||||
// Auth represents a particular Dovecot auth service to use.
|
||||
type Auth struct {
|
||||
userdbAddr string
|
||||
clientAddr string
|
||||
|
||||
// Timeout for connection and I/O operations (applies on each call).
|
||||
// Set to DefaultTimeout by NewAuth.
|
||||
Timeout time.Duration
|
||||
}
|
||||
|
||||
// NewAuth returns a new connection against Dovecot authentication service. It
|
||||
// takes the addresses of userdb and client sockets (usually paths as
|
||||
// configured in dovecot).
|
||||
func NewAuth(userdb, client string) *Auth {
|
||||
return &Auth{
|
||||
userdbAddr: userdb,
|
||||
clientAddr: client,
|
||||
Timeout: DefaultTimeout,
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Auth) String() string {
|
||||
return fmt.Sprintf("DovecotAuth(%q, %q)", a.userdbAddr, a.clientAddr)
|
||||
}
|
||||
|
||||
// Check to see if this auth is valid (but may not be working).
|
||||
func (a *Auth) Check() error {
|
||||
// We intentionally don't connect or complete any handshakes because
|
||||
// dovecot may not be up yet, even thought it may be configured properly.
|
||||
// Just check that the addresses are valid sockets.
|
||||
if !isUnixSocket(a.userdbAddr) {
|
||||
return fmt.Errorf("userdb is not an unix socket")
|
||||
}
|
||||
if !isUnixSocket(a.clientAddr) {
|
||||
return fmt.Errorf("client is not an unix socket")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Does user exist?
|
||||
func (a *Auth) Exists(user string) (bool, error) {
|
||||
if !isUsernameSafe(user) {
|
||||
return false, ErrUsernameNotSafe
|
||||
}
|
||||
|
||||
conn, err := a.dial("unix", a.userdbAddr)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
// Dovecot greets us with version and server pid.
|
||||
// VERSION\t<major>\t<minor>
|
||||
// SPID\t<pid>
|
||||
err = expect(conn, "VERSION\t1")
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("error receiving version: %v", err)
|
||||
}
|
||||
err = expect(conn, "SPID\t")
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("error receiving SPID: %v", err)
|
||||
}
|
||||
|
||||
// Send our version, and then the request.
|
||||
err = write(conn, "VERSION\t1\t1\n")
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
err = write(conn, fmt.Sprintf("USER\t1\t%s\tservice=smtp\n", user))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// Get the response, and we're done.
|
||||
resp, err := conn.ReadLine()
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("error receiving response: %v", err)
|
||||
} else if strings.HasPrefix(resp, "USER\t1\t"+user+"\t") {
|
||||
return true, nil
|
||||
} else if strings.HasPrefix(resp, "NOTFOUND\t") {
|
||||
return false, nil
|
||||
}
|
||||
return false, fmt.Errorf("invalid response: %q", resp)
|
||||
}
|
||||
|
||||
// Is the password valud for the user?
|
||||
func (a *Auth) Authenticate(user, passwd string) (bool, error) {
|
||||
if !isUsernameSafe(user) {
|
||||
return false, ErrUsernameNotSafe
|
||||
}
|
||||
|
||||
conn, err := a.dial("unix", a.clientAddr)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
// Send our version, and then our PID.
|
||||
err = write(conn, fmt.Sprintf("VERSION\t1\t1\nCPID\t%d\n", os.Getpid()))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// Read the server-side handshake. We don't care about the contents
|
||||
// really, so just read all lines until we see the DONE.
|
||||
for {
|
||||
resp, err := conn.ReadLine()
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("error receiving handshake: %v", err)
|
||||
}
|
||||
if resp == "DONE" {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// We only support PLAIN authentication, so construct the request.
|
||||
// Note we set the "secured" option, with the assumpition that we got the
|
||||
// password via a secure channel (like TLS). This is always true for
|
||||
// chasquid by design, and simplifies the API.
|
||||
// TODO: does dovecot handle utf8 domains well? do we need to encode them
|
||||
// in IDNA first?
|
||||
resp := base64.StdEncoding.EncodeToString(
|
||||
[]byte(fmt.Sprintf("%s\x00%s\x00%s", user, user, passwd)))
|
||||
err = write(conn, fmt.Sprintf(
|
||||
"AUTH\t1\tPLAIN\tservice=smtp\tsecured\tno-penalty\tnologin\tresp=%s\n", resp))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// Get the response, and we're done.
|
||||
resp, err = conn.ReadLine()
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("error receiving response: %v", err)
|
||||
} else if strings.HasPrefix(resp, "OK\t1") {
|
||||
return true, nil
|
||||
} else if strings.HasPrefix(resp, "FAIL\t1") {
|
||||
return false, nil
|
||||
}
|
||||
return false, fmt.Errorf("invalid response: %q", resp)
|
||||
}
|
||||
|
||||
func (a *Auth) Reload() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Auth) dial(network, addr string) (*textproto.Conn, error) {
|
||||
nc, err := net.DialTimeout(network, addr, a.Timeout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nc.SetDeadline(time.Now().Add(a.Timeout))
|
||||
|
||||
return textproto.NewConn(nc), nil
|
||||
}
|
||||
|
||||
func expect(conn *textproto.Conn, prefix string) error {
|
||||
resp, err := conn.ReadLine()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !strings.HasPrefix(resp, prefix) {
|
||||
return fmt.Errorf("got %q", resp)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func write(conn *textproto.Conn, msg string) error {
|
||||
_, err := fmt.Fprintf(conn.W, msg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return conn.W.Flush()
|
||||
}
|
||||
|
||||
// isUsernameSafe to use in the dovecot protocol?
|
||||
// Unfotunately dovecot's protocol is not very robust wrt. whitespace,
|
||||
// so we need to be careful.
|
||||
func isUsernameSafe(user string) bool {
|
||||
for _, r := range user {
|
||||
if unicode.IsSpace(r) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Autodetect where the dovecot authentication paths are, and return an Auth
|
||||
// instance for them. If any of userdb or client are != "", they will be used
|
||||
// and not autodetected.
|
||||
func Autodetect(userdb, client string) *Auth {
|
||||
// If both are given, no need to autodtect.
|
||||
if userdb != "" && client != "" {
|
||||
return NewAuth(userdb, client)
|
||||
}
|
||||
|
||||
var userdbs, clients []string
|
||||
if userdb != "" {
|
||||
userdbs = append(userdbs, userdb)
|
||||
}
|
||||
if client != "" {
|
||||
clients = append(clients, client)
|
||||
}
|
||||
|
||||
if len(userdbs) == 0 {
|
||||
userdbs = append(userdbs, defaultUserdbPaths...)
|
||||
}
|
||||
|
||||
if len(clients) == 0 {
|
||||
clients = append(clients, defaultClientPaths...)
|
||||
}
|
||||
|
||||
// Go through each possiblity, return the first auth that works.
|
||||
for _, u := range userdbs {
|
||||
for _, c := range clients {
|
||||
a := NewAuth(u, c)
|
||||
if a.Check() == nil {
|
||||
return a
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func isUnixSocket(path string) bool {
|
||||
fi, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return fi.Mode()&os.ModeSocket != 0
|
||||
}
|
||||
111
internal/dovecot/dovecot_test.go
Normal file
111
internal/dovecot/dovecot_test.go
Normal file
@@ -0,0 +1,111 @@
|
||||
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) {
|
||||
// If we give both parameters to autodetect, it should return a new Auth
|
||||
// using them, even if they're not valid.
|
||||
a := Autodetect("uDoesNotExist", "cDoesNotExist")
|
||||
if a == nil {
|
||||
t.Errorf("Autodetection with two params failed")
|
||||
} else if *a != *NewAuth("uDoesNotExist", "cDoesNotExist") {
|
||||
t.Errorf("Autodetection with two params: got %v", a)
|
||||
}
|
||||
|
||||
// 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 = Autodetect("", "")
|
||||
if a != nil {
|
||||
t.Errorf("Autodetection worked with only /dev/null, got %v", a)
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
||||
defaultUserdbPaths = append(defaultUserdbPaths, userdb)
|
||||
defaultClientPaths = append(defaultClientPaths, client)
|
||||
|
||||
// Autodetect should work fine against open sockets.
|
||||
a = Autodetect("", "")
|
||||
if a == nil {
|
||||
t.Errorf("Autodetection failed (open sockets)")
|
||||
} else if a.userdbAddr != userdb || a.clientAddr != client {
|
||||
t.Errorf("Expected autodetect to pick {%q, %q}, but got {%q, %q}",
|
||||
userdb, client, a.userdbAddr, a.clientAddr)
|
||||
}
|
||||
|
||||
// TODO: Close the two sockets, and re-do the test from above: Autodetect
|
||||
// should work fine against closed sockets.
|
||||
// To implement this test, we should call SetUnlinkOnClose, but
|
||||
// unfortunately that is only available in Go >= 1.8.
|
||||
// We want to support Go 1.7 for a while as it is in Debian stable; once
|
||||
// Debian stable moves on, we can implement this test easily.
|
||||
|
||||
// Autodetect should pick the suggestions passed as parameters (if
|
||||
// possible).
|
||||
defaultUserdbPaths = []string{"/dev/null"}
|
||||
defaultClientPaths = []string{"/dev/null", client}
|
||||
a = Autodetect(userdb, "")
|
||||
if a == nil {
|
||||
t.Errorf("Autodetection failed (single parameter)")
|
||||
} else if a.userdbAddr != userdb || a.clientAddr != client {
|
||||
t.Errorf("Expected autodetect to pick {%q, %q}, but got {%q, %q}",
|
||||
userdb, client, a.userdbAddr, a.clientAddr)
|
||||
}
|
||||
|
||||
uL.Close()
|
||||
cL.Close()
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user