switch to slog.Logger for logging, for easier reuse of packages by external software

we don't want external software to include internal details like mlog.
slog.Logger is/will be the standard.

we still have mlog for its helper functions, and its handler that logs in
concise logfmt used by mox.

packages that are not meant for reuse still pass around mlog.Log for
convenience.

we use golang.org/x/exp/slog because we also support the previous Go toolchain
version. with the next Go release, we'll switch to the builtin slog.
This commit is contained in:
Mechiel Lukkien
2023-12-05 13:35:58 +01:00
parent 56b2a9d980
commit 5b20cba50a
150 changed files with 5176 additions and 1898 deletions

View File

@ -20,6 +20,7 @@ import (
"time"
"golang.org/x/exp/maps"
"golang.org/x/exp/slog"
"github.com/mjl-/adns"
@ -28,7 +29,6 @@ import (
"github.com/mjl-/mox/dmarc"
"github.com/mjl-/mox/dns"
"github.com/mjl-/mox/junk"
"github.com/mjl-/mox/mlog"
"github.com/mjl-/mox/mtasts"
"github.com/mjl-/mox/smtp"
"github.com/mjl-/mox/tlsrpt"
@ -154,7 +154,7 @@ func MakeAccountConfig(addr smtp.Address) config.Account {
// MakeDomainConfig makes a new config for a domain, creating DKIM keys, using
// accountName for DMARC and TLS reports.
func MakeDomainConfig(ctx context.Context, domain, hostname dns.Domain, accountName string, withMTASTS bool) (config.Domain, []string, error) {
log := xlog.WithContext(ctx)
log := pkglog.WithContext(ctx)
now := time.Now()
year := now.Format("2006")
@ -164,7 +164,7 @@ func MakeDomainConfig(ctx context.Context, domain, hostname dns.Domain, accountN
defer func() {
for _, p := range paths {
err := os.Remove(p)
log.Check(err, "removing path for domain config", mlog.Field("path", p))
log.Check(err, "removing path for domain config", slog.String("path", p))
}
}()
@ -180,7 +180,7 @@ func MakeDomainConfig(ctx context.Context, domain, hostname dns.Domain, accountN
err := f.Close()
log.Check(err, "closing file after error")
err = os.Remove(path)
log.Check(err, "removing file after error", mlog.Field("path", path))
log.Check(err, "removing file after error", slog.String("path", path))
}
}()
if _, err := f.Write(data); err != nil {
@ -288,10 +288,10 @@ func MakeDomainConfig(ctx context.Context, domain, hostname dns.Domain, accountN
// If the account does not exist, it is created with localpart. Localpart must be
// set only if the account does not yet exist.
func DomainAdd(ctx context.Context, domain dns.Domain, accountName string, localpart smtp.Localpart) (rerr error) {
log := xlog.WithContext(ctx)
log := pkglog.WithContext(ctx)
defer func() {
if rerr != nil {
log.Errorx("adding domain", rerr, mlog.Field("domain", domain), mlog.Field("account", accountName), mlog.Field("localpart", localpart))
log.Errorx("adding domain", rerr, slog.Any("domain", domain), slog.String("account", accountName), slog.Any("localpart", localpart))
}
}()
@ -327,7 +327,7 @@ func DomainAdd(ctx context.Context, domain dns.Domain, accountName string, local
defer func() {
for _, f := range cleanupFiles {
err := os.Remove(f)
log.Check(err, "cleaning up file after error", mlog.Field("path", f))
log.Check(err, "cleaning up file after error", slog.String("path", f))
}
}()
@ -356,7 +356,7 @@ func DomainAdd(ctx context.Context, domain dns.Domain, accountName string, local
if err := writeDynamic(ctx, log, nc); err != nil {
return fmt.Errorf("writing domains.conf: %v", err)
}
log.Info("domain added", mlog.Field("domain", domain))
log.Info("domain added", slog.Any("domain", domain))
cleanupFiles = nil // All good, don't cleanup.
return nil
}
@ -365,10 +365,10 @@ func DomainAdd(ctx context.Context, domain dns.Domain, accountName string, local
//
// No accounts are removed, also not when they still reference this domain.
func DomainRemove(ctx context.Context, domain dns.Domain) (rerr error) {
log := xlog.WithContext(ctx)
log := pkglog.WithContext(ctx)
defer func() {
if rerr != nil {
log.Errorx("removing domain", rerr, mlog.Field("domain", domain))
log.Errorx("removing domain", rerr, slog.Any("domain", domain))
}
}()
@ -418,16 +418,16 @@ func DomainRemove(ctx context.Context, domain dns.Domain) (rerr error) {
err = os.Rename(src, dst)
}
if err != nil {
log.Errorx("renaming dkim private key file for removed domain", err, mlog.Field("src", src), mlog.Field("dst", dst))
log.Errorx("renaming dkim private key file for removed domain", err, slog.String("src", src), slog.String("dst", dst))
}
}
log.Info("domain removed", mlog.Field("domain", domain))
log.Info("domain removed", slog.Any("domain", domain))
return nil
}
func WebserverConfigSet(ctx context.Context, domainRedirects map[string]string, webhandlers []config.WebHandler) (rerr error) {
log := xlog.WithContext(ctx)
log := pkglog.WithContext(ctx)
defer func() {
if rerr != nil {
log.Errorx("saving webserver config", rerr)
@ -680,10 +680,10 @@ func DomainRecords(domConf config.Domain, domain dns.Domain, hasDNSSEC bool) ([]
//
// Catchall addresses are not supported for AccountAdd. Add separately with AddressAdd.
func AccountAdd(ctx context.Context, account, address string) (rerr error) {
log := xlog.WithContext(ctx)
log := pkglog.WithContext(ctx)
defer func() {
if rerr != nil {
log.Errorx("adding account", rerr, mlog.Field("account", account), mlog.Field("address", address))
log.Errorx("adding account", rerr, slog.String("account", account), slog.String("address", address))
}
}()
@ -716,16 +716,16 @@ func AccountAdd(ctx context.Context, account, address string) (rerr error) {
if err := writeDynamic(ctx, log, nc); err != nil {
return fmt.Errorf("writing domains.conf: %v", err)
}
log.Info("account added", mlog.Field("account", account), mlog.Field("address", addr))
log.Info("account added", slog.String("account", account), slog.Any("address", addr))
return nil
}
// AccountRemove removes an account and reloads the configuration.
func AccountRemove(ctx context.Context, account string) (rerr error) {
log := xlog.WithContext(ctx)
log := pkglog.WithContext(ctx)
defer func() {
if rerr != nil {
log.Errorx("adding account", rerr, mlog.Field("account", account))
log.Errorx("adding account", rerr, slog.String("account", account))
}
}()
@ -750,7 +750,7 @@ func AccountRemove(ctx context.Context, account string) (rerr error) {
if err := writeDynamic(ctx, log, nc); err != nil {
return fmt.Errorf("writing domains.conf: %v", err)
}
log.Info("account removed", mlog.Field("account", account))
log.Info("account removed", slog.String("account", account))
return nil
}
@ -775,10 +775,10 @@ func checkAddressAvailable(addr smtp.Address) error {
// AddressAdd adds an email address to an account and reloads the configuration. If
// address starts with an @ it is treated as a catchall address for the domain.
func AddressAdd(ctx context.Context, address, account string) (rerr error) {
log := xlog.WithContext(ctx)
log := pkglog.WithContext(ctx)
defer func() {
if rerr != nil {
log.Errorx("adding address", rerr, mlog.Field("address", address), mlog.Field("account", account))
log.Errorx("adding address", rerr, slog.String("address", address), slog.String("account", account))
}
}()
@ -834,16 +834,16 @@ func AddressAdd(ctx context.Context, address, account string) (rerr error) {
if err := writeDynamic(ctx, log, nc); err != nil {
return fmt.Errorf("writing domains.conf: %v", err)
}
log.Info("address added", mlog.Field("address", address), mlog.Field("account", account))
log.Info("address added", slog.String("address", address), slog.String("account", account))
return nil
}
// AddressRemove removes an email address and reloads the configuration.
func AddressRemove(ctx context.Context, address string) (rerr error) {
log := xlog.WithContext(ctx)
log := pkglog.WithContext(ctx)
defer func() {
if rerr != nil {
log.Errorx("removing address", rerr, mlog.Field("address", address))
log.Errorx("removing address", rerr, slog.String("address", address))
}
}()
@ -884,16 +884,16 @@ func AddressRemove(ctx context.Context, address string) (rerr error) {
if err := writeDynamic(ctx, log, nc); err != nil {
return fmt.Errorf("writing domains.conf: %v", err)
}
log.Info("address removed", mlog.Field("address", address), mlog.Field("account", ad.Account))
log.Info("address removed", slog.String("address", address), slog.String("account", ad.Account))
return nil
}
// AccountFullNameSave updates the full name for an account and reloads the configuration.
func AccountFullNameSave(ctx context.Context, account, fullName string) (rerr error) {
log := xlog.WithContext(ctx)
log := pkglog.WithContext(ctx)
defer func() {
if rerr != nil {
log.Errorx("saving account full name", rerr, mlog.Field("account", account))
log.Errorx("saving account full name", rerr, slog.String("account", account))
}
}()
@ -920,16 +920,16 @@ func AccountFullNameSave(ctx context.Context, account, fullName string) (rerr er
if err := writeDynamic(ctx, log, nc); err != nil {
return fmt.Errorf("writing domains.conf: %v", err)
}
log.Info("account full name saved", mlog.Field("account", account))
log.Info("account full name saved", slog.String("account", account))
return nil
}
// DestinationSave updates a destination for an account and reloads the configuration.
func DestinationSave(ctx context.Context, account, destName string, newDest config.Destination) (rerr error) {
log := xlog.WithContext(ctx)
log := pkglog.WithContext(ctx)
defer func() {
if rerr != nil {
log.Errorx("saving destination", rerr, mlog.Field("account", account), mlog.Field("destname", destName), mlog.Field("destination", newDest))
log.Errorx("saving destination", rerr, slog.String("account", account), slog.String("destname", destName), slog.Any("destination", newDest))
}
}()
@ -965,16 +965,16 @@ func DestinationSave(ctx context.Context, account, destName string, newDest conf
if err := writeDynamic(ctx, log, nc); err != nil {
return fmt.Errorf("writing domains.conf: %v", err)
}
log.Info("destination saved", mlog.Field("account", account), mlog.Field("destname", destName))
log.Info("destination saved", slog.String("account", account), slog.String("destname", destName))
return nil
}
// AccountLimitsSave saves new message sending limits for an account.
func AccountLimitsSave(ctx context.Context, account string, maxOutgoingMessagesPerDay, maxFirstTimeRecipientsPerDay int) (rerr error) {
log := xlog.WithContext(ctx)
log := pkglog.WithContext(ctx)
defer func() {
if rerr != nil {
log.Errorx("saving account limits", rerr, mlog.Field("account", account))
log.Errorx("saving account limits", rerr, slog.String("account", account))
}
}()
@ -1001,7 +1001,7 @@ func AccountLimitsSave(ctx context.Context, account string, maxOutgoingMessagesP
if err := writeDynamic(ctx, log, nc); err != nil {
return fmt.Errorf("writing domains.conf: %v", err)
}
log.Info("account limits saved", mlog.Field("account", account))
log.Info("account limits saved", slog.String("account", account))
return nil
}
@ -1157,7 +1157,7 @@ func ClientConfigsDomain(d dns.Domain) (ClientConfigs, error) {
// IPs returns ip addresses we may be listening/receiving mail on or
// connecting/sending from to the outside.
func IPs(ctx context.Context, receiveOnly bool) ([]net.IP, error) {
log := xlog.WithContext(ctx)
log := pkglog.WithContext(ctx)
// Try to gather all IPs we are listening on by going through the config.
// If we encounter 0.0.0.0 or ::, we'll gather all local IPs afterwards.
@ -1208,7 +1208,7 @@ func IPs(ctx context.Context, receiveOnly bool) ([]net.IP, error) {
for _, addr := range addrs {
ip, _, err := net.ParseCIDR(addr.String())
if err != nil {
log.Errorx("bad interface addr", err, mlog.Field("address", addr))
log.Errorx("bad interface addr", err, slog.Any("address", addr))
continue
}
v4 := ip.To4() != nil

View File

@ -28,6 +28,7 @@ import (
"sync"
"time"
"golang.org/x/exp/slog"
"golang.org/x/text/unicode/norm"
"github.com/mjl-/autocert"
@ -44,14 +45,14 @@ import (
"github.com/mjl-/mox/smtp"
)
var xlog = mlog.New("mox")
var pkglog = mlog.New("mox", nil)
// Config paths are set early in program startup. They will point to files in
// the same directory.
var (
ConfigStaticPath string
ConfigDynamicPath string
Conf = Config{Log: map[string]mlog.Level{"": mlog.LevelError}}
Conf = Config{Log: map[string]slog.Level{"": slog.LevelError}}
)
// Config as used in the code, a processed version of what is in the config file.
@ -61,7 +62,7 @@ type Config struct {
Static config.Static // Does not change during the lifetime of a running instance.
logMutex sync.Mutex // For accessing the log levels.
Log map[string]mlog.Level
Log map[string]slog.Level
dynamicMutex sync.Mutex
Dynamic config.Dynamic // Can only be accessed directly by tests. Use methods on Config for locked access.
@ -83,31 +84,31 @@ type AccountDestination struct {
// LogLevelSet sets a new log level for pkg. An empty pkg sets the default log
// value that is used if no explicit log level is configured for a package.
// This change is ephemeral, no config file is changed.
func (c *Config) LogLevelSet(pkg string, level mlog.Level) {
func (c *Config) LogLevelSet(log mlog.Log, pkg string, level slog.Level) {
c.logMutex.Lock()
defer c.logMutex.Unlock()
l := c.copyLogLevels()
l[pkg] = level
c.Log = l
xlog.Print("log level changed", mlog.Field("pkg", pkg), mlog.Field("level", mlog.LevelStrings[level]))
log.Print("log level changed", slog.String("pkg", pkg), slog.Any("level", mlog.LevelStrings[level]))
mlog.SetConfig(c.Log)
}
// LogLevelRemove removes a configured log level for a package.
func (c *Config) LogLevelRemove(pkg string) {
func (c *Config) LogLevelRemove(log mlog.Log, pkg string) {
c.logMutex.Lock()
defer c.logMutex.Unlock()
l := c.copyLogLevels()
delete(l, pkg)
c.Log = l
xlog.Print("log level cleared", mlog.Field("pkg", pkg))
log.Print("log level cleared", slog.String("pkg", pkg))
mlog.SetConfig(c.Log)
}
// copyLogLevels returns a copy of c.Log, for modifications.
// must be called with log lock held.
func (c *Config) copyLogLevels() map[string]mlog.Level {
m := map[string]mlog.Level{}
func (c *Config) copyLogLevels() map[string]slog.Level {
m := map[string]slog.Level{}
for pkg, level := range c.Log {
m[pkg] = level
}
@ -115,7 +116,7 @@ func (c *Config) copyLogLevels() map[string]mlog.Level {
}
// LogLevels returns a copy of the current log levels.
func (c *Config) LogLevels() map[string]mlog.Level {
func (c *Config) LogLevels() map[string]slog.Level {
c.logMutex.Lock()
defer c.logMutex.Unlock()
return c.copyLogLevels()
@ -128,12 +129,12 @@ func (c *Config) withDynamicLock(fn func()) {
if now.Sub(c.DynamicLastCheck) > time.Second {
c.DynamicLastCheck = now
if fi, err := os.Stat(ConfigDynamicPath); err != nil {
xlog.Errorx("stat domains config", err)
pkglog.Errorx("stat domains config", err)
} else if !fi.ModTime().Equal(c.dynamicMtime) {
if errs := c.loadDynamic(); len(errs) > 0 {
xlog.Errorx("loading domains config", errs[0], mlog.Field("errors", errs))
pkglog.Errorx("loading domains config", errs[0], slog.Any("errors", errs))
} else {
xlog.Info("domains config reloaded")
pkglog.Info("domains config reloaded")
c.dynamicMtime = fi.ModTime()
}
}
@ -143,14 +144,14 @@ func (c *Config) withDynamicLock(fn func()) {
// must be called with dynamic lock held.
func (c *Config) loadDynamic() []error {
d, mtime, accDests, err := ParseDynamicConfig(context.Background(), ConfigDynamicPath, c.Static)
d, mtime, accDests, err := ParseDynamicConfig(context.Background(), pkglog, ConfigDynamicPath, c.Static)
if err != nil {
return err
}
c.Dynamic = d
c.dynamicMtime = mtime
c.accountDestinations = accDests
c.allowACMEHosts(true)
c.allowACMEHosts(pkglog, true)
return nil
}
@ -236,7 +237,7 @@ func (c *Config) Routes(accountName string, domain dns.Domain) (accountRoutes, d
return
}
func (c *Config) allowACMEHosts(checkACMEHosts bool) {
func (c *Config) allowACMEHosts(log mlog.Log, checkACMEHosts bool) {
for _, l := range c.Static.Listeners {
if l.TLS == nil || l.TLS.ACME == "" {
continue
@ -259,7 +260,7 @@ func (c *Config) allowACMEHosts(checkACMEHosts bool) {
if l.AutoconfigHTTPS.Enabled && !l.AutoconfigHTTPS.NonTLS {
if d, err := dns.ParseDomain("autoconfig." + dom.Domain.ASCII); err != nil {
xlog.Errorx("parsing autoconfig domain", err, mlog.Field("domain", dom.Domain))
log.Errorx("parsing autoconfig domain", err, slog.Any("domain", dom.Domain))
} else {
hostnames[d] = struct{}{}
}
@ -268,7 +269,7 @@ func (c *Config) allowACMEHosts(checkACMEHosts bool) {
if l.MTASTSHTTPS.Enabled && dom.MTASTS != nil && !l.MTASTSHTTPS.NonTLS {
d, err := dns.ParseDomain("mta-sts." + dom.Domain.ASCII)
if err != nil {
xlog.Errorx("parsing mta-sts domain", err, mlog.Field("domain", dom.Domain))
log.Errorx("parsing mta-sts domain", err, slog.Any("domain", dom.Domain))
} else {
hostnames[d] = struct{}{}
}
@ -292,15 +293,15 @@ func (c *Config) allowACMEHosts(checkACMEHosts bool) {
if public.IPsNATed {
ips = nil
}
m.SetAllowedHostnames(dns.StrictResolver{Pkg: "autotls"}, hostnames, ips, checkACMEHosts)
m.SetAllowedHostnames(log, dns.StrictResolver{Pkg: "autotls", Log: log.Logger}, hostnames, ips, checkACMEHosts)
}
}
// todo future: write config parsing & writing code that can read a config and remembers the exact tokens including newlines and comments, and can write back a modified file. the goal is to be able to write a config file automatically (after changing fields through the ui), but not loose comments and whitespace, to still get useful diffs for storing the config in a version control system.
// must be called with lock held.
func writeDynamic(ctx context.Context, log *mlog.Log, c config.Dynamic) error {
accDests, errs := prepareDynamicConfig(ctx, ConfigDynamicPath, Conf.Static, &c)
func writeDynamic(ctx context.Context, log mlog.Log, c config.Dynamic) error {
accDests, errs := prepareDynamicConfig(ctx, log, ConfigDynamicPath, Conf.Static, &c)
if len(errs) > 0 {
return errs[0]
}
@ -330,7 +331,7 @@ func writeDynamic(ctx context.Context, log *mlog.Log, c config.Dynamic) error {
if err := f.Sync(); err != nil {
return fmt.Errorf("sync domains.conf after write: %v", err)
}
if err := moxio.SyncDir(filepath.Dir(ConfigDynamicPath)); err != nil {
if err := moxio.SyncDir(log, filepath.Dir(ConfigDynamicPath)); err != nil {
return fmt.Errorf("sync dir of domains.conf after write: %v", err)
}
@ -349,32 +350,32 @@ func writeDynamic(ctx context.Context, log *mlog.Log, c config.Dynamic) error {
Conf.Dynamic = c
Conf.accountDestinations = accDests
Conf.allowACMEHosts(true)
Conf.allowACMEHosts(log, true)
return nil
}
// MustLoadConfig loads the config, quitting on errors.
func MustLoadConfig(doLoadTLSKeyCerts, checkACMEHosts bool) {
errs := LoadConfig(context.Background(), doLoadTLSKeyCerts, checkACMEHosts)
errs := LoadConfig(context.Background(), pkglog, doLoadTLSKeyCerts, checkACMEHosts)
if len(errs) > 1 {
xlog.Error("loading config file: multiple errors")
pkglog.Error("loading config file: multiple errors")
for _, err := range errs {
xlog.Errorx("config error", err)
pkglog.Errorx("config error", err)
}
xlog.Fatal("stopping after multiple config errors")
pkglog.Fatal("stopping after multiple config errors")
} else if len(errs) == 1 {
xlog.Fatalx("loading config file", errs[0])
pkglog.Fatalx("loading config file", errs[0])
}
}
// LoadConfig attempts to parse and load a config, returning any errors
// encountered.
func LoadConfig(ctx context.Context, doLoadTLSKeyCerts, checkACMEHosts bool) []error {
func LoadConfig(ctx context.Context, log mlog.Log, doLoadTLSKeyCerts, checkACMEHosts bool) []error {
Shutdown, ShutdownCancel = context.WithCancel(context.Background())
Context, ContextCancel = context.WithCancel(context.Background())
c, errs := ParseConfig(ctx, ConfigStaticPath, false, doLoadTLSKeyCerts, checkACMEHosts)
c, errs := ParseConfig(ctx, log, ConfigStaticPath, false, doLoadTLSKeyCerts, checkACMEHosts)
if len(errs) > 0 {
return errs
}
@ -405,7 +406,7 @@ func SetConfig(c *Config) {
// quickstart in the case the user is going to provide their own certificates.
// If checkACMEHosts is true, the hosts allowed for acme are compared with the
// explicitly configured ips we are listening on.
func ParseConfig(ctx context.Context, p string, checkOnly, doLoadTLSKeyCerts, checkACMEHosts bool) (c *Config, errs []error) {
func ParseConfig(ctx context.Context, log mlog.Log, p string, checkOnly, doLoadTLSKeyCerts, checkACMEHosts bool) (c *Config, errs []error) {
c = &Config{
Static: config.Static{
DataDir: ".",
@ -424,15 +425,15 @@ func ParseConfig(ctx context.Context, p string, checkOnly, doLoadTLSKeyCerts, ch
return nil, []error{fmt.Errorf("parsing %s%v", p, err)}
}
if xerrs := PrepareStaticConfig(ctx, p, c, checkOnly, doLoadTLSKeyCerts); len(xerrs) > 0 {
if xerrs := PrepareStaticConfig(ctx, log, p, c, checkOnly, doLoadTLSKeyCerts); len(xerrs) > 0 {
return nil, xerrs
}
pp := filepath.Join(filepath.Dir(p), "domains.conf")
c.Dynamic, c.dynamicMtime, c.accountDestinations, errs = ParseDynamicConfig(ctx, pp, c.Static)
c.Dynamic, c.dynamicMtime, c.accountDestinations, errs = ParseDynamicConfig(ctx, log, pp, c.Static)
if !checkOnly {
c.allowACMEHosts(checkACMEHosts)
c.allowACMEHosts(log, checkACMEHosts)
}
return c, errs
@ -441,13 +442,11 @@ func ParseConfig(ctx context.Context, p string, checkOnly, doLoadTLSKeyCerts, ch
// PrepareStaticConfig parses the static config file and prepares data structures
// for starting mox. If checkOnly is set no substantial changes are made, like
// creating an ACME registration.
func PrepareStaticConfig(ctx context.Context, configFile string, conf *Config, checkOnly, doLoadTLSKeyCerts bool) (errs []error) {
func PrepareStaticConfig(ctx context.Context, log mlog.Log, configFile string, conf *Config, checkOnly, doLoadTLSKeyCerts bool) (errs []error) {
addErrorf := func(format string, args ...any) {
errs = append(errs, fmt.Errorf(format, args...))
}
log := xlog.WithContext(ctx)
c := &conf.Static
// check that mailbox is in unicode NFC normalized form.
@ -461,7 +460,7 @@ func PrepareStaticConfig(ctx context.Context, configFile string, conf *Config, c
// Post-process logging config.
if logLevel, ok := mlog.Levels[c.LogLevel]; ok {
conf.Log = map[string]mlog.Level{"": logLevel}
conf.Log = map[string]slog.Level{"": logLevel}
} else {
addErrorf("invalid log level %q", c.LogLevel)
}
@ -569,10 +568,10 @@ func PrepareStaticConfig(ctx context.Context, configFile string, conf *Config, c
key = findACMEHostPrivateKey(acmeName, host, keyType, 2)
}
if key != nil {
log.Debug("found existing private key for certificate for host", mlog.Field("acmename", acmeName), mlog.Field("host", host), mlog.Field("keytype", keyType))
log.Debug("found existing private key for certificate for host", slog.String("acmename", acmeName), slog.String("host", host), slog.Any("keytype", keyType))
return key, nil
}
log.Debug("generating new private key for certificate for host", mlog.Field("acmename", acmeName), mlog.Field("host", host), mlog.Field("keytype", keyType))
log.Debug("generating new private key for certificate for host", slog.String("acmename", acmeName), slog.String("host", host), slog.Any("keytype", keyType))
switch keyType {
case autocert.KeyRSA2048:
return rsa.GenerateKey(cryptorand.Reader, 2048)
@ -658,18 +657,18 @@ func PrepareStaticConfig(ctx context.Context, configFile string, conf *Config, c
switch k := privKey.(type) {
case *rsa.PrivateKey:
if k.N.BitLen() != 2048 {
log.Error("need rsa key with 2048 bits, for host private key for DANE/ACME certificates, ignoring", mlog.Field("listener", name), mlog.Field("file", keyPath), mlog.Field("bits", k.N.BitLen()))
log.Error("need rsa key with 2048 bits, for host private key for DANE/ACME certificates, ignoring", slog.String("listener", name), slog.String("file", keyPath), slog.Int("bits", k.N.BitLen()))
continue
}
l.TLS.HostPrivateRSA2048Keys = append(l.TLS.HostPrivateRSA2048Keys, k)
case *ecdsa.PrivateKey:
if k.Curve != elliptic.P256() {
log.Error("unrecognized ecdsa curve for host private key for DANE/ACME certificates, ignoring", mlog.Field("listener", name), mlog.Field("file", keyPath))
log.Error("unrecognized ecdsa curve for host private key for DANE/ACME certificates, ignoring", slog.String("listener", name), slog.String("file", keyPath))
continue
}
l.TLS.HostPrivateECDSAP256Keys = append(l.TLS.HostPrivateECDSAP256Keys, k)
default:
log.Error("unrecognized key type for host private key for DANE/ACME certificates, ignoring", mlog.Field("listener", name), mlog.Field("file", keyPath), mlog.Field("keytype", fmt.Sprintf("%T", privKey)))
log.Error("unrecognized key type for host private key for DANE/ACME certificates, ignoring", slog.String("listener", name), slog.String("file", keyPath), slog.String("keytype", fmt.Sprintf("%T", privKey)))
continue
}
}
@ -914,7 +913,7 @@ func PrepareStaticConfig(ctx context.Context, configFile string, conf *Config, c
}
// PrepareDynamicConfig parses the dynamic config file given a static file.
func ParseDynamicConfig(ctx context.Context, dynamicPath string, static config.Static) (c config.Dynamic, mtime time.Time, accDests map[string]AccountDestination, errs []error) {
func ParseDynamicConfig(ctx context.Context, log mlog.Log, dynamicPath string, static config.Static) (c config.Dynamic, mtime time.Time, accDests map[string]AccountDestination, errs []error) {
addErrorf := func(format string, args ...any) {
errs = append(errs, fmt.Errorf(format, args...))
}
@ -934,13 +933,11 @@ func ParseDynamicConfig(ctx context.Context, dynamicPath string, static config.S
return
}
accDests, errs = prepareDynamicConfig(ctx, dynamicPath, static, &c)
accDests, errs = prepareDynamicConfig(ctx, log, dynamicPath, static, &c)
return c, fi.ModTime(), accDests, errs
}
func prepareDynamicConfig(ctx context.Context, dynamicPath string, static config.Static, c *config.Dynamic) (accDests map[string]AccountDestination, errs []error) {
log := xlog.WithContext(ctx)
func prepareDynamicConfig(ctx context.Context, log mlog.Log, dynamicPath string, static config.Static, c *config.Dynamic) (accDests map[string]AccountDestination, errs []error) {
addErrorf := func(format string, args ...any) {
errs = append(errs, fmt.Errorf(format, args...))
}
@ -1321,7 +1318,7 @@ func prepareDynamicConfig(ctx context.Context, dynamicPath string, static config
if !ok {
addErrorf("could not find localpart %q to replace with address in destinations", lp)
} else {
log.Error(`deprecation warning: support for account destination addresses specified as just localpart ("username") instead of full email address will be removed in the future; update domains.conf, for each Account, for each Destination, ensure each key is an email address by appending "@" and the default domain for the account`, mlog.Field("localpart", lp), mlog.Field("address", addr), mlog.Field("account", accName))
log.Error(`deprecation warning: support for account destination addresses specified as just localpart ("username") instead of full email address will be removed in the future; update domains.conf, for each Account, for each Destination, ensure each key is an email address by appending "@" and the default domain for the account`, slog.Any("localpart", lp), slog.Any("address", addr), slog.String("account", accName))
acc.Destinations[addr] = dest
delete(acc.Destinations, lp)
}

View File

@ -8,7 +8,7 @@ import (
"strings"
"syscall"
"github.com/mjl-/mox/mlog"
"golang.org/x/exp/slog"
)
// Fork and exec as unprivileged user.
@ -19,7 +19,7 @@ import (
func ForkExecUnprivileged() {
prog, err := os.Executable()
if err != nil {
xlog.Fatalx("finding executable for exec", err)
pkglog.Fatalx("finding executable for exec", err)
}
files := []*os.File{os.Stdin, os.Stdout, os.Stderr}
@ -49,7 +49,7 @@ func ForkExecUnprivileged() {
},
})
if err != nil {
xlog.Fatalx("fork and exec", err)
pkglog.Fatalx("fork and exec", err)
}
CleanupPassedFiles()
@ -66,9 +66,9 @@ func ForkExecUnprivileged() {
st, err := p.Wait()
if err != nil {
xlog.Fatalx("wait", err)
pkglog.Fatalx("wait", err)
}
code := st.ExitCode()
xlog.Print("stopping after child exit", mlog.Field("exitcode", code))
pkglog.Print("stopping after child exit", slog.Int("exitcode", code))
os.Exit(code)
}

View File

@ -32,7 +32,7 @@ func RestorePassedFiles() {
if runtime.GOOS == "linux" {
linuxhint = " If you updated from v0.0.1, update the mox.service file to start as root (privileges are dropped): ./mox config printservice >mox.service && sudo systemctl daemon-reload && sudo systemctl restart mox."
}
xlog.Fatal("mox must be started as root, and will drop privileges after binding required sockets (missing environment variable MOX_SOCKETS)." + linuxhint)
pkglog.Fatal("mox must be started as root, and will drop privileges after binding required sockets (missing environment variable MOX_SOCKETS)." + linuxhint)
}
// 0,1,2 are stdin,stdout,stderr, 3 is the first passed fd (first listeners, then files).
@ -59,12 +59,12 @@ func RestorePassedFiles() {
func CleanupPassedFiles() {
for _, f := range passedListeners {
err := f.Close()
xlog.Check(err, "closing listener socket file descriptor")
pkglog.Check(err, "closing listener socket file descriptor")
}
for _, fl := range passedFiles {
for _, f := range fl {
err := f.Close()
xlog.Check(err, "closing path file descriptor")
pkglog.Check(err, "closing path file descriptor")
}
}
}
@ -193,7 +193,7 @@ func (c *connections) Register(nc net.Conn, protocol, listener string) {
// doesn't hurt to log it.
select {
case <-Shutdown.Done():
xlog.Error("new connection added while shutting down")
pkglog.Error("new connection added while shutting down")
debug.PrintStack()
default:
}
@ -258,7 +258,7 @@ func (c *connections) Shutdown() {
defer c.Unlock()
for nc := range c.conns {
if err := nc.SetDeadline(now); err != nil {
xlog.Errorx("setting immediate read/write deadline for shutdown", err)
pkglog.Errorx("setting immediate read/write deadline for shutdown", err)
}
}
}

48
mox-/tlsrecv.go Normal file
View File

@ -0,0 +1,48 @@
package mox
import (
"crypto/tls"
"fmt"
"golang.org/x/exp/slog"
"github.com/mjl-/mox/mlog"
)
// TLSReceivedComment returns a comment about TLS of the connection for use in a Receive header.
func TLSReceivedComment(log mlog.Log, cs tls.ConnectionState) []string {
// todo future: we could use the "tls" clause for the Received header as specified in ../rfc/8314:496. however, the text implies it is only for submission, not regular smtp. and it cannot specify the tls version. for now, not worth the trouble.
// Comments from other mail servers:
// gmail.com: (version=TLS1_3 cipher=TLS_AES_128_GCM_SHA256 bits=128/128)
// yahoo.com: (version=TLS1_3 cipher=TLS_AES_128_GCM_SHA256)
// proton.me: (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested)
// outlook.com: (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384)
var l []string
add := func(s string) {
l = append(l, s)
}
versions := map[uint16]string{
tls.VersionTLS10: "TLS1.0",
tls.VersionTLS11: "TLS1.1",
tls.VersionTLS12: "TLS1.2",
tls.VersionTLS13: "TLS1.3",
}
if version, ok := versions[cs.Version]; ok {
add(version)
} else {
log.Info("unknown tls version identifier", slog.Any("version", cs.Version))
add(fmt.Sprintf("TLS identifier %x", cs.Version))
}
add(tls.CipherSuiteName(cs.CipherSuite))
// Make it a comment.
l[0] = "(" + l[0]
l[len(l)-1] = l[len(l)-1] + ")"
return l
}