This commit is contained in:
parent
7612e1002b
commit
9e78499a65
@ -107,6 +107,27 @@ func (s *Session) Rcpt(to string, opts *smtp.RcptOptions) error {
|
|||||||
if err := s.checkAuthorization(); err != nil {
|
if err := s.checkAuthorization(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if !validation.IsMailboxAddress(to) {
|
||||||
|
return &smtp.SMTPError{Code: 550, EnhancedCode: smtp.EnhancedCode{5, 1, 3}, Message: "Invalid recipient address"}
|
||||||
|
}
|
||||||
|
// 匿名连接仅接收受管域名的邮件,不能等到正文阶段再静默丢弃外域收件人。
|
||||||
|
if s.authorization == nil {
|
||||||
|
domains := config.CurrentMailDomains().Accepted
|
||||||
|
if len(domains) == 0 {
|
||||||
|
return &smtp.SMTPError{Code: 451, EnhancedCode: smtp.EnhancedCode{4, 3, 0}, Message: "Mail domains are unavailable"}
|
||||||
|
}
|
||||||
|
domain := to[strings.LastIndex(to, "@")+1:]
|
||||||
|
local := false
|
||||||
|
for _, accepted := range domains {
|
||||||
|
if strings.EqualFold(domain, accepted) {
|
||||||
|
local = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !local {
|
||||||
|
return &smtp.SMTPError{Code: 550, EnhancedCode: smtp.EnhancedCode{5, 7, 1}, Message: "Relay access denied"}
|
||||||
|
}
|
||||||
|
}
|
||||||
if validation.IsNotificationAddress(to, config.CurrentMailDomains().Accepted) {
|
if validation.IsNotificationAddress(to, config.CurrentMailDomains().Accepted) {
|
||||||
return &smtp.SMTPError{Code: 550, EnhancedCode: smtp.EnhancedCode{5, 1, 1}, Message: "Notification address does not accept mail"}
|
return &smtp.SMTPError{Code: 550, EnhancedCode: smtp.EnhancedCode{5, 1, 1}, Message: "Notification address does not accept mail"}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,10 +5,44 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"git.pcux.cn/xuans/Smail/config"
|
||||||
"git.pcux.cn/xuans/Smail/utils/context"
|
"git.pcux.cn/xuans/Smail/utils/context"
|
||||||
"github.com/emersion/go-smtp"
|
"github.com/emersion/go-smtp"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestAnonymousRcptRejectsForeignDomains(t *testing.T) {
|
||||||
|
oldConfig := config.Instance
|
||||||
|
t.Cleanup(func() { config.Instance = oldConfig })
|
||||||
|
config.Instance = &config.Config{Domain: "example.com", Domains: []string{"example.com", "alias.example"}}
|
||||||
|
for _, tt := range []struct {
|
||||||
|
address string
|
||||||
|
allowed bool
|
||||||
|
}{
|
||||||
|
{"alice@example.com", true},
|
||||||
|
{"alice@EXAMPLE.COM", true},
|
||||||
|
{"alice@alias.example", true},
|
||||||
|
{"alice@external.example", false},
|
||||||
|
{"alice@example.com.attacker.test", false},
|
||||||
|
{"alice@sub.example.com", false},
|
||||||
|
{"not-an-address", false},
|
||||||
|
} {
|
||||||
|
t.Run(tt.address, func(t *testing.T) {
|
||||||
|
session := &Session{Ctx: &context.Context{}, To: []string{"existing@example.com"}}
|
||||||
|
err := session.Rcpt(tt.address, nil)
|
||||||
|
if tt.allowed {
|
||||||
|
if err != nil || len(session.To) != 2 {
|
||||||
|
t.Fatalf("受管域名收件失败:%v", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var denied *smtp.SMTPError
|
||||||
|
if !errors.As(err, &denied) || denied.Code != 550 || len(session.To) != 1 {
|
||||||
|
t.Fatalf("匿名外域收件未明确拒绝或污染收件列表:%v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestSessionResetClearsTransactionStateOnly(t *testing.T) {
|
func TestSessionResetClearsTransactionStateOnly(t *testing.T) {
|
||||||
ctx := &context.Context{UserID: 42, UserAccount: "smail", IsAdmin: true}
|
ctx := &context.Context{UserID: 42, UserAccount: "smail", IsAdmin: true}
|
||||||
session := &Session{
|
session := &Session{
|
||||||
|
|||||||
@ -287,10 +287,22 @@ func doSend(ctx *context.Context, fromDomain string, data []byte, to []*parsemai
|
|||||||
}
|
}
|
||||||
err = deliverMXContext(deliveryContext, hosts, func(host, port string) error {
|
err = deliverMXContext(deliveryContext, hosts, func(host, port string) error {
|
||||||
address := net.JoinHostPort(host, port)
|
address := net.JoinHostPort(host, port)
|
||||||
|
var attemptErr error
|
||||||
if port == "465" {
|
if port == "465" {
|
||||||
return smtp.SendMailWithTLSContext(deliveryContext, host, address, nil, from, fromDomain, buildAddress(tos), data)
|
attemptErr = smtp.SendMailWithTLSContext(deliveryContext, host, address, nil, from, fromDomain, buildAddress(tos), data)
|
||||||
|
} else {
|
||||||
|
attemptErr = smtp.SendMailContext(deliveryContext, host, address, nil, from, fromDomain, buildAddress(tos), data)
|
||||||
}
|
}
|
||||||
return smtp.SendMailContext(deliveryContext, host, address, nil, from, fromDomain, buildAddress(tos), data)
|
if attemptErr != nil {
|
||||||
|
// 分端口记录网络失败,不记录邮件内容、凭据及远端 SMTP 响应原文。
|
||||||
|
fields := log.Fields{"action": "外域邮件投递尝试", "mxHost": host, "mxPort": port, "reason": logstore.MailFailureReason(attemptErr)}
|
||||||
|
var networkErr *net.OpError
|
||||||
|
if errors.As(attemptErr, &networkErr) {
|
||||||
|
fields["networkError"] = networkErr.Error()
|
||||||
|
}
|
||||||
|
log.WithContext(ctx).WithFields(fields).Warn("外域邮件投递通道失败")
|
||||||
|
}
|
||||||
|
return attemptErr
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
recordFailure(err)
|
recordFailure(err)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user