1/*2Maddy Mail Server - Composable all-in-one email server.3Copyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Maddy Mail Server contributors45This program is free software: you can redistribute it and/or modify6it under the terms of the GNU General Public License as published by7the Free Software Foundation, either version 3 of the License, or8(at your option) any later version.910This program is distributed in the hope that it will be useful,11but WITHOUT ANY WARRANTY; without even the implied warranty of12MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the13GNU General Public License for more details.1415You should have received a copy of the GNU General Public License16along with this program. If not, see <https://www.gnu.org/licenses/>.17*/1819package modify2021import (22 "context"23 "fmt"24 "strings"2526 "github.com/emersion/go-message/textproto"27 "github.com/foxcpp/maddy/framework/address"28 "github.com/foxcpp/maddy/framework/buffer"29 "github.com/foxcpp/maddy/framework/config"30 modconfig "github.com/foxcpp/maddy/framework/config/module"31 "github.com/foxcpp/maddy/framework/module"32)3334// replaceAddr is a simple module that replaces matching sender (or recipient) address35// in messages using module.Table implementation.36//37// If created with modName = "modify.replace_sender", it will change sender address.38// If created with modName = "modify.replace_rcpt", it will change recipient addresses.39type replaceAddr struct {40 modName string41 instName string42 inlineArgs []string4344 replaceSender bool45 replaceRcpt bool46 table module.MultiTable47}4849func NewReplaceAddr(modName, instName string, _, inlineArgs []string) (module.Module, error) {50 r := replaceAddr{51 modName: modName,52 instName: instName,53 inlineArgs: inlineArgs,54 replaceSender: modName == "modify.replace_sender",55 replaceRcpt: modName == "modify.replace_rcpt",56 }5758 return &r, nil59}6061func (r *replaceAddr) Init(cfg *config.Map) error {62 return modconfig.ModuleFromNode("table", r.inlineArgs, cfg.Block, cfg.Globals, &r.table)63}6465func (r replaceAddr) Name() string {66 return r.modName67}6869func (r replaceAddr) InstanceName() string {70 return r.instName71}7273func (r replaceAddr) ModStateForMsg(ctx context.Context, msgMeta *module.MsgMetadata) (module.ModifierState, error) {74 return r, nil75}7677func (r replaceAddr) RewriteSender(ctx context.Context, mailFrom string) (string, error) {78 if r.replaceSender {79 results, err := r.rewrite(ctx, mailFrom)80 if err != nil {81 return mailFrom, err82 }83 mailFrom = results[0]84 }85 return mailFrom, nil86}8788func (r replaceAddr) RewriteRcpt(ctx context.Context, rcptTo string) ([]string, error) {89 if r.replaceRcpt {90 return r.rewrite(ctx, rcptTo)91 }92 return []string{rcptTo}, nil93}9495func (r replaceAddr) RewriteBody(ctx context.Context, h *textproto.Header, body buffer.Buffer) error {96 return nil97}9899func (r replaceAddr) Close() error {100 return nil101}102103func (r replaceAddr) rewrite(ctx context.Context, val string) ([]string, error) {104 normAddr, err := address.ForLookup(val)105 if err != nil {106 return []string{val}, fmt.Errorf("malformed address: %v", err)107 }108109 replacements, err := r.table.LookupMulti(ctx, normAddr)110 if err != nil {111 return []string{val}, err112 }113 if len(replacements) > 0 {114 for _, replacement := range replacements {115 if !address.Valid(replacement) {116 return []string{""}, fmt.Errorf("refusing to replace recipient with the invalid address %s", replacement)117 }118 }119 return replacements, nil120 }121122 mbox, domain, err := address.Split(normAddr)123 if err != nil {124 // If we have malformed address here, something is really wrong, but let's125 // ignore it silently then anyway.126 return []string{val}, nil127 }128129 // mbox is already normalized, since it is a part of address.ForLookup130 // result.131 replacements, err = r.table.LookupMulti(ctx, mbox)132 if err != nil {133 return []string{val}, err134 }135 if len(replacements) > 0 {136 var results = make([]string, len(replacements))137 for i, replacement := range replacements {138 if strings.Contains(replacement, "@") && !strings.HasPrefix(replacement, `"`) && !strings.HasSuffix(replacement, `"`) {139 if !address.Valid(replacement) {140 return []string{""}, fmt.Errorf("refusing to replace recipient with invalid address %s", replacement)141 }142 results[i] = replacement143 } else {144 results[i] = replacement + "@" + domain145 }146 }147 return results, nil148 }149150 return []string{val}, nil151}152153func init() {154 module.Register("modify.replace_sender", NewReplaceAddr)155 module.Register("modify.replace_rcpt", NewReplaceAddr)156}