218 lines
5.8 KiB
Go
218 lines
5.8 KiB
Go
package smtpserver
|
|
|
|
import (
|
|
"net"
|
|
"net/smtp"
|
|
"os"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"gopkg.in/ini.v1"
|
|
"mailgoserver/internal/db"
|
|
"mailgoserver/internal/dkim"
|
|
"mailgoserver/internal/relay"
|
|
"mailgoserver/internal/toolbox"
|
|
)
|
|
|
|
func newTestBackend(t *testing.T) *Backend {
|
|
t.Helper()
|
|
f, err := os.CreateTemp("", "smtp-test-*.db")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
f.Close()
|
|
t.Cleanup(func() { os.Remove(f.Name()) })
|
|
|
|
database, err := db.Open(f.Name())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(func() { database.Close() })
|
|
|
|
if _, err := database.Exec(`INSERT INTO esrv_domains (domain_name, is_active, is_verified) VALUES ('example.com', 1, 1)`); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
hash, err := db.HashPassword("testpass123")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := database.Exec(`INSERT INTO esrv_senders (email, password_hash, domain_id, is_active) VALUES (?, ?, 1, 1)`, "test@example.com", hash); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := database.Exec(`INSERT INTO esrv_whitelisted_ips (ip_address, domain_id, is_active) VALUES ('127.0.0.1', 1, 1)`); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
cfg := ini.Empty()
|
|
logger := toolbox.GetLogger("test")
|
|
|
|
return &Backend{
|
|
DB: database,
|
|
DKIM: dkim.New(database, 1024),
|
|
Relay: relay.New(database, cfg, logger),
|
|
Cfg: cfg,
|
|
Logger: logger,
|
|
HeloHostname: "mail.example.com",
|
|
AttachmentsBasePath: t.TempDir(),
|
|
}
|
|
}
|
|
|
|
func startTestServer(t *testing.T, backend *Backend) string {
|
|
t.Helper()
|
|
l, err := net.Listen("tcp", "127.0.0.1:0")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
srv := NewPlainServer(backend, l.Addr().String(), "mail.example.com")
|
|
go srv.Serve(l)
|
|
t.Cleanup(func() { srv.Close() })
|
|
return l.Addr().String()
|
|
}
|
|
|
|
func TestAuthSuccessAndSenderAuthorization(t *testing.T) {
|
|
backend := newTestBackend(t)
|
|
addr := startTestServer(t, backend)
|
|
|
|
c, err := smtp.Dial(addr)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer c.Close()
|
|
|
|
if err := c.Auth(smtp.PlainAuth("", "test@example.com", "testpass123", "127.0.0.1")); err != nil {
|
|
t.Fatalf("expected auth success, got: %v", err)
|
|
}
|
|
if err := c.Mail("test@example.com"); err != nil {
|
|
t.Fatalf("expected MAIL FROM as own address to succeed, got: %v", err)
|
|
}
|
|
if err := c.Rcpt("someone@elsewhere.example"); err != nil {
|
|
t.Fatalf("expected RCPT to accept any address, got: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestAuthFailureClosesConnection(t *testing.T) {
|
|
backend := newTestBackend(t)
|
|
addr := startTestServer(t, backend)
|
|
|
|
c, err := smtp.Dial(addr)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer c.Close()
|
|
|
|
err = c.Auth(smtp.PlainAuth("", "test@example.com", "wrongpassword", "127.0.0.1"))
|
|
if err == nil {
|
|
t.Fatal("expected auth failure")
|
|
}
|
|
if !strings.Contains(err.Error(), "535") {
|
|
t.Fatalf("expected 535 response, got: %v", err)
|
|
}
|
|
|
|
// The server should close the connection shortly after — a subsequent command
|
|
// must fail rather than succeed.
|
|
time.Sleep(300 * time.Millisecond)
|
|
if err := c.Mail("test@example.com"); err == nil {
|
|
t.Fatal("expected connection to have been closed after failed AUTH")
|
|
}
|
|
}
|
|
|
|
func TestIPWhitelistFallbackWithoutAuth(t *testing.T) {
|
|
backend := newTestBackend(t)
|
|
addr := startTestServer(t, backend)
|
|
|
|
c, err := smtp.Dial(addr)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer c.Close()
|
|
|
|
// No AUTH at all: MAIL FROM a domain whitelisted for our (loopback) peer IP.
|
|
if err := c.Mail("anyone@example.com"); err != nil {
|
|
t.Fatalf("expected IP-whitelist fallback to authorize, got: %v", err)
|
|
}
|
|
if err := c.Rcpt("rcpt@elsewhere.example"); err != nil {
|
|
t.Fatalf("expected RCPT to accept, got: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestMailFromRejectedForUnauthorizedDomain(t *testing.T) {
|
|
backend := newTestBackend(t)
|
|
addr := startTestServer(t, backend)
|
|
|
|
c, err := smtp.Dial(addr)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer c.Close()
|
|
|
|
err = c.Mail("nobody@not-whitelisted.example")
|
|
if err == nil {
|
|
t.Fatal("expected MAIL FROM to be rejected for a non-whitelisted, non-authenticated domain")
|
|
}
|
|
if !strings.Contains(err.Error(), "550") {
|
|
t.Fatalf("expected 550 response, got: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestUnverifiedDomainCannotSend(t *testing.T) {
|
|
backend := newTestBackend(t)
|
|
|
|
domainID, err := backend.DB.CreateDomain("unverified.example")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
hash, _ := db.HashPassword("testpass123")
|
|
if _, err := backend.DB.CreateSender("sender@unverified.example", hash, domainID, false, false); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
addr := startTestServer(t, backend)
|
|
c, err := smtp.Dial(addr)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer c.Close()
|
|
|
|
if err := c.Auth(smtp.PlainAuth("", "sender@unverified.example", "testpass123", "127.0.0.1")); err != nil {
|
|
t.Fatalf("auth: %v", err)
|
|
}
|
|
err = c.Mail("sender@unverified.example")
|
|
if err == nil {
|
|
t.Fatal("expected MAIL FROM to be rejected for an unverified domain, even for an authenticated sender")
|
|
}
|
|
if !strings.Contains(err.Error(), "550") || !strings.Contains(err.Error(), "verif") {
|
|
t.Fatalf("expected a 550 mentioning verification, got: %v", err)
|
|
}
|
|
|
|
// Now verify the domain directly (bypassing DNS) and confirm sending is unblocked.
|
|
if err := backend.DB.SetDomainVerified(domainID, true); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := c.Mail("sender@unverified.example"); err != nil {
|
|
t.Fatalf("expected MAIL FROM to succeed once domain is verified, got: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestSenderCannotSpoofOtherAddress(t *testing.T) {
|
|
backend := newTestBackend(t)
|
|
addr := startTestServer(t, backend)
|
|
|
|
c, err := smtp.Dial(addr)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer c.Close()
|
|
|
|
if err := c.Auth(smtp.PlainAuth("", "test@example.com", "testpass123", "127.0.0.1")); err != nil {
|
|
t.Fatalf("auth: %v", err)
|
|
}
|
|
err = c.Mail("someoneelse@example.com")
|
|
if err == nil {
|
|
t.Fatal("expected MAIL FROM spoofing another address to be rejected (can_send_as_domain is false)")
|
|
}
|
|
if !strings.Contains(err.Error(), "550") {
|
|
t.Fatalf("expected 550 response, got: %v", err)
|
|
}
|
|
}
|