mox/vendor/github.com/mjl-/adns/conf.go

500 lines
15 KiB
Go
Raw Normal View History

implement dnssec-awareness throughout code, and dane for incoming/outgoing mail delivery the vendored dns resolver code is a copy of the go stdlib dns resolver, with awareness of the "authentic data" (i.e. dnssec secure) added, as well as support for enhanced dns errors, and looking up tlsa records (for dane). ideally it would be upstreamed, but the chances seem slim. dnssec-awareness is added to all packages, e.g. spf, dkim, dmarc, iprev. their dnssec status is added to the Received message headers for incoming email. but the main reason to add dnssec was for implementing dane. with dane, the verification of tls certificates can be done through certificates/public keys published in dns (in the tlsa records). this only makes sense (is trustworthy) if those dns records can be verified to be authentic. mox now applies dane to delivering messages over smtp. mox already implemented mta-sts for webpki/pkix-verification of certificates against the (large) pool of CA's, and still enforces those policies when present. but it now also checks for dane records, and will verify those if present. if dane and mta-sts are both absent, the regular opportunistic tls with starttls is still done. and the fallback to plaintext is also still done. mox also makes it easy to setup dane for incoming deliveries, so other servers can deliver with dane tls certificate verification. the quickstart now generates private keys that are used when requesting certificates with acme. the private keys are pre-generated because they must be static and known during setup, because their public keys must be published in tlsa records in dns. autocert would generate private keys on its own, so had to be forked to add the option to provide the private key when requesting a new certificate. hopefully upstream will accept the change and we can drop the fork. with this change, using the quickstart to setup a new mox instance, the checks at internet.nl result in a 100% score, provided the domain is dnssec-signed and the network doesn't have any issues.
2023-10-10 13:09:35 +03:00
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build !js
package adns
import (
"errors"
"io/fs"
"os"
"runtime"
"sync"
"syscall"
"github.com/mjl-/adns/internal/bytealg"
)
// The net package's name resolution is rather complicated.
// There are two main approaches, go and cgo.
// The cgo resolver uses C functions like getaddrinfo.
// The go resolver reads system files directly and
// sends DNS packets directly to servers.
//
// The netgo build tag prefers the go resolver.
// The netcgo build tag prefers the cgo resolver.
//
// The netgo build tag also prohibits the use of the cgo tool.
// However, on Darwin, Plan 9, and Windows the cgo resolver is still available.
// On those systems the cgo resolver does not require the cgo tool.
// (The term "cgo resolver" was locked in by GODEBUG settings
// at a time when the cgo resolver did require the cgo tool.)
//
// Adding netdns=go to GODEBUG will prefer the go resolver.
// Adding netdns=cgo to GODEBUG will prefer the cgo resolver.
//
// The Resolver struct has a PreferGo field that user code
// may set to prefer the go resolver. It is documented as being
// equivalent to adding netdns=go to GODEBUG.
//
// When deciding which resolver to use, we first check the PreferGo field.
// If that is not set, we check the GODEBUG setting.
// If that is not set, we check the netgo or netcgo build tag.
// If none of those are set, we normally prefer the go resolver by default.
// However, if the cgo resolver is available,
// there is a complex set of conditions for which we prefer the cgo resolver.
//
// Other files define the netGoBuildTag, netCgoBuildTag, and cgoAvailable
// constants.
// conf is used to determine name resolution configuration.
type conf struct {
netGo bool // prefer go approach, based on build tag and GODEBUG
netCgo bool // prefer cgo approach, based on build tag and GODEBUG
dnsDebugLevel int // from GODEBUG
preferCgo bool // if no explicit preference, use cgo
goos string // copy of runtime.GOOS, used for testing
mdnsTest mdnsTest // assume /etc/mdns.allow exists, for testing
}
// mdnsTest is for testing only.
type mdnsTest int
const (
mdnsFromSystem mdnsTest = iota
mdnsAssumeExists
mdnsAssumeDoesNotExist
)
var (
confOnce sync.Once // guards init of confVal via initConfVal
confVal = &conf{goos: runtime.GOOS}
)
// systemConf returns the machine's network configuration.
func systemConf() *conf {
confOnce.Do(initConfVal)
return confVal
}
var netGoBuildTag = true
var netCgoBuildTag = false
var cgoAvailable = false
// initConfVal initializes confVal based on the environment
// that will not change during program execution.
func initConfVal() {
dnsMode, debugLevel := goDebugNetDNS()
confVal.netGo = netGoBuildTag || dnsMode == "go"
confVal.netCgo = netCgoBuildTag || dnsMode == "cgo"
confVal.dnsDebugLevel = debugLevel
if confVal.dnsDebugLevel > 0 {
defer func() {
if confVal.dnsDebugLevel > 1 {
println("go package net: confVal.netCgo =", confVal.netCgo, " netGo =", confVal.netGo)
}
switch {
case confVal.netGo:
if netGoBuildTag {
println("go package net: built with netgo build tag; using Go's DNS resolver")
} else {
println("go package net: GODEBUG setting forcing use of Go's resolver")
}
case !cgoAvailable:
println("go package net: cgo resolver not supported; using Go's DNS resolver")
case confVal.netCgo || confVal.preferCgo:
println("go package net: using cgo DNS resolver")
default:
println("go package net: dynamic selection of DNS resolver")
}
}()
}
// The remainder of this function sets preferCgo based on
// conditions that will not change during program execution.
// By default, prefer the go resolver.
confVal.preferCgo = false
// If the cgo resolver is not available, we can't prefer it.
if !cgoAvailable {
return
}
// Some operating systems always prefer the cgo resolver.
if goosPrefersCgo() {
confVal.preferCgo = true
return
}
// The remaining checks are specific to Unix systems.
switch runtime.GOOS {
case "plan9", "windows", "js", "wasip1":
return
}
// If any environment-specified resolver options are specified,
// prefer the cgo resolver.
// Note that LOCALDOMAIN can change behavior merely by being
// specified with the empty string.
_, localDomainDefined := syscall.Getenv("LOCALDOMAIN")
if localDomainDefined || os.Getenv("RES_OPTIONS") != "" || os.Getenv("HOSTALIASES") != "" {
confVal.preferCgo = true
return
}
// OpenBSD apparently lets you override the location of resolv.conf
// with ASR_CONFIG. If we notice that, defer to libc.
if runtime.GOOS == "openbsd" && os.Getenv("ASR_CONFIG") != "" {
confVal.preferCgo = true
return
}
}
// goosPrefersCgo reports whether the GOOS value passed in prefers
implement dnssec-awareness throughout code, and dane for incoming/outgoing mail delivery the vendored dns resolver code is a copy of the go stdlib dns resolver, with awareness of the "authentic data" (i.e. dnssec secure) added, as well as support for enhanced dns errors, and looking up tlsa records (for dane). ideally it would be upstreamed, but the chances seem slim. dnssec-awareness is added to all packages, e.g. spf, dkim, dmarc, iprev. their dnssec status is added to the Received message headers for incoming email. but the main reason to add dnssec was for implementing dane. with dane, the verification of tls certificates can be done through certificates/public keys published in dns (in the tlsa records). this only makes sense (is trustworthy) if those dns records can be verified to be authentic. mox now applies dane to delivering messages over smtp. mox already implemented mta-sts for webpki/pkix-verification of certificates against the (large) pool of CA's, and still enforces those policies when present. but it now also checks for dane records, and will verify those if present. if dane and mta-sts are both absent, the regular opportunistic tls with starttls is still done. and the fallback to plaintext is also still done. mox also makes it easy to setup dane for incoming deliveries, so other servers can deliver with dane tls certificate verification. the quickstart now generates private keys that are used when requesting certificates with acme. the private keys are pre-generated because they must be static and known during setup, because their public keys must be published in tlsa records in dns. autocert would generate private keys on its own, so had to be forked to add the option to provide the private key when requesting a new certificate. hopefully upstream will accept the change and we can drop the fork. with this change, using the quickstart to setup a new mox instance, the checks at internet.nl result in a 100% score, provided the domain is dnssec-signed and the network doesn't have any issues.
2023-10-10 13:09:35 +03:00
// the cgo resolver.
func goosPrefersCgo() bool {
switch runtime.GOOS {
// Historically on Windows and Plan 9 we prefer the
// cgo resolver (which doesn't use the cgo tool) rather than
// the go resolver. This is because originally these
// systems did not support the go resolver.
// Keep it this way for better compatibility.
// Perhaps we can revisit this some day.
case "windows", "plan9":
return true
// Darwin pops up annoying dialog boxes if programs try to
// do their own DNS requests, so prefer cgo.
case "darwin", "ios":
return true
// DNS requests don't work on Android, so prefer the cgo resolver.
// Issue #10714.
case "android":
return true
default:
return false
}
}
// mustUseGoResolver reports whether a DNS lookup of any sort is
// required to use the go resolver. The provided Resolver is optional.
// This will report true if the cgo resolver is not available.
func (c *conf) mustUseGoResolver(r *Resolver) bool {
return c.netGo || r.preferGo() || !cgoAvailable
}
// addrLookupOrder determines which strategy to use to resolve addresses.
// The provided Resolver is optional. nil means to not consider its options.
// It also returns dnsConfig when it was used to determine the lookup order.
func (c *conf) addrLookupOrder(r *Resolver, addr string) (ret hostLookupOrder, dnsConf *dnsConfig) {
if c.dnsDebugLevel > 1 {
defer func() {
print("go package net: addrLookupOrder(", addr, ") = ", ret.String(), "\n")
}()
}
return c.lookupOrder(r, "")
}
// hostLookupOrder determines which strategy to use to resolve hostname.
// The provided Resolver is optional. nil means to not consider its options.
// It also returns dnsConfig when it was used to determine the lookup order.
func (c *conf) hostLookupOrder(r *Resolver, hostname string) (ret hostLookupOrder, dnsConf *dnsConfig) {
if c.dnsDebugLevel > 1 {
defer func() {
print("go package net: hostLookupOrder(", hostname, ") = ", ret.String(), "\n")
}()
}
return c.lookupOrder(r, hostname)
}
func (c *conf) lookupOrder(r *Resolver, hostname string) (ret hostLookupOrder, dnsConf *dnsConfig) {
// fallbackOrder is the order we return if we can't figure it out.
var fallbackOrder hostLookupOrder
var canUseCgo bool
if c.mustUseGoResolver(r) {
// Go resolver was explicitly requested
// or cgo resolver is not available.
// Figure out the order below.
fallbackOrder = hostLookupFilesDNS
implement dnssec-awareness throughout code, and dane for incoming/outgoing mail delivery the vendored dns resolver code is a copy of the go stdlib dns resolver, with awareness of the "authentic data" (i.e. dnssec secure) added, as well as support for enhanced dns errors, and looking up tlsa records (for dane). ideally it would be upstreamed, but the chances seem slim. dnssec-awareness is added to all packages, e.g. spf, dkim, dmarc, iprev. their dnssec status is added to the Received message headers for incoming email. but the main reason to add dnssec was for implementing dane. with dane, the verification of tls certificates can be done through certificates/public keys published in dns (in the tlsa records). this only makes sense (is trustworthy) if those dns records can be verified to be authentic. mox now applies dane to delivering messages over smtp. mox already implemented mta-sts for webpki/pkix-verification of certificates against the (large) pool of CA's, and still enforces those policies when present. but it now also checks for dane records, and will verify those if present. if dane and mta-sts are both absent, the regular opportunistic tls with starttls is still done. and the fallback to plaintext is also still done. mox also makes it easy to setup dane for incoming deliveries, so other servers can deliver with dane tls certificate verification. the quickstart now generates private keys that are used when requesting certificates with acme. the private keys are pre-generated because they must be static and known during setup, because their public keys must be published in tlsa records in dns. autocert would generate private keys on its own, so had to be forked to add the option to provide the private key when requesting a new certificate. hopefully upstream will accept the change and we can drop the fork. with this change, using the quickstart to setup a new mox instance, the checks at internet.nl result in a 100% score, provided the domain is dnssec-signed and the network doesn't have any issues.
2023-10-10 13:09:35 +03:00
canUseCgo = false
} else if c.netCgo {
// Cgo resolver was explicitly requested.
return hostLookupCgo, nil
} else if c.preferCgo {
// Given a choice, we prefer the cgo resolver.
return hostLookupCgo, nil
} else {
// Neither resolver was explicitly requested
// and we have no preference.
if bytealg.IndexByteString(hostname, '\\') != -1 || bytealg.IndexByteString(hostname, '%') != -1 {
// Don't deal with special form hostnames
// with backslashes or '%'.
return hostLookupCgo, nil
}
// If something is unrecognized, use cgo.
fallbackOrder = hostLookupCgo
canUseCgo = true
}
// On systems that don't use /etc/resolv.conf or /etc/nsswitch.conf, we are done.
switch c.goos {
case "windows", "plan9", "android", "ios":
return fallbackOrder, nil
}
// Try to figure out the order to use for searches.
// If we don't recognize something, use fallbackOrder.
// That will use cgo unless the Go resolver was explicitly requested.
// If we do figure out the order, return something other
// than fallbackOrder to use the Go resolver with that order.
dnsConf = getSystemDNSConfig()
if canUseCgo && dnsConf.err != nil && !errors.Is(dnsConf.err, fs.ErrNotExist) && !errors.Is(dnsConf.err, fs.ErrPermission) {
// We can't read the resolv.conf file, so use cgo if we can.
return hostLookupCgo, dnsConf
}
if canUseCgo && dnsConf.unknownOpt {
// We didn't recognize something in resolv.conf,
// so use cgo if we can.
return hostLookupCgo, dnsConf
}
// OpenBSD is unique and doesn't use nsswitch.conf.
// It also doesn't support mDNS.
if c.goos == "openbsd" {
// OpenBSD's resolv.conf manpage says that a
// non-existent resolv.conf means "lookup" defaults
// to only "files", without DNS lookups.
if errors.Is(dnsConf.err, fs.ErrNotExist) {
return hostLookupFiles, dnsConf
}
lookup := dnsConf.lookup
if len(lookup) == 0 {
// https://www.openbsd.org/cgi-bin/man.cgi/OpenBSD-current/man5/resolv.conf.5
// "If the lookup keyword is not used in the
// system's resolv.conf file then the assumed
// order is 'bind file'"
return hostLookupDNSFiles, dnsConf
}
if len(lookup) < 1 || len(lookup) > 2 {
// We don't recognize this format.
return fallbackOrder, dnsConf
}
switch lookup[0] {
case "bind":
if len(lookup) == 2 {
if lookup[1] == "file" {
return hostLookupDNSFiles, dnsConf
}
// Unrecognized.
return fallbackOrder, dnsConf
}
return hostLookupDNS, dnsConf
case "file":
if len(lookup) == 2 {
if lookup[1] == "bind" {
return hostLookupFilesDNS, dnsConf
}
// Unrecognized.
return fallbackOrder, dnsConf
}
return hostLookupFiles, dnsConf
default:
// Unrecognized.
return fallbackOrder, dnsConf
}
// We always return before this point.
// The code below is for non-OpenBSD.
}
// Canonicalize the hostname by removing any trailing dot.
if stringsHasSuffix(hostname, ".") {
hostname = hostname[:len(hostname)-1]
}
if canUseCgo && stringsHasSuffixFold(hostname, ".local") {
// Per RFC 6762, the ".local" TLD is special. And
// because Go's native resolver doesn't do mDNS or
// similar local resolution mechanisms, assume that
// libc might (via Avahi, etc) and use cgo.
return hostLookupCgo, dnsConf
}
nss := getSystemNSS()
srcs := nss.sources["hosts"]
// If /etc/nsswitch.conf doesn't exist or doesn't specify any
// sources for "hosts", assume Go's DNS will work fine.
if errors.Is(nss.err, fs.ErrNotExist) || (nss.err == nil && len(srcs) == 0) {
if canUseCgo && c.goos == "solaris" {
// illumos defaults to
// "nis [NOTFOUND=return] files",
// which the go resolver doesn't support.
return hostLookupCgo, dnsConf
}
return hostLookupFilesDNS, dnsConf
}
if nss.err != nil {
// We failed to parse or open nsswitch.conf, so
// we have nothing to base an order on.
return fallbackOrder, dnsConf
}
var hasDNSSource bool
var hasDNSSourceChecked bool
var filesSource, dnsSource bool
var first string
for i, src := range srcs {
if src.source == "files" || src.source == "dns" {
if canUseCgo && !src.standardCriteria() {
// non-standard; let libc deal with it.
return hostLookupCgo, dnsConf
}
if src.source == "files" {
filesSource = true
} else {
hasDNSSource = true
hasDNSSourceChecked = true
dnsSource = true
}
if first == "" {
first = src.source
}
continue
}
if canUseCgo {
switch {
case hostname != "" && src.source == "myhostname":
// Let the cgo resolver handle myhostname
// if we are looking up the local hostname.
if isLocalhost(hostname) || isGateway(hostname) || isOutbound(hostname) {
return hostLookupCgo, dnsConf
}
hn, err := getHostname()
if err != nil || stringsEqualFold(hostname, hn) {
return hostLookupCgo, dnsConf
}
continue
case hostname != "" && stringsHasPrefix(src.source, "mdns"):
// e.g. "mdns4", "mdns4_minimal"
// We already returned true before if it was *.local.
// libc wouldn't have found a hit on this anyway.
// We don't parse mdns.allow files. They're rare. If one
// exists, it might list other TLDs (besides .local) or even
// '*', so just let libc deal with it.
var haveMDNSAllow bool
switch c.mdnsTest {
case mdnsFromSystem:
_, err := os.Stat("/etc/mdns.allow")
if err != nil && !errors.Is(err, fs.ErrNotExist) {
// Let libc figure out what is going on.
return hostLookupCgo, dnsConf
}
haveMDNSAllow = err == nil
case mdnsAssumeExists:
haveMDNSAllow = true
case mdnsAssumeDoesNotExist:
haveMDNSAllow = false
}
if haveMDNSAllow {
return hostLookupCgo, dnsConf
}
continue
default:
// Some source we don't know how to deal with.
return hostLookupCgo, dnsConf
}
}
if !hasDNSSourceChecked {
hasDNSSourceChecked = true
for _, v := range srcs[i+1:] {
if v.source == "dns" {
hasDNSSource = true
break
}
}
}
// If we saw a source we don't recognize, which can only
// happen if we can't use the cgo resolver, treat it as DNS,
// but only when there is no dns in all other sources.
if !hasDNSSource {
dnsSource = true
if first == "" {
first = "dns"
}
}
}
// Cases where Go can handle it without cgo and C thread overhead,
// or where the Go resolver has been forced.
switch {
case filesSource && dnsSource:
if first == "files" {
return hostLookupFilesDNS, dnsConf
} else {
return hostLookupDNSFiles, dnsConf
}
case filesSource:
return hostLookupFiles, dnsConf
case dnsSource:
return hostLookupDNS, dnsConf
}
// Something weird. Fallback to the default.
return fallbackOrder, dnsConf
}
// goDebugNetDNS parses the value of the GODEBUG "netdns" value.
// The netdns value can be of the form:
//
// 1 // debug level 1
// 2 // debug level 2
// cgo // use cgo for DNS lookups
// go // use go for DNS lookups
// cgo+1 // use cgo for DNS lookups + debug level 1
// 1+cgo // same
// cgo+2 // same, but debug level 2
//
// etc.
func goDebugNetDNS() (dnsMode string, debugLevel int) {
return "go", 0
}
// isLocalhost reports whether h should be considered a "localhost"
// name for the myhostname NSS module.
func isLocalhost(h string) bool {
return stringsEqualFold(h, "localhost") || stringsEqualFold(h, "localhost.localdomain") || stringsHasSuffixFold(h, ".localhost") || stringsHasSuffixFold(h, ".localhost.localdomain")
}
// isGateway reports whether h should be considered a "gateway"
// name for the myhostname NSS module.
func isGateway(h string) bool {
return stringsEqualFold(h, "_gateway")
}
// isOutbound reports whether h should be considered a "outbound"
// name for the myhostname NSS module.
func isOutbound(h string) bool {
return stringsEqualFold(h, "_outbound")
}