mirror of
https://github.com/axllent/mailpit.git
synced 2025-03-17 21:18:19 +02:00
Merge branch 'feature/smtp-allowed-recipients' into develop
This commit is contained in:
commit
d705571cb5
@ -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")
|
||||
|
@ -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)
|
||||
|
||||
}
|
||||
|
||||
|
@ -25,14 +25,12 @@ func runCSSTests(html string) ([]Warning, int, error) {
|
||||
|
||||
inlined, err := inlineRemoteCSS(html)
|
||||
if err != nil {
|
||||
// logger.Log().Warn(err)
|
||||
inlined = html
|
||||
}
|
||||
|
||||
// merge all CSS inline
|
||||
merged, err := mergeInlineCSS(inlined)
|
||||
if err != nil {
|
||||
// logger.Log().Warn(err)
|
||||
merged = inlined
|
||||
}
|
||||
|
||||
@ -157,7 +155,7 @@ func inlineRemoteCSS(h string) (string, error) {
|
||||
|
||||
resp, err := downloadToBytes(a.Val)
|
||||
if err != nil {
|
||||
logger.Log().Warningf("html check failed to download %s", a.Val)
|
||||
logger.Log().Warnf("[html-check] failed to download %s", a.Val)
|
||||
continue
|
||||
}
|
||||
|
||||
@ -179,7 +177,7 @@ func inlineRemoteCSS(h string) (string, error) {
|
||||
|
||||
newDoc, err := doc.Html()
|
||||
if err != nil {
|
||||
logger.Log().Warning(err)
|
||||
logger.Log().Warnf("[html-check] failed to download %s", err.Error())
|
||||
return h, err
|
||||
}
|
||||
|
||||
|
@ -79,7 +79,7 @@ func doHead(link string, followRedirects bool) (int, error) {
|
||||
|
||||
req, err := http.NewRequest("HEAD", link, nil)
|
||||
if err != nil {
|
||||
logger.Log().Error(err)
|
||||
logger.Log().Errorf("[link-check] %s", err.Error())
|
||||
return 0, err
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
}
|
||||
|
||||
|
@ -110,7 +110,7 @@ func InitDB() error {
|
||||
func Close() {
|
||||
if db != nil {
|
||||
if err := db.Close(); err != nil {
|
||||
logger.Log().Warning("[db] error closing database, ignoring")
|
||||
logger.Log().Warn("[db] error closing database, ignoring")
|
||||
}
|
||||
}
|
||||
|
||||
@ -128,7 +128,7 @@ func Store(body *[]byte) (string, error) {
|
||||
// Parse message body with enmime
|
||||
env, err := enmime.ReadEnvelope(bytes.NewReader(*body))
|
||||
if err != nil {
|
||||
logger.Log().Warningf("[db] %s", err.Error())
|
||||
logger.Log().Warnf("[message] %s", err.Error())
|
||||
return "", nil
|
||||
}
|
||||
|
||||
@ -271,12 +271,12 @@ func List(start, limit int) ([]MessageSummary, error) {
|
||||
em := MessageSummary{}
|
||||
|
||||
if err := row.Scan(&created, &id, &messageID, &subject, &metadata, &size, &attachments, &read, &snippet); err != nil {
|
||||
logger.Log().Error(err)
|
||||
logger.Log().Errorf("[db] %s", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if err := json.Unmarshal([]byte(metadata), &em); err != nil {
|
||||
logger.Log().Error(err)
|
||||
logger.Log().Errorf("[json] %s", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
@ -349,7 +349,7 @@ func GetMessage(id string) (*Message, error) {
|
||||
var created int64
|
||||
|
||||
if err := row.Scan(&created); err != nil {
|
||||
logger.Log().Error(err)
|
||||
logger.Log().Errorf("[db] %s", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
@ -357,7 +357,7 @@ func GetMessage(id string) (*Message, error) {
|
||||
|
||||
date = time.UnixMilli(created)
|
||||
}); err != nil {
|
||||
logger.Log().Error(err)
|
||||
logger.Log().Errorf("[db] %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -36,7 +36,7 @@ func migrateTagsToManyMany() {
|
||||
tags := []string{}
|
||||
|
||||
if err := json.Unmarshal([]byte(jsonTags), &tags); err != nil {
|
||||
logger.Log().Error(err)
|
||||
logger.Log().Errorf("[json] %s", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -29,7 +29,7 @@ func ReindexAll() {
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
logger.Log().Error(err)
|
||||
logger.Log().Errorf("[db] %s", err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
@ -59,7 +59,7 @@ func ReindexAll() {
|
||||
|
||||
env, err := enmime.ReadEnvelope(r)
|
||||
if err != nil {
|
||||
logger.Log().Error(err)
|
||||
logger.Log().Errorf("[message] %s", err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
@ -77,7 +77,7 @@ func ReindexAll() {
|
||||
ctx := context.Background()
|
||||
tx, err := db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
logger.Log().Error(err)
|
||||
logger.Log().Errorf("[db] %s", err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
@ -88,13 +88,13 @@ func ReindexAll() {
|
||||
for _, u := range updates {
|
||||
_, err = tx.Exec("UPDATE mailbox SET SearchText = ?, Snippet = ? WHERE ID = ?", u.SearchText, u.Snippet, u.ID)
|
||||
if err != nil {
|
||||
logger.Log().Error(err)
|
||||
logger.Log().Errorf("[db] %s", err.Error())
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if err := tx.Commit(); err != nil {
|
||||
logger.Log().Error(err)
|
||||
logger.Log().Errorf("[db] %s", err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -43,12 +43,12 @@ func Search(search string, start, limit int) ([]MessageSummary, int, error) {
|
||||
em := MessageSummary{}
|
||||
|
||||
if err := row.Scan(&created, &id, &messageID, &subject, &metadata, &size, &attachments, &read, &snippet, &ignore, &ignore, &ignore, &ignore); err != nil {
|
||||
logger.Log().Error(err)
|
||||
logger.Log().Errorf("[db] %s", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if err := json.Unmarshal([]byte(metadata), &em); err != nil {
|
||||
logger.Log().Error(err)
|
||||
logger.Log().Errorf("[db] %s", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
@ -114,7 +114,7 @@ func DeleteSearch(search string) error {
|
||||
var ignore string
|
||||
|
||||
if err := row.Scan(&created, &id, &messageID, &subject, &metadata, &size, &attachments, &read, &snippet, &ignore, &ignore, &ignore, &ignore); err != nil {
|
||||
logger.Log().Error(err)
|
||||
logger.Log().Errorf("[db] %s", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -149,7 +149,7 @@ func GetAllTags() []string {
|
||||
QueryAndClose(nil, db, func(row *sql.Rows) {
|
||||
tags = append(tags, name)
|
||||
}); err != nil {
|
||||
logger.Log().Error(err)
|
||||
logger.Log().Errorf("[db] %s", err.Error())
|
||||
}
|
||||
|
||||
return tags
|
||||
@ -172,7 +172,7 @@ func GetAllTagsCount() map[string]int64 {
|
||||
tags[name] = total
|
||||
// tags = append(tags, name)
|
||||
}); err != nil {
|
||||
logger.Log().Error(err)
|
||||
logger.Log().Errorf("[db] %s", err.Error())
|
||||
}
|
||||
|
||||
return tags
|
||||
@ -193,7 +193,7 @@ func pruneUnusedTags() error {
|
||||
var c int
|
||||
|
||||
if err := row.Scan(&id, &n, &c); err != nil {
|
||||
logger.Log().Error("[tags]", err)
|
||||
logger.Log().Errorf("[tags] %s", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -77,7 +77,7 @@ func Thumbnail(w http.ResponseWriter, r *http.Request) {
|
||||
img, err := imaging.Decode(buf)
|
||||
if err != nil {
|
||||
// it's not an image, return default
|
||||
logger.Log().Warning(err)
|
||||
logger.Log().Warnf("[image] %s", err.Error())
|
||||
blankImage(a, w)
|
||||
return
|
||||
}
|
||||
@ -99,7 +99,7 @@ func Thumbnail(w http.ResponseWriter, r *http.Request) {
|
||||
dst = imaging.OverlayCenter(dst, dstImageFill, 1.0)
|
||||
|
||||
if err := jpeg.Encode(foo, dst, &jpeg.Options{Quality: 70}); err != nil {
|
||||
logger.Log().Warning(err)
|
||||
logger.Log().Warnf("[image] %s", err.Error())
|
||||
blankImage(a, w)
|
||||
return
|
||||
}
|
||||
@ -120,7 +120,7 @@ func blankImage(a *enmime.Part, w http.ResponseWriter) {
|
||||
dstImageFill := imaging.Fill(img, thumbWidth, thumbHeight, imaging.Center, imaging.Lanczos)
|
||||
|
||||
if err := jpeg.Encode(foo, dstImageFill, &jpeg.Options{Quality: 70}); err != nil {
|
||||
logger.Log().Warning(err)
|
||||
logger.Log().Warnf("[image] %s", err.Error())
|
||||
}
|
||||
|
||||
fileName := a.FileName
|
||||
|
@ -95,7 +95,7 @@ func ProxyHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
address, err := absoluteURL(parts[3], uri)
|
||||
if err != nil {
|
||||
logger.Log().Error(err)
|
||||
logger.Log().Errorf("[proxy] %s", err.Error())
|
||||
return []byte(parts[3])
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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>
|
||||
|
@ -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"
|
||||
},
|
||||
|
@ -132,7 +132,7 @@ func ServeWs(hub *Hub, w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
conn, err := upgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
logger.Log().Error(err)
|
||||
logger.Log().Errorf("[websocket] %s", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user