Files
zeekayandHanzo Dev 15909c7b79 Rename Go module path to github.com/hanzoai/mail
Fork of github.com/mjl-/mox at 5656ca6d1b
(upstream v0.0.15 plus 55 commits).

Rename the module path in go.mod and the import paths in 268 Go files. Also
update the five scripts that "go install" the module (genwebsite.sh,
docker-release.sh, apidiff.sh, test-upgrade.sh, Dockerfile.release), which
would otherwise build upstream rather than this fork.

Add NOTICE recording the upstream project, author, license, and fork point,
and stating which license covers which files: MIT for the mox source and our
changes, MPL-2.0 for publicsuffix/public_suffix_list.txt alone, and the
vendored dependencies' own licenses under licenses/. Embed NOTICE so
"mail licenses" prints the lineage from the running binary. LICENSE.MIT,
LICENSE.MPLv2.0 and the licenses/ tree are preserved verbatim; no code is
relicensed.

The mox command name, config keys, data directory layout and website content
are unchanged. LLM.md records the fork point, the rename, what was left alone,
and the steps to stand the server up.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-27 23:16:19 -07:00

289 lines
9.5 KiB
Go

package smtpserver
import (
"errors"
"path/filepath"
"strings"
"testing"
"github.com/hanzoai/mail/dns"
"github.com/hanzoai/mail/smtp"
"github.com/hanzoai/mail/smtpclient"
"github.com/hanzoai/mail/store"
)
// Check user can submit message with message From address they are member of.
func TestAliasSubmitMsgFrom(t *testing.T) {
ts := newTestServer(t, filepath.FromSlash("../testdata/smtp/mox.conf"), dns.MockResolver{})
defer ts.close()
ts.submission = true
ts.user = "[email protected]"
ts.pass = password0
var msg = strings.ReplaceAll(`From: <[email protected]>
To: <[email protected]>
Subject: test
test email
`, "\n", "\r\n")
ts.run(func(client *smtpclient.Client) {
mailFrom := "[email protected]"
rcptTo := "[email protected]"
err := client.Deliver(ctxbg, mailFrom, rcptTo, int64(len(msg)), strings.NewReader(msg), false, false, false)
ts.smtpErr(err, nil)
})
ts.run(func(client *smtpclient.Client) {
mailFrom := "[email protected]" // List address as smtp mail from.
rcptTo := "[email protected]"
err := client.Deliver(ctxbg, mailFrom, rcptTo, int64(len(msg)), strings.NewReader(msg), false, false, false)
ts.smtpErr(err, nil)
})
msg = strings.ReplaceAll(`From: <[email protected]>
To: <[email protected]>
Subject: test
test email
`, "\n", "\r\n")
ts.run(func(client *smtpclient.Client) {
mailFrom := "[email protected]"
rcptTo := "[email protected]"
err := client.Deliver(ctxbg, mailFrom, rcptTo, int64(len(msg)), strings.NewReader(msg), false, false, false)
ts.smtpErr(err, &smtpclient.Error{Permanent: true, Code: smtp.C550MailboxUnavail, Secode: smtp.SePol7DeliveryUnauth1})
})
ts.run(func(client *smtpclient.Client) {
mailFrom := "[email protected]" // List address as smtp mail from.
rcptTo := "[email protected]"
err := client.Deliver(ctxbg, mailFrom, rcptTo, int64(len(msg)), strings.NewReader(msg), false, false, false)
ts.smtpErr(err, &smtpclient.Error{Permanent: true, Code: smtp.C550MailboxUnavail, Secode: smtp.SePol7DeliveryUnauth1})
})
}
// Non-member cannot submit as alias that allows it for members.
func TestAliasSubmitMsgFromDenied(t *testing.T) {
ts := newTestServer(t, filepath.FromSlash("../testdata/smtp/mox.conf"), dns.MockResolver{})
defer ts.close()
// Trying to open account by alias should result in proper error.
_, _, _, err := store.OpenEmail(pkglog, "[email protected]", false)
if err == nil || !errors.Is(err, store.ErrUnknownCredentials) {
t.Fatalf("opening alias, got err %v, expected store.ErrUnknownCredentials", err)
}
acc, err := store.OpenAccount(pkglog, "☺", false)
tcheck(t, err, "open account")
err = acc.SetPassword(pkglog, password0)
tcheck(t, err, "set password")
err = acc.Close()
tcheck(t, err, "close account")
acc.WaitClosed()
ts.submission = true
ts.user = "☺@mox.example"
ts.pass = password0
var msg = strings.ReplaceAll(`From: <[email protected]>
To: <[email protected]>
Subject: test
test email
`, "\n", "\r\n")
ts.run(func(client *smtpclient.Client) {
mailFrom := "☺@mox.example"
rcptTo := "[email protected]"
err := client.Deliver(ctxbg, mailFrom, rcptTo, int64(len(msg)), strings.NewReader(msg), true, true, false)
ts.smtpErr(err, &smtpclient.Error{Permanent: true, Code: smtp.C550MailboxUnavail, Secode: smtp.SePol7DeliveryUnauth1})
})
ts.run(func(client *smtpclient.Client) {
mailFrom := "[email protected]" // List address as message from.
rcptTo := "[email protected]"
err := client.Deliver(ctxbg, mailFrom, rcptTo, int64(len(msg)), strings.NewReader(msg), true, true, false)
ts.smtpErr(err, &smtpclient.Error{Permanent: true, Code: smtp.C550MailboxUnavail, Secode: smtp.SePol7DeliveryUnauth1})
})
}
// Non-member can deliver to public list, not to private list.
func TestAliasDeliverNonMember(t *testing.T) {
resolver := dns.MockResolver{
A: map[string][]string{
"example.org.": {"127.0.0.10"}, // For mx check.
},
PTR: map[string][]string{
"127.0.0.10": {"example.org."}, // To get passed junk filter.
},
}
ts := newTestServer(t, filepath.FromSlash("../testdata/smtp/mox.conf"), resolver)
defer ts.close()
var msg = strings.ReplaceAll(`From: <[email protected]>
To: <[email protected]>
test email
`, "\n", "\r\n")
ts.run(func(client *smtpclient.Client) {
mailFrom := "[email protected]"
rcptTo := "[email protected]"
err := client.Deliver(ctxbg, mailFrom, rcptTo, int64(len(msg)), strings.NewReader(msg), false, false, false)
ts.smtpErr(err, &smtpclient.Error{Permanent: true, Code: smtp.C550MailboxUnavail, Secode: smtp.SePol7ExpnProhibited2})
})
msg = strings.ReplaceAll(`From: <[email protected]>
To: <[email protected]>
test email
`, "\n", "\r\n")
ts.run(func(client *smtpclient.Client) {
mailFrom := "[email protected]"
rcptTo := "[email protected]"
err := client.Deliver(ctxbg, mailFrom, rcptTo, int64(len(msg)), strings.NewReader(msg), false, false, false)
ts.smtpErr(err, &smtpclient.Error{Permanent: true, Code: smtp.C550MailboxUnavail, Secode: smtp.SePol7ExpnProhibited2})
})
msg = strings.ReplaceAll(`From: <[email protected]>
To: <[email protected]>
Subject: test
test email
`, "\n", "\r\n")
ts.run(func(client *smtpclient.Client) {
mailFrom := "[email protected]"
rcptTo := "[email protected]"
err := client.Deliver(ctxbg, mailFrom, rcptTo, int64(len(msg)), strings.NewReader(msg), false, false, false)
ts.smtpErr(err, nil)
ts.checkCount("Inbox", 2) // Receiving for both mjl@ and móx@.
})
ts.run(func(client *smtpclient.Client) {
mailFrom := "[email protected]" // List address as message from.
rcptTo := "[email protected]"
err := client.Deliver(ctxbg, mailFrom, rcptTo, int64(len(msg)), strings.NewReader(msg), false, false, false)
ts.smtpErr(err, nil)
ts.checkCount("Inbox", 4) // Receiving for both mjl@ and móx@.
})
}
// Member can deliver to private list, but still not with alias address as message
// from. Message with alias from address as message from is allowed.
func TestAliasDeliverMember(t *testing.T) {
resolver := dns.MockResolver{
A: map[string][]string{
"mox.example.": {"127.0.0.10"}, // For mx check.
},
PTR: map[string][]string{
"127.0.0.10": {"mox.example."}, // To get passed junk filter.
},
TXT: map[string][]string{
"mox.example.": {"v=spf1 ip4:127.0.0.10 -all"}, // To allow multiple recipients in transaction.
},
}
ts := newTestServer(t, filepath.FromSlash("../testdata/smtp/mox.conf"), resolver)
defer ts.close()
var msg = strings.ReplaceAll(`From: <[email protected]>
To: <[email protected]>
test email
`, "\n", "\r\n")
ts.run(func(client *smtpclient.Client) {
mailFrom := "[email protected]"
rcptTo := []string{"[email protected]", "mó[email protected]"}
_, err := client.DeliverMultiple(ctxbg, mailFrom, rcptTo, int64(len(msg)), strings.NewReader(msg), true, true, false)
// assuming there wasn't a per-recipient error
ts.smtpErr(err, nil)
ts.checkCount("Inbox", 1) // Receiving once. For explicit móx@ recipient, not for mjl@ due to msgfrom, and another again for móx@ due to rcpt to.
})
ts.run(func(client *smtpclient.Client) {
mailFrom := "[email protected]"
rcptTo := "[email protected]"
err := client.Deliver(ctxbg, mailFrom, rcptTo, int64(len(msg)), strings.NewReader(msg), false, false, false)
ts.smtpErr(err, nil)
ts.checkCount("Inbox", 2) // Only receiving 1 new message compared to previous, for mó[email protected], not mjl@.
})
msg = strings.ReplaceAll(`From: <[email protected]>
To: <[email protected]>
Subject: test
test email
`, "\n", "\r\n")
ts.run(func(client *smtpclient.Client) {
mailFrom := "[email protected]"
rcptTo := "[email protected]"
err := client.Deliver(ctxbg, mailFrom, rcptTo, int64(len(msg)), strings.NewReader(msg), false, false, false)
ts.smtpErr(err, &smtpclient.Error{Permanent: true, Code: smtp.C550MailboxUnavail, Secode: smtp.SePol7ExpnProhibited2})
})
msg = strings.ReplaceAll(`From: <[email protected]>
To: <[email protected]>
Subject: test
test email
`, "\n", "\r\n")
ts.run(func(client *smtpclient.Client) {
mailFrom := "[email protected]"
rcptTo := "[email protected]"
err := client.Deliver(ctxbg, mailFrom, rcptTo, int64(len(msg)), strings.NewReader(msg), false, false, false)
ts.smtpErr(err, nil)
})
}
// Message is rejected if no member accepts it.
func TestAliasDeliverReject(t *testing.T) {
resolver := dns.MockResolver{
A: map[string][]string{
"mox.example.": {"127.0.0.10"}, // For mx check.
},
PTR: map[string][]string{
"127.0.0.10": {"mox.example."}, // To get passed junk filter.
},
TXT: map[string][]string{
"mox.example.": {"v=spf1 ip4:127.0.0.10 -all"}, // To allow multiple recipients in transaction.
},
}
ts := newTestServer(t, filepath.FromSlash("../testdata/smtp/mox.conf"), resolver)
defer ts.close()
var msg = strings.ReplaceAll(`From: <[email protected]>
To: <[email protected]>
test email
`, "\n", "\r\n")
ts.run(func(client *smtpclient.Client) {
mailFrom := "[email protected]"
rcptTo := "[email protected]"
err := client.Deliver(ctxbg, mailFrom, rcptTo, int64(len(msg)), strings.NewReader(msg), false, false, false)
ts.smtpErr(err, nil)
ts.checkCount("Inbox", 1) // Only receiving for mó[email protected], not mjl@.
})
// Mark message as junk.
ts.xops.MessageFlagsAdd(ctxbg, pkglog, ts.acc, []int64{1}, []string{"$Junk"})
ts.run(func(client *smtpclient.Client) {
mailFrom := "[email protected]"
rcptTo := "[email protected]"
err := client.Deliver(ctxbg, mailFrom, rcptTo, int64(len(msg)), strings.NewReader(msg), false, false, false)
ts.smtpErr(err, &smtpclient.Error{Code: smtp.C451LocalErr, Secode: smtp.SeSys3Other0})
})
}