maddy

Fork https://github.com/foxcpp/maddy

git clone git://git.lin.moe/go/maddy.git

  1/*
  2Maddy Mail Server - Composable all-in-one email server.
  3Copyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Maddy Mail Server contributors
  4
  5This program is free software: you can redistribute it and/or modify
  6it under the terms of the GNU General Public License as published by
  7the Free Software Foundation, either version 3 of the License, or
  8(at your option) any later version.
  9
 10This program is distributed in the hope that it will be useful,
 11but WITHOUT ANY WARRANTY; without even the implied warranty of
 12MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 13GNU General Public License for more details.
 14
 15You should have received a copy of the GNU General Public License
 16along with this program.  If not, see <https://www.gnu.org/licenses/>.
 17*/
 18
 19package modify
 20
 21import (
 22	"context"
 23	"fmt"
 24	"strings"
 25
 26	"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)
 33
 34// replaceAddr is a simple module that replaces matching sender (or recipient) address
 35// 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    string
 41	instName   string
 42	inlineArgs []string
 43
 44	replaceSender bool
 45	replaceRcpt   bool
 46	table         module.MultiTable
 47}
 48
 49func 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	}
 57
 58	return &r, nil
 59}
 60
 61func (r *replaceAddr) Init(cfg *config.Map) error {
 62	return modconfig.ModuleFromNode("table", r.inlineArgs, cfg.Block, cfg.Globals, &r.table)
 63}
 64
 65func (r replaceAddr) Name() string {
 66	return r.modName
 67}
 68
 69func (r replaceAddr) InstanceName() string {
 70	return r.instName
 71}
 72
 73func (r replaceAddr) ModStateForMsg(ctx context.Context, msgMeta *module.MsgMetadata) (module.ModifierState, error) {
 74	return r, nil
 75}
 76
 77func (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, err
 82		}
 83		mailFrom = results[0]
 84	}
 85	return mailFrom, nil
 86}
 87
 88func (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}, nil
 93}
 94
 95func (r replaceAddr) RewriteBody(ctx context.Context, h *textproto.Header, body buffer.Buffer) error {
 96	return nil
 97}
 98
 99func (r replaceAddr) Close() error {
100	return nil
101}
102
103func (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	}
108
109	replacements, err := r.table.LookupMulti(ctx, normAddr)
110	if err != nil {
111		return []string{val}, err
112	}
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, nil
120	}
121
122	mbox, domain, err := address.Split(normAddr)
123	if err != nil {
124		// If we have malformed address here, something is really wrong, but let's
125		// ignore it silently then anyway.
126		return []string{val}, nil
127	}
128
129	// mbox is already normalized, since it is a part of address.ForLookup
130	// result.
131	replacements, err = r.table.LookupMulti(ctx, mbox)
132	if err != nil {
133		return []string{val}, err
134	}
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] = replacement
143			} else {
144				results[i] = replacement + "@" + domain
145			}
146		}
147		return results, nil
148	}
149
150	return []string{val}, nil
151}
152
153func init() {
154	module.Register("modify.replace_sender", NewReplaceAddr)
155	module.Register("modify.replace_rcpt", NewReplaceAddr)
156}