mirror of
https://github.com/foxcpp/maddy.git
synced 2025-04-05 14:07:38 +03:00
It is useful to define background tasks lifetimes more precisely, especially involving timeouts and other cancellation methods. On top of that, several tracing facilities are context-based (e.g. runtime/trace), so it is possible to use them now.
177 lines
4.2 KiB
Go
177 lines
4.2 KiB
Go
package modify
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"github.com/emersion/go-message/textproto"
|
|
"github.com/foxcpp/maddy/internal/address"
|
|
"github.com/foxcpp/maddy/internal/buffer"
|
|
"github.com/foxcpp/maddy/internal/config"
|
|
"github.com/foxcpp/maddy/internal/module"
|
|
)
|
|
|
|
// replaceAddr is a simple module that replaces matching sender (or recipient) address
|
|
// in messages.
|
|
//
|
|
// If created with modName = "replace_sender", it will change sender address.
|
|
// If created with modName = "replace_rcpt", it will change recipient addresses.
|
|
//
|
|
// Matching is done by either regexp or plain string. Module arguments are as
|
|
// arguments for brevity..
|
|
type replaceAddr struct {
|
|
modName string
|
|
instName string
|
|
|
|
inlineFromArg string
|
|
inlineToArg string
|
|
replaceSender bool
|
|
replaceRcpt bool
|
|
|
|
// Matchers. Only one is used.
|
|
fromString string
|
|
fromRegex *regexp.Regexp
|
|
|
|
// Replacement string
|
|
to string
|
|
}
|
|
|
|
func NewReplaceAddr(modName, instName string, _, inlineArgs []string) (module.Module, error) {
|
|
r := replaceAddr{
|
|
modName: modName,
|
|
instName: instName,
|
|
replaceSender: modName == "replace_sender",
|
|
replaceRcpt: modName == "replace_rcpt",
|
|
}
|
|
|
|
switch len(inlineArgs) {
|
|
case 0:
|
|
// Not inline definition.
|
|
case 2:
|
|
r.inlineFromArg = inlineArgs[0]
|
|
r.inlineToArg = inlineArgs[1]
|
|
default:
|
|
return nil, fmt.Errorf("%s: invalid amount of inline arguments", modName)
|
|
}
|
|
|
|
return &r, nil
|
|
}
|
|
|
|
func (r *replaceAddr) Init(m *config.Map) error {
|
|
var fromCfg, toCfg string
|
|
m.String("from", false, false, r.inlineFromArg, &fromCfg)
|
|
m.String("to", false, false, r.inlineToArg, &toCfg)
|
|
if _, err := m.Process(); err != nil {
|
|
return err
|
|
}
|
|
|
|
if fromCfg == "" {
|
|
return fmt.Errorf("%s: missing 'from' argument or directive", r.modName)
|
|
}
|
|
if toCfg == "" {
|
|
return fmt.Errorf("%s: missing 'to' argument or directive", r.modName)
|
|
}
|
|
|
|
if strings.HasPrefix(fromCfg, "/") {
|
|
if !strings.HasSuffix(fromCfg, "/") {
|
|
return fmt.Errorf("%s: missing trailing slash in 'from' value", r.modName)
|
|
}
|
|
|
|
regex := fromCfg[1 : len(fromCfg)-1]
|
|
|
|
// Regexp should match entire string, so add anchors
|
|
// if they are not present.
|
|
if !strings.HasPrefix(regex, "^") {
|
|
regex = "^" + regex
|
|
}
|
|
if !strings.HasSuffix(regex, "$") {
|
|
regex = regex + "$"
|
|
}
|
|
|
|
regex = "(?i)" + regex
|
|
|
|
var err error
|
|
r.fromRegex, err = regexp.Compile(regex)
|
|
if err != nil {
|
|
return fmt.Errorf("%s: %v", r.modName, err)
|
|
}
|
|
}
|
|
if strings.HasSuffix(fromCfg, "/") && !strings.HasPrefix(fromCfg, "/") {
|
|
return fmt.Errorf("%s: missing leading slash in 'from' value", r.modName)
|
|
}
|
|
|
|
r.fromString = fromCfg
|
|
|
|
if strings.HasPrefix(toCfg, "/") || strings.HasSuffix(toCfg, "/") {
|
|
return fmt.Errorf("%s: can't use regexp in 'to' value", r.modName)
|
|
}
|
|
if r.fromRegex == nil && strings.Contains(toCfg, "$") {
|
|
return fmt.Errorf("%s: can't reference capture groups in 'to' if 'from' is not a regexp", r.modName)
|
|
}
|
|
r.to = toCfg
|
|
|
|
return nil
|
|
}
|
|
|
|
func (r replaceAddr) Name() string {
|
|
return r.modName
|
|
}
|
|
|
|
func (r replaceAddr) InstanceName() string {
|
|
return r.instName
|
|
}
|
|
|
|
func (r replaceAddr) ModStateForMsg(ctx context.Context, msgMeta *module.MsgMetadata) (module.ModifierState, error) {
|
|
return r, nil
|
|
}
|
|
|
|
func (r replaceAddr) RewriteSender(ctx context.Context, mailFrom string) (string, error) {
|
|
if r.replaceSender {
|
|
return r.rewrite(mailFrom)
|
|
}
|
|
return mailFrom, nil
|
|
}
|
|
|
|
func (r replaceAddr) RewriteRcpt(ctx context.Context, rcptTo string) (string, error) {
|
|
if r.replaceRcpt {
|
|
return r.rewrite(rcptTo)
|
|
}
|
|
return rcptTo, nil
|
|
}
|
|
|
|
func (r replaceAddr) RewriteBody(ctx context.Context, h *textproto.Header, body buffer.Buffer) error {
|
|
return nil
|
|
}
|
|
|
|
func (r replaceAddr) Close() error {
|
|
return nil
|
|
}
|
|
|
|
func (r replaceAddr) rewrite(val string) (string, error) {
|
|
if r.fromRegex == nil {
|
|
if address.Equal(r.fromString, val) {
|
|
return r.to, nil
|
|
}
|
|
return val, nil
|
|
}
|
|
|
|
normVal, err := address.ForLookup(val)
|
|
if err != nil {
|
|
// Ouch. Should not happen at this point.
|
|
return val, err
|
|
}
|
|
|
|
indx := r.fromRegex.FindStringSubmatchIndex(normVal)
|
|
if indx == nil {
|
|
return val, nil
|
|
}
|
|
|
|
return string(r.fromRegex.ExpandString([]byte{}, r.to, val, indx)), nil
|
|
}
|
|
|
|
func init() {
|
|
module.Register("replace_sender", NewReplaceAddr)
|
|
module.Register("replace_rcpt", NewReplaceAddr)
|
|
}
|