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 target
 20
 21import (
 22	"context"
 23	"errors"
 24	"net"
 25	"strings"
 26	"time"
 27
 28	"github.com/foxcpp/maddy/framework/address"
 29	"github.com/foxcpp/maddy/framework/dns"
 30	"github.com/foxcpp/maddy/framework/module"
 31)
 32
 33func SanitizeForHeader(raw string) string {
 34	return strings.Replace(raw, "\n", "", -1)
 35}
 36
 37func GenerateReceived(ctx context.Context, msgMeta *module.MsgMetadata, ourHostname, mailFrom string) (string, error) {
 38	if msgMeta.Conn == nil {
 39		return "", errors.New("can't generate Received for a locally generated message")
 40	}
 41
 42	builder := strings.Builder{}
 43
 44	// Empirically guessed value that should be enough to fit
 45	// the entire value in most cases.
 46	builder.Grow(256 + len(msgMeta.Conn.Hostname))
 47
 48	if !msgMeta.DontTraceSender && (strings.Contains(msgMeta.Conn.Proto, "SMTP") ||
 49		strings.Contains(msgMeta.Conn.Proto, "LMTP")) {
 50		// INTERNATIONALIZATION: See RFC 6531 Section 3.7.3.
 51		hostname, err := dns.SelectIDNA(msgMeta.SMTPOpts.UTF8, msgMeta.Conn.Hostname)
 52		if err == nil {
 53			builder.WriteString("from ")
 54			builder.WriteString(hostname)
 55		}
 56
 57		if tcpAddr, ok := msgMeta.Conn.RemoteAddr.(*net.TCPAddr); ok {
 58			builder.WriteString(" (")
 59			if msgMeta.Conn.RDNSName != nil {
 60				rdnsName, err := msgMeta.Conn.RDNSName.GetContext(ctx)
 61				if err == nil && rdnsName != nil && rdnsName.(string) != "" {
 62					// INTERNATIONALIZATION: See RFC 6531 Section 3.7.3.
 63					encoded, err := dns.SelectIDNA(msgMeta.SMTPOpts.UTF8, rdnsName.(string))
 64					if err == nil {
 65						builder.WriteString(encoded)
 66						builder.WriteRune(' ')
 67					}
 68				}
 69			}
 70			builder.WriteRune('[')
 71			builder.WriteString(tcpAddr.IP.String())
 72			builder.WriteString("])")
 73		}
 74	}
 75
 76	if ourHostname != "" {
 77		ourHostname, err := dns.SelectIDNA(msgMeta.SMTPOpts.UTF8, ourHostname)
 78		if err == nil {
 79			builder.WriteString(" by ")
 80			builder.WriteString(SanitizeForHeader(ourHostname))
 81		}
 82	}
 83
 84	if mailFrom != "" {
 85		// INTERNATIONALIZATION: See RFC 6531 Section 3.7.3.
 86		mailFrom, err := address.SelectIDNA(msgMeta.SMTPOpts.UTF8, mailFrom)
 87		if err == nil {
 88			builder.WriteString(" (envelope-sender <")
 89			builder.WriteString(SanitizeForHeader(mailFrom))
 90			builder.WriteString(">)")
 91		}
 92	}
 93
 94	if msgMeta.Conn.Proto != "" {
 95		builder.WriteString(" with ")
 96		if msgMeta.SMTPOpts.UTF8 {
 97			builder.WriteString("UTF8")
 98		}
 99		builder.WriteString(msgMeta.Conn.Proto)
100	}
101	builder.WriteString(" id ")
102	builder.WriteString(msgMeta.ID)
103	builder.WriteString("; ")
104	builder.WriteString(time.Now().Format(time.RFC1123Z))
105
106	return strings.TrimSpace(builder.String()), nil
107}