1package mox
2
3import (
4 "bytes"
5 "context"
6 "crypto"
7 "crypto/ecdsa"
8 "crypto/ed25519"
9 "crypto/elliptic"
10 cryptorand "crypto/rand"
11 "crypto/rsa"
12 "crypto/tls"
13 "crypto/x509"
14 "encoding/base64"
15 "encoding/pem"
16 "errors"
17 "fmt"
18 "io"
19 "net"
20 "net/http"
21 "net/url"
22 "os"
23 "os/user"
24 "path/filepath"
25 "regexp"
26 "sort"
27 "strconv"
28 "strings"
29 "sync"
30 "time"
31
32 "golang.org/x/exp/slog"
33 "golang.org/x/text/unicode/norm"
34
35 "github.com/mjl-/autocert"
36
37 "github.com/mjl-/sconf"
38
39 "github.com/mjl-/mox/autotls"
40 "github.com/mjl-/mox/config"
41 "github.com/mjl-/mox/dkim"
42 "github.com/mjl-/mox/dns"
43 "github.com/mjl-/mox/message"
44 "github.com/mjl-/mox/mlog"
45 "github.com/mjl-/mox/moxio"
46 "github.com/mjl-/mox/mtasts"
47 "github.com/mjl-/mox/smtp"
48)
49
50var pkglog = mlog.New("mox", nil)
51
52// Pedantic enables stricter parsing.
53var Pedantic bool
54
55// Config paths are set early in program startup. They will point to files in
56// the same directory.
57var (
58 ConfigStaticPath string
59 ConfigDynamicPath string
60 Conf = Config{Log: map[string]slog.Level{"": slog.LevelError}}
61)
62
63// Config as used in the code, a processed version of what is in the config file.
64//
65// Use methods to lookup a domain/account/address in the dynamic configuration.
66type Config struct {
67 Static config.Static // Does not change during the lifetime of a running instance.
68
69 logMutex sync.Mutex // For accessing the log levels.
70 Log map[string]slog.Level
71
72 dynamicMutex sync.Mutex
73 Dynamic config.Dynamic // Can only be accessed directly by tests. Use methods on Config for locked access.
74 dynamicMtime time.Time
75 DynamicLastCheck time.Time // For use by quickstart only to skip checks.
76 // From canonical full address (localpart@domain, lower-cased when
77 // case-insensitive, stripped of catchall separator) to account and address.
78 // Domains are IDNA names in utf8.
79 accountDestinations map[string]AccountDestination
80}
81
82type AccountDestination struct {
83 Catchall bool // If catchall destination for its domain.
84 Localpart smtp.Localpart // In original casing as written in config file.
85 Account string
86 Destination config.Destination
87}
88
89// LogLevelSet sets a new log level for pkg. An empty pkg sets the default log
90// value that is used if no explicit log level is configured for a package.
91// This change is ephemeral, no config file is changed.
92func (c *Config) LogLevelSet(log mlog.Log, pkg string, level slog.Level) {
93 c.logMutex.Lock()
94 defer c.logMutex.Unlock()
95 l := c.copyLogLevels()
96 l[pkg] = level
97 c.Log = l
98 log.Print("log level changed", slog.String("pkg", pkg), slog.Any("level", mlog.LevelStrings[level]))
99 mlog.SetConfig(c.Log)
100}
101
102// LogLevelRemove removes a configured log level for a package.
103func (c *Config) LogLevelRemove(log mlog.Log, pkg string) {
104 c.logMutex.Lock()
105 defer c.logMutex.Unlock()
106 l := c.copyLogLevels()
107 delete(l, pkg)
108 c.Log = l
109 log.Print("log level cleared", slog.String("pkg", pkg))
110 mlog.SetConfig(c.Log)
111}
112
113// copyLogLevels returns a copy of c.Log, for modifications.
114// must be called with log lock held.
115func (c *Config) copyLogLevels() map[string]slog.Level {
116 m := map[string]slog.Level{}
117 for pkg, level := range c.Log {
118 m[pkg] = level
119 }
120 return m
121}
122
123// LogLevels returns a copy of the current log levels.
124func (c *Config) LogLevels() map[string]slog.Level {
125 c.logMutex.Lock()
126 defer c.logMutex.Unlock()
127 return c.copyLogLevels()
128}
129
130func (c *Config) withDynamicLock(fn func()) {
131 c.dynamicMutex.Lock()
132 defer c.dynamicMutex.Unlock()
133 now := time.Now()
134 if now.Sub(c.DynamicLastCheck) > time.Second {
135 c.DynamicLastCheck = now
136 if fi, err := os.Stat(ConfigDynamicPath); err != nil {
137 pkglog.Errorx("stat domains config", err)
138 } else if !fi.ModTime().Equal(c.dynamicMtime) {
139 if errs := c.loadDynamic(); len(errs) > 0 {
140 pkglog.Errorx("loading domains config", errs[0], slog.Any("errors", errs))
141 } else {
142 pkglog.Info("domains config reloaded")
143 c.dynamicMtime = fi.ModTime()
144 }
145 }
146 }
147 fn()
148}
149
150// must be called with dynamic lock held.
151func (c *Config) loadDynamic() []error {
152 d, mtime, accDests, err := ParseDynamicConfig(context.Background(), pkglog, ConfigDynamicPath, c.Static)
153 if err != nil {
154 return err
155 }
156 c.Dynamic = d
157 c.dynamicMtime = mtime
158 c.accountDestinations = accDests
159 c.allowACMEHosts(pkglog, true)
160 return nil
161}
162
163func (c *Config) Domains() (l []string) {
164 c.withDynamicLock(func() {
165 for name := range c.Dynamic.Domains {
166 l = append(l, name)
167 }
168 })
169 sort.Slice(l, func(i, j int) bool {
170 return l[i] < l[j]
171 })
172 return l
173}
174
175func (c *Config) Accounts() (l []string) {
176 c.withDynamicLock(func() {
177 for name := range c.Dynamic.Accounts {
178 l = append(l, name)
179 }
180 })
181 return
182}
183
184// DomainLocalparts returns a mapping of encoded localparts to account names for a
185// domain. An empty localpart is a catchall destination for a domain.
186func (c *Config) DomainLocalparts(d dns.Domain) map[string]string {
187 suffix := "@" + d.Name()
188 m := map[string]string{}
189 c.withDynamicLock(func() {
190 for addr, ad := range c.accountDestinations {
191 if strings.HasSuffix(addr, suffix) {
192 if ad.Catchall {
193 m[""] = ad.Account
194 } else {
195 m[ad.Localpart.String()] = ad.Account
196 }
197 }
198 }
199 })
200 return m
201}
202
203func (c *Config) Domain(d dns.Domain) (dom config.Domain, ok bool) {
204 c.withDynamicLock(func() {
205 dom, ok = c.Dynamic.Domains[d.Name()]
206 })
207 return
208}
209
210func (c *Config) Account(name string) (acc config.Account, ok bool) {
211 c.withDynamicLock(func() {
212 acc, ok = c.Dynamic.Accounts[name]
213 })
214 return
215}
216
217func (c *Config) AccountDestination(addr string) (accDests AccountDestination, ok bool) {
218 c.withDynamicLock(func() {
219 accDests, ok = c.accountDestinations[addr]
220 })
221 return
222}
223
224func (c *Config) WebServer() (r map[dns.Domain]dns.Domain, l []config.WebHandler) {
225 c.withDynamicLock(func() {
226 r = c.Dynamic.WebDNSDomainRedirects
227 l = c.Dynamic.WebHandlers
228 })
229 return r, l
230}
231
232func (c *Config) Routes(accountName string, domain dns.Domain) (accountRoutes, domainRoutes, globalRoutes []config.Route) {
233 c.withDynamicLock(func() {
234 acc := c.Dynamic.Accounts[accountName]
235 accountRoutes = acc.Routes
236
237 dom := c.Dynamic.Domains[domain.Name()]
238 domainRoutes = dom.Routes
239
240 globalRoutes = c.Dynamic.Routes
241 })
242 return
243}
244
245func (c *Config) allowACMEHosts(log mlog.Log, checkACMEHosts bool) {
246 for _, l := range c.Static.Listeners {
247 if l.TLS == nil || l.TLS.ACME == "" {
248 continue
249 }
250
251 m := c.Static.ACME[l.TLS.ACME].Manager
252 hostnames := map[dns.Domain]struct{}{}
253
254 hostnames[c.Static.HostnameDomain] = struct{}{}
255 if l.HostnameDomain.ASCII != "" {
256 hostnames[l.HostnameDomain] = struct{}{}
257 }
258
259 for _, dom := range c.Dynamic.Domains {
260 if dom.DMARC != nil && dom.DMARC.Domain != "" && dom.DMARC.DNSDomain != dom.Domain {
261 // Do not allow TLS certificates for domains for which we only accept DMARC reports
262 // as external party.
263 continue
264 }
265
266 if l.AutoconfigHTTPS.Enabled && !l.AutoconfigHTTPS.NonTLS {
267 if d, err := dns.ParseDomain("autoconfig." + dom.Domain.ASCII); err != nil {
268 log.Errorx("parsing autoconfig domain", err, slog.Any("domain", dom.Domain))
269 } else {
270 hostnames[d] = struct{}{}
271 }
272 }
273
274 if l.MTASTSHTTPS.Enabled && dom.MTASTS != nil && !l.MTASTSHTTPS.NonTLS {
275 d, err := dns.ParseDomain("mta-sts." + dom.Domain.ASCII)
276 if err != nil {
277 log.Errorx("parsing mta-sts domain", err, slog.Any("domain", dom.Domain))
278 } else {
279 hostnames[d] = struct{}{}
280 }
281 }
282
283 if dom.ClientSettingsDomain != "" {
284 hostnames[dom.ClientSettingsDNSDomain] = struct{}{}
285 }
286 }
287
288 if l.WebserverHTTPS.Enabled {
289 for from := range c.Dynamic.WebDNSDomainRedirects {
290 hostnames[from] = struct{}{}
291 }
292 for _, wh := range c.Dynamic.WebHandlers {
293 hostnames[wh.DNSDomain] = struct{}{}
294 }
295 }
296
297 public := c.Static.Listeners["public"]
298 ips := public.IPs
299 if len(public.NATIPs) > 0 {
300 ips = public.NATIPs
301 }
302 if public.IPsNATed {
303 ips = nil
304 }
305 m.SetAllowedHostnames(log, dns.StrictResolver{Pkg: "autotls", Log: log.Logger}, hostnames, ips, checkACMEHosts)
306 }
307}
308
309// 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.
310
311// must be called with lock held.
312func writeDynamic(ctx context.Context, log mlog.Log, c config.Dynamic) error {
313 accDests, errs := prepareDynamicConfig(ctx, log, ConfigDynamicPath, Conf.Static, &c)
314 if len(errs) > 0 {
315 return errs[0]
316 }
317
318 var b bytes.Buffer
319 err := sconf.Write(&b, c)
320 if err != nil {
321 return err
322 }
323 f, err := os.OpenFile(ConfigDynamicPath, os.O_WRONLY, 0660)
324 if err != nil {
325 return err
326 }
327 defer func() {
328 if f != nil {
329 err := f.Close()
330 log.Check(err, "closing file after error")
331 }
332 }()
333 buf := b.Bytes()
334 if _, err := f.Write(buf); err != nil {
335 return fmt.Errorf("write domains.conf: %v", err)
336 }
337 if err := f.Truncate(int64(len(buf))); err != nil {
338 return fmt.Errorf("truncate domains.conf after write: %v", err)
339 }
340 if err := f.Sync(); err != nil {
341 return fmt.Errorf("sync domains.conf after write: %v", err)
342 }
343 if err := moxio.SyncDir(log, filepath.Dir(ConfigDynamicPath)); err != nil {
344 return fmt.Errorf("sync dir of domains.conf after write: %v", err)
345 }
346
347 fi, err := f.Stat()
348 if err != nil {
349 return fmt.Errorf("stat after writing domains.conf: %v", err)
350 }
351
352 if err := f.Close(); err != nil {
353 return fmt.Errorf("close written domains.conf: %v", err)
354 }
355 f = nil
356
357 Conf.dynamicMtime = fi.ModTime()
358 Conf.DynamicLastCheck = time.Now()
359 Conf.Dynamic = c
360 Conf.accountDestinations = accDests
361
362 Conf.allowACMEHosts(log, true)
363
364 return nil
365}
366
367// MustLoadConfig loads the config, quitting on errors.
368func MustLoadConfig(doLoadTLSKeyCerts, checkACMEHosts bool) {
369 errs := LoadConfig(context.Background(), pkglog, doLoadTLSKeyCerts, checkACMEHosts)
370 if len(errs) > 1 {
371 pkglog.Error("loading config file: multiple errors")
372 for _, err := range errs {
373 pkglog.Errorx("config error", err)
374 }
375 pkglog.Fatal("stopping after multiple config errors")
376 } else if len(errs) == 1 {
377 pkglog.Fatalx("loading config file", errs[0])
378 }
379}
380
381// LoadConfig attempts to parse and load a config, returning any errors
382// encountered.
383func LoadConfig(ctx context.Context, log mlog.Log, doLoadTLSKeyCerts, checkACMEHosts bool) []error {
384 Shutdown, ShutdownCancel = context.WithCancel(context.Background())
385 Context, ContextCancel = context.WithCancel(context.Background())
386
387 c, errs := ParseConfig(ctx, log, ConfigStaticPath, false, doLoadTLSKeyCerts, checkACMEHosts)
388 if len(errs) > 0 {
389 return errs
390 }
391
392 mlog.SetConfig(c.Log)
393 SetConfig(c)
394 return nil
395}
396
397// SetConfig sets a new config. Not to be used during normal operation.
398func SetConfig(c *Config) {
399 // Cannot just assign *c to Conf, it would copy the mutex.
400 Conf = Config{c.Static, sync.Mutex{}, c.Log, sync.Mutex{}, c.Dynamic, c.dynamicMtime, c.DynamicLastCheck, c.accountDestinations}
401
402 // If we have non-standard CA roots, use them for all HTTPS requests.
403 if Conf.Static.TLS.CertPool != nil {
404 http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{
405 RootCAs: Conf.Static.TLS.CertPool,
406 }
407 }
408
409 SetPedantic(c.Static.Pedantic)
410}
411
412// Set pedantic in all packages.
413func SetPedantic(p bool) {
414 dkim.Pedantic = p
415 dns.Pedantic = p
416 message.Pedantic = p
417 smtp.Pedantic = p
418 Pedantic = p
419}
420
421// ParseConfig parses the static config at path p. If checkOnly is true, no changes
422// are made, such as registering ACME identities. If doLoadTLSKeyCerts is true,
423// the TLS KeyCerts configuration is loaded and checked. This is used during the
424// quickstart in the case the user is going to provide their own certificates.
425// If checkACMEHosts is true, the hosts allowed for acme are compared with the
426// explicitly configured ips we are listening on.
427func ParseConfig(ctx context.Context, log mlog.Log, p string, checkOnly, doLoadTLSKeyCerts, checkACMEHosts bool) (c *Config, errs []error) {
428 c = &Config{
429 Static: config.Static{
430 DataDir: ".",
431 },
432 }
433
434 f, err := os.Open(p)
435 if err != nil {
436 if os.IsNotExist(err) && os.Getenv("MOXCONF") == "" {
437 return nil, []error{fmt.Errorf("open config file: %v (hint: use mox -config ... or set MOXCONF=...)", err)}
438 }
439 return nil, []error{fmt.Errorf("open config file: %v", err)}
440 }
441 defer f.Close()
442 if err := sconf.Parse(f, &c.Static); err != nil {
443 return nil, []error{fmt.Errorf("parsing %s%v", p, err)}
444 }
445
446 if xerrs := PrepareStaticConfig(ctx, log, p, c, checkOnly, doLoadTLSKeyCerts); len(xerrs) > 0 {
447 return nil, xerrs
448 }
449
450 pp := filepath.Join(filepath.Dir(p), "domains.conf")
451 c.Dynamic, c.dynamicMtime, c.accountDestinations, errs = ParseDynamicConfig(ctx, log, pp, c.Static)
452
453 if !checkOnly {
454 c.allowACMEHosts(log, checkACMEHosts)
455 }
456
457 return c, errs
458}
459
460// PrepareStaticConfig parses the static config file and prepares data structures
461// for starting mox. If checkOnly is set no substantial changes are made, like
462// creating an ACME registration.
463func PrepareStaticConfig(ctx context.Context, log mlog.Log, configFile string, conf *Config, checkOnly, doLoadTLSKeyCerts bool) (errs []error) {
464 addErrorf := func(format string, args ...any) {
465 errs = append(errs, fmt.Errorf(format, args...))
466 }
467
468 c := &conf.Static
469
470 // check that mailbox is in unicode NFC normalized form.
471 checkMailboxNormf := func(mailbox string, format string, args ...any) {
472 s := norm.NFC.String(mailbox)
473 if mailbox != s {
474 msg := fmt.Sprintf(format, args...)
475 addErrorf("%s: mailbox %q is not in NFC normalized form, should be %q", msg, mailbox, s)
476 }
477 }
478
479 // Post-process logging config.
480 if logLevel, ok := mlog.Levels[c.LogLevel]; ok {
481 conf.Log = map[string]slog.Level{"": logLevel}
482 } else {
483 addErrorf("invalid log level %q", c.LogLevel)
484 }
485 for pkg, s := range c.PackageLogLevels {
486 if logLevel, ok := mlog.Levels[s]; ok {
487 conf.Log[pkg] = logLevel
488 } else {
489 addErrorf("invalid package log level %q", s)
490 }
491 }
492
493 if c.User == "" {
494 c.User = "mox"
495 }
496 u, err := user.Lookup(c.User)
497 if err != nil {
498 uid, err := strconv.ParseUint(c.User, 10, 32)
499 if err != nil {
500 addErrorf("parsing unknown user %s as uid: %v (hint: add user mox with \"useradd -d $PWD mox\" or specify a different username on the quickstart command-line)", c.User, err)
501 } else {
502 // We assume the same gid as uid.
503 c.UID = uint32(uid)
504 c.GID = uint32(uid)
505 }
506 } else {
507 if uid, err := strconv.ParseUint(u.Uid, 10, 32); err != nil {
508 addErrorf("parsing uid %s: %v", u.Uid, err)
509 } else {
510 c.UID = uint32(uid)
511 }
512 if gid, err := strconv.ParseUint(u.Gid, 10, 32); err != nil {
513 addErrorf("parsing gid %s: %v", u.Gid, err)
514 } else {
515 c.GID = uint32(gid)
516 }
517 }
518
519 hostname, err := dns.ParseDomain(c.Hostname)
520 if err != nil {
521 addErrorf("parsing hostname: %s", err)
522 } else if hostname.Name() != c.Hostname {
523 addErrorf("hostname must be in IDNA form %q", hostname.Name())
524 }
525 c.HostnameDomain = hostname
526
527 if c.HostTLSRPT.Account != "" {
528 tlsrptLocalpart, err := smtp.ParseLocalpart(c.HostTLSRPT.Localpart)
529 if err != nil {
530 addErrorf("invalid localpart %q for host tlsrpt: %v", c.HostTLSRPT.Localpart, err)
531 } else if tlsrptLocalpart.IsInternational() {
532 // Does not appear documented in ../rfc/8460, but similar to DMARC it makes sense
533 // to keep this ascii-only addresses.
534 addErrorf("host TLSRPT localpart %q is an internationalized address, only conventional ascii-only address allowed for interopability", tlsrptLocalpart)
535 }
536 c.HostTLSRPT.ParsedLocalpart = tlsrptLocalpart
537 }
538
539 // Return private key for host name for use with an ACME. Used to return the same
540 // private key as pre-generated for use with DANE, with its public key in DNS.
541 // We only use this key for Listener's that have this ACME configured, and for
542 // which the effective listener host name (either specific to the listener, or the
543 // global name) is requested. Other host names can get a fresh private key, they
544 // don't appear in DANE records.
545 //
546 // - run 0: only use listener with explicitly matching host name in listener
547 // (default quickstart config does not set it).
548 // - run 1: only look at public listener (and host matching mox host name)
549 // - run 2: all listeners (and host matching mox host name)
550 findACMEHostPrivateKey := func(acmeName, host string, keyType autocert.KeyType, run int) crypto.Signer {
551 for listenerName, l := range Conf.Static.Listeners {
552 if l.TLS == nil || l.TLS.ACME != acmeName {
553 continue
554 }
555 if run == 0 && host != l.HostnameDomain.ASCII {
556 continue
557 }
558 if run == 1 && listenerName != "public" || host != Conf.Static.HostnameDomain.ASCII {
559 continue
560 }
561 switch keyType {
562 case autocert.KeyRSA2048:
563 if len(l.TLS.HostPrivateRSA2048Keys) == 0 {
564 continue
565 }
566 return l.TLS.HostPrivateRSA2048Keys[0]
567 case autocert.KeyECDSAP256:
568 if len(l.TLS.HostPrivateECDSAP256Keys) == 0 {
569 continue
570 }
571 return l.TLS.HostPrivateECDSAP256Keys[0]
572 default:
573 return nil
574 }
575 }
576 return nil
577 }
578 // Make a function for an autocert.Manager.GetPrivateKey, using findACMEHostPrivateKey.
579 makeGetPrivateKey := func(acmeName string) func(host string, keyType autocert.KeyType) (crypto.Signer, error) {
580 return func(host string, keyType autocert.KeyType) (crypto.Signer, error) {
581 key := findACMEHostPrivateKey(acmeName, host, keyType, 0)
582 if key == nil {
583 key = findACMEHostPrivateKey(acmeName, host, keyType, 1)
584 }
585 if key == nil {
586 key = findACMEHostPrivateKey(acmeName, host, keyType, 2)
587 }
588 if key != nil {
589 log.Debug("found existing private key for certificate for host",
590 slog.String("acmename", acmeName),
591 slog.String("host", host),
592 slog.Any("keytype", keyType))
593 return key, nil
594 }
595 log.Debug("generating new private key for certificate for host",
596 slog.String("acmename", acmeName),
597 slog.String("host", host),
598 slog.Any("keytype", keyType))
599 switch keyType {
600 case autocert.KeyRSA2048:
601 return rsa.GenerateKey(cryptorand.Reader, 2048)
602 case autocert.KeyECDSAP256:
603 return ecdsa.GenerateKey(elliptic.P256(), cryptorand.Reader)
604 default:
605 return nil, fmt.Errorf("unrecognized requested key type %v", keyType)
606 }
607 }
608 }
609 for name, acme := range c.ACME {
610 var eabKeyID string
611 var eabKey []byte
612 if acme.ExternalAccountBinding != nil {
613 eabKeyID = acme.ExternalAccountBinding.KeyID
614 p := configDirPath(configFile, acme.ExternalAccountBinding.KeyFile)
615 buf, err := os.ReadFile(p)
616 if err != nil {
617 addErrorf("reading external account binding key for acme provider %q: %s", name, err)
618 } else {
619 dec := make([]byte, base64.RawURLEncoding.DecodedLen(len(buf)))
620 n, err := base64.RawURLEncoding.Decode(dec, buf)
621 if err != nil {
622 addErrorf("parsing external account binding key as base64 for acme provider %q: %s", name, err)
623 } else {
624 eabKey = dec[:n]
625 }
626 }
627 }
628
629 if checkOnly {
630 continue
631 }
632
633 acmeDir := dataDirPath(configFile, c.DataDir, "acme")
634 os.MkdirAll(acmeDir, 0770)
635 manager, err := autotls.Load(name, acmeDir, acme.ContactEmail, acme.DirectoryURL, eabKeyID, eabKey, makeGetPrivateKey(name), Shutdown.Done())
636 if err != nil {
637 addErrorf("loading ACME identity for %q: %s", name, err)
638 }
639 acme.Manager = manager
640
641 // Help configurations from older quickstarts.
642 if acme.IssuerDomainName == "" && acme.DirectoryURL == "https://acme-v02.api.letsencrypt.org/directory" {
643 acme.IssuerDomainName = "letsencrypt.org"
644 }
645
646 c.ACME[name] = acme
647 }
648
649 var haveUnspecifiedSMTPListener bool
650 for name, l := range c.Listeners {
651 if l.Hostname != "" {
652 d, err := dns.ParseDomain(l.Hostname)
653 if err != nil {
654 addErrorf("bad listener hostname %q: %s", l.Hostname, err)
655 }
656 l.HostnameDomain = d
657 }
658 if l.TLS != nil {
659 if l.TLS.ACME != "" && len(l.TLS.KeyCerts) != 0 {
660 addErrorf("listener %q: cannot have ACME and static key/certificates", name)
661 } else if l.TLS.ACME != "" {
662 acme, ok := c.ACME[l.TLS.ACME]
663 if !ok {
664 addErrorf("listener %q: unknown ACME provider %q", name, l.TLS.ACME)
665 }
666
667 // If only checking or with missing ACME definition, we don't have an acme manager,
668 // so set an empty tls config to continue.
669 var tlsconfig *tls.Config
670 if checkOnly || acme.Manager == nil {
671 tlsconfig = &tls.Config{}
672 } else {
673 tlsconfig = acme.Manager.TLSConfig.Clone()
674 l.TLS.ACMEConfig = acme.Manager.ACMETLSConfig
675
676 // SMTP STARTTLS connections are commonly made without SNI, because certificates
677 // often aren't verified.
678 hostname := c.HostnameDomain
679 if l.Hostname != "" {
680 hostname = l.HostnameDomain
681 }
682 getCert := tlsconfig.GetCertificate
683 tlsconfig.GetCertificate = func(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
684 if hello.ServerName == "" {
685 hello.ServerName = hostname.ASCII
686 }
687 return getCert(hello)
688 }
689 }
690 l.TLS.Config = tlsconfig
691 } else if len(l.TLS.KeyCerts) != 0 {
692 if doLoadTLSKeyCerts {
693 if err := loadTLSKeyCerts(configFile, "listener "+name, l.TLS); err != nil {
694 addErrorf("%w", err)
695 }
696 }
697 } else {
698 addErrorf("listener %q: cannot have TLS config without ACME and without static keys/certificates", name)
699 }
700 for _, privKeyFile := range l.TLS.HostPrivateKeyFiles {
701 keyPath := configDirPath(configFile, privKeyFile)
702 privKey, err := loadPrivateKeyFile(keyPath)
703 if err != nil {
704 addErrorf("listener %q: parsing host private key for DANE and ACME certificates: %v", name, err)
705 continue
706 }
707 switch k := privKey.(type) {
708 case *rsa.PrivateKey:
709 if k.N.BitLen() != 2048 {
710 log.Error("need rsa key with 2048 bits, for host private key for DANE/ACME certificates, ignoring",
711 slog.String("listener", name),
712 slog.String("file", keyPath),
713 slog.Int("bits", k.N.BitLen()))
714 continue
715 }
716 l.TLS.HostPrivateRSA2048Keys = append(l.TLS.HostPrivateRSA2048Keys, k)
717 case *ecdsa.PrivateKey:
718 if k.Curve != elliptic.P256() {
719 log.Error("unrecognized ecdsa curve for host private key for DANE/ACME certificates, ignoring", slog.String("listener", name), slog.String("file", keyPath))
720 continue
721 }
722 l.TLS.HostPrivateECDSAP256Keys = append(l.TLS.HostPrivateECDSAP256Keys, k)
723 default:
724 log.Error("unrecognized key type for host private key for DANE/ACME certificates, ignoring",
725 slog.String("listener", name),
726 slog.String("file", keyPath),
727 slog.String("keytype", fmt.Sprintf("%T", privKey)))
728 continue
729 }
730 }
731 if l.TLS.ACME != "" && (len(l.TLS.HostPrivateRSA2048Keys) == 0) != (len(l.TLS.HostPrivateECDSAP256Keys) == 0) {
732 log.Error("warning: uncommon configuration with either only an RSA 2048 or ECDSA P256 host private key for DANE/ACME certificates; this ACME implementation can retrieve certificates for both type of keys, it is recommended to set either both or none; continuing")
733 }
734
735 // TLS 1.2 was introduced in 2008. TLS <1.2 was deprecated by ../rfc/8996:31 and ../rfc/8997:66 in 2021.
736 var minVersion uint16 = tls.VersionTLS12
737 if l.TLS.MinVersion != "" {
738 versions := map[string]uint16{
739 "TLSv1.0": tls.VersionTLS10,
740 "TLSv1.1": tls.VersionTLS11,
741 "TLSv1.2": tls.VersionTLS12,
742 "TLSv1.3": tls.VersionTLS13,
743 }
744 v, ok := versions[l.TLS.MinVersion]
745 if !ok {
746 addErrorf("listener %q: unknown TLS mininum version %q", name, l.TLS.MinVersion)
747 }
748 minVersion = v
749 }
750 if l.TLS.Config != nil {
751 l.TLS.Config.MinVersion = minVersion
752 }
753 if l.TLS.ACMEConfig != nil {
754 l.TLS.ACMEConfig.MinVersion = minVersion
755 }
756 } else {
757 var needsTLS []string
758 needtls := func(s string, v bool) {
759 if v {
760 needsTLS = append(needsTLS, s)
761 }
762 }
763 needtls("IMAPS", l.IMAPS.Enabled)
764 needtls("SMTP", l.SMTP.Enabled && !l.SMTP.NoSTARTTLS)
765 needtls("Submissions", l.Submissions.Enabled)
766 needtls("Submission", l.Submission.Enabled && !l.Submission.NoRequireSTARTTLS)
767 needtls("AccountHTTPS", l.AccountHTTPS.Enabled)
768 needtls("AdminHTTPS", l.AdminHTTPS.Enabled)
769 needtls("AutoconfigHTTPS", l.AutoconfigHTTPS.Enabled && !l.AutoconfigHTTPS.NonTLS)
770 needtls("MTASTSHTTPS", l.MTASTSHTTPS.Enabled && !l.MTASTSHTTPS.NonTLS)
771 needtls("WebserverHTTPS", l.WebserverHTTPS.Enabled)
772 if len(needsTLS) > 0 {
773 addErrorf("listener %q does not specify tls config, but requires tls for %s", name, strings.Join(needsTLS, ", "))
774 }
775 }
776 if l.AutoconfigHTTPS.Enabled && l.MTASTSHTTPS.Enabled && l.AutoconfigHTTPS.Port == l.MTASTSHTTPS.Port && l.AutoconfigHTTPS.NonTLS != l.MTASTSHTTPS.NonTLS {
777 addErrorf("listener %q tries to enable autoconfig and mta-sts enabled on same port but with both http and https", name)
778 }
779 if l.SMTP.Enabled {
780 if len(l.IPs) == 0 {
781 haveUnspecifiedSMTPListener = true
782 }
783 for _, ipstr := range l.IPs {
784 ip := net.ParseIP(ipstr)
785 if ip == nil {
786 addErrorf("listener %q has invalid IP %q", name, ipstr)
787 continue
788 }
789 if ip.IsUnspecified() {
790 haveUnspecifiedSMTPListener = true
791 break
792 }
793 if len(c.SpecifiedSMTPListenIPs) >= 2 {
794 haveUnspecifiedSMTPListener = true
795 } else if len(c.SpecifiedSMTPListenIPs) > 0 && (c.SpecifiedSMTPListenIPs[0].To4() == nil) == (ip.To4() == nil) {
796 haveUnspecifiedSMTPListener = true
797 } else {
798 c.SpecifiedSMTPListenIPs = append(c.SpecifiedSMTPListenIPs, ip)
799 }
800 }
801 }
802 for _, s := range l.SMTP.DNSBLs {
803 d, err := dns.ParseDomain(s)
804 if err != nil {
805 addErrorf("listener %q has invalid DNSBL zone %q", name, s)
806 continue
807 }
808 l.SMTP.DNSBLZones = append(l.SMTP.DNSBLZones, d)
809 }
810 if l.IPsNATed && len(l.NATIPs) > 0 {
811 addErrorf("listener %q has both IPsNATed and NATIPs (remove deprecated IPsNATed)", name)
812 }
813 for _, ipstr := range l.NATIPs {
814 ip := net.ParseIP(ipstr)
815 if ip == nil {
816 addErrorf("listener %q has invalid ip %q", name, ipstr)
817 } else if ip.IsUnspecified() || ip.IsLoopback() {
818 addErrorf("listener %q has NAT ip that is the unspecified or loopback address %s", name, ipstr)
819 }
820 }
821 checkPath := func(kind string, enabled bool, path string) {
822 if enabled && path != "" && !strings.HasPrefix(path, "/") {
823 addErrorf("listener %q has %s with path %q that must start with a slash", name, kind, path)
824 }
825 }
826 checkPath("AccountHTTP", l.AccountHTTP.Enabled, l.AccountHTTP.Path)
827 checkPath("AccountHTTPS", l.AccountHTTPS.Enabled, l.AccountHTTPS.Path)
828 checkPath("AdminHTTP", l.AdminHTTP.Enabled, l.AdminHTTP.Path)
829 checkPath("AdminHTTPS", l.AdminHTTPS.Enabled, l.AdminHTTPS.Path)
830 c.Listeners[name] = l
831 }
832 if haveUnspecifiedSMTPListener {
833 c.SpecifiedSMTPListenIPs = nil
834 }
835
836 var zerouse config.SpecialUseMailboxes
837 if len(c.DefaultMailboxes) > 0 && (c.InitialMailboxes.SpecialUse != zerouse || len(c.InitialMailboxes.Regular) > 0) {
838 addErrorf("cannot have both DefaultMailboxes and InitialMailboxes")
839 }
840 // DefaultMailboxes is deprecated.
841 for _, mb := range c.DefaultMailboxes {
842 checkMailboxNormf(mb, "default mailbox")
843 }
844 checkSpecialUseMailbox := func(nameOpt string) {
845 if nameOpt != "" {
846 checkMailboxNormf(nameOpt, "special-use initial mailbox")
847 if strings.EqualFold(nameOpt, "inbox") {
848 addErrorf("initial mailbox cannot be set to Inbox (Inbox is always created)")
849 }
850 }
851 }
852 checkSpecialUseMailbox(c.InitialMailboxes.SpecialUse.Archive)
853 checkSpecialUseMailbox(c.InitialMailboxes.SpecialUse.Draft)
854 checkSpecialUseMailbox(c.InitialMailboxes.SpecialUse.Junk)
855 checkSpecialUseMailbox(c.InitialMailboxes.SpecialUse.Sent)
856 checkSpecialUseMailbox(c.InitialMailboxes.SpecialUse.Trash)
857 for _, name := range c.InitialMailboxes.Regular {
858 checkMailboxNormf(name, "regular initial mailbox")
859 if strings.EqualFold(name, "inbox") {
860 addErrorf("initial regular mailbox cannot be set to Inbox (Inbox is always created)")
861 }
862 }
863
864 checkTransportSMTP := func(name string, isTLS bool, t *config.TransportSMTP) {
865 var err error
866 t.DNSHost, err = dns.ParseDomain(t.Host)
867 if err != nil {
868 addErrorf("transport %s: bad host %s: %v", name, t.Host, err)
869 }
870
871 if isTLS && t.STARTTLSInsecureSkipVerify {
872 addErrorf("transport %s: cannot have STARTTLSInsecureSkipVerify with immediate TLS")
873 }
874 if isTLS && t.NoSTARTTLS {
875 addErrorf("transport %s: cannot have NoSTARTTLS with immediate TLS")
876 }
877
878 if t.Auth == nil {
879 return
880 }
881 seen := map[string]bool{}
882 for _, m := range t.Auth.Mechanisms {
883 if seen[m] {
884 addErrorf("transport %s: duplicate authentication mechanism %s", name, m)
885 }
886 seen[m] = true
887 switch m {
888 case "SCRAM-SHA-256-PLUS":
889 case "SCRAM-SHA-256":
890 case "SCRAM-SHA-1-PLUS":
891 case "SCRAM-SHA-1":
892 case "CRAM-MD5":
893 case "PLAIN":
894 default:
895 addErrorf("transport %s: unknown authentication mechanism %s", name, m)
896 }
897 }
898
899 t.Auth.EffectiveMechanisms = t.Auth.Mechanisms
900 if len(t.Auth.EffectiveMechanisms) == 0 {
901 t.Auth.EffectiveMechanisms = []string{"SCRAM-SHA-256-PLUS", "SCRAM-SHA-256", "SCRAM-SHA-1-PLUS", "SCRAM-SHA-1", "CRAM-MD5"}
902 }
903 }
904
905 checkTransportSocks := func(name string, t *config.TransportSocks) {
906 _, _, err := net.SplitHostPort(t.Address)
907 if err != nil {
908 addErrorf("transport %s: bad address %s: %v", name, t.Address, err)
909 }
910 for _, ipstr := range t.RemoteIPs {
911 ip := net.ParseIP(ipstr)
912 if ip == nil {
913 addErrorf("transport %s: bad ip %s", name, ipstr)
914 } else {
915 t.IPs = append(t.IPs, ip)
916 }
917 }
918 t.Hostname, err = dns.ParseDomain(t.RemoteHostname)
919 if err != nil {
920 addErrorf("transport %s: bad hostname %s: %v", name, t.RemoteHostname, err)
921 }
922 }
923
924 for name, t := range c.Transports {
925 n := 0
926 if t.Submissions != nil {
927 n++
928 checkTransportSMTP(name, true, t.Submissions)
929 }
930 if t.Submission != nil {
931 n++
932 checkTransportSMTP(name, false, t.Submission)
933 }
934 if t.SMTP != nil {
935 n++
936 checkTransportSMTP(name, false, t.SMTP)
937 }
938 if t.Socks != nil {
939 n++
940 checkTransportSocks(name, t.Socks)
941 }
942 if n > 1 {
943 addErrorf("transport %s: cannot have multiple methods in a transport", name)
944 }
945 }
946
947 // Load CA certificate pool.
948 if c.TLS.CA != nil {
949 if c.TLS.CA.AdditionalToSystem {
950 var err error
951 c.TLS.CertPool, err = x509.SystemCertPool()
952 if err != nil {
953 addErrorf("fetching system CA cert pool: %v", err)
954 }
955 } else {
956 c.TLS.CertPool = x509.NewCertPool()
957 }
958 for _, certfile := range c.TLS.CA.CertFiles {
959 p := configDirPath(configFile, certfile)
960 pemBuf, err := os.ReadFile(p)
961 if err != nil {
962 addErrorf("reading TLS CA cert file: %v", err)
963 continue
964 } else if !c.TLS.CertPool.AppendCertsFromPEM(pemBuf) {
965 // todo: can we check more fully if we're getting some useful data back?
966 addErrorf("no CA certs added from %q", p)
967 }
968 }
969 }
970 return
971}
972
973// PrepareDynamicConfig parses the dynamic config file given a static file.
974func ParseDynamicConfig(ctx context.Context, log mlog.Log, dynamicPath string, static config.Static) (c config.Dynamic, mtime time.Time, accDests map[string]AccountDestination, errs []error) {
975 addErrorf := func(format string, args ...any) {
976 errs = append(errs, fmt.Errorf(format, args...))
977 }
978
979 f, err := os.Open(dynamicPath)
980 if err != nil {
981 addErrorf("parsing domains config: %v", err)
982 return
983 }
984 defer f.Close()
985 fi, err := f.Stat()
986 if err != nil {
987 addErrorf("stat domains config: %v", err)
988 }
989 if err := sconf.Parse(f, &c); err != nil {
990 addErrorf("parsing dynamic config file: %v", err)
991 return
992 }
993
994 accDests, errs = prepareDynamicConfig(ctx, log, dynamicPath, static, &c)
995 return c, fi.ModTime(), accDests, errs
996}
997
998func prepareDynamicConfig(ctx context.Context, log mlog.Log, dynamicPath string, static config.Static, c *config.Dynamic) (accDests map[string]AccountDestination, errs []error) {
999 addErrorf := func(format string, args ...any) {
1000 errs = append(errs, fmt.Errorf(format, args...))
1001 }
1002
1003 // Check that mailbox is in unicode NFC normalized form.
1004 checkMailboxNormf := func(mailbox string, format string, args ...any) {
1005 s := norm.NFC.String(mailbox)
1006 if mailbox != s {
1007 msg := fmt.Sprintf(format, args...)
1008 addErrorf("%s: mailbox %q is not in NFC normalized form, should be %q", msg, mailbox, s)
1009 }
1010 }
1011
1012 // Validate postmaster account exists.
1013 if _, ok := c.Accounts[static.Postmaster.Account]; !ok {
1014 addErrorf("postmaster account %q does not exist", static.Postmaster.Account)
1015 }
1016 checkMailboxNormf(static.Postmaster.Mailbox, "postmaster mailbox")
1017
1018 accDests = map[string]AccountDestination{}
1019
1020 // Validate host TLSRPT account/address.
1021 if static.HostTLSRPT.Account != "" {
1022 if _, ok := c.Accounts[static.HostTLSRPT.Account]; !ok {
1023 addErrorf("host tlsrpt account %q does not exist", static.HostTLSRPT.Account)
1024 }
1025 checkMailboxNormf(static.HostTLSRPT.Mailbox, "host tlsrpt mailbox")
1026
1027 // Localpart has been parsed already.
1028
1029 addrFull := smtp.NewAddress(static.HostTLSRPT.ParsedLocalpart, static.HostnameDomain).String()
1030 dest := config.Destination{
1031 Mailbox: static.HostTLSRPT.Mailbox,
1032 HostTLSReports: true,
1033 }
1034 accDests[addrFull] = AccountDestination{false, static.HostTLSRPT.ParsedLocalpart, static.HostTLSRPT.Account, dest}
1035 }
1036
1037 var haveSTSListener, haveWebserverListener bool
1038 for _, l := range static.Listeners {
1039 if l.MTASTSHTTPS.Enabled {
1040 haveSTSListener = true
1041 }
1042 if l.WebserverHTTP.Enabled || l.WebserverHTTPS.Enabled {
1043 haveWebserverListener = true
1044 }
1045 }
1046
1047 checkRoutes := func(descr string, routes []config.Route) {
1048 parseRouteDomains := func(l []string) []string {
1049 var r []string
1050 for _, e := range l {
1051 if e == "." {
1052 r = append(r, e)
1053 continue
1054 }
1055 prefix := ""
1056 if strings.HasPrefix(e, ".") {
1057 prefix = "."
1058 e = e[1:]
1059 }
1060 d, err := dns.ParseDomain(e)
1061 if err != nil {
1062 addErrorf("%s: invalid domain %s: %v", descr, e, err)
1063 }
1064 r = append(r, prefix+d.ASCII)
1065 }
1066 return r
1067 }
1068
1069 for i := range routes {
1070 routes[i].FromDomainASCII = parseRouteDomains(routes[i].FromDomain)
1071 routes[i].ToDomainASCII = parseRouteDomains(routes[i].ToDomain)
1072 var ok bool
1073 routes[i].ResolvedTransport, ok = static.Transports[routes[i].Transport]
1074 if !ok {
1075 addErrorf("%s: route references undefined transport %s", descr, routes[i].Transport)
1076 }
1077 }
1078 }
1079
1080 checkRoutes("global routes", c.Routes)
1081
1082 // Validate domains.
1083 for d, domain := range c.Domains {
1084 dnsdomain, err := dns.ParseDomain(d)
1085 if err != nil {
1086 addErrorf("bad domain %q: %s", d, err)
1087 } else if dnsdomain.Name() != d {
1088 addErrorf("domain %s must be specified in IDNA form, %s", d, dnsdomain.Name())
1089 }
1090
1091 domain.Domain = dnsdomain
1092
1093 if domain.ClientSettingsDomain != "" {
1094 csd, err := dns.ParseDomain(domain.ClientSettingsDomain)
1095 if err != nil {
1096 addErrorf("bad client settings domain %q: %s", domain.ClientSettingsDomain, err)
1097 }
1098 domain.ClientSettingsDNSDomain = csd
1099 }
1100
1101 for _, sign := range domain.DKIM.Sign {
1102 if _, ok := domain.DKIM.Selectors[sign]; !ok {
1103 addErrorf("selector %s for signing is missing in domain %s", sign, d)
1104 }
1105 }
1106 for name, sel := range domain.DKIM.Selectors {
1107 seld, err := dns.ParseDomain(name)
1108 if err != nil {
1109 addErrorf("bad selector %q: %s", name, err)
1110 } else if seld.Name() != name {
1111 addErrorf("selector %q must be specified in IDNA form, %q", name, seld.Name())
1112 }
1113 sel.Domain = seld
1114
1115 if sel.Expiration != "" {
1116 exp, err := time.ParseDuration(sel.Expiration)
1117 if err != nil {
1118 addErrorf("selector %q has invalid expiration %q: %v", name, sel.Expiration, err)
1119 } else {
1120 sel.ExpirationSeconds = int(exp / time.Second)
1121 }
1122 }
1123
1124 sel.HashEffective = sel.Hash
1125 switch sel.HashEffective {
1126 case "":
1127 sel.HashEffective = "sha256"
1128 case "sha1":
1129 log.Error("using sha1 with DKIM is deprecated as not secure enough, switch to sha256")
1130 case "sha256":
1131 default:
1132 addErrorf("unsupported hash %q for selector %q in domain %s", sel.HashEffective, name, d)
1133 }
1134
1135 pemBuf, err := os.ReadFile(configDirPath(dynamicPath, sel.PrivateKeyFile))
1136 if err != nil {
1137 addErrorf("reading private key for selector %s in domain %s: %s", name, d, err)
1138 continue
1139 }
1140 p, _ := pem.Decode(pemBuf)
1141 if p == nil {
1142 addErrorf("private key for selector %s in domain %s has no PEM block", name, d)
1143 continue
1144 }
1145 key, err := x509.ParsePKCS8PrivateKey(p.Bytes)
1146 if err != nil {
1147 addErrorf("parsing private key for selector %s in domain %s: %s", name, d, err)
1148 continue
1149 }
1150 switch k := key.(type) {
1151 case *rsa.PrivateKey:
1152 if k.N.BitLen() < 1024 {
1153 // ../rfc/6376:757
1154 // Let's help user do the right thing.
1155 addErrorf("rsa keys should be >= 1024 bits")
1156 }
1157 sel.Key = k
1158 case ed25519.PrivateKey:
1159 if sel.HashEffective != "sha256" {
1160 addErrorf("hash algorithm %q is not supported with ed25519, only sha256 is", sel.HashEffective)
1161 }
1162 sel.Key = k
1163 default:
1164 addErrorf("private key type %T not yet supported, at selector %s in domain %s", key, name, d)
1165 }
1166
1167 if len(sel.Headers) == 0 {
1168 // ../rfc/6376:2139
1169 // ../rfc/6376:2203
1170 // ../rfc/6376:2212
1171 // By default we seal signed headers, and we sign user-visible headers to
1172 // prevent/limit reuse of previously signed messages: All addressing fields, date
1173 // and subject, message-referencing fields, parsing instructions (content-type).
1174 sel.HeadersEffective = strings.Split("From,To,Cc,Bcc,Reply-To,References,In-Reply-To,Subject,Date,Message-Id,Content-Type", ",")
1175 } else {
1176 var from bool
1177 for _, h := range sel.Headers {
1178 from = from || strings.EqualFold(h, "From")
1179 // ../rfc/6376:2269
1180 if strings.EqualFold(h, "DKIM-Signature") || strings.EqualFold(h, "Received") || strings.EqualFold(h, "Return-Path") {
1181 log.Error("DKIM-signing header %q is recommended against as it may be modified in transit")
1182 }
1183 }
1184 if !from {
1185 addErrorf("From-field must always be DKIM-signed")
1186 }
1187 sel.HeadersEffective = sel.Headers
1188 }
1189
1190 domain.DKIM.Selectors[name] = sel
1191 }
1192
1193 if domain.MTASTS != nil {
1194 if !haveSTSListener {
1195 addErrorf("MTA-STS enabled for domain %q, but there is no listener for MTASTS", d)
1196 }
1197 sts := domain.MTASTS
1198 if sts.PolicyID == "" {
1199 addErrorf("invalid empty MTA-STS PolicyID")
1200 }
1201 switch sts.Mode {
1202 case mtasts.ModeNone, mtasts.ModeTesting, mtasts.ModeEnforce:
1203 default:
1204 addErrorf("invalid mtasts mode %q", sts.Mode)
1205 }
1206 }
1207
1208 checkRoutes("routes for domain", domain.Routes)
1209
1210 c.Domains[d] = domain
1211 }
1212
1213 // Validate email addresses.
1214 for accName, acc := range c.Accounts {
1215 var err error
1216 acc.DNSDomain, err = dns.ParseDomain(acc.Domain)
1217 if err != nil {
1218 addErrorf("parsing domain %s for account %q: %s", acc.Domain, accName, err)
1219 }
1220
1221 if strings.EqualFold(acc.RejectsMailbox, "Inbox") {
1222 addErrorf("account %q: cannot set RejectsMailbox to inbox, messages will be removed automatically from the rejects mailbox", accName)
1223 }
1224 checkMailboxNormf(acc.RejectsMailbox, "account %q", accName)
1225
1226 if acc.AutomaticJunkFlags.JunkMailboxRegexp != "" {
1227 r, err := regexp.Compile(acc.AutomaticJunkFlags.JunkMailboxRegexp)
1228 if err != nil {
1229 addErrorf("invalid JunkMailboxRegexp regular expression: %v", err)
1230 }
1231 acc.JunkMailbox = r
1232 }
1233 if acc.AutomaticJunkFlags.NeutralMailboxRegexp != "" {
1234 r, err := regexp.Compile(acc.AutomaticJunkFlags.NeutralMailboxRegexp)
1235 if err != nil {
1236 addErrorf("invalid NeutralMailboxRegexp regular expression: %v", err)
1237 }
1238 acc.NeutralMailbox = r
1239 }
1240 if acc.AutomaticJunkFlags.NotJunkMailboxRegexp != "" {
1241 r, err := regexp.Compile(acc.AutomaticJunkFlags.NotJunkMailboxRegexp)
1242 if err != nil {
1243 addErrorf("invalid NotJunkMailboxRegexp regular expression: %v", err)
1244 }
1245 acc.NotJunkMailbox = r
1246 }
1247 c.Accounts[accName] = acc
1248
1249 // todo deprecated: only localpart as keys for Destinations, we are replacing them with full addresses. if domains.conf is written, we won't have to do this again.
1250 replaceLocalparts := map[string]string{}
1251
1252 for addrName, dest := range acc.Destinations {
1253 checkMailboxNormf(dest.Mailbox, "account %q, destination %q", accName, addrName)
1254
1255 for i, rs := range dest.Rulesets {
1256 checkMailboxNormf(rs.Mailbox, "account %q, destination %q, ruleset %d", accName, addrName, i+1)
1257
1258 n := 0
1259
1260 if rs.SMTPMailFromRegexp != "" {
1261 n++
1262 r, err := regexp.Compile(rs.SMTPMailFromRegexp)
1263 if err != nil {
1264 addErrorf("invalid SMTPMailFrom regular expression: %v", err)
1265 }
1266 c.Accounts[accName].Destinations[addrName].Rulesets[i].SMTPMailFromRegexpCompiled = r
1267 }
1268 if rs.VerifiedDomain != "" {
1269 n++
1270 d, err := dns.ParseDomain(rs.VerifiedDomain)
1271 if err != nil {
1272 addErrorf("invalid VerifiedDomain: %v", err)
1273 }
1274 c.Accounts[accName].Destinations[addrName].Rulesets[i].VerifiedDNSDomain = d
1275 }
1276
1277 var hdr [][2]*regexp.Regexp
1278 for k, v := range rs.HeadersRegexp {
1279 n++
1280 if strings.ToLower(k) != k {
1281 addErrorf("header field %q must only have lower case characters", k)
1282 }
1283 if strings.ToLower(v) != v {
1284 addErrorf("header value %q must only have lower case characters", v)
1285 }
1286 rk, err := regexp.Compile(k)
1287 if err != nil {
1288 addErrorf("invalid rule header regexp %q: %v", k, err)
1289 }
1290 rv, err := regexp.Compile(v)
1291 if err != nil {
1292 addErrorf("invalid rule header regexp %q: %v", v, err)
1293 }
1294 hdr = append(hdr, [...]*regexp.Regexp{rk, rv})
1295 }
1296 c.Accounts[accName].Destinations[addrName].Rulesets[i].HeadersRegexpCompiled = hdr
1297
1298 if n == 0 {
1299 addErrorf("ruleset must have at least one rule")
1300 }
1301
1302 if rs.IsForward && rs.ListAllowDomain != "" {
1303 addErrorf("ruleset cannot have both IsForward and ListAllowDomain")
1304 }
1305 if rs.IsForward {
1306 if rs.SMTPMailFromRegexp == "" || rs.VerifiedDomain == "" {
1307 addErrorf("ruleset with IsForward must have both SMTPMailFromRegexp and VerifiedDomain too")
1308 }
1309 }
1310 if rs.ListAllowDomain != "" {
1311 d, err := dns.ParseDomain(rs.ListAllowDomain)
1312 if err != nil {
1313 addErrorf("invalid ListAllowDomain %q: %v", rs.ListAllowDomain, err)
1314 }
1315 c.Accounts[accName].Destinations[addrName].Rulesets[i].ListAllowDNSDomain = d
1316 }
1317
1318 checkMailboxNormf(rs.AcceptRejectsToMailbox, "account %q, destination %q, ruleset %d, rejects mailbox", accName, addrName, i+1)
1319 if strings.EqualFold(rs.AcceptRejectsToMailbox, "inbox") {
1320 addErrorf("account %q, destination %q, ruleset %d: AcceptRejectsToMailbox cannot be set to Inbox", accName, addrName, i+1)
1321 }
1322 }
1323
1324 // Catchall destination for domain.
1325 if strings.HasPrefix(addrName, "@") {
1326 d, err := dns.ParseDomain(addrName[1:])
1327 if err != nil {
1328 addErrorf("parsing domain %q in account %q", addrName[1:], accName)
1329 continue
1330 } else if _, ok := c.Domains[d.Name()]; !ok {
1331 addErrorf("unknown domain for address %q in account %q", addrName, accName)
1332 continue
1333 }
1334 addrFull := "@" + d.Name()
1335 if _, ok := accDests[addrFull]; ok {
1336 addErrorf("duplicate canonicalized catchall destination address %s", addrFull)
1337 }
1338 accDests[addrFull] = AccountDestination{true, "", accName, dest}
1339 continue
1340 }
1341
1342 // todo deprecated: remove support for parsing destination as just a localpart instead full address.
1343 var address smtp.Address
1344 if localpart, err := smtp.ParseLocalpart(addrName); err != nil && errors.Is(err, smtp.ErrBadLocalpart) {
1345 address, err = smtp.ParseAddress(addrName)
1346 if err != nil {
1347 addErrorf("invalid email address %q in account %q", addrName, accName)
1348 continue
1349 } else if _, ok := c.Domains[address.Domain.Name()]; !ok {
1350 addErrorf("unknown domain for address %q in account %q", addrName, accName)
1351 continue
1352 }
1353 } else {
1354 if err != nil {
1355 addErrorf("invalid localpart %q in account %q", addrName, accName)
1356 continue
1357 }
1358 address = smtp.NewAddress(localpart, acc.DNSDomain)
1359 if _, ok := c.Domains[acc.DNSDomain.Name()]; !ok {
1360 addErrorf("unknown domain %s for account %q", acc.DNSDomain.Name(), accName)
1361 continue
1362 }
1363 replaceLocalparts[addrName] = address.Pack(true)
1364 }
1365
1366 origLP := address.Localpart
1367 dc := c.Domains[address.Domain.Name()]
1368 if lp, err := CanonicalLocalpart(address.Localpart, dc); err != nil {
1369 addErrorf("canonicalizing localpart %s: %v", address.Localpart, err)
1370 } else if dc.LocalpartCatchallSeparator != "" && strings.Contains(string(address.Localpart), dc.LocalpartCatchallSeparator) {
1371 addErrorf("localpart of address %s includes domain catchall separator %s", address, dc.LocalpartCatchallSeparator)
1372 } else {
1373 address.Localpart = lp
1374 }
1375 addrFull := address.Pack(true)
1376 if _, ok := accDests[addrFull]; ok {
1377 addErrorf("duplicate canonicalized destination address %s", addrFull)
1378 }
1379 accDests[addrFull] = AccountDestination{false, origLP, accName, dest}
1380 }
1381
1382 for lp, addr := range replaceLocalparts {
1383 dest, ok := acc.Destinations[lp]
1384 if !ok {
1385 addErrorf("could not find localpart %q to replace with address in destinations", lp)
1386 } else {
1387 log.Warn(`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`,
1388 slog.Any("localpart", lp),
1389 slog.Any("address", addr),
1390 slog.String("account", accName))
1391 acc.Destinations[addr] = dest
1392 delete(acc.Destinations, lp)
1393 }
1394 }
1395
1396 checkRoutes("routes for account", acc.Routes)
1397 }
1398
1399 // Set DMARC destinations.
1400 for d, domain := range c.Domains {
1401 dmarc := domain.DMARC
1402 if dmarc == nil {
1403 continue
1404 }
1405 if _, ok := c.Accounts[dmarc.Account]; !ok {
1406 addErrorf("DMARC account %q does not exist", dmarc.Account)
1407 }
1408 lp, err := smtp.ParseLocalpart(dmarc.Localpart)
1409 if err != nil {
1410 addErrorf("invalid DMARC localpart %q: %s", dmarc.Localpart, err)
1411 }
1412 if lp.IsInternational() {
1413 // ../rfc/8616:234
1414 addErrorf("DMARC localpart %q is an internationalized address, only conventional ascii-only address possible for interopability", lp)
1415 }
1416 addrdom := domain.Domain
1417 if dmarc.Domain != "" {
1418 addrdom, err = dns.ParseDomain(dmarc.Domain)
1419 if err != nil {
1420 addErrorf("DMARC domain %q: %s", dmarc.Domain, err)
1421 } else if _, ok := c.Domains[addrdom.Name()]; !ok {
1422 addErrorf("unknown domain %q for DMARC address in domain %q", dmarc.Domain, d)
1423 }
1424 }
1425
1426 domain.DMARC.ParsedLocalpart = lp
1427 domain.DMARC.DNSDomain = addrdom
1428 c.Domains[d] = domain
1429 addrFull := smtp.NewAddress(lp, addrdom).String()
1430 dest := config.Destination{
1431 Mailbox: dmarc.Mailbox,
1432 DMARCReports: true,
1433 }
1434 checkMailboxNormf(dmarc.Mailbox, "DMARC mailbox for account %q", dmarc.Account)
1435 accDests[addrFull] = AccountDestination{false, lp, dmarc.Account, dest}
1436 }
1437
1438 // Set TLSRPT destinations.
1439 for d, domain := range c.Domains {
1440 tlsrpt := domain.TLSRPT
1441 if tlsrpt == nil {
1442 continue
1443 }
1444 if _, ok := c.Accounts[tlsrpt.Account]; !ok {
1445 addErrorf("TLSRPT account %q does not exist", tlsrpt.Account)
1446 }
1447 lp, err := smtp.ParseLocalpart(tlsrpt.Localpart)
1448 if err != nil {
1449 addErrorf("invalid TLSRPT localpart %q: %s", tlsrpt.Localpart, err)
1450 }
1451 if lp.IsInternational() {
1452 // Does not appear documented in ../rfc/8460, but similar to DMARC it makes sense
1453 // to keep this ascii-only addresses.
1454 addErrorf("TLSRPT localpart %q is an internationalized address, only conventional ascii-only address allowed for interopability", lp)
1455 }
1456 addrdom := domain.Domain
1457 if tlsrpt.Domain != "" {
1458 addrdom, err = dns.ParseDomain(tlsrpt.Domain)
1459 if err != nil {
1460 addErrorf("TLSRPT domain %q: %s", tlsrpt.Domain, err)
1461 } else if _, ok := c.Domains[addrdom.Name()]; !ok {
1462 addErrorf("unknown domain %q for TLSRPT address in domain %q", tlsrpt.Domain, d)
1463 }
1464 }
1465
1466 domain.TLSRPT.ParsedLocalpart = lp
1467 domain.TLSRPT.DNSDomain = addrdom
1468 c.Domains[d] = domain
1469 addrFull := smtp.NewAddress(lp, addrdom).String()
1470 dest := config.Destination{
1471 Mailbox: tlsrpt.Mailbox,
1472 DomainTLSReports: true,
1473 }
1474 checkMailboxNormf(tlsrpt.Mailbox, "TLSRPT mailbox for account %q", tlsrpt.Account)
1475 accDests[addrFull] = AccountDestination{false, lp, tlsrpt.Account, dest}
1476 }
1477
1478 // Check webserver configs.
1479 if (len(c.WebDomainRedirects) > 0 || len(c.WebHandlers) > 0) && !haveWebserverListener {
1480 addErrorf("WebDomainRedirects or WebHandlers configured but no listener with WebserverHTTP or WebserverHTTPS enabled")
1481 }
1482
1483 c.WebDNSDomainRedirects = map[dns.Domain]dns.Domain{}
1484 for from, to := range c.WebDomainRedirects {
1485 fromdom, err := dns.ParseDomain(from)
1486 if err != nil {
1487 addErrorf("parsing domain for redirect %s: %v", from, err)
1488 }
1489 todom, err := dns.ParseDomain(to)
1490 if err != nil {
1491 addErrorf("parsing domain for redirect %s: %v", to, err)
1492 } else if fromdom == todom {
1493 addErrorf("will not redirect domain %s to itself", todom)
1494 }
1495 var zerodom dns.Domain
1496 if _, ok := c.WebDNSDomainRedirects[fromdom]; ok && fromdom != zerodom {
1497 addErrorf("duplicate redirect domain %s", from)
1498 }
1499 c.WebDNSDomainRedirects[fromdom] = todom
1500 }
1501
1502 for i := range c.WebHandlers {
1503 wh := &c.WebHandlers[i]
1504
1505 if wh.LogName == "" {
1506 wh.Name = fmt.Sprintf("%d", i)
1507 } else {
1508 wh.Name = wh.LogName
1509 }
1510
1511 dom, err := dns.ParseDomain(wh.Domain)
1512 if err != nil {
1513 addErrorf("webhandler %s %s: parsing domain: %v", wh.Domain, wh.PathRegexp, err)
1514 }
1515 wh.DNSDomain = dom
1516
1517 if !strings.HasPrefix(wh.PathRegexp, "^") {
1518 addErrorf("webhandler %s %s: path regexp must start with a ^", wh.Domain, wh.PathRegexp)
1519 }
1520 re, err := regexp.Compile(wh.PathRegexp)
1521 if err != nil {
1522 addErrorf("webhandler %s %s: compiling regexp: %v", wh.Domain, wh.PathRegexp, err)
1523 }
1524 wh.Path = re
1525
1526 var n int
1527 if wh.WebStatic != nil {
1528 n++
1529 ws := wh.WebStatic
1530 if ws.StripPrefix != "" && !strings.HasPrefix(ws.StripPrefix, "/") {
1531 addErrorf("webstatic %s %s: prefix to strip %s must start with a slash", wh.Domain, wh.PathRegexp, ws.StripPrefix)
1532 }
1533 for k := range ws.ResponseHeaders {
1534 xk := k
1535 k := strings.TrimSpace(xk)
1536 if k != xk || k == "" {
1537 addErrorf("webstatic %s %s: bad header %q", wh.Domain, wh.PathRegexp, xk)
1538 }
1539 }
1540 }
1541 if wh.WebRedirect != nil {
1542 n++
1543 wr := wh.WebRedirect
1544 if wr.BaseURL != "" {
1545 u, err := url.Parse(wr.BaseURL)
1546 if err != nil {
1547 addErrorf("webredirect %s %s: parsing redirect url %s: %v", wh.Domain, wh.PathRegexp, wr.BaseURL, err)
1548 }
1549 switch u.Path {
1550 case "", "/":
1551 u.Path = "/"
1552 default:
1553 addErrorf("webredirect %s %s: BaseURL must have empty path", wh.Domain, wh.PathRegexp, wr.BaseURL)
1554 }
1555 wr.URL = u
1556 }
1557 if wr.OrigPathRegexp != "" && wr.ReplacePath != "" {
1558 re, err := regexp.Compile(wr.OrigPathRegexp)
1559 if err != nil {
1560 addErrorf("webredirect %s %s: compiling regexp %s: %v", wh.Domain, wh.PathRegexp, wr.OrigPathRegexp, err)
1561 }
1562 wr.OrigPath = re
1563 } else if wr.OrigPathRegexp != "" || wr.ReplacePath != "" {
1564 addErrorf("webredirect %s %s: must have either both OrigPathRegexp and ReplacePath, or neither", wh.Domain, wh.PathRegexp)
1565 } else if wr.BaseURL == "" {
1566 addErrorf("webredirect %s %s: must at least one of BaseURL and OrigPathRegexp+ReplacePath", wh.Domain, wh.PathRegexp)
1567 }
1568 if wr.StatusCode != 0 && (wr.StatusCode < 300 || wr.StatusCode >= 400) {
1569 addErrorf("webredirect %s %s: invalid redirect status code %d", wh.Domain, wh.PathRegexp, wr.StatusCode)
1570 }
1571 }
1572 if wh.WebForward != nil {
1573 n++
1574 wf := wh.WebForward
1575 u, err := url.Parse(wf.URL)
1576 if err != nil {
1577 addErrorf("webforward %s %s: parsing url %s: %v", wh.Domain, wh.PathRegexp, wf.URL, err)
1578 }
1579 wf.TargetURL = u
1580
1581 for k := range wf.ResponseHeaders {
1582 xk := k
1583 k := strings.TrimSpace(xk)
1584 if k != xk || k == "" {
1585 addErrorf("webforward %s %s: bad header %q", wh.Domain, wh.PathRegexp, xk)
1586 }
1587 }
1588 }
1589 if n != 1 {
1590 addErrorf("webhandler %s %s: must have exactly one handler, not %d", wh.Domain, wh.PathRegexp, n)
1591 }
1592 }
1593
1594 return
1595}
1596
1597func loadPrivateKeyFile(keyPath string) (crypto.Signer, error) {
1598 keyBuf, err := os.ReadFile(keyPath)
1599 if err != nil {
1600 return nil, fmt.Errorf("reading host private key: %v", err)
1601 }
1602 b, _ := pem.Decode(keyBuf)
1603 if b == nil {
1604 return nil, fmt.Errorf("parsing pem block for private key: %v", err)
1605 }
1606 var privKey any
1607 switch b.Type {
1608 case "PRIVATE KEY":
1609 privKey, err = x509.ParsePKCS8PrivateKey(b.Bytes)
1610 case "RSA PRIVATE KEY":
1611 privKey, err = x509.ParsePKCS1PrivateKey(b.Bytes)
1612 case "EC PRIVATE KEY":
1613 privKey, err = x509.ParseECPrivateKey(b.Bytes)
1614 default:
1615 err = fmt.Errorf("unknown pem type %q", b.Type)
1616 }
1617 if err != nil {
1618 return nil, fmt.Errorf("parsing private key: %v", err)
1619 }
1620 if k, ok := privKey.(crypto.Signer); ok {
1621 return k, nil
1622 }
1623 return nil, fmt.Errorf("parsed private key not a crypto.Signer, but %T", privKey)
1624}
1625
1626func loadTLSKeyCerts(configFile, kind string, ctls *config.TLS) error {
1627 certs := []tls.Certificate{}
1628 for _, kp := range ctls.KeyCerts {
1629 certPath := configDirPath(configFile, kp.CertFile)
1630 keyPath := configDirPath(configFile, kp.KeyFile)
1631 cert, err := loadX509KeyPairPrivileged(certPath, keyPath)
1632 if err != nil {
1633 return fmt.Errorf("tls config for %q: parsing x509 key pair: %v", kind, err)
1634 }
1635 certs = append(certs, cert)
1636 }
1637 ctls.Config = &tls.Config{
1638 Certificates: certs,
1639 }
1640 return nil
1641}
1642
1643// load x509 key/cert files from file descriptor possibly passed in by privileged
1644// process.
1645func loadX509KeyPairPrivileged(certPath, keyPath string) (tls.Certificate, error) {
1646 certBuf, err := readFilePrivileged(certPath)
1647 if err != nil {
1648 return tls.Certificate{}, fmt.Errorf("reading tls certificate: %v", err)
1649 }
1650 keyBuf, err := readFilePrivileged(keyPath)
1651 if err != nil {
1652 return tls.Certificate{}, fmt.Errorf("reading tls key: %v", err)
1653 }
1654 return tls.X509KeyPair(certBuf, keyBuf)
1655}
1656
1657// like os.ReadFile, but open privileged file possibly passed in by root process.
1658func readFilePrivileged(path string) ([]byte, error) {
1659 f, err := OpenPrivileged(path)
1660 if err != nil {
1661 return nil, err
1662 }
1663 defer f.Close()
1664 return io.ReadAll(f)
1665}
1666