maddy/internal/modify/replace_addr.go
fox.cpp 305fdddf24
Use context.Context all over the place
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.
2019-12-13 17:31:35 +03:00

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)
}