mirror of
https://github.com/axllent/mailpit.git
synced 2025-08-13 20:04:49 +02:00
Merge branch 'feature/drop-rejected-recipients' into develop
This commit is contained in:
@@ -126,6 +126,7 @@ func init() {
|
|||||||
rootCmd.Flags().BoolVar(&config.SMTPStrictRFCHeaders, "smtp-strict-rfc-headers", config.SMTPStrictRFCHeaders, "Return SMTP error if message headers contain <CR><CR><LF>")
|
rootCmd.Flags().BoolVar(&config.SMTPStrictRFCHeaders, "smtp-strict-rfc-headers", config.SMTPStrictRFCHeaders, "Return SMTP error if message headers contain <CR><CR><LF>")
|
||||||
rootCmd.Flags().IntVar(&config.SMTPMaxRecipients, "smtp-max-recipients", config.SMTPMaxRecipients, "Maximum SMTP recipients allowed")
|
rootCmd.Flags().IntVar(&config.SMTPMaxRecipients, "smtp-max-recipients", config.SMTPMaxRecipients, "Maximum SMTP recipients allowed")
|
||||||
rootCmd.Flags().StringVar(&config.SMTPAllowedRecipients, "smtp-allowed-recipients", config.SMTPAllowedRecipients, "Only allow SMTP recipients matching a regular expression (default allow all)")
|
rootCmd.Flags().StringVar(&config.SMTPAllowedRecipients, "smtp-allowed-recipients", config.SMTPAllowedRecipients, "Only allow SMTP recipients matching a regular expression (default allow all)")
|
||||||
|
rootCmd.Flags().BoolVar(&config.SMTPIgnoreRejectedRecipients, "smtp-ignore-rejected-recipients", config.SMTPIgnoreRejectedRecipients, "Ignore rejected SMTP recipients with 2xx response")
|
||||||
rootCmd.Flags().BoolVar(&smtpd.DisableReverseDNS, "smtp-disable-rdns", smtpd.DisableReverseDNS, "Disable SMTP reverse DNS lookups")
|
rootCmd.Flags().BoolVar(&smtpd.DisableReverseDNS, "smtp-disable-rdns", smtpd.DisableReverseDNS, "Disable SMTP reverse DNS lookups")
|
||||||
|
|
||||||
// SMTP relay
|
// SMTP relay
|
||||||
@@ -301,6 +302,9 @@ func initConfigFromEnv() {
|
|||||||
if len(os.Getenv("MP_SMTP_ALLOWED_RECIPIENTS")) > 0 {
|
if len(os.Getenv("MP_SMTP_ALLOWED_RECIPIENTS")) > 0 {
|
||||||
config.SMTPAllowedRecipients = os.Getenv("MP_SMTP_ALLOWED_RECIPIENTS")
|
config.SMTPAllowedRecipients = os.Getenv("MP_SMTP_ALLOWED_RECIPIENTS")
|
||||||
}
|
}
|
||||||
|
if getEnabledFromEnv("MP_SMTP_IGNORE_REJECTED_RECIPIENTS") {
|
||||||
|
config.SMTPIgnoreRejectedRecipients = true
|
||||||
|
}
|
||||||
if getEnabledFromEnv("MP_SMTP_DISABLE_RDNS") {
|
if getEnabledFromEnv("MP_SMTP_DISABLE_RDNS") {
|
||||||
smtpd.DisableReverseDNS = true
|
smtpd.DisableReverseDNS = true
|
||||||
}
|
}
|
||||||
|
@@ -181,6 +181,9 @@ var (
|
|||||||
// SMTPAllowedRecipientsRegexp is the compiled version of SMTPAllowedRecipients
|
// SMTPAllowedRecipientsRegexp is the compiled version of SMTPAllowedRecipients
|
||||||
SMTPAllowedRecipientsRegexp *regexp.Regexp
|
SMTPAllowedRecipientsRegexp *regexp.Regexp
|
||||||
|
|
||||||
|
// SMTPIgnoreRejectedRecipients if true, will accept emails to rejected recipients with 2xx response but silently drop them
|
||||||
|
SMTPIgnoreRejectedRecipients bool
|
||||||
|
|
||||||
// POP3Listen address - if set then Mailpit will start the POP3 server and listen on this address
|
// POP3Listen address - if set then Mailpit will start the POP3 server and listen on this address
|
||||||
POP3Listen = "[::]:1110"
|
POP3Listen = "[::]:1110"
|
||||||
|
|
||||||
@@ -581,6 +584,14 @@ func VerifyConfig() error {
|
|||||||
logger.Log().Infof("[smtp] only allowing recipients matching regexp: %s", SMTPAllowedRecipients)
|
logger.Log().Infof("[smtp] only allowing recipients matching regexp: %s", SMTPAllowedRecipients)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if SMTPIgnoreRejectedRecipients {
|
||||||
|
if SMTPAllowedRecipientsRegexp == nil {
|
||||||
|
logger.Log().Warn("[smtp] ignoring rejected recipients has no effect without setting smtp-allowed-recipients")
|
||||||
|
} else {
|
||||||
|
logger.Log().Info("[smtp] ignoring rejected recipients")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if err := parseRelayConfig(SMTPRelayConfigFile); err != nil {
|
if err := parseRelayConfig(SMTPRelayConfigFile); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@@ -194,15 +194,16 @@ func listenAndServe(addr string, handler MsgIDHandler, authHandler AuthHandler)
|
|||||||
|
|
||||||
Debug = true // to enable Mailpit logging
|
Debug = true // to enable Mailpit logging
|
||||||
srv := &Server{
|
srv := &Server{
|
||||||
Addr: addr,
|
Addr: addr,
|
||||||
MsgIDHandler: handler,
|
MsgIDHandler: handler,
|
||||||
HandlerRcpt: handlerRcpt,
|
HandlerRcpt: handlerRcpt,
|
||||||
AppName: "Mailpit",
|
AppName: "Mailpit",
|
||||||
Hostname: "",
|
Hostname: "",
|
||||||
AuthHandler: nil,
|
AuthHandler: nil,
|
||||||
AuthRequired: false,
|
AuthRequired: false,
|
||||||
MaxRecipients: config.SMTPMaxRecipients,
|
MaxRecipients: config.SMTPMaxRecipients,
|
||||||
DisableReverseDNS: DisableReverseDNS,
|
IgnoreRejectedRecipients: config.SMTPIgnoreRejectedRecipients,
|
||||||
|
DisableReverseDNS: DisableReverseDNS,
|
||||||
LogRead: func(remoteIP, verb, line string) {
|
LogRead: func(remoteIP, verb, line string) {
|
||||||
logger.Log().Debugf("[smtpd] %s (%s) %s", verbLogTranslator(verb), remoteIP, line)
|
logger.Log().Debugf("[smtpd] %s (%s) %s", verbLogTranslator(verb), remoteIP, line)
|
||||||
},
|
},
|
||||||
|
@@ -92,26 +92,27 @@ type LogFunc func(remoteIP, verb, line string)
|
|||||||
|
|
||||||
// Server is an SMTP server.
|
// Server is an SMTP server.
|
||||||
type Server struct {
|
type Server struct {
|
||||||
Addr string // TCP address to listen on, defaults to ":25" (all addresses, port 25) if empty
|
Addr string // TCP address to listen on, defaults to ":25" (all addresses, port 25) if empty
|
||||||
AppName string
|
AppName string
|
||||||
AuthHandler AuthHandler
|
AuthHandler AuthHandler
|
||||||
AuthMechs map[string]bool // Override list of allowed authentication mechanisms. Currently supported: LOGIN, PLAIN, CRAM-MD5. Enabling LOGIN and PLAIN will reduce RFC 4954 compliance.
|
AuthMechs map[string]bool // Override list of allowed authentication mechanisms. Currently supported: LOGIN, PLAIN, CRAM-MD5. Enabling LOGIN and PLAIN will reduce RFC 4954 compliance.
|
||||||
AuthRequired bool // Require authentication for every command except AUTH, EHLO, HELO, NOOP, RSET or QUIT as per RFC 4954. Ignored if AuthHandler is not configured.
|
AuthRequired bool // Require authentication for every command except AUTH, EHLO, HELO, NOOP, RSET or QUIT as per RFC 4954. Ignored if AuthHandler is not configured.
|
||||||
DisableReverseDNS bool // Disable reverse DNS lookups, enforces "unknown" hostname
|
DisableReverseDNS bool // Disable reverse DNS lookups, enforces "unknown" hostname
|
||||||
Handler Handler
|
Handler Handler
|
||||||
HandlerRcpt HandlerRcpt
|
HandlerRcpt HandlerRcpt
|
||||||
Hostname string
|
Hostname string
|
||||||
LogRead LogFunc
|
LogRead LogFunc
|
||||||
LogWrite LogFunc
|
LogWrite LogFunc
|
||||||
MaxSize int // Maximum message size allowed, in bytes
|
MaxSize int // Maximum message size allowed, in bytes
|
||||||
MaxRecipients int // Maximum number of recipients, defaults to 100.
|
MaxRecipients int // Maximum number of recipients, defaults to 100.
|
||||||
MsgIDHandler MsgIDHandler
|
MsgIDHandler MsgIDHandler
|
||||||
Timeout time.Duration
|
IgnoreRejectedRecipients bool // Accept emails to rejected recipients with 2xx response but silently drop them
|
||||||
TLSConfig *tls.Config
|
Timeout time.Duration
|
||||||
TLSListener bool // Listen for incoming TLS connections only (not recommended as it may reduce compatibility). Ignored if TLS is not configured.
|
TLSConfig *tls.Config
|
||||||
TLSRequired bool // Require TLS for every command except NOOP, EHLO, STARTTLS, or QUIT as per RFC 3207. Ignored if TLS is not configured.
|
TLSListener bool // Listen for incoming TLS connections only (not recommended as it may reduce compatibility). Ignored if TLS is not configured.
|
||||||
Protocol string // Default tcp, supports unix
|
TLSRequired bool // Require TLS for every command except NOOP, EHLO, STARTTLS, or QUIT as per RFC 3207. Ignored if TLS is not configured.
|
||||||
SocketPerm fs.FileMode // if using Unix socket, socket permissions
|
Protocol string // Default tcp, supports unix
|
||||||
|
SocketPerm fs.FileMode // if using Unix socket, socket permissions
|
||||||
|
|
||||||
inShutdown int32 // server was closed or shutdown
|
inShutdown int32 // server was closed or shutdown
|
||||||
openSessions int32 // count of open sessions
|
openSessions int32 // count of open sessions
|
||||||
@@ -363,6 +364,7 @@ func (s *session) serve() {
|
|||||||
var from string
|
var from string
|
||||||
var gotFrom bool
|
var gotFrom bool
|
||||||
var to []string
|
var to []string
|
||||||
|
var hasRejectedRecipients bool
|
||||||
var buffer bytes.Buffer
|
var buffer bytes.Buffer
|
||||||
|
|
||||||
// RFC 5321 specifies support for minimum of 100 recipients is required.
|
// RFC 5321 specifies support for minimum of 100 recipients is required.
|
||||||
@@ -397,6 +399,7 @@ loop:
|
|||||||
from = ""
|
from = ""
|
||||||
gotFrom = false
|
gotFrom = false
|
||||||
to = nil
|
to = nil
|
||||||
|
hasRejectedRecipients = false
|
||||||
buffer.Reset()
|
buffer.Reset()
|
||||||
case "EHLO":
|
case "EHLO":
|
||||||
s.remoteName = args
|
s.remoteName = args
|
||||||
@@ -406,6 +409,7 @@ loop:
|
|||||||
from = ""
|
from = ""
|
||||||
gotFrom = false
|
gotFrom = false
|
||||||
to = nil
|
to = nil
|
||||||
|
hasRejectedRecipients = false
|
||||||
buffer.Reset()
|
buffer.Reset()
|
||||||
case "MAIL":
|
case "MAIL":
|
||||||
if s.srv.TLSConfig != nil && s.srv.TLSRequired && !s.tls {
|
if s.srv.TLSConfig != nil && s.srv.TLSRequired && !s.tls {
|
||||||
@@ -457,6 +461,7 @@ loop:
|
|||||||
}
|
}
|
||||||
|
|
||||||
to = nil
|
to = nil
|
||||||
|
hasRejectedRecipients = false
|
||||||
buffer.Reset()
|
buffer.Reset()
|
||||||
case "RCPT":
|
case "RCPT":
|
||||||
if s.srv.TLSConfig != nil && s.srv.TLSRequired && !s.tls {
|
if s.srv.TLSConfig != nil && s.srv.TLSRequired && !s.tls {
|
||||||
@@ -492,6 +497,9 @@ loop:
|
|||||||
if accept {
|
if accept {
|
||||||
to = append(to, match[1])
|
to = append(to, match[1])
|
||||||
s.writef("250 2.1.5 Ok")
|
s.writef("250 2.1.5 Ok")
|
||||||
|
} else if s.srv.IgnoreRejectedRecipients {
|
||||||
|
hasRejectedRecipients = true
|
||||||
|
s.writef("250 2.1.5 Ok")
|
||||||
} else {
|
} else {
|
||||||
s.writef("550 5.1.0 Requested action not taken: mailbox unavailable")
|
s.writef("550 5.1.0 Requested action not taken: mailbox unavailable")
|
||||||
}
|
}
|
||||||
@@ -506,7 +514,8 @@ loop:
|
|||||||
s.writef("530 5.7.0 Authentication required")
|
s.writef("530 5.7.0 Authentication required")
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if !gotFrom || len(to) == 0 {
|
hasRecipients := len(to) > 0 || hasRejectedRecipients
|
||||||
|
if !gotFrom || !hasRecipients {
|
||||||
s.writef("503 5.5.1 Bad sequence of commands (MAIL & RCPT required before DATA)")
|
s.writef("503 5.5.1 Bad sequence of commands (MAIL & RCPT required before DATA)")
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -536,11 +545,13 @@ loop:
|
|||||||
|
|
||||||
// Create Received header & write message body into buffer.
|
// Create Received header & write message body into buffer.
|
||||||
buffer.Reset()
|
buffer.Reset()
|
||||||
buffer.Write(s.makeHeaders(to))
|
if len(to) > 0 {
|
||||||
|
buffer.Write(s.makeHeaders(to))
|
||||||
|
}
|
||||||
buffer.Write(data)
|
buffer.Write(data)
|
||||||
|
|
||||||
// Pass mail on to handler.
|
// Pass mail on to handler only if there are valid recipients.
|
||||||
if s.srv.Handler != nil {
|
if len(to) > 0 && s.srv.Handler != nil {
|
||||||
err := s.srv.Handler(s.conn.RemoteAddr(), from, to, buffer.Bytes())
|
err := s.srv.Handler(s.conn.RemoteAddr(), from, to, buffer.Bytes())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
checkErrFormat := regexp.MustCompile(`^([2-5][0-9]{2})[\s\-](.+)$`)
|
checkErrFormat := regexp.MustCompile(`^([2-5][0-9]{2})[\s\-](.+)$`)
|
||||||
@@ -552,7 +563,7 @@ loop:
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
s.writef("250 2.0.0 Ok: queued")
|
s.writef("250 2.0.0 Ok: queued")
|
||||||
} else if s.srv.MsgIDHandler != nil {
|
} else if len(to) > 0 && s.srv.MsgIDHandler != nil {
|
||||||
msgID, err := s.srv.MsgIDHandler(s.conn.RemoteAddr(), from, to, buffer.Bytes(), s.username)
|
msgID, err := s.srv.MsgIDHandler(s.conn.RemoteAddr(), from, to, buffer.Bytes(), s.username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
checkErrFormat := regexp.MustCompile(`^([2-5][0-9]{2})[\s\-](.+)$`)
|
checkErrFormat := regexp.MustCompile(`^([2-5][0-9]{2})[\s\-](.+)$`)
|
||||||
@@ -570,6 +581,13 @@ loop:
|
|||||||
s.writef("250 2.0.0 Ok: queued")
|
s.writef("250 2.0.0 Ok: queued")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
if hasRejectedRecipients && Debug {
|
||||||
|
if s.srv.LogWrite != nil {
|
||||||
|
s.srv.LogWrite(s.remoteIP, "DEBUG", "Message from sender silently dropped (rejected recipients)")
|
||||||
|
} else {
|
||||||
|
log.Printf("%s DEBUG Message from sender silently dropped (rejected recipients)", s.remoteIP)
|
||||||
|
}
|
||||||
|
}
|
||||||
s.writef("250 2.0.0 Ok: queued")
|
s.writef("250 2.0.0 Ok: queued")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -577,6 +595,7 @@ loop:
|
|||||||
from = ""
|
from = ""
|
||||||
gotFrom = false
|
gotFrom = false
|
||||||
to = nil
|
to = nil
|
||||||
|
hasRejectedRecipients = false
|
||||||
buffer.Reset()
|
buffer.Reset()
|
||||||
case "QUIT":
|
case "QUIT":
|
||||||
s.writef("221 2.0.0 %s %s ESMTP Service closing transmission channel", s.srv.Hostname, s.srv.AppName)
|
s.writef("221 2.0.0 %s %s ESMTP Service closing transmission channel", s.srv.Hostname, s.srv.AppName)
|
||||||
@@ -590,6 +609,7 @@ loop:
|
|||||||
from = ""
|
from = ""
|
||||||
gotFrom = false
|
gotFrom = false
|
||||||
to = nil
|
to = nil
|
||||||
|
hasRejectedRecipients = false
|
||||||
buffer.Reset()
|
buffer.Reset()
|
||||||
case "NOOP":
|
case "NOOP":
|
||||||
s.writef("250 2.0.0 Ok")
|
s.writef("250 2.0.0 Ok")
|
||||||
@@ -666,6 +686,7 @@ loop:
|
|||||||
from = ""
|
from = ""
|
||||||
gotFrom = false
|
gotFrom = false
|
||||||
to = nil
|
to = nil
|
||||||
|
hasRejectedRecipients = false
|
||||||
buffer.Reset()
|
buffer.Reset()
|
||||||
case "AUTH":
|
case "AUTH":
|
||||||
if s.srv.TLSConfig != nil && s.srv.TLSRequired && !s.tls {
|
if s.srv.TLSConfig != nil && s.srv.TLSRequired && !s.tls {
|
||||||
|
@@ -1598,3 +1598,172 @@ func TestCmdShutdown(t *testing.T) {
|
|||||||
|
|
||||||
_ = conn.Close()
|
_ = conn.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type mockDropRejectedHandler struct {
|
||||||
|
handlerCalled int
|
||||||
|
lastFrom string
|
||||||
|
lastTo []string
|
||||||
|
msgIDCalled int
|
||||||
|
lastMsgIDFrom string
|
||||||
|
lastMsgIDTo []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockDropRejectedHandler) handler(remoteAddr net.Addr, from string, to []string, data []byte) error {
|
||||||
|
m.handlerCalled++
|
||||||
|
m.lastFrom = from
|
||||||
|
m.lastTo = append([]string{}, to...) // copy slice
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockDropRejectedHandler) msgIDHandler(remoteAddr net.Addr, from string, to []string, data []byte, username *string) (string, error) {
|
||||||
|
m.msgIDCalled++
|
||||||
|
m.lastMsgIDFrom = from
|
||||||
|
m.lastMsgIDTo = append([]string{}, to...) // copy slice
|
||||||
|
return "test-message-id", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test the IgnoreRejectedRecipients option
|
||||||
|
func TestIgnoreRejectedRecipients(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
IgnoreRejectedRecipients bool
|
||||||
|
handlerRcpt func(net.Addr, string, string) bool
|
||||||
|
rcptCommands []struct{ addr, expectedCode string }
|
||||||
|
expectedHandlerCalls int
|
||||||
|
expectedHandlerRecipients []string
|
||||||
|
useMsgIDHandler bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Disabled_DefaultBehavior",
|
||||||
|
IgnoreRejectedRecipients: false,
|
||||||
|
handlerRcpt: func(remoteAddr net.Addr, from string, to string) bool {
|
||||||
|
return !strings.HasSuffix(to, "@rejected.com")
|
||||||
|
},
|
||||||
|
rcptCommands: []struct{ addr, expectedCode string }{
|
||||||
|
{"valid@example.com", "250"},
|
||||||
|
{"invalid@rejected.com", "550"},
|
||||||
|
},
|
||||||
|
expectedHandlerCalls: 1,
|
||||||
|
expectedHandlerRecipients: []string{"valid@example.com"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Enabled_MixedRecipients",
|
||||||
|
IgnoreRejectedRecipients: true,
|
||||||
|
handlerRcpt: func(remoteAddr net.Addr, from string, to string) bool {
|
||||||
|
return !strings.HasSuffix(to, "@rejected.com")
|
||||||
|
},
|
||||||
|
rcptCommands: []struct{ addr, expectedCode string }{
|
||||||
|
{"valid1@example.com", "250"},
|
||||||
|
{"valid2@example.com", "250"},
|
||||||
|
{"invalid1@rejected.com", "250"}, // Now accepted but dropped
|
||||||
|
{"invalid2@rejected.com", "250"}, // Now accepted but dropped
|
||||||
|
},
|
||||||
|
expectedHandlerCalls: 1,
|
||||||
|
expectedHandlerRecipients: []string{"valid1@example.com", "valid2@example.com"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Enabled_AllRejected",
|
||||||
|
IgnoreRejectedRecipients: true,
|
||||||
|
handlerRcpt: func(remoteAddr net.Addr, from string, to string) bool {
|
||||||
|
return false // Reject all
|
||||||
|
},
|
||||||
|
rcptCommands: []struct{ addr, expectedCode string }{
|
||||||
|
{"test1@example.com", "250"}, // Accepted but dropped
|
||||||
|
{"test2@example.com", "250"}, // Accepted but dropped
|
||||||
|
},
|
||||||
|
expectedHandlerCalls: 0, // No handler calls since all rejected
|
||||||
|
expectedHandlerRecipients: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Enabled_OnlyValid",
|
||||||
|
IgnoreRejectedRecipients: true,
|
||||||
|
handlerRcpt: func(remoteAddr net.Addr, from string, to string) bool {
|
||||||
|
return strings.HasSuffix(to, "@valid.com")
|
||||||
|
},
|
||||||
|
rcptCommands: []struct{ addr, expectedCode string }{
|
||||||
|
{"user1@valid.com", "250"},
|
||||||
|
{"user2@valid.com", "250"},
|
||||||
|
{"user3@valid.com", "250"},
|
||||||
|
},
|
||||||
|
expectedHandlerCalls: 1,
|
||||||
|
expectedHandlerRecipients: []string{"user1@valid.com", "user2@valid.com", "user3@valid.com"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Enabled_WithMsgIDHandler",
|
||||||
|
IgnoreRejectedRecipients: true,
|
||||||
|
handlerRcpt: func(remoteAddr net.Addr, from string, to string) bool {
|
||||||
|
return !strings.HasSuffix(to, "@rejected.com")
|
||||||
|
},
|
||||||
|
rcptCommands: []struct{ addr, expectedCode string }{
|
||||||
|
{"valid@example.com", "250"},
|
||||||
|
{"invalid@rejected.com", "250"}, // Accepted but dropped
|
||||||
|
},
|
||||||
|
expectedHandlerCalls: 1,
|
||||||
|
expectedHandlerRecipients: []string{"valid@example.com"},
|
||||||
|
useMsgIDHandler: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
mock := &mockDropRejectedHandler{}
|
||||||
|
|
||||||
|
server := &Server{
|
||||||
|
Hostname: "mail.example.com",
|
||||||
|
AppName: "TestMail",
|
||||||
|
MaxRecipients: 100,
|
||||||
|
HandlerRcpt: tt.handlerRcpt,
|
||||||
|
IgnoreRejectedRecipients: tt.IgnoreRejectedRecipients,
|
||||||
|
}
|
||||||
|
|
||||||
|
if tt.useMsgIDHandler {
|
||||||
|
server.MsgIDHandler = mock.msgIDHandler
|
||||||
|
} else {
|
||||||
|
server.Handler = mock.handler
|
||||||
|
}
|
||||||
|
|
||||||
|
conn := newConn(t, server)
|
||||||
|
defer func() { _ = conn.Close() }()
|
||||||
|
|
||||||
|
cmdCode(t, conn, "HELO host.example.com", "250")
|
||||||
|
cmdCode(t, conn, "MAIL FROM:<sender@example.com>", "250")
|
||||||
|
|
||||||
|
// Send RCPT commands
|
||||||
|
for _, rcpt := range tt.rcptCommands {
|
||||||
|
cmdCode(t, conn, "RCPT TO:<"+rcpt.addr+">", rcpt.expectedCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send DATA
|
||||||
|
cmdCode(t, conn, "DATA", "354")
|
||||||
|
cmdCode(t, conn, "Subject: Test\r\n\r\nTest message\r\n.", "250")
|
||||||
|
cmdCode(t, conn, "QUIT", "221")
|
||||||
|
|
||||||
|
// Verify handler calls
|
||||||
|
if tt.useMsgIDHandler {
|
||||||
|
if mock.msgIDCalled != tt.expectedHandlerCalls {
|
||||||
|
t.Errorf("Expected %d MsgIDHandler calls, got %d", tt.expectedHandlerCalls, mock.msgIDCalled)
|
||||||
|
}
|
||||||
|
if tt.expectedHandlerCalls > 0 {
|
||||||
|
if mock.lastMsgIDFrom != "sender@example.com" {
|
||||||
|
t.Errorf("Expected from 'sender@example.com', got '%s'", mock.lastMsgIDFrom)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(mock.lastMsgIDTo, tt.expectedHandlerRecipients) {
|
||||||
|
t.Errorf("Expected recipients %v, got %v", tt.expectedHandlerRecipients, mock.lastMsgIDTo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if mock.handlerCalled != tt.expectedHandlerCalls {
|
||||||
|
t.Errorf("Expected %d handler calls, got %d", tt.expectedHandlerCalls, mock.handlerCalled)
|
||||||
|
}
|
||||||
|
if tt.expectedHandlerCalls > 0 {
|
||||||
|
if mock.lastFrom != "sender@example.com" {
|
||||||
|
t.Errorf("Expected from 'sender@example.com', got '%s'", mock.lastFrom)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(mock.lastTo, tt.expectedHandlerRecipients) {
|
||||||
|
t.Errorf("Expected recipients %v, got %v", tt.expectedHandlerRecipients, mock.lastTo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user