maddy

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

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

  1/*
  2Maddy Mail Server - Composable all-in-one email server.
  3Copyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Maddy Mail Server contributors
  4
  5This program is free software: you can redistribute it and/or modify
  6it under the terms of the GNU General Public License as published by
  7the Free Software Foundation, either version 3 of the License, or
  8(at your option) any later version.
  9
 10This program is distributed in the hope that it will be useful,
 11but WITHOUT ANY WARRANTY; without even the implied warranty of
 12MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 13GNU General Public License for more details.
 14
 15You should have received a copy of the GNU General Public License
 16along with this program.  If not, see <https://www.gnu.org/licenses/>.
 17*/
 18
 19package remote
 20
 21import (
 22	"context"
 23	"crypto/tls"
 24	"errors"
 25	"os"
 26	"runtime/debug"
 27	"time"
 28
 29	"github.com/foxcpp/go-mtasts"
 30	"github.com/foxcpp/maddy/framework/config"
 31	"github.com/foxcpp/maddy/framework/dns"
 32	"github.com/foxcpp/maddy/framework/exterrors"
 33	"github.com/foxcpp/maddy/framework/future"
 34	"github.com/foxcpp/maddy/framework/log"
 35	"github.com/foxcpp/maddy/framework/module"
 36	"github.com/foxcpp/maddy/internal/target"
 37)
 38
 39type (
 40	mtastsPolicy struct {
 41		cache       *mtasts.Cache
 42		mtastsGet   func(context.Context, string) (*mtasts.Policy, error)
 43		updaterStop chan struct{}
 44		log         log.Logger
 45		instName    string
 46	}
 47	mtastsDelivery struct {
 48		c         *mtastsPolicy
 49		domain    string
 50		policyFut *future.Future
 51		log       log.Logger
 52	}
 53)
 54
 55func NewMTASTSPolicy(_, instName string, _, _ []string) (module.Module, error) {
 56	return &mtastsPolicy{
 57		instName: instName,
 58		log:      log.Logger{Name: "mx_auth.mtasts", Debug: log.DefaultLogger.Debug},
 59	}, nil
 60}
 61
 62func (c *mtastsPolicy) Name() string {
 63	return c.log.Name
 64}
 65
 66func (c *mtastsPolicy) InstanceName() string {
 67	return c.instName
 68}
 69
 70func (c *mtastsPolicy) Weight() int {
 71	return 10
 72}
 73
 74func (c *mtastsPolicy) Init(cfg *config.Map) error {
 75	var (
 76		storeType string
 77		storeDir  string
 78	)
 79	cfg.Enum("cache", false, false, []string{"ram", "fs"}, "fs", &storeType)
 80	cfg.String("fs_dir", false, false, "mtasts_cache", &storeDir)
 81	if _, err := cfg.Process(); err != nil {
 82		return err
 83	}
 84
 85	switch storeType {
 86	case "fs":
 87		if err := os.MkdirAll(storeDir, os.ModePerm); err != nil {
 88			return err
 89		}
 90		c.cache = mtasts.NewFSCache(storeDir)
 91	case "ram":
 92		c.cache = mtasts.NewRAMCache()
 93	default:
 94		panic("mtasts policy init: unknown cache type")
 95	}
 96	c.cache.Resolver = dns.DefaultResolver()
 97	c.mtastsGet = c.cache.Get
 98
 99	return nil
100}
101
102// StartUpdater starts a goroutine to update MTA-STS cache periodically until
103// Close is called.
104//
105// It can be called only once per mtastsPolicy instance.
106func (c *mtastsPolicy) StartUpdater() {
107	c.updaterStop = make(chan struct{})
108	go c.updater()
109}
110
111func (c *mtastsPolicy) updater() {
112	defer func() {
113		if err := recover(); err != nil {
114			stack := debug.Stack()
115			log.Printf("panic during MTA-STS update: %v\n%s", err, stack)
116			log.Printf("MTA-STS cache refresh disabled due to critical error")
117			c.updaterStop = nil
118		}
119	}()
120
121	// Always update cache on start-up since we may have been down for some
122	// time.
123	c.log.Debugln("updating MTA-STS cache...")
124	if err := c.cache.Refresh(); err != nil {
125		c.log.Error("MTA-STS cache update error", err)
126	}
127	c.log.Debugln("updating MTA-STS cache... done!")
128
129	t := time.NewTicker(12 * time.Hour)
130	for {
131		select {
132		case <-t.C:
133			c.log.Debugln("updating MTA-STS cache...")
134			if err := c.cache.Refresh(); err != nil {
135				c.log.Error("MTA-STS cache opdate error", err)
136			}
137			c.log.Debugln("updating MTA-STS cache... done!")
138		case <-c.updaterStop:
139			c.updaterStop <- struct{}{}
140			return
141		}
142	}
143}
144
145func (c *mtastsPolicy) Start(msgMeta *module.MsgMetadata) module.DeliveryMXAuthPolicy {
146	return &mtastsDelivery{
147		c:   c,
148		log: target.DeliveryLogger(c.log, msgMeta),
149	}
150}
151
152func (c *mtastsPolicy) Close() error {
153	if c.updaterStop != nil {
154		c.updaterStop <- struct{}{}
155		<-c.updaterStop
156		c.updaterStop = nil
157	}
158	return nil
159}
160
161func (c *mtastsDelivery) PrepareDomain(ctx context.Context, domain string) {
162	c.policyFut = future.New()
163	go func() {
164		c.policyFut.Set(c.c.mtastsGet(ctx, domain))
165	}()
166}
167
168func (c *mtastsDelivery) PrepareConn(ctx context.Context, mx string) {}
169
170func (c *mtastsDelivery) CheckMX(ctx context.Context, mxLevel module.MXLevel, domain, mx string, dnssec bool) (module.MXLevel, error) {
171	policyI, err := c.policyFut.GetContext(ctx)
172	if err != nil {
173		c.log.DebugMsg("MTA-STS error", "err", err)
174		return module.MXNone, nil
175	}
176	policy := policyI.(*mtasts.Policy)
177
178	if !policy.Match(mx) {
179		if policy.Mode == mtasts.ModeEnforce {
180			return module.MXNone, &exterrors.SMTPError{
181				Code:         550,
182				EnhancedCode: exterrors.EnhancedCode{5, 7, 0},
183				Message:      "Failed to establish the MX record authenticity (MTA-STS)",
184			}
185		}
186		c.log.Msg("MX does not match published non-enforced MTA-STS policy", "mx", mx, "domain", c.domain)
187		return module.MXNone, nil
188	}
189	return module.MX_MTASTS, nil
190}
191
192func (c *mtastsDelivery) CheckConn(ctx context.Context, mxLevel module.MXLevel, tlsLevel module.TLSLevel, domain, mx string, tlsState tls.ConnectionState) (module.TLSLevel, error) {
193	policyI, err := c.policyFut.GetContext(ctx)
194	if err != nil {
195		c.c.log.DebugMsg("MTA-STS error", "err", err)
196		return module.TLSNone, nil
197	}
198	policy := policyI.(*mtasts.Policy)
199
200	if policy.Mode != mtasts.ModeEnforce {
201		return module.TLSNone, nil
202	}
203
204	if !tlsState.HandshakeComplete {
205		return module.TLSNone, &exterrors.SMTPError{
206			Code:         451,
207			EnhancedCode: exterrors.EnhancedCode{4, 7, 1},
208			Message:      "TLS is required but unavailable or failed (MTA-STS)",
209		}
210	}
211
212	if tlsState.VerifiedChains == nil {
213		return module.TLSNone, &exterrors.SMTPError{
214			Code:         451,
215			EnhancedCode: exterrors.EnhancedCode{4, 7, 1},
216			Message: "Recipient server TLS certificate is not trusted but " +
217				"authentication is required by MTA-STS",
218			Misc: map[string]interface{}{
219				"tls_level": tlsLevel,
220			},
221		}
222	}
223
224	return module.TLSNone, nil
225}
226
227func (c *mtastsDelivery) Reset(msgMeta *module.MsgMetadata) {
228	c.policyFut = nil
229	if msgMeta != nil {
230		c.log = target.DeliveryLogger(c.c.log, msgMeta)
231	}
232}
233
234// Stub that will be removed in 0.5.
235type stsPreloadPolicy struct {
236	log      log.Logger
237	instName string
238}
239
240func NewSTSPreload(_, instName string, _, _ []string) (module.Module, error) {
241	return &stsPreloadPolicy{
242		instName: instName,
243		log:      log.Logger{Name: "mx_auth.sts_preload", Debug: log.DefaultLogger.Debug},
244	}, nil
245}
246
247func (c *stsPreloadPolicy) Name() string {
248	return c.log.Name
249}
250
251func (c *stsPreloadPolicy) InstanceName() string {
252	return c.instName
253}
254
255func (c *stsPreloadPolicy) Weight() int {
256	return 30 // after MTA-STS
257}
258
259func (c *stsPreloadPolicy) Init(cfg *config.Map) error {
260	c.log.Println("sts_preload module is deprecated and is no-op as the list is expired and unmaintained")
261
262	var (
263		sourcePath     string
264		enforceTesting bool
265	)
266	cfg.String("source", false, false, "eff", &sourcePath)
267	cfg.Bool("enforce_testing", false, true, &enforceTesting)
268	if _, err := cfg.Process(); err != nil {
269		return err
270	}
271
272	return nil
273}
274
275type preloadDelivery struct {
276	*stsPreloadPolicy
277}
278
279func (p *stsPreloadPolicy) Start(*module.MsgMetadata) module.DeliveryMXAuthPolicy {
280	return &preloadDelivery{stsPreloadPolicy: p}
281}
282
283func (p *preloadDelivery) Reset(*module.MsgMetadata)                        {}
284func (p *preloadDelivery) PrepareDomain(ctx context.Context, domain string) {}
285func (p *preloadDelivery) PrepareConn(ctx context.Context, mx string)       {}
286func (p *preloadDelivery) CheckMX(ctx context.Context, mxLevel module.MXLevel, domain, mx string, dnssec bool) (module.MXLevel, error) {
287	return mxLevel, nil
288}
289
290func (p *preloadDelivery) CheckConn(ctx context.Context, mxLevel module.MXLevel, tlsLevel module.TLSLevel, domain, mx string, tlsState tls.ConnectionState) (module.TLSLevel, error) {
291	return tlsLevel, nil
292}
293
294func (p *stsPreloadPolicy) Close() error {
295	return nil
296}
297
298type dnssecPolicy struct {
299	instName string
300}
301
302func NewDNSSECPolicy(_, instName string, _, _ []string) (module.Module, error) {
303	return &dnssecPolicy{
304		instName: instName,
305	}, nil
306}
307
308func (c *dnssecPolicy) Name() string {
309	return "mx_auth.dnssec"
310}
311
312func (c *dnssecPolicy) InstanceName() string {
313	return c.instName
314}
315
316func (c *dnssecPolicy) Weight() int {
317	return 1
318}
319
320func (c *dnssecPolicy) Init(cfg *config.Map) error {
321	_, err := cfg.Process() // will fail if there is any directive
322	return err
323}
324
325func (dnssecPolicy) Start(*module.MsgMetadata) module.DeliveryMXAuthPolicy {
326	return dnssecPolicy{}
327}
328
329func (dnssecPolicy) Close() error {
330	return nil
331}
332
333func (dnssecPolicy) Reset(*module.MsgMetadata)                        {}
334func (dnssecPolicy) PrepareDomain(ctx context.Context, domain string) {}
335func (dnssecPolicy) PrepareConn(ctx context.Context, mx string)       {}
336
337func (dnssecPolicy) CheckMX(ctx context.Context, mxLevel module.MXLevel, domain, mx string, dnssec bool) (module.MXLevel, error) {
338	if dnssec {
339		return module.MX_DNSSEC, nil
340	}
341	return module.MXNone, nil
342}
343
344func (dnssecPolicy) CheckConn(ctx context.Context, mxLevel module.MXLevel, tlsLevel module.TLSLevel, domain, mx string, tlsState tls.ConnectionState) (module.TLSLevel, error) {
345	return module.TLSNone, nil
346}
347
348type (
349	danePolicy struct {
350		extResolver *dns.ExtResolver
351		log         log.Logger
352		instName    string
353	}
354	daneDelivery struct {
355		c       *danePolicy
356		tlsaFut *future.Future
357	}
358)
359
360func NewDANEPolicy(_, instName string, _, _ []string) (module.Module, error) {
361	return &danePolicy{
362		instName: instName,
363		log:      log.Logger{Name: "remote/dane", Debug: log.DefaultLogger.Debug},
364	}, nil
365}
366
367func (c *danePolicy) Name() string {
368	return "mx_auth.dane"
369}
370
371func (c *danePolicy) InstanceName() string {
372	return c.instName
373}
374
375func (c *danePolicy) Weight() int {
376	return 10
377}
378
379func (c *danePolicy) Init(cfg *config.Map) error {
380	var err error
381	c.extResolver, err = dns.NewExtResolver()
382	if err != nil {
383		c.log.Error("DANE support is no-op: unable to init EDNS resolver", err)
384	}
385
386	cfg.Bool("debug", true, log.DefaultLogger.Debug, &c.log.Debug)
387
388	_, err = cfg.Process()
389	return err
390}
391
392func (c *danePolicy) Start(*module.MsgMetadata) module.DeliveryMXAuthPolicy {
393	return &daneDelivery{c: c}
394}
395
396func (c *danePolicy) Close() error {
397	return nil
398}
399
400func (c *daneDelivery) PrepareDomain(ctx context.Context, domain string) {}
401
402func (c *daneDelivery) discoverTLSA(ctx context.Context, mx string) ([]dns.TLSA, error) {
403	adA, rname, err := c.c.extResolver.CheckCNAMEAD(ctx, mx)
404	if err != nil {
405		// This may indicate a bogus DNSSEC signature or other lookup issue
406		// (including non-existing domain).
407		// Per RFC 7672, any I/O errors (including SERVFAIL) should
408		// cause delivery to be delayed.
409		return nil, err
410	}
411	if rname == "" {
412		// No A/AAAA records, short-circuit discovery instead of doing useless
413		// queries.
414		return nil, errors.New("no address associated with the host")
415	}
416	if !adA {
417		// If A lookup is not DNSSEC-authenticated we assume the server cannot
418		// have TLSA record and skip trying to actually lookup TLSA
419		// to avoid hitting weird errors like SERVFAIL, NOTIMP
420		// e.g. see https://github.com/foxcpp/maddy/issues/287
421		if rname == mx {
422			c.c.log.Debugln("skipping DANE for", mx, "due to non-authenticated A records")
423			return nil, nil
424		}
425
426		// But if it is CNAME'd then we may not want to skip it and actually
427		// consider initial name since it may be signed. To confirm the
428		// initial name is signed, do CNAME lookup.
429		cnameAD, _, err := c.c.extResolver.AuthLookupCNAME(ctx, mx)
430		if err != nil {
431			return nil, err
432		}
433		if !cnameAD {
434			c.c.log.Debugln("skipping DANE for", mx, "due to non-authenticated CNAME record")
435			return nil, nil
436		}
437	}
438
439	// If there was a CNAME - try it first.
440	if rname != mx {
441		ad, recs, err := c.c.extResolver.AuthLookupTLSA(ctx, "25", "tcp", rname)
442		if err != nil && !dns.IsNotFound(err) {
443			return nil, err
444		}
445		if ad && len(recs) != 0 {
446			// recs may be empty or contain only unusable records - this is
447			// okay per RFC 7672, no fallback to initial name is done.
448			c.c.log.Debugln("using", len(recs), "DANE records at", rname, "to authenticate", mx)
449			return recs, nil
450		}
451		// Per RFC 7672 Section 2.2 we interpret a non-authenticated RRset just
452		// like an empty RRset and fallback to trying original name.
453		c.c.log.Debugln("ignoring non-authenticated TLSA records for", rname)
454	}
455
456	// If initial name is not a CNAME or final canonical name is not "secure"
457	// - we consider TLSA under the initial name.
458	ad, recs, err := c.c.extResolver.AuthLookupTLSA(ctx, "25", "tcp", mx)
459	if err != nil && !dns.IsNotFound(err) {
460		return nil, err
461	}
462	if !ad {
463		c.c.log.Debugln("ignoring non-authenticated TLSA records for", mx)
464		return nil, nil
465	}
466
467	c.c.log.Debugln("using", len(recs), "DANE records at original name to authenticate", mx)
468	return recs, nil
469}
470
471func (c *daneDelivery) PrepareConn(ctx context.Context, mx string) {
472	// No DNSSEC support.
473	if c.c.extResolver == nil {
474		return
475	}
476
477	c.tlsaFut = future.New()
478
479	go func() {
480		defer func() {
481			if err := recover(); err != nil {
482				stack := debug.Stack()
483				log.Printf("panic during extended resolver lookup: %v\n%s", err, stack)
484			}
485		}()
486
487		c.tlsaFut.Set(c.discoverTLSA(ctx, dns.FQDN(mx)))
488	}()
489}
490
491func (c *daneDelivery) CheckMX(ctx context.Context, mxLevel module.MXLevel, domain, mx string, dnssec bool) (module.MXLevel, error) {
492	return module.MXNone, nil
493}
494
495func (c *daneDelivery) CheckConn(ctx context.Context, mxLevel module.MXLevel, tlsLevel module.TLSLevel, domain, mx string, tlsState tls.ConnectionState) (module.TLSLevel, error) {
496	// No DNSSEC support.
497	if c.c.extResolver == nil {
498		return module.TLSNone, nil
499	}
500
501	recsI, err := c.tlsaFut.GetContext(ctx)
502	if err != nil {
503		// No records.
504		if dns.IsNotFound(err) {
505			return module.TLSNone, nil
506		}
507
508		// Lookup error here indicates a resolution failure or may also
509		// indicate a bogus DNSSEC signature.
510		// There is a big problem with differentiating these two.
511		//
512		// We assume DANE failure in both cases as a safety measure.
513		// However, there is a possibility of a temporary error condition,
514		// so we mark it as such.
515		return module.TLSNone, exterrors.WithTemporary(err, true)
516	}
517	recs := recsI.([]dns.TLSA)
518
519	overridePKIX, err := verifyDANE(recs, tlsState)
520	if err != nil {
521		return module.TLSNone, err
522	}
523	if overridePKIX {
524		return module.TLSAuthenticated, nil
525	}
526	return module.TLSNone, nil
527}
528
529func (c *daneDelivery) Reset(*module.MsgMetadata) {}
530
531type (
532	localPolicy struct {
533		instName    string
534		minTLSLevel module.TLSLevel
535		minMXLevel  module.MXLevel
536	}
537)
538
539func NewLocalPolicy(_, instName string, _, _ []string) (module.Module, error) {
540	return &localPolicy{
541		instName: instName,
542	}, nil
543}
544
545func (c *localPolicy) Name() string {
546	return "mx_auth.local_policy"
547}
548
549func (c *localPolicy) InstanceName() string {
550	return c.instName
551}
552
553func (c *localPolicy) Weight() int {
554	return 1000
555}
556
557func (c *localPolicy) Init(cfg *config.Map) error {
558	var (
559		minTLSLevel string
560		minMXLevel  string
561	)
562
563	cfg.Enum("min_tls_level", false, false,
564		[]string{"none", "encrypted", "authenticated"}, "encrypted", &minTLSLevel)
565	cfg.Enum("min_mx_level", false, false,
566		[]string{"none", "mtasts", "dnssec"}, "none", &minMXLevel)
567	if _, err := cfg.Process(); err != nil {
568		return err
569	}
570
571	// Enum checks the value against allowed list, no 'default' necessary.
572	switch minTLSLevel {
573	case "none":
574		c.minTLSLevel = module.TLSNone
575	case "encrypted":
576		c.minTLSLevel = module.TLSEncrypted
577	case "authenticated":
578		c.minTLSLevel = module.TLSAuthenticated
579	}
580	switch minMXLevel {
581	case "none":
582		c.minMXLevel = module.MXNone
583	case "mtasts":
584		c.minMXLevel = module.MX_MTASTS
585	case "dnssec":
586		c.minMXLevel = module.MX_DNSSEC
587	}
588
589	return nil
590}
591
592func (l localPolicy) Start(msgMeta *module.MsgMetadata) module.DeliveryMXAuthPolicy {
593	return l
594}
595
596func (l localPolicy) Close() error {
597	return nil
598}
599
600func (l localPolicy) Reset(*module.MsgMetadata)                        {}
601func (l localPolicy) PrepareDomain(ctx context.Context, domain string) {}
602func (l localPolicy) PrepareConn(ctx context.Context, mx string)       {}
603
604func (l localPolicy) CheckMX(ctx context.Context, mxLevel module.MXLevel, domain, mx string, dnssec bool) (module.MXLevel, error) {
605	if mxLevel < l.minMXLevel {
606		return module.MXNone, &exterrors.SMTPError{
607			// Err on the side of caution if policy evaluation was messed up by
608			// a temporary error (we can't know with the current design).
609			Code:         451,
610			EnhancedCode: exterrors.EnhancedCode{4, 7, 0},
611			Message:      "Failed to establish the MX record authenticity",
612			Misc: map[string]interface{}{
613				"mx_level":          mxLevel,
614				"required_mx_level": l.minMXLevel,
615			},
616		}
617	}
618	return module.MXNone, nil
619}
620
621func (l localPolicy) CheckConn(ctx context.Context, mxLevel module.MXLevel, tlsLevel module.TLSLevel, domain, mx string, tlsState tls.ConnectionState) (module.TLSLevel, error) {
622	if tlsLevel < l.minTLSLevel {
623		return module.TLSNone, &exterrors.SMTPError{
624			Code:         451,
625			EnhancedCode: exterrors.EnhancedCode{4, 7, 1},
626			Message:      "TLS it not available or unauthenticated but required",
627			Misc: map[string]interface{}{
628				"tls_level":          tlsLevel,
629				"required_tls_level": l.minTLSLevel,
630			},
631		}
632	}
633	return module.TLSNone, nil
634}
635
636func init() {
637	module.Register("mx_auth.mtasts", NewMTASTSPolicy)
638	module.Register("mx_auth.sts_preload", NewSTSPreload)
639	module.Register("mx_auth.dnssec", NewDNSSECPolicy)
640	module.Register("mx_auth.dane", NewDANEPolicy)
641	module.Register("mx_auth.local_policy", NewLocalPolicy)
642}