1
0
mirror of https://blitiri.com.ar/repos/chasquid synced 2026-10-28 15:02:28 +00:00
Files
go-chasquid-smtp/internal/smtpsrv/server_test.go
T
Alberto Bertogli 9de93edf77 smtpsrv: Reject MAIL FROM/RCPT TO addresses with control characters
Today we rely on net/mail.ParseAddress to do most of the validation of
the addresses, and it is quite effective at it. But it does not reject
addresses with control characters, which could end up verbatim in
problematic places (e.g. logs).

Specifically the C1 characters, like U+009B (Control Sequence
Introducer) will pass through and could be problematic, since it is
interpreted by some terminals.

It also allows (quoted) parenthesis, which could confuse Received header
parsers. This is mainly harmless, but it can be confusing when
troubleshooting.

Today, we also reject addresses with more than one @, but as a
side-effect of domain validation.

To reduce the chances of problems later, this patch rejects addresses if
they contain control characters (which is a behaviour change), or more
than one @ (which is pre-existing, but now we fail in a more explicit
way).

It also escapes the addresses containing parenthesis in the Received
header, to make debugging info clearer, and keep the header structure
compliant.
2026-10-12 20:47:51 +01:00

882 lines
21 KiB
Go

package smtpsrv
import (
"crypto/tls"
"errors"
"flag"
"fmt"
"io"
"io/fs"
"net"
"net/smtp"
"net/textproto"
"os"
"strings"
"syscall"
"testing"
"time"
"blitiri.com.ar/go/chasquid/internal/aliases"
"blitiri.com.ar/go/chasquid/internal/auth"
"blitiri.com.ar/go/chasquid/internal/domaininfo"
"blitiri.com.ar/go/chasquid/internal/maillog"
"blitiri.com.ar/go/chasquid/internal/testlib"
"blitiri.com.ar/go/chasquid/internal/userdb"
)
// Flags.
var (
externalSMTPAddr = flag.String("external_smtp_addr", "",
"SMTP server address to test (defaults to use internal)")
externalSubmissionAddr = flag.String("external_submission_addr", "",
"submission server address to test (defaults to use internal)")
externalSubmissionTLSAddr = flag.String("external_submission_tls_addr", "",
"submission+TLS server address to test (defaults to use internal)")
)
var (
// Server addresses. Will be filled in at init time.
// We default to internal ones, but may get overridden via flags.
smtpAddr = ""
submissionAddr = ""
submissionTLSAddr = ""
// TLS configuration to use in the clients.
// Will contain the generated server certificate as root CA.
tlsConfig *tls.Config
// Test couriers, so we can validate that emails got sent.
localC = testlib.NewTestCourier()
remoteC = testlib.NewTestCourier()
// Max data size, in MiB.
maxDataSizeMiB = 5
)
//
// === Tests ===
//
func mustDial(tb testing.TB, mode SocketMode, startTLS bool) *smtp.Client {
addr := ""
switch mode {
case ModeSMTP:
addr = smtpAddr
case ModeSubmission:
addr = submissionAddr
case ModeSubmissionTLS:
addr = submissionTLSAddr
}
var err error
var conn net.Conn
if mode.TLS {
conn, err = tls.Dial("tcp", addr, tlsConfig)
} else {
conn, err = net.Dial("tcp", addr)
}
if err != nil {
tb.Fatalf("(net||tls).Dial: %v", err)
}
c, err := smtp.NewClient(conn, "127.0.0.1")
if err != nil {
tb.Fatalf("smtp.Dial: %v", err)
}
if err = c.Hello("test"); err != nil {
tb.Fatalf("c.Hello: %v", err)
}
if startTLS {
if ok, _ := c.Extension("STARTTLS"); !ok {
tb.Fatalf("STARTTLS not advertised in EHLO")
}
if err = c.StartTLS(tlsConfig); err != nil {
tb.Fatalf("StartTLS: %v", err)
}
}
return c
}
func sendEmail(tb testing.TB, c *smtp.Client) {
sendEmailWithAuth(tb, c, nil)
}
func sendEmailWithAuth(tb testing.TB, c *smtp.Client, auth smtp.Auth) {
var err error
from := "from@from"
if auth != nil {
if err = c.Auth(auth); err != nil {
tb.Errorf("Auth: %v", err)
}
// If we authenticated, we must use the user as from, as the server
// checks otherwise.
from = "testuser@localhost"
}
if err = c.Mail(from); err != nil {
tb.Errorf("Mail: %v", err)
}
if err = c.Rcpt("to@localhost"); err != nil {
tb.Errorf("Rcpt: %v", err)
}
w, err := c.Data()
if err != nil {
tb.Fatalf("Data: %v", err)
}
msg := []byte("Subject: Hi!\n\n This is an email\n")
if _, err = w.Write(msg); err != nil {
tb.Errorf("Data write: %v", err)
}
localC.Expect(1)
if err = w.Close(); err != nil {
tb.Errorf("Data close: %v", err)
}
localC.Wait()
}
// Checks err is a textproto.Error with the given code and message.
func expectTPErr(tb testing.TB, err error, code int, msg string) {
tb.Helper()
if err == nil {
tb.Fatalf("Expected error %d %q, got nil", code, msg)
}
netErr, ok := err.(*textproto.Error)
if !ok {
tb.Fatalf("Expected textproto.Error(%d %q), got %T: %v",
code, msg, err, err)
}
if netErr.Code != code || netErr.Msg != msg {
tb.Fatalf("Expected textproto.Error(%d %q), got %d %q",
code, msg, netErr.Code, netErr.Msg)
}
}
func TestSimple(t *testing.T) {
c := mustDial(t, ModeSMTP, false)
defer c.Close()
sendEmail(t, c)
}
func TestSimpleTLS(t *testing.T) {
c := mustDial(t, ModeSMTP, true)
defer c.Close()
sendEmail(t, c)
}
func TestManyEmails(t *testing.T) {
c := mustDial(t, ModeSMTP, true)
defer c.Close()
sendEmail(t, c)
sendEmail(t, c)
sendEmail(t, c)
}
func TestAuth(t *testing.T) {
c := mustDial(t, ModeSubmission, true)
defer c.Close()
auth := smtp.PlainAuth("", "testuser@localhost", "testpasswd", "127.0.0.1")
sendEmailWithAuth(t, c, auth)
}
func TestSubmissionWithoutAuth(t *testing.T) {
c := mustDial(t, ModeSubmission, true)
defer c.Close()
if err := c.Mail("from@from"); err == nil {
t.Errorf("Mail not failed as expected")
}
}
func TestAuthOnTLS(t *testing.T) {
c := mustDial(t, ModeSubmissionTLS, false)
defer c.Close()
auth := smtp.PlainAuth("", "testuser@localhost", "testpasswd", "127.0.0.1")
sendEmailWithAuth(t, c, auth)
}
func TestAuthOnSMTP(t *testing.T) {
c := mustDial(t, ModeSMTP, true)
defer c.Close()
auth := smtp.PlainAuth("", "testuser@localhost", "testpasswd", "127.0.0.1")
// At least for now, we allow AUTH over the SMTP port to avoid unnecessary
// complexity, so we expect it to work.
sendEmailWithAuth(t, c, auth)
}
func TestBrokenAuth(t *testing.T) {
c := mustDial(t, ModeSubmission, true)
defer c.Close()
auth := smtp.PlainAuth("", "user@broken", "passwd", "127.0.0.1")
err := c.Auth(auth)
expectTPErr(t, err, 454, "4.7.0 Temporary authentication failure")
}
func TestWrongMailParsing(t *testing.T) {
addrs := []string{"from", "a b c", "a @ b", "<x>", "<x y>", "><",
"a@b/c", "a@[1.2.3.4]"}
for _, addr := range addrs {
c := mustDial(t, ModeSMTP, false)
if err := c.Mail(addr); err == nil {
t.Errorf("Mail not failed as expected with %q", addr)
}
if err := c.Mail("from@plain"); err != nil {
t.Errorf("Mail: %v", err)
}
for _, addr := range addrs {
if err := c.Rcpt(addr); err == nil {
t.Errorf("Rcpt not failed as expected with %q", addr)
}
}
c.Close()
}
}
func TestMalformedDomain(t *testing.T) {
// Unauthenticated MAIL FROM.
c := mustDial(t, ModeSMTP, false)
defer c.Close()
err := c.Mail("a@b/c")
expectTPErr(t, err, 501, "5.1.8 Malformed sender domain")
// Authenticated RCPT TO. We need to authenticate, otherwise it may get
// rejected as relaying.
c = mustDial(t, ModeSubmission, true)
defer c.Close()
auth := smtp.PlainAuth("", "testuser@localhost", "testpasswd", "127.0.0.1")
if err := c.Auth(auth); err != nil {
t.Fatalf("Auth: %v", err)
}
if err := c.Mail("testuser@localhost"); err != nil {
t.Fatalf("Mail: %v", err)
}
err = c.Rcpt("a@b/c")
expectTPErr(t, err, 501, "5.1.2 Malformed destination domain")
}
func TestUnsafeAddress(t *testing.T) {
// Unauthenticated MAIL FROM.
c := mustDial(t, ModeSMTP, false)
defer c.Close()
err := c.Mail(`"a@b"@from`)
expectTPErr(t, err, 501, "5.1.7 Sender address malformed")
err = c.Mail("a\u009bb@from")
expectTPErr(t, err, 501, "5.1.7 Sender address malformed")
// Characters that need quoting are fine.
if err := c.Mail(`"a;b"@from`); err != nil {
t.Errorf("Mail: %v", err)
}
// Authenticated RCPT TO. We need to authenticate, otherwise it may get
// rejected as relaying.
c = mustDial(t, ModeSubmission, true)
defer c.Close()
auth := smtp.PlainAuth("", "testuser@localhost", "testpasswd", "127.0.0.1")
if err := c.Auth(auth); err != nil {
t.Fatalf("Auth: %v", err)
}
if err := c.Mail("testuser@localhost"); err != nil {
t.Fatalf("Mail: %v", err)
}
err = c.Rcpt(`"a@b"@remote`)
expectTPErr(t, err, 501, "5.1.3 Malformed destination address")
err = c.Rcpt("a\u009bb@remote")
expectTPErr(t, err, 501, "5.1.3 Malformed destination address")
}
func TestNullMailFrom(t *testing.T) {
c := mustDial(t, ModeSMTP, false)
defer c.Close()
addrs := []string{"<>", " <>", "<> OPTION"}
for _, addr := range addrs {
simpleCmd(t, c, fmt.Sprintf("MAIL FROM:%s", addr), 250)
}
}
func TestRcptBeforeMail(t *testing.T) {
c := mustDial(t, ModeSMTP, false)
defer c.Close()
if err := c.Rcpt("to@to"); err == nil {
t.Errorf("Rcpt not failed as expected")
}
}
func TestRcptOption(t *testing.T) {
c := mustDial(t, ModeSMTP, true)
defer c.Close()
if err := c.Mail("from@localhost"); err != nil {
t.Fatalf("Mail: %v", err)
}
params := []string{
"<to@localhost>", " <to@localhost>", "<to@localhost> OPTION"}
for _, p := range params {
simpleCmd(t, c, fmt.Sprintf("RCPT TO:%s", p), 250)
}
}
func TestRelayForbidden(t *testing.T) {
c := mustDial(t, ModeSMTP, false)
defer c.Close()
if err := c.Mail("from@somewhere"); err != nil {
t.Errorf("Mail: %v", err)
}
if err := c.Rcpt("to@somewhere"); err == nil {
t.Errorf("Accepted relay email")
}
}
func TestTooManyRecipients(t *testing.T) {
c := mustDial(t, ModeSubmission, true)
defer c.Close()
auth := smtp.PlainAuth("", "testuser@localhost", "testpasswd", "127.0.0.1")
if err := c.Auth(auth); err != nil {
t.Fatalf("Auth: %v", err)
}
if err := c.Mail("testuser@localhost"); err != nil {
t.Fatalf("Mail: %v", err)
}
for i := range 101 {
if err := c.Rcpt(fmt.Sprintf("to%d@somewhere", i)); err != nil {
t.Fatalf("Rcpt: %v", err)
}
}
err := c.Rcpt("to102@somewhere")
expectTPErr(t, err, 452, "4.5.3 Too many recipients")
}
func TestRcptBrokenExists(t *testing.T) {
c := mustDial(t, ModeSMTP, true)
defer c.Close()
if err := c.Mail("from@localhost"); err != nil {
t.Fatalf("Mail: %v", err)
}
err := c.Rcpt("to@broken")
expectTPErr(t, err, 451, "4.4.3 Temporary error checking address")
}
func TestRcptUserDoesNotExist(t *testing.T) {
c := mustDial(t, ModeSMTP, true)
defer c.Close()
if err := c.Mail("from@localhost"); err != nil {
t.Fatalf("Mail: %v", err)
}
err := c.Rcpt("doesnotexist@localhost")
expectTPErr(t, err, 550,
"5.1.1 Destination address is unknown (user does not exist)")
}
var str1MiB string
func sendLargeEmail(tb testing.TB, c *smtp.Client, sizeMiB int) error {
tb.Helper()
if err := c.Mail("from@from"); err != nil {
tb.Fatalf("Mail: %v", err)
}
if err := c.Rcpt("to@localhost"); err != nil {
tb.Fatalf("Rcpt: %v", err)
}
w, err := c.Data()
if err != nil {
tb.Fatalf("Data: %v", err)
}
if _, err := w.Write([]byte("Subject: I ate too much\n\n")); err != nil {
tb.Fatalf("Data write: %v", err)
}
// Write the 1 MiB string sizeMiB times.
for range sizeMiB {
if _, err := w.Write([]byte(str1MiB)); err != nil {
tb.Fatalf("Data write: %v", err)
}
}
return w.Close()
}
func TestTooMuchData(t *testing.T) {
c := mustDial(t, ModeSMTP, true)
defer c.Close()
localC.Expect(1)
err := sendLargeEmail(t, c, maxDataSizeMiB-1)
if err != nil {
t.Errorf("Error sending large but ok email: %v", err)
}
localC.Wait()
// Repeat the test - we want to check that the limit applies to each
// message, not the entire connection.
localC.Expect(1)
err = sendLargeEmail(t, c, maxDataSizeMiB-1)
if err != nil {
t.Errorf("Error sending large but ok email: %v", err)
}
localC.Wait()
err = sendLargeEmail(t, c, maxDataSizeMiB+1)
expectTPErr(t, err, 552, "5.3.4 Message too big")
// Repeat the test once again, the limit should not prevent connection
// from continuing.
localC.Expect(1)
err = sendLargeEmail(t, c, maxDataSizeMiB-1)
if err != nil {
t.Errorf("Error sending large but ok email: %v", err)
}
localC.Wait()
}
func simpleCmd(t *testing.T, c *smtp.Client, cmd string, expected int) string {
t.Helper()
if err := c.Text.PrintfLine("%s", cmd); err != nil {
t.Fatalf("Failed to write %s: %v", cmd, err)
}
_, msg, err := c.Text.ReadResponse(expected)
if err != nil {
t.Errorf("Incorrect %s response: %v", cmd, err)
}
return msg
}
func TestSimpleCommands(t *testing.T) {
c := mustDial(t, ModeSMTP, false)
defer c.Close()
simpleCmd(t, c, "HELP", 214)
simpleCmd(t, c, "NOOP", 250)
simpleCmd(t, c, "VRFY", 502)
simpleCmd(t, c, "EXPN", 502)
}
func TestLongLines(t *testing.T) {
c := mustDial(t, ModeSMTP, false)
defer c.Close()
// Send a not-too-long line.
simpleCmd(t, c, fmt.Sprintf("%1000s", "x"), 500)
// Send a very long line, expect an error.
msg := simpleCmd(t, c, fmt.Sprintf("%1001s", "x"), 554)
if msg != "error reading command: line too long" {
t.Errorf("Expected 'line too long', got %v", msg)
}
}
func TestReset(t *testing.T) {
c := mustDial(t, ModeSMTP, false)
defer c.Close()
if err := c.Mail("from@plain"); err != nil {
t.Fatalf("MAIL FROM: %v", err)
}
if err := c.Reset(); err != nil {
t.Errorf("RSET: %v", err)
}
if err := c.Mail("from@plain"); err != nil {
t.Errorf("MAIL after RSET: %v", err)
}
}
func TestRepeatedStartTLS(t *testing.T) {
c, err := smtp.Dial(smtpAddr)
if err != nil {
t.Fatalf("smtp.Dial: %v", err)
}
if err = c.StartTLS(tlsConfig); err != nil {
t.Fatalf("StartTLS: %v", err)
}
if err = c.StartTLS(tlsConfig); err == nil {
t.Errorf("Second STARTTLS did not fail as expected")
}
}
// Test that STARTTLS fails on a TLS connection.
func TestStartTLSOnTLS(t *testing.T) {
c := mustDial(t, ModeSubmissionTLS, false)
defer c.Close()
if err := c.StartTLS(tlsConfig); err == nil {
t.Errorf("STARTTLS did not fail as expected")
}
}
func TestAddDKIMSigner(t *testing.T) {
s := NewServer()
err := s.AddDKIMSigner("example.com", "selector", "keyfile-does-not-exist")
if !errors.Is(err, fs.ErrNotExist) {
t.Errorf("AddDKIMSigner: expected not exist, got %v", err)
}
tmpDir := t.ArtifactDir()
// Invalid PEM file.
kf1 := tmpDir + "/key1-bad_pem.pem"
testlib.Rewrite(t, kf1, "not a valid PEM file")
err = s.AddDKIMSigner("example.com", "selector", kf1)
if !errors.Is(err, errDecodingPEMBlock) {
t.Errorf("AddDKIMSigner: expected %v, got %v",
errDecodingPEMBlock, err)
}
// Unsupported block type.
kf2 := tmpDir + "/key2.pem"
testlib.Rewrite(t, kf2,
"-----BEGIN TEST KEY-----\n-----END TEST KEY-----")
err = s.AddDKIMSigner("example.com", "selector", kf2)
if !errors.Is(err, errUnsupportedBlockType) {
t.Errorf("AddDKIMSigner: expected %v, got %v",
errUnsupportedBlockType, err)
}
// x509 error: this is an ed448 key, which is not supported.
kf3 := tmpDir + "/key3.pem"
testlib.Rewrite(t, kf3, `-----BEGIN PRIVATE KEY-----
MEcCAQAwBQYDK2VxBDsEOSBHT9DNG6/FNBnRGrLay+jIrK8WrViiVMz9AoXqYSb6
ghwTZSd3E0X8oIFTgs9ch3pxJM1KDrs4NA==
-----END PRIVATE KEY-----`)
err = s.AddDKIMSigner("example.com", "selector", kf3)
if !strings.Contains(err.Error(),
"x509: PKCS#8 wrapping contained private key with unknown algorithm") {
t.Errorf("AddDKIMSigner: expected x509 error, got %q", err.Error())
}
// Unsupported key type: X25519.
kf4 := tmpDir + "/key4.pem"
testlib.Rewrite(t, kf4, `-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VuBCIEIKBUDwEDc5cCv/yEvnA93yk0gXyiTZe7Qip8QU3rJuZC
-----END PRIVATE KEY-----`)
err = s.AddDKIMSigner("example.com", "selector", kf4)
if !errors.Is(err, errUnsupportedKeyType) {
t.Errorf("AddDKIMSigner: expected %v, got %v",
errUnsupportedKeyType, err)
}
// Successful.
kf5 := tmpDir + "/key5.pem"
testlib.Rewrite(t, kf5, `-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VwBCIEID6bjSoiW6g6NJA67RNl0SZ7zpylVOq9w/VGAXF5whnS
-----END PRIVATE KEY-----`)
err = s.AddDKIMSigner("example.com", "selector", kf5)
if err != nil {
t.Errorf("AddDKIMSigner: %v", err)
}
}
// chanListener is a net.Listener that returns the results sent to it from
// Accept, and net.ErrClosed once the channel is closed.
type chanListener chan acceptResult
type acceptResult struct {
conn net.Conn
err error
}
func (l chanListener) Accept() (net.Conn, error) {
r, ok := <-l
if !ok {
return nil, net.ErrClosed
}
return r.conn, r.err
}
func (l chanListener) Close() error { return nil }
func (l chanListener) Addr() net.Addr { return &net.TCPAddr{} }
// serveInBackground runs s.serve(l) in a goroutine, and returns a channel
// that is closed once it returns.
func serveInBackground(s *Server, l net.Listener) chan struct{} {
done := make(chan struct{})
go func() {
s.serve(l, ModeSMTP)
close(done)
}()
return done
}
func waitForServe(t *testing.T, done chan struct{}) {
t.Helper()
select {
case <-done:
case <-time.After(5 * time.Second):
t.Fatalf("serve did not return after net.ErrClosed")
}
}
// newPipeConn sends a new connection to the listener, and returns the client
// side of it.
func newPipeConn(l chanListener) *textproto.Conn {
srvConn, cliConn := net.Pipe()
l <- acceptResult{conn: srvConn}
return textproto.NewConn(cliConn)
}
func expectGreeting(t *testing.T, c *textproto.Conn, hostname string) {
t.Helper()
greeting, err := c.ReadLine()
if err != nil {
t.Fatalf("error reading greeting: %v", err)
}
if want := "220 " + hostname + " ESMTP chasquid"; greeting != want {
t.Errorf("got greeting %q, expected %q", greeting, want)
}
}
func TestAcceptErrors(t *testing.T) {
s := NewServer()
s.Hostname = "acceptsrv"
prevErrors := acceptErrors.Value()
emfile := &net.OpError{Op: "accept", Net: "tcp",
Err: os.NewSyscallError("accept4", syscall.EMFILE)}
srvConn, cliConn := net.Pipe()
defer cliConn.Close()
l := make(chanListener, 4)
l <- acceptResult{err: emfile}
l <- acceptResult{err: emfile}
l <- acceptResult{conn: srvConn}
l <- acceptResult{err: emfile}
close(l)
// serve should retry on errors, and return once the listener reports
// net.ErrClosed.
done := serveInBackground(s, l)
// The connection we got in between the errors must have been handled.
expectGreeting(t, textproto.NewConn(cliConn), "acceptsrv")
waitForServe(t, done)
if n := acceptErrors.Value() - prevErrors; n != 3 {
t.Errorf("acceptErrors increased by %d, expected 3", n)
}
}
func TestConnectionLimit(t *testing.T) {
s := NewServer()
s.Hostname = "limitsrv"
s.maxConns = 2
prevOverLimit := connectionsOverLimit.Value()
l := make(chanListener)
done := serveInBackground(s, l)
// The first two connections are within the limit.
c1 := newPipeConn(l)
expectGreeting(t, c1, "limitsrv")
c2 := newPipeConn(l)
defer c2.Close()
expectGreeting(t, c2, "limitsrv")
// The third one is over the limit, and gets closed without a greeting.
c3 := newPipeConn(l)
if line, err := c3.ReadLine(); err != io.EOF {
t.Errorf("over the limit: got %q, %v; expected EOF", line, err)
}
if n := connectionsOverLimit.Value() - prevOverLimit; n != 1 {
t.Errorf("connectionsOverLimit increased by %d, expected 1", n)
}
// Once a connection finishes, we can accept new ones again.
c1.Close()
deadline := time.Now().Add(5 * time.Second)
for s.activeConns.Load() != 1 {
if time.Now().After(deadline) {
t.Fatalf("activeConns = %d, expected 1", s.activeConns.Load())
}
time.Sleep(time.Millisecond)
}
c4 := newPipeConn(l)
defer c4.Close()
expectGreeting(t, c4, "limitsrv")
close(l)
waitForServe(t, done)
}
//
// === Benchmarks ===
//
func BenchmarkManyEmails(b *testing.B) {
c := mustDial(b, ModeSMTP, false)
defer c.Close()
for b.Loop() {
sendEmail(b, c)
}
}
func BenchmarkManyEmailsParallel(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
c := mustDial(b, ModeSMTP, false)
defer c.Close()
for pb.Next() {
sendEmail(b, c)
}
})
}
//
// === Test environment ===
//
type brokenAuthBE struct{}
func (b brokenAuthBE) Authenticate(user, password string) (bool, error) {
return false, fmt.Errorf("failed to auth")
}
func (b brokenAuthBE) Exists(user string) (bool, error) {
return false, fmt.Errorf("failed to check if user exists")
}
func (b brokenAuthBE) Reload() error {
return fmt.Errorf("failed to reload")
}
// realMain is the real main function, which returns the value to pass to
// os.Exit(). We have to do this so we can use defer.
func realMain(m *testing.M) int {
flag.Parse()
// Create a 1MiB string, which the large message tests use.
str1MiB = strings.Repeat("a", 1024*1024)
// Set up the mail log to stdout, which is captured by the test runner,
// so we have better debugging information on failures.
maillog.Default = maillog.New(os.Stdout)
if *externalSMTPAddr != "" {
smtpAddr = *externalSMTPAddr
submissionAddr = *externalSubmissionAddr
submissionTLSAddr = *externalSubmissionTLSAddr
tlsConfig = &tls.Config{
InsecureSkipVerify: true,
}
} else {
// Generate certificates in a temporary directory.
tmpDir, err := os.MkdirTemp("", "chasquid_test:")
if err != nil {
fmt.Printf("Failed to create temp dir: %v\n", tmpDir)
return 1
}
defer os.RemoveAll(tmpDir)
tlsConfig, err = testlib.GenerateCert(tmpDir)
if err != nil {
fmt.Printf("Failed to generate cert for testing: %v\n", err)
return 1
}
s := NewServer()
s.Hostname = "localhost"
s.MaxDataSize = int64(maxDataSizeMiB) * 1024 * 1024
s.AddCerts(tmpDir+"/cert.pem", tmpDir+"/key.pem")
// Open the listeners ourselves, so the ports are already bound
// (and we know their addresses) by the time the tests run.
for _, l := range []struct {
addr *string
mode SocketMode
}{
{&smtpAddr, ModeSMTP},
{&submissionAddr, ModeSubmission},
{&submissionTLSAddr, ModeSubmissionTLS},
} {
ln, err := net.Listen("tcp", "localhost:0")
if err != nil {
fmt.Printf("Failed to listen: %v\n", err)
return 1
}
*l.addr = ln.Addr().String()
s.AddListeners([]net.Listener{ln}, l.mode)
}
s.InitQueue(tmpDir+"/queue", localC, remoteC)
dinfo, err := domaininfo.New(tmpDir + "/domaininfo")
if err != nil {
fmt.Printf("Error initializing domaininfo: %v", err)
return 1
}
s.SetDomainInfo(dinfo)
udb := userdb.New("/dev/null")
udb.AddUser("testuser", "testpasswd")
s.aliasesR.AddAliasForTesting(
"to@localhost", "testuser@localhost", nil, aliases.EMAIL)
s.authr.Register("localhost", auth.WrapNoErrorBackend(udb))
s.AddDomain("localhost")
s.AddDomain("broken")
s.authr.Register("broken", &brokenAuthBE{})
// Disable SPF lookups, to avoid leaking DNS queries.
disableSPFForTesting = true
// Disable reloading.
reloadEvery = nil
go s.ListenAndServe()
}
return m.Run()
}
func TestMain(m *testing.M) {
os.Exit(realMain(m))
}