mtasts

package
v0.0.10 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 9, 2024 License: MIT Imports: 14 Imported by: 1

Documentation

Overview

Package mtasts implements MTA-STS (SMTP MTA Strict Transport Security, RFC 8461) which allows a domain to specify SMTP TLS requirements.

SMTP for message delivery to a remote mail server always starts out unencrypted, in plain text. STARTTLS allows upgrading the connection to TLS, but is optional and by default mail servers will fall back to plain text communication if STARTTLS does not work (which can be sabotaged by DNS manipulation or SMTP connection manipulation). MTA-STS can specify a policy for requiring STARTTLS to be used for message delivery. A TXT DNS record at "_mta-sts.<domain>" specifies the version of the policy, and "https://mta-sts.<domain>/.well-known/mta-sts.txt" serves the policy.

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	MetricGet         stub.HistogramVec                                                                                           = stub.HistogramVecIgnore{}
	HTTPClientObserve func(ctx context.Context, log *slog.Logger, pkg, method string, statusCode int, err error, start time.Time) = stub.HTTPClientObserveIgnore
)
View Source
var (
	ErrNoRecord        = errors.New("mtasts: no mta-sts dns txt record") // Domain does not implement MTA-STS. If a cached non-expired policy is available, it should still be used.
	ErrMultipleRecords = errors.New("mtasts: multiple mta-sts records")  // Should be treated as if domain does not implement MTA-STS, unless a cached non-expired policy is available.
	ErrDNS             = errors.New("mtasts: dns lookup")                // For temporary DNS errors.
	ErrRecordSyntax    = errors.New("mtasts: record syntax error")
)

Lookup errors.

View Source
var (
	ErrNoPolicy     = errors.New("mtasts: no policy served")    // If the name "mta-sts.<domain>" does not exist in DNS or if webserver returns HTTP status 404 "File not found".
	ErrPolicyFetch  = errors.New("mtasts: cannot fetch policy") // E.g. for HTTP request errors.
	ErrPolicySyntax = errors.New("mtasts: policy syntax error")
)

Policy fetch errors.

View Source
var HTTPClient = &http.Client{
	CheckRedirect: func(req *http.Request, via []*http.Request) error {
		return fmt.Errorf("redirect not allowed for MTA-STS policies")
	},
}

HTTPClient is used by FetchPolicy for HTTP requests.

Functions

func Get

func Get(ctx context.Context, elog *slog.Logger, resolver dns.Resolver, domain dns.Domain) (record *Record, policy *Policy, policyText string, err error)

Get looks up the MTA-STS DNS record and fetches the policy.

Errors can be those returned by LookupRecord and FetchPolicy.

If a valid policy cannot be retrieved, a sender must treat the domain as not implementing MTA-STS. If a sender has a non-expired cached policy, that policy would still apply.

If a record was retrieved, but a policy could not be retrieved/parsed, the record is still returned.

Also see Get in package mtastsdb.

Example
package main

import (
	"context"
	"errors"
	"log"
	"log/slog"

	"github.com/mjl-/mox/dns"
	"github.com/mjl-/mox/mtasts"
)

func main() {
	ctx := context.Background()
	resolver := dns.StrictResolver{}

	// Get for example.org does a DNS TXT lookup at _mta-sts.example.org.
	// If the record exists, the policy is fetched from https://mta-sts.<domain>/.well-known/mta-sts.txt, and parsed.
	record, policy, policyText, err := mtasts.Get(ctx, slog.Default(), resolver, dns.Domain{ASCII: "example.org"})
	if err != nil {
		log.Printf("looking up mta-sts record and fetching policy: %v", err)
		if !errors.Is(err, mtasts.ErrDNS) {
			log.Printf("domain does not implement mta-sts")
		}
		// Continuing, we may have a record but not a policy.
	} else {
		log.Printf("domain implements mta-sts")
	}
	if record != nil {
		log.Printf("mta-sts DNS record: %#v", record)
	}
	if policy != nil {
		log.Printf("mta-sts policy: %#v", policy)
		log.Printf("mta-sts policy text:\n%s", policyText)
	}
}
Output:

func TLSReportFailureReason added in v0.0.8

func TLSReportFailureReason(err error) string

TLSReportFailureReason returns a concise error for known error types, or an empty string. For use in TLSRPT.

Types

type Mode

type Mode string

Mode indicates how the policy should be interpreted.

const (
	ModeEnforce Mode = "enforce" // Policy must be followed, i.e. deliveries must fail if a TLS connection cannot be made.
	ModeTesting Mode = "testing" // In case TLS cannot be negotiated, plain SMTP can be used, but failures must be reported, e.g. with TLSRPT.
	ModeNone    Mode = "none"    // In case MTA-STS is not or no longer implemented.
)

type Pair

type Pair struct {
	Key   string
	Value string
}

Pair is an extension key/value pair in a MTA-STS DNS record or policy.

type Policy

type Policy struct {
	Version       string // "STSv1"
	Mode          Mode
	MX            []STSMX
	MaxAgeSeconds int // How long this policy can be cached. Suggested values are in weeks or more.
	Extensions    []Pair
}

Policy is an MTA-STS policy as served at "https://mta-sts.<domain>/.well-known/mta-sts.txt".

func FetchPolicy

func FetchPolicy(ctx context.Context, elog *slog.Logger, domain dns.Domain) (policy *Policy, policyText string, rerr error)

FetchPolicy fetches a new policy for the domain, at https://mta-sts.<domain>/.well-known/mta-sts.txt.

FetchPolicy returns the parsed policy and the literal policy text as fetched from the server. If a policy was fetched but could not be parsed, the policyText return value will be set.

Policies longer than 64KB result in a syntax error.

If an error is returned, callers should back off for 5 minutes until the next attempt.

func ParsePolicy

func ParsePolicy(s string) (policy *Policy, err error)

ParsePolicy parses an MTA-STS policy.

func (*Policy) Matches

func (p *Policy) Matches(host dns.Domain) bool

Matches returns whether the hostname matches the mx list in the policy.

func (Policy) String

func (p Policy) String() string

String returns a textual representation for serving at the well-known URL.

type Record

type Record struct {
	Version    string // "STSv1", for "v=". Required.
	ID         string // Record version, for "id=". Required.
	Extensions []Pair // Optional extensions.
}

Record is an MTA-STS DNS record, served under "_mta-sts.<domain>" as a TXT record.

Example:

v=STSv1; id=20160831085700Z

func LookupRecord

func LookupRecord(ctx context.Context, elog *slog.Logger, resolver dns.Resolver, domain dns.Domain) (rrecord *Record, rtxt string, rerr error)

LookupRecord looks up the MTA-STS TXT DNS record at "_mta-sts.<domain>", following CNAME records, and returns the parsed MTA-STS record and the DNS TXT record.

func ParseRecord

func ParseRecord(txt string) (record *Record, ismtasts bool, err error)

ParseRecord parses an MTA-STS record.

func (Record) String

func (r Record) String() string

String returns a textual version of the MTA-STS record for use as DNS TXT record.

type STSMX

type STSMX struct {
	// "*." wildcard, e.g. if a subdomain matches. A wildcard must match exactly one
	// label. *.example.com matches mail.example.com, but not example.com, and not
	// foor.bar.example.com.
	Wildcard bool

	Domain dns.Domain
}

STSMX is an allowlisted MX host name/pattern. todo: find a way to name this just STSMX without getting duplicate names for "MX" in the sherpa api.

func (STSMX) LogString added in v0.0.4

func (s STSMX) LogString() string

LogString returns a loggable string representing the host, with both unicode and ascii version for IDNA domains.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL