mirror of
https://github.com/mjl-/mox.git
synced 2025-07-13 12:14:37 +03:00
@ -26,6 +26,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
"unicode"
|
||||||
|
|
||||||
"golang.org/x/exp/maps"
|
"golang.org/x/exp/maps"
|
||||||
"golang.org/x/text/unicode/norm"
|
"golang.org/x/text/unicode/norm"
|
||||||
@ -331,7 +332,7 @@ type conn struct {
|
|||||||
futureRelease time.Time // MAIL FROM with HOLDFOR or HOLDUNTIL.
|
futureRelease time.Time // MAIL FROM with HOLDFOR or HOLDUNTIL.
|
||||||
futureReleaseRequest string // For use in DSNs, either "for;" or "until;" plus original value. ../rfc/4865:305
|
futureReleaseRequest string // For use in DSNs, either "for;" or "until;" plus original value. ../rfc/4865:305
|
||||||
has8bitmime bool // If MAIL FROM parameter BODY=8BITMIME was sent. Required for SMTPUTF8.
|
has8bitmime bool // If MAIL FROM parameter BODY=8BITMIME was sent. Required for SMTPUTF8.
|
||||||
smtputf8 bool // todo future: we should keep track of this per recipient. perhaps only a specific recipient requires smtputf8, e.g. due to a utf8 localpart. we should decide ourselves if the message needs smtputf8, e.g. due to utf8 header values.
|
smtputf8 bool // todo future: we should keep track of this per recipient. perhaps only a specific recipient requires smtputf8, e.g. due to a utf8 localpart.
|
||||||
recipients []rcptAccount
|
recipients []rcptAccount
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1899,6 +1900,34 @@ func (c *conn) submit(ctx context.Context, recvHdrFor func(string) string, msgWr
|
|||||||
xsmtpUserErrorf(smtp.C550MailboxUnavail, smtp.SePol7DeliveryUnauth1, "must match authenticated user")
|
xsmtpUserErrorf(smtp.C550MailboxUnavail, smtp.SePol7DeliveryUnauth1, "must match authenticated user")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if the message contains non-ascii characters. If no such characters are found,
|
||||||
|
// the SMTPUTF8 extension is not required.
|
||||||
|
// ../rfc/6531:497
|
||||||
|
isASCII := func(s string) bool {
|
||||||
|
for _, c := range s {
|
||||||
|
if c > unicode.MaxASCII {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
c.smtputf8 = !isASCII(c.mailFrom.Localpart.String())
|
||||||
|
for _, rcpt := range c.recipients {
|
||||||
|
if !isASCII(rcpt.rcptTo.Localpart.String()) {
|
||||||
|
c.smtputf8 = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, values := range header {
|
||||||
|
for _, value := range values {
|
||||||
|
if !isASCII(value) {
|
||||||
|
c.smtputf8 = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// TLS-Required: No header makes us not enforce recipient domain's TLS policy.
|
// TLS-Required: No header makes us not enforce recipient domain's TLS policy.
|
||||||
// ../rfc/8689:206
|
// ../rfc/8689:206
|
||||||
// Only when requiretls smtp extension wasn't used. ../rfc/8689:246
|
// Only when requiretls smtp extension wasn't used. ../rfc/8689:246
|
||||||
|
@ -1767,3 +1767,53 @@ func TestFutureRelease(t *testing.T) {
|
|||||||
test(" HOLDFOR=1 HOLDFOR=1", "501") // Duplicate.
|
test(" HOLDFOR=1 HOLDFOR=1", "501") // Duplicate.
|
||||||
test(" HOLDFOR=1 HOLDUNTIL="+time.Now().Add(time.Hour).UTC().Format(time.RFC3339), "501") // Duplicate.
|
test(" HOLDFOR=1 HOLDUNTIL="+time.Now().Add(time.Hour).UTC().Format(time.RFC3339), "501") // Duplicate.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test SMTPUTF8
|
||||||
|
func TestSMTPUTF8(t *testing.T) {
|
||||||
|
ts := newTestServer(t, filepath.FromSlash("../testdata/smtp/mox.conf"), dns.MockResolver{})
|
||||||
|
defer ts.close()
|
||||||
|
|
||||||
|
ts.user = "mjl@mox.example"
|
||||||
|
ts.pass = password0
|
||||||
|
ts.submission = true
|
||||||
|
|
||||||
|
test := func(mailFrom string, rcptTo string, headerValue string, clientSmtputf8 bool, expectedSmtputf8 bool, expErr *smtpclient.Error) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
ts.run(func(_ error, client *smtpclient.Client) {
|
||||||
|
t.Helper()
|
||||||
|
msg := strings.ReplaceAll(fmt.Sprintf(`From: <%s>
|
||||||
|
To: <%s>
|
||||||
|
Subject: test
|
||||||
|
X-Custom-Test-Header: %s
|
||||||
|
|
||||||
|
test email
|
||||||
|
`, mailFrom, rcptTo, headerValue), "\n", "\r\n")
|
||||||
|
|
||||||
|
err := client.Deliver(ctxbg, mailFrom, rcptTo, int64(len(msg)), strings.NewReader(msg), false, clientSmtputf8, false)
|
||||||
|
var cerr smtpclient.Error
|
||||||
|
if expErr == nil && err != nil || expErr != nil && (err == nil || !errors.As(err, &cerr) || cerr.Code != expErr.Code || cerr.Secode != expErr.Secode) {
|
||||||
|
t.Fatalf("got err %#v, expected %#v", err, expErr)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
msgs, _ := queue.List(ctxbg, queue.Filter{})
|
||||||
|
queuedMsg := msgs[len(msgs)-1]
|
||||||
|
if queuedMsg.SMTPUTF8 != expectedSmtputf8 {
|
||||||
|
t.Fatalf("[%s / %s / %s] got SMTPUTF8 %t, expected %t", mailFrom, rcptTo, headerValue, queuedMsg.SMTPUTF8, expectedSmtputf8)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
test(`mjl@mox.example`, `remote@example.org`, "ascii", false, false, nil)
|
||||||
|
test(`mjl@mox.example`, `remote@example.org`, "ascii", true, false, nil)
|
||||||
|
test(`mjl@mox.example`, `🙂@example.org`, "ascii", true, true, nil)
|
||||||
|
test(`mjl@mox.example`, `🙂@example.org`, "ascii", false, true, &smtpclient.Error{Permanent: true, Code: smtp.C553BadMailbox, Secode: smtp.SeMsg6NonASCIIAddrNotPermitted7})
|
||||||
|
test(`Ω@mox.example`, `remote@example.org`, "ascii", true, true, nil)
|
||||||
|
test(`Ω@mox.example`, `remote@example.org`, "ascii", false, true, &smtpclient.Error{Permanent: true, Code: smtp.C550MailboxUnavail, Secode: smtp.SeMsg6NonASCIIAddrNotPermitted7})
|
||||||
|
test(`mjl@mox.example`, `remote@example.org`, "non-ascii-😍", false, true, nil)
|
||||||
|
test(`mjl@mox.example`, `remote@example.org`, "non-ascii-😍", true, true, nil)
|
||||||
|
test(`Ω@mox.example`, `🙂@example.org`, "non-ascii-😍", true, true, nil)
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user