1
0
mirror of https://github.com/axllent/mailpit.git synced 2024-12-26 22:56:43 +02:00

Feature: Add option to only allow SMTP recipients matching a regular expression (disable open-relay behaviour #219)

This commit is contained in:
Ralph Slooten 2024-01-03 12:06:36 +13:00
parent aad15945b3
commit cdab59b295
6 changed files with 80 additions and 40 deletions

View File

@ -103,6 +103,7 @@ func init() {
rootCmd.Flags().BoolVar(&config.SMTPAuthAllowInsecure, "smtp-auth-allow-insecure", config.SMTPAuthAllowInsecure, "Enable insecure PLAIN & LOGIN authentication")
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().StringVar(&config.SMTPAllowedRecipients, "smtp-allowed-recipients", config.SMTPAllowedRecipients, "Only allow SMTP recipients matching a regular expression (default allow all)")
rootCmd.Flags().StringVar(&config.SMTPRelayConfigFile, "smtp-relay-config", config.SMTPRelayConfigFile, "SMTP configuration file to allow releasing messages")
rootCmd.Flags().BoolVar(&config.SMTPRelayAllIncoming, "smtp-relay-all", config.SMTPRelayAllIncoming, "Relay all incoming messages via external SMTP server (caution!)")
@ -170,6 +171,9 @@ func initConfigFromEnv() {
if len(os.Getenv("MP_SMTP_MAX_RECIPIENTS")) > 0 {
config.SMTPMaxRecipients, _ = strconv.Atoi(os.Getenv("MP_SMTP_MAX_RECIPIENTS"))
}
if len(os.Getenv("MP_SMTP_ALLOWED_RECIPIENTS")) > 0 {
config.SMTPAllowedRecipients = os.Getenv("MP_SMTP_ALLOWED_RECIPIENTS")
}
// Relay server config
config.SMTPRelayConfigFile = os.Getenv("MP_SMTP_RELAY_CONFIG")

View File

@ -93,6 +93,12 @@ var (
// @see https://github.com/axllent/mailpit/issues/87 & https://github.com/axllent/mailpit/issues/153
SMTPStrictRFCHeaders bool
// SMTPAllowedRecipients if set, will only accept recipients matching this regular expression
SMTPAllowedRecipients string
// SMTPAllowedRecipientsRegexp is the compiled version of SMTPAllowedRecipients
SMTPAllowedRecipientsRegexp *regexp.Regexp
// ReleaseEnabled is whether message releases are enabled, requires a valid SMTPRelayConfigFile
ReleaseEnabled = false
@ -262,6 +268,16 @@ func VerifyConfig() error {
}
}
if SMTPAllowedRecipients != "" {
restrictRegexp, err := regexp.Compile(SMTPAllowedRecipients)
if err != nil {
return fmt.Errorf("Failed to compile smtp-allowed-recipients regexp: %s", err.Error())
}
SMTPAllowedRecipientsRegexp = restrictRegexp
logger.Log().Infof("[smtp] only allowing recipients matching the following regexp: %s", SMTPAllowedRecipients)
}
if err := parseRelayConfig(SMTPRelayConfigFile); err != nil {
return err
}
@ -335,11 +351,11 @@ func parseRelayConfig(c string) error {
if SMTPRelayConfig.RecipientAllowlist != "" {
if err != nil {
return fmt.Errorf("failed to compile recipient allowlist regexp: %e", err)
return fmt.Errorf("Failed to compile relay recipient allowlist regexp: %s", err.Error())
}
SMTPRelayConfig.RecipientAllowlistRegexp = allowlistRegexp
logger.Log().Infof("[smtp] recipient allowlist is active with the following regexp: %s", SMTPRelayConfig.RecipientAllowlist)
logger.Log().Infof("[smtp] relay recipient allowlist is active with the following regexp: %s", SMTPRelayConfig.RecipientAllowlist)
}

View File

@ -21,9 +21,9 @@ var (
mu sync.RWMutex
smtpReceived int
smtpReceivedSize int
smtpErrors int
smtpAccepted int
smtpAcceptedSize int
smtpRejected int
smtpIgnored int
)
@ -52,13 +52,13 @@ type AppInformation struct {
Memory uint64
// Messages deleted
MessagesDeleted int
// SMTP messages received via since run
SMTPReceived int
// Total size in bytes of received messages since run
SMTPReceivedSize int
// SMTP errors since run
SMTPErrors int
// SMTP messages ignored since run (duplicate IDs)
// SMTP accepted messages since run
SMTPAccepted int
// Total size in bytes of accepted messages since run
SMTPAcceptedSize int
// SMTP rejected messages since run
SMTPRejected int
// SMTP ignored messages since run (duplicate IDs)
SMTPIgnored int
}
}
@ -75,9 +75,9 @@ func Load() AppInformation {
info.RuntimeStats.Uptime = int(time.Since(startedAt).Seconds())
info.RuntimeStats.MessagesDeleted = storage.StatsDeleted
info.RuntimeStats.SMTPReceived = smtpReceived
info.RuntimeStats.SMTPReceivedSize = smtpReceivedSize
info.RuntimeStats.SMTPErrors = smtpErrors
info.RuntimeStats.SMTPAccepted = smtpAccepted
info.RuntimeStats.SMTPAcceptedSize = smtpAcceptedSize
info.RuntimeStats.SMTPRejected = smtpRejected
info.RuntimeStats.SMTPIgnored = smtpIgnored
if latestVersionCache != "" {
@ -116,18 +116,18 @@ func Track() {
startedAt = time.Now()
}
// LogSMTPReceived logs a successfully SMTP transaction
func LogSMTPReceived(size int) {
// LogSMTPAccepted logs a successful SMTP transaction
func LogSMTPAccepted(size int) {
mu.Lock()
smtpReceived = smtpReceived + 1
smtpReceivedSize = smtpReceivedSize + size
smtpAccepted = smtpAccepted + 1
smtpAcceptedSize = smtpAcceptedSize + size
mu.Unlock()
}
// LogSMTPError logs a failed SMTP transaction
func LogSMTPError() {
// LogSMTPRejected logs a rejected SMTP transaction
func LogSMTPRejected() {
mu.Lock()
smtpErrors = smtpErrors + 1
smtpRejected = smtpRejected + 1
mu.Unlock()
}

View File

@ -28,7 +28,7 @@ func mailHandler(origin net.Addr, from string, to []string, data []byte) error {
msg, err := mail.ReadMessage(bytes.NewReader(data))
if err != nil {
logger.Log().Errorf("[smtpd] error parsing message: %s", err.Error())
stats.LogSMTPError()
stats.LogSMTPRejected()
return err
}
@ -121,11 +121,10 @@ func mailHandler(origin net.Addr, from string, to []string, data []byte) error {
_, err = storage.Store(&data)
if err != nil {
logger.Log().Errorf("[db] error storing message: %s", err.Error())
stats.LogSMTPError()
return err
}
stats.LogSMTPReceived(len(data))
stats.LogSMTPAccepted(len(data))
data = nil // avoid memory leaks
@ -153,6 +152,22 @@ func authHandlerAny(remoteAddr net.Addr, mechanism string, username []byte, _ []
return true, nil
}
// HandlerRcpt used to optionally restrict recipients based on `--smtp-allowed-recipients`
func handlerRcpt(remoteAddr net.Addr, from string, to string) bool {
if config.SMTPAllowedRecipientsRegexp == nil {
return true
}
result := config.SMTPAllowedRecipientsRegexp.MatchString(to)
if !result {
logger.Log().Warnf("[smtpd] rejected message to %s from %s (%s)", to, from, cleanIP(remoteAddr))
stats.LogSMTPRejected()
}
return result
}
// Listen starts the SMTPD server
func Listen() error {
if config.SMTPAuthAllowInsecure {
@ -178,6 +193,7 @@ func listenAndServe(addr string, handler smtpd.Handler, authHandler smtpd.AuthHa
srv := &smtpd.Server{
Addr: addr,
Handler: handler,
HandlerRcpt: handlerRcpt,
Appname: "Mailpit",
Hostname: "",
AuthHandler: nil,

View File

@ -240,19 +240,23 @@ export default {
</tr>
<tr>
<td>
SMTP messages received
SMTP messages accepted
</td>
<td>
{{ formatNumber(mailbox.appInfo.RuntimeStats.SMTPReceived) }}
({{ getFileSize(mailbox.appInfo.RuntimeStats.SMTPReceivedSize) }})
{{ formatNumber(mailbox.appInfo.RuntimeStats.SMTPAccepted) }}
<small class="text-secondary">
({{
getFileSize(mailbox.appInfo.RuntimeStats.SMTPAcceptedSize)
}})
</small>
</td>
</tr>
<tr>
<td>
SMTP errors
SMTP messages rejected
</td>
<td>
{{ formatNumber(mailbox.appInfo.RuntimeStats.SMTPErrors) }}
{{ formatNumber(mailbox.appInfo.RuntimeStats.SMTPRejected) }}
</td>
</tr>
<tr>

View File

@ -762,23 +762,23 @@
"type": "integer",
"format": "int64"
},
"SMTPErrors": {
"description": "SMTP errors since run",
"SMTPAccepted": {
"description": "SMTP accepted messages since run",
"type": "integer",
"format": "int64"
},
"SMTPAcceptedSize": {
"description": "Total size in bytes of accepted messages since run",
"type": "integer",
"format": "int64"
},
"SMTPIgnored": {
"description": "SMTP messages ignored since run (duplicate IDs)",
"description": "SMTP ignored messages since run (duplicate IDs)",
"type": "integer",
"format": "int64"
},
"SMTPReceived": {
"description": "SMTP messages received via since run",
"type": "integer",
"format": "int64"
},
"SMTPReceivedSize": {
"description": "Total size in bytes of received messages since run",
"SMTPRejected": {
"description": "SMTP rejected messages since run",
"type": "integer",
"format": "int64"
},