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