2026-08-12 21:14:19 +01:00
|
|
|
package smtpserver
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"net/smtp"
|
|
|
|
|
"strings"
|
|
|
|
|
"testing"
|
|
|
|
|
|
|
|
|
|
"mailgoserver/internal/db"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// createAlias mirrors createMailboxFor in webui's multitenant tests, for aliases.
|
|
|
|
|
func createAlias(t *testing.T, backend *Backend, mailboxID, domainID int64, email string, canSendAs bool) {
|
|
|
|
|
t.Helper()
|
|
|
|
|
if _, err := backend.DB.CreateAlias(mailboxID, email, domainID, canSendAs); err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestLocalDeliveryToAliasLandsInOwningMailbox(t *testing.T) {
|
|
|
|
|
backend, mailboxID := newTestBackendWithMailbox(t)
|
|
|
|
|
createAlias(t, backend, mailboxID, 1, "alias@example.com", false)
|
|
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
if err := c.Mail("test@example.com"); err != nil {
|
|
|
|
|
t.Fatalf("MAIL FROM: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if err := c.Rcpt("alias@example.com"); err != nil {
|
|
|
|
|
t.Fatalf("expected RCPT to a receive-only alias to succeed, got: %v", err)
|
|
|
|
|
}
|
|
|
|
|
w, err := c.Data()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
w.Write([]byte("Subject: via alias\r\n\r\nhi"))
|
|
|
|
|
if err := w.Close(); err != nil {
|
|
|
|
|
t.Fatalf("expected DATA to succeed delivering to an alias, got: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mbox, err := backend.DB.GetMailboxByID(mailboxID)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
if mbox.UsedBytes == 0 {
|
|
|
|
|
t.Fatal("expected mail delivered to an alias to land in the owning mailbox")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestMailboxAppPasswordCanSendAsPrimaryButNotArbitraryAddress(t *testing.T) {
|
|
|
|
|
backend, mailboxID := newTestBackendWithMailbox(t)
|
|
|
|
|
appPassword := "a-long-enough-app-password-123456"
|
|
|
|
|
hash, err := db.HashPassword(appPassword)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
2026-08-13 07:03:40 +01:00
|
|
|
if _, err := backend.DB.CreateAppPassword(mailboxID, "test", hash, nil); err != nil {
|
2026-08-12 21:14:19 +01:00
|
|
|
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("", "inbox@example.com", appPassword, "127.0.0.1")); err != nil {
|
|
|
|
|
t.Fatalf("expected app-password auth to succeed, got: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if err := c.Mail("inbox@example.com"); err != nil {
|
|
|
|
|
t.Fatalf("expected MAIL FROM as own primary address to succeed, got: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
c2, err := smtp.Dial(addr)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
defer c2.Close()
|
|
|
|
|
if err := c2.Auth(smtp.PlainAuth("", "inbox@example.com", appPassword, "127.0.0.1")); err != nil {
|
|
|
|
|
t.Fatalf("auth: %v", err)
|
|
|
|
|
}
|
|
|
|
|
err = c2.Mail("someoneelse@example.com")
|
|
|
|
|
if err == nil {
|
|
|
|
|
t.Fatal("expected MAIL FROM as an address with no alias relationship to be rejected")
|
|
|
|
|
}
|
|
|
|
|
if !strings.Contains(err.Error(), "550") {
|
|
|
|
|
t.Fatalf("expected 550, got: %v", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestMailboxAppPasswordCanSendAsEnabledAlias(t *testing.T) {
|
|
|
|
|
backend, mailboxID := newTestBackendWithMailbox(t)
|
|
|
|
|
createAlias(t, backend, mailboxID, 1, "sendalias@example.com", true)
|
|
|
|
|
appPassword := "another-long-enough-app-password-9"
|
|
|
|
|
hash, err := db.HashPassword(appPassword)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
2026-08-13 07:03:40 +01:00
|
|
|
if _, err := backend.DB.CreateAppPassword(mailboxID, "test", hash, nil); err != nil {
|
2026-08-12 21:14:19 +01:00
|
|
|
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("", "inbox@example.com", appPassword, "127.0.0.1")); err != nil {
|
|
|
|
|
t.Fatalf("auth: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if err := c.Mail("sendalias@example.com"); err != nil {
|
|
|
|
|
t.Fatalf("expected MAIL FROM as a send-as-enabled alias to succeed, got: %v", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestMailboxAppPasswordCannotSendAsReceiveOnlyAlias(t *testing.T) {
|
|
|
|
|
backend, mailboxID := newTestBackendWithMailbox(t)
|
|
|
|
|
createAlias(t, backend, mailboxID, 1, "receivealias@example.com", false)
|
|
|
|
|
appPassword := "yet-another-long-enough-app-pass1"
|
|
|
|
|
hash, err := db.HashPassword(appPassword)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
2026-08-13 07:03:40 +01:00
|
|
|
if _, err := backend.DB.CreateAppPassword(mailboxID, "test", hash, nil); err != nil {
|
2026-08-12 21:14:19 +01:00
|
|
|
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("", "inbox@example.com", appPassword, "127.0.0.1")); err != nil {
|
|
|
|
|
t.Fatalf("auth: %v", err)
|
|
|
|
|
}
|
|
|
|
|
err = c.Mail("receivealias@example.com")
|
|
|
|
|
if err == nil {
|
|
|
|
|
t.Fatal("expected MAIL FROM as a receive-only alias to be rejected")
|
|
|
|
|
}
|
|
|
|
|
if !strings.Contains(err.Error(), "550") {
|
|
|
|
|
t.Fatalf("expected 550, got: %v", err)
|
|
|
|
|
}
|
|
|
|
|
}
|