mirror of
https://github.com/axllent/mailpit.git
synced 2025-04-23 12:18:56 +02:00
Feature: Option to override the From email address in SMTP relay configuration (#414)
This commit is contained in:
parent
f278933bb9
commit
a95bc3d29f
@ -283,6 +283,7 @@ func initConfigFromEnv() {
|
|||||||
config.SMTPRelayConfig.Password = os.Getenv("MP_SMTP_RELAY_PASSWORD")
|
config.SMTPRelayConfig.Password = os.Getenv("MP_SMTP_RELAY_PASSWORD")
|
||||||
config.SMTPRelayConfig.Secret = os.Getenv("MP_SMTP_RELAY_SECRET")
|
config.SMTPRelayConfig.Secret = os.Getenv("MP_SMTP_RELAY_SECRET")
|
||||||
config.SMTPRelayConfig.ReturnPath = os.Getenv("MP_SMTP_RELAY_RETURN_PATH")
|
config.SMTPRelayConfig.ReturnPath = os.Getenv("MP_SMTP_RELAY_RETURN_PATH")
|
||||||
|
config.SMTPRelayConfig.OverrideFrom = os.Getenv("MP_SMTP_RELAY_OVERRIDE_FROM")
|
||||||
config.SMTPRelayConfig.AllowedRecipients = os.Getenv("MP_SMTP_RELAY_ALLOWED_RECIPIENTS")
|
config.SMTPRelayConfig.AllowedRecipients = os.Getenv("MP_SMTP_RELAY_ALLOWED_RECIPIENTS")
|
||||||
config.SMTPRelayConfig.BlockedRecipients = os.Getenv("MP_SMTP_RELAY_BLOCKED_RECIPIENTS")
|
config.SMTPRelayConfig.BlockedRecipients = os.Getenv("MP_SMTP_RELAY_BLOCKED_RECIPIENTS")
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"net/mail"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
@ -204,6 +205,7 @@ type SMTPRelayConfigStruct struct {
|
|||||||
Password string `yaml:"password"` // plain
|
Password string `yaml:"password"` // plain
|
||||||
Secret string `yaml:"secret"` // cram-md5
|
Secret string `yaml:"secret"` // cram-md5
|
||||||
ReturnPath string `yaml:"return-path"` // allow overriding the bounce address
|
ReturnPath string `yaml:"return-path"` // allow overriding the bounce address
|
||||||
|
OverrideFrom string `yaml:"override-from"` // allow overriding of the from address
|
||||||
AllowedRecipients string `yaml:"allowed-recipients"` // regex, if set needs to match for mails to be relayed
|
AllowedRecipients string `yaml:"allowed-recipients"` // regex, if set needs to match for mails to be relayed
|
||||||
AllowedRecipientsRegexp *regexp.Regexp // compiled regexp using AllowedRecipients
|
AllowedRecipientsRegexp *regexp.Regexp // compiled regexp using AllowedRecipients
|
||||||
BlockedRecipients string `yaml:"blocked-recipients"` // regex, if set prevents relating to these addresses
|
BlockedRecipients string `yaml:"blocked-recipients"` // regex, if set prevents relating to these addresses
|
||||||
@ -611,6 +613,15 @@ func validateRelayConfig() error {
|
|||||||
logger.Log().Infof("[smtp] relay recipient blocklist is active with the following regexp: %s", SMTPRelayConfig.BlockedRecipients)
|
logger.Log().Infof("[smtp] relay recipient blocklist is active with the following regexp: %s", SMTPRelayConfig.BlockedRecipients)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if SMTPRelayConfig.OverrideFrom != "" {
|
||||||
|
m, err := mail.ParseAddress(SMTPRelayConfig.OverrideFrom)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("[smtp] relay override-from is not a valid email address: %s", SMTPRelayConfig.OverrideFrom)
|
||||||
|
}
|
||||||
|
|
||||||
|
SMTPRelayConfig.OverrideFrom = m.Address
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ import (
|
|||||||
|
|
||||||
"github.com/axllent/mailpit/config"
|
"github.com/axllent/mailpit/config"
|
||||||
"github.com/axllent/mailpit/internal/logger"
|
"github.com/axllent/mailpit/internal/logger"
|
||||||
|
"github.com/axllent/mailpit/internal/tools"
|
||||||
)
|
)
|
||||||
|
|
||||||
func autoRelayMessage(from string, to []string, data *[]byte) {
|
func autoRelayMessage(from string, to []string, data *[]byte) {
|
||||||
@ -86,6 +87,15 @@ func Relay(from string, to []string, msg []byte) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if config.SMTPRelayConfig.OverrideFrom != "" {
|
||||||
|
msg, err = tools.OverrideFromHeader(msg, config.SMTPRelayConfig.OverrideFrom)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error overriding From header: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
from = config.SMTPRelayConfig.OverrideFrom
|
||||||
|
}
|
||||||
|
|
||||||
if err = c.Mail(from); err != nil {
|
if err = c.Mail(from); err != nil {
|
||||||
return fmt.Errorf("error response to MAIL command: %s", err.Error())
|
return fmt.Errorf("error response to MAIL command: %s", err.Error())
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"net/mail"
|
"net/mail"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/axllent/mailpit/internal/logger"
|
"github.com/axllent/mailpit/internal/logger"
|
||||||
)
|
)
|
||||||
@ -97,3 +98,63 @@ func UpdateMessageHeader(msg []byte, header, value string) ([]byte, error) {
|
|||||||
|
|
||||||
return msg, nil
|
return msg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OverrideFromHeader scans a message for the From header and replaces it with a different email address.
|
||||||
|
func OverrideFromHeader(msg []byte, address string) ([]byte, error) {
|
||||||
|
reader := bytes.NewReader(msg)
|
||||||
|
m, err := mail.ReadMessage(reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.Header.Get("From") != "" {
|
||||||
|
reBlank := regexp.MustCompile(`^\s+`)
|
||||||
|
reHdr := regexp.MustCompile(`(?i)^` + regexp.QuoteMeta("From:"))
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(bytes.NewReader(msg))
|
||||||
|
found := false
|
||||||
|
hdr := []byte("")
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := scanner.Bytes()
|
||||||
|
if !found && reHdr.Match(line) {
|
||||||
|
// add the first line starting with <header>:
|
||||||
|
hdr = append(hdr, line...)
|
||||||
|
hdr = append(hdr, []byte("\r\n")...)
|
||||||
|
found = true
|
||||||
|
} else if found && reBlank.Match(line) {
|
||||||
|
// add any following lines starting with a whitespace (tab or space)
|
||||||
|
hdr = append(hdr, line...)
|
||||||
|
hdr = append(hdr, []byte("\r\n")...)
|
||||||
|
} else if found {
|
||||||
|
// stop scanning, we have the full <header>
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(hdr) > 0 {
|
||||||
|
originalFrom := strings.TrimRight(string(hdr[5:]), "\r\n")
|
||||||
|
|
||||||
|
from, err := mail.ParseAddress(originalFrom)
|
||||||
|
if err != nil {
|
||||||
|
// error parsing the from address, so just replace the whole line
|
||||||
|
msg = bytes.Replace(msg, hdr, []byte("From: "+address+"\r\n"), 1)
|
||||||
|
} else {
|
||||||
|
originalFrom = from.Address
|
||||||
|
// replace the from email, but keep the original name
|
||||||
|
from.Address = address
|
||||||
|
msg = bytes.Replace(msg, hdr, []byte("From: "+from.String()+"\r\n"), 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// insert the original From header as X-Original-From
|
||||||
|
msg = append([]byte("X-Original-From: "+originalFrom+"\r\n"), msg...)
|
||||||
|
|
||||||
|
logger.Log().Debugf("[release] Replaced From email address with %s", address)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// no From header, so add one
|
||||||
|
msg = append([]byte("From: "+address+"\r\n"), msg...)
|
||||||
|
logger.Log().Debugf("[release] Added From email: %s", address)
|
||||||
|
}
|
||||||
|
|
||||||
|
return msg, nil
|
||||||
|
}
|
||||||
|
@ -60,6 +60,8 @@ type webUIConfiguration struct {
|
|||||||
AllowedRecipients string
|
AllowedRecipients string
|
||||||
// Block relaying to these recipients (regex)
|
// Block relaying to these recipients (regex)
|
||||||
BlockedRecipients string
|
BlockedRecipients string
|
||||||
|
// Overrides the "From" address for all relayed messages
|
||||||
|
OverrideFrom string
|
||||||
// DEPRECATED 2024/03/12
|
// DEPRECATED 2024/03/12
|
||||||
// swagger:ignore
|
// swagger:ignore
|
||||||
RecipientAllowlist string
|
RecipientAllowlist string
|
||||||
@ -111,6 +113,7 @@ func WebUIConfig(w http.ResponseWriter, _ *http.Request) {
|
|||||||
conf.MessageRelay.ReturnPath = config.SMTPRelayConfig.ReturnPath
|
conf.MessageRelay.ReturnPath = config.SMTPRelayConfig.ReturnPath
|
||||||
conf.MessageRelay.AllowedRecipients = config.SMTPRelayConfig.AllowedRecipients
|
conf.MessageRelay.AllowedRecipients = config.SMTPRelayConfig.AllowedRecipients
|
||||||
conf.MessageRelay.BlockedRecipients = config.SMTPRelayConfig.BlockedRecipients
|
conf.MessageRelay.BlockedRecipients = config.SMTPRelayConfig.BlockedRecipients
|
||||||
|
conf.MessageRelay.OverrideFrom = config.SMTPRelayConfig.OverrideFrom
|
||||||
// DEPRECATED 2024/03/12
|
// DEPRECATED 2024/03/12
|
||||||
conf.MessageRelay.RecipientAllowlist = config.SMTPRelayConfig.AllowedRecipients
|
conf.MessageRelay.RecipientAllowlist = config.SMTPRelayConfig.AllowedRecipients
|
||||||
}
|
}
|
||||||
|
@ -86,7 +86,13 @@ export default {
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<label class="col-sm-2 col-form-label text-body-secondary">From</label>
|
<label class="col-sm-2 col-form-label text-body-secondary">From</label>
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
<input type="text" aria-label="From address" readonly class="form-control-plaintext"
|
<div v-if="mailbox.uiConfig.MessageRelay.OverrideFrom != ''" class="form-control-plaintext">
|
||||||
|
{{ mailbox.uiConfig.MessageRelay.OverrideFrom }}
|
||||||
|
<span class="text-muted small ms-2">
|
||||||
|
* address overridden by the relay configuration.
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<input v-else type="text" aria-label="From address" readonly class="form-control-plaintext"
|
||||||
:value="message.From ? message.From.Address : ''">
|
:value="message.From ? message.From.Address : ''">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -125,15 +131,17 @@ export default {
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h6>Notes</h6>
|
<h6>Notes</h6>
|
||||||
<ul>
|
<ul>
|
||||||
<li v-if="mailbox.uiConfig.MessageRelay.AllowedRecipients != ''" class="form-text">
|
<li v-if="mailbox.uiConfig.MessageRelay.AllowedRecipients != ''" class="form-text">
|
||||||
A recipient <b>allowlist</b> has been configured. Any mail address not matching the following will be rejected:
|
A recipient <b>allowlist</b> has been configured. Any mail address not matching the
|
||||||
|
following will be rejected:
|
||||||
<code>{{ mailbox.uiConfig.MessageRelay.AllowedRecipients }}</code>
|
<code>{{ mailbox.uiConfig.MessageRelay.AllowedRecipients }}</code>
|
||||||
</li>
|
</li>
|
||||||
<li v-if="mailbox.uiConfig.MessageRelay.BlockedRecipients != ''" class="form-text">
|
<li v-if="mailbox.uiConfig.MessageRelay.BlockedRecipients != ''" class="form-text">
|
||||||
A recipient <b>blocklist</b> has been configured. Any mail address matching the following will be rejected:
|
A recipient <b>blocklist</b> has been configured. Any mail address matching the following
|
||||||
|
will be rejected:
|
||||||
<code>{{ mailbox.uiConfig.MessageRelay.BlockedRecipients }}</code>
|
<code>{{ mailbox.uiConfig.MessageRelay.BlockedRecipients }}</code>
|
||||||
</li>
|
</li>
|
||||||
<li class="form-text">
|
<li class="form-text">
|
||||||
@ -142,8 +150,8 @@ export default {
|
|||||||
<li class="form-text">
|
<li class="form-text">
|
||||||
SMTP delivery failures will bounce back to
|
SMTP delivery failures will bounce back to
|
||||||
<code v-if="mailbox.uiConfig.MessageRelay.ReturnPath != ''">
|
<code v-if="mailbox.uiConfig.MessageRelay.ReturnPath != ''">
|
||||||
{{ mailbox.uiConfig.MessageRelay.ReturnPath }}
|
{{ mailbox.uiConfig.MessageRelay.ReturnPath }}
|
||||||
</code>
|
</code>
|
||||||
<code v-else>{{ message.ReturnPath }}</code>.
|
<code v-else>{{ message.ReturnPath }}</code>.
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -1948,6 +1948,10 @@
|
|||||||
"description": "Whether message relaying (release) is enabled",
|
"description": "Whether message relaying (release) is enabled",
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
|
"OverrideFrom": {
|
||||||
|
"description": "Overrides the \"From\" address for all relayed messages",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"ReturnPath": {
|
"ReturnPath": {
|
||||||
"description": "Enforced Return-Path (if set) for relay bounces",
|
"description": "Enforced Return-Path (if set) for relay bounces",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user