mirror of
https://github.com/axllent/mailpit.git
synced 2025-04-23 12:18:56 +02:00
Feature: Add option to only allow SMTP recipients matching a regular expression (disable open-relay behaviour #219)
This commit is contained in:
parent
aad15945b3
commit
cdab59b295
cmd
config
internal/stats
server
@ -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.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().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.SMTPRelayConfigFile, "smtp-relay-config", config.SMTPRelayConfigFile, "SMTP configuration file to allow releasing messages")
|
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!)")
|
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 {
|
if len(os.Getenv("MP_SMTP_MAX_RECIPIENTS")) > 0 {
|
||||||
config.SMTPMaxRecipients, _ = strconv.Atoi(os.Getenv("MP_SMTP_MAX_RECIPIENTS"))
|
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
|
// Relay server config
|
||||||
config.SMTPRelayConfigFile = os.Getenv("MP_SMTP_RELAY_CONFIG")
|
config.SMTPRelayConfigFile = os.Getenv("MP_SMTP_RELAY_CONFIG")
|
||||||
|
@ -93,6 +93,12 @@ var (
|
|||||||
// @see https://github.com/axllent/mailpit/issues/87 & https://github.com/axllent/mailpit/issues/153
|
// @see https://github.com/axllent/mailpit/issues/87 & https://github.com/axllent/mailpit/issues/153
|
||||||
SMTPStrictRFCHeaders bool
|
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 is whether message releases are enabled, requires a valid SMTPRelayConfigFile
|
||||||
ReleaseEnabled = false
|
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 {
|
if err := parseRelayConfig(SMTPRelayConfigFile); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -335,11 +351,11 @@ func parseRelayConfig(c string) error {
|
|||||||
|
|
||||||
if SMTPRelayConfig.RecipientAllowlist != "" {
|
if SMTPRelayConfig.RecipientAllowlist != "" {
|
||||||
if err != nil {
|
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
|
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)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,9 +21,9 @@ var (
|
|||||||
|
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
|
|
||||||
smtpReceived int
|
smtpAccepted int
|
||||||
smtpReceivedSize int
|
smtpAcceptedSize int
|
||||||
smtpErrors int
|
smtpRejected int
|
||||||
smtpIgnored int
|
smtpIgnored int
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -52,13 +52,13 @@ type AppInformation struct {
|
|||||||
Memory uint64
|
Memory uint64
|
||||||
// Messages deleted
|
// Messages deleted
|
||||||
MessagesDeleted int
|
MessagesDeleted int
|
||||||
// SMTP messages received via since run
|
// SMTP accepted messages since run
|
||||||
SMTPReceived int
|
SMTPAccepted int
|
||||||
// Total size in bytes of received messages since run
|
// Total size in bytes of accepted messages since run
|
||||||
SMTPReceivedSize int
|
SMTPAcceptedSize int
|
||||||
// SMTP errors since run
|
// SMTP rejected messages since run
|
||||||
SMTPErrors int
|
SMTPRejected int
|
||||||
// SMTP messages ignored since run (duplicate IDs)
|
// SMTP ignored messages since run (duplicate IDs)
|
||||||
SMTPIgnored int
|
SMTPIgnored int
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -75,9 +75,9 @@ func Load() AppInformation {
|
|||||||
|
|
||||||
info.RuntimeStats.Uptime = int(time.Since(startedAt).Seconds())
|
info.RuntimeStats.Uptime = int(time.Since(startedAt).Seconds())
|
||||||
info.RuntimeStats.MessagesDeleted = storage.StatsDeleted
|
info.RuntimeStats.MessagesDeleted = storage.StatsDeleted
|
||||||
info.RuntimeStats.SMTPReceived = smtpReceived
|
info.RuntimeStats.SMTPAccepted = smtpAccepted
|
||||||
info.RuntimeStats.SMTPReceivedSize = smtpReceivedSize
|
info.RuntimeStats.SMTPAcceptedSize = smtpAcceptedSize
|
||||||
info.RuntimeStats.SMTPErrors = smtpErrors
|
info.RuntimeStats.SMTPRejected = smtpRejected
|
||||||
info.RuntimeStats.SMTPIgnored = smtpIgnored
|
info.RuntimeStats.SMTPIgnored = smtpIgnored
|
||||||
|
|
||||||
if latestVersionCache != "" {
|
if latestVersionCache != "" {
|
||||||
@ -116,18 +116,18 @@ func Track() {
|
|||||||
startedAt = time.Now()
|
startedAt = time.Now()
|
||||||
}
|
}
|
||||||
|
|
||||||
// LogSMTPReceived logs a successfully SMTP transaction
|
// LogSMTPAccepted logs a successful SMTP transaction
|
||||||
func LogSMTPReceived(size int) {
|
func LogSMTPAccepted(size int) {
|
||||||
mu.Lock()
|
mu.Lock()
|
||||||
smtpReceived = smtpReceived + 1
|
smtpAccepted = smtpAccepted + 1
|
||||||
smtpReceivedSize = smtpReceivedSize + size
|
smtpAcceptedSize = smtpAcceptedSize + size
|
||||||
mu.Unlock()
|
mu.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// LogSMTPError logs a failed SMTP transaction
|
// LogSMTPRejected logs a rejected SMTP transaction
|
||||||
func LogSMTPError() {
|
func LogSMTPRejected() {
|
||||||
mu.Lock()
|
mu.Lock()
|
||||||
smtpErrors = smtpErrors + 1
|
smtpRejected = smtpRejected + 1
|
||||||
mu.Unlock()
|
mu.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ func mailHandler(origin net.Addr, from string, to []string, data []byte) error {
|
|||||||
msg, err := mail.ReadMessage(bytes.NewReader(data))
|
msg, err := mail.ReadMessage(bytes.NewReader(data))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Log().Errorf("[smtpd] error parsing message: %s", err.Error())
|
logger.Log().Errorf("[smtpd] error parsing message: %s", err.Error())
|
||||||
stats.LogSMTPError()
|
stats.LogSMTPRejected()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -121,11 +121,10 @@ func mailHandler(origin net.Addr, from string, to []string, data []byte) error {
|
|||||||
_, err = storage.Store(&data)
|
_, err = storage.Store(&data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Log().Errorf("[db] error storing message: %s", err.Error())
|
logger.Log().Errorf("[db] error storing message: %s", err.Error())
|
||||||
stats.LogSMTPError()
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
stats.LogSMTPReceived(len(data))
|
stats.LogSMTPAccepted(len(data))
|
||||||
|
|
||||||
data = nil // avoid memory leaks
|
data = nil // avoid memory leaks
|
||||||
|
|
||||||
@ -153,6 +152,22 @@ func authHandlerAny(remoteAddr net.Addr, mechanism string, username []byte, _ []
|
|||||||
return true, nil
|
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
|
// Listen starts the SMTPD server
|
||||||
func Listen() error {
|
func Listen() error {
|
||||||
if config.SMTPAuthAllowInsecure {
|
if config.SMTPAuthAllowInsecure {
|
||||||
@ -178,6 +193,7 @@ func listenAndServe(addr string, handler smtpd.Handler, authHandler smtpd.AuthHa
|
|||||||
srv := &smtpd.Server{
|
srv := &smtpd.Server{
|
||||||
Addr: addr,
|
Addr: addr,
|
||||||
Handler: handler,
|
Handler: handler,
|
||||||
|
HandlerRcpt: handlerRcpt,
|
||||||
Appname: "Mailpit",
|
Appname: "Mailpit",
|
||||||
Hostname: "",
|
Hostname: "",
|
||||||
AuthHandler: nil,
|
AuthHandler: nil,
|
||||||
|
@ -240,19 +240,23 @@ export default {
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
SMTP messages received
|
SMTP messages accepted
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{ formatNumber(mailbox.appInfo.RuntimeStats.SMTPReceived) }}
|
{{ formatNumber(mailbox.appInfo.RuntimeStats.SMTPAccepted) }}
|
||||||
({{ getFileSize(mailbox.appInfo.RuntimeStats.SMTPReceivedSize) }})
|
<small class="text-secondary">
|
||||||
|
({{
|
||||||
|
getFileSize(mailbox.appInfo.RuntimeStats.SMTPAcceptedSize)
|
||||||
|
}})
|
||||||
|
</small>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
SMTP errors
|
SMTP messages rejected
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{ formatNumber(mailbox.appInfo.RuntimeStats.SMTPErrors) }}
|
{{ formatNumber(mailbox.appInfo.RuntimeStats.SMTPRejected) }}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -762,23 +762,23 @@
|
|||||||
"type": "integer",
|
"type": "integer",
|
||||||
"format": "int64"
|
"format": "int64"
|
||||||
},
|
},
|
||||||
"SMTPErrors": {
|
"SMTPAccepted": {
|
||||||
"description": "SMTP errors since run",
|
"description": "SMTP accepted messages since run",
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64"
|
||||||
|
},
|
||||||
|
"SMTPAcceptedSize": {
|
||||||
|
"description": "Total size in bytes of accepted messages since run",
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"format": "int64"
|
"format": "int64"
|
||||||
},
|
},
|
||||||
"SMTPIgnored": {
|
"SMTPIgnored": {
|
||||||
"description": "SMTP messages ignored since run (duplicate IDs)",
|
"description": "SMTP ignored messages since run (duplicate IDs)",
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"format": "int64"
|
"format": "int64"
|
||||||
},
|
},
|
||||||
"SMTPReceived": {
|
"SMTPRejected": {
|
||||||
"description": "SMTP messages received via since run",
|
"description": "SMTP rejected messages since run",
|
||||||
"type": "integer",
|
|
||||||
"format": "int64"
|
|
||||||
},
|
|
||||||
"SMTPReceivedSize": {
|
|
||||||
"description": "Total size in bytes of received messages since run",
|
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"format": "int64"
|
"format": "int64"
|
||||||
},
|
},
|
||||||
|
Loading…
x
Reference in New Issue
Block a user