diff --git a/README.md b/README.md index d263be6..509b1d7 100644 --- a/README.md +++ b/README.md @@ -19,16 +19,15 @@ See Quickstart below to get started. - Internationalized email, with unicode in email address usernames ("localparts"), and in domain names (IDNA). - Automatic TLS with ACME, for use with Let's Encrypt and other CA's. -- TLSRPT, parsing reports about TLS usage and issues. -- MTA-STS, for ensuring TLS is used whenever it is required. Both serving of - policies, and tracking and applying policies of remote servers. +- DANE and MTA-STS for inbound and outbound delivery over SMTP with STARTTLS, + with incoming TLSRPT reporting. - Web admin interface that helps you set up your domains and accounts (instructions to create DNS records, configure SPF/DKIM/DMARC/TLSRPT/MTA-STS), for status information, managing accounts/domains, and modifying the configuration file. -- Autodiscovery (with SRV records, Microsoft-style, Thunderbird-style, and Apple - device management profiles) for easy account setup (though client support is - limited). +- Account autodiscovery (with SRV records, Microsoft-style, Thunderbird-style, + and Apple device management profiles) for easy account setup (though client + support is limited). - Webserver with serving static files and forwarding requests (reverse proxy), so port 443 can also be used to serve websites. - Prometheus metrics and structured logging for operational insight. @@ -111,7 +110,6 @@ https://nlnet.nl/project/Mox/. ## Roadmap -- DANE and DNSSEC - Authentication other than HTTP-basic for webmail/webadmin/webaccount - Per-domain webmail and IMAP/SMTP host name (and TLS cert) and client settings - Require TLS SMTP extension (RFC 8689) diff --git a/autotls/autotls.go b/autotls/autotls.go index 20221d6..d7a25e8 100644 --- a/autotls/autotls.go +++ b/autotls/autotls.go @@ -31,7 +31,8 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" "golang.org/x/crypto/acme" - "golang.org/x/crypto/acme/autocert" + + "github.com/mjl-/autocert" "github.com/mjl-/mox/dns" "github.com/mjl-/mox/mlog" @@ -64,10 +65,16 @@ type Manager struct { // Load returns an initialized autotls manager for "name" (used for the ACME key // file and requested certs and their keys). All files are stored within acmeDir. +// // contactEmail must be a valid email address to which notifications about ACME can -// be sent. directoryURL is the ACME starting point. When shutdown is closed, no -// new TLS connections can be created. -func Load(name, acmeDir, contactEmail, directoryURL string, shutdown <-chan struct{}) (*Manager, error) { +// be sent. directoryURL is the ACME starting point. +// +// getPrivateKey is called to get the private key for the host and key type. It +// can be used to deliver a specific (e.g. always the same) private key for a +// host, or a newly generated key. +// +// When shutdown is closed, no new TLS connections can be created. +func Load(name, acmeDir, contactEmail, directoryURL string, getPrivateKey func(host string, keyType autocert.KeyType) (crypto.Signer, error), shutdown <-chan struct{}) (*Manager, error) { if directoryURL == "" { return nil, fmt.Errorf("empty ACME directory URL") } @@ -136,6 +143,7 @@ func Load(name, acmeDir, contactEmail, directoryURL string, shutdown <-chan stru Key: key, UserAgent: "mox/" + moxvar.Version, }, + GetPrivateKey: getPrivateKey, // HostPolicy set below. } @@ -146,7 +154,7 @@ func Load(name, acmeDir, contactEmail, directoryURL string, shutdown <-chan stru // At startup, during config initialization, we already adjust the tls config to // inject the listener hostname if there isn't one in the TLS client hello. This is // common for SMTP STARTTLS connections, which often do not care about the - // validation of the certificate. + // verification of the certificate. if hello.ServerName == "" { log.Debug("tls request without sni servername, rejecting", mlog.Field("localaddr", hello.Conn.LocalAddr()), mlog.Field("supportedprotos", hello.SupportedProtos)) return nil, fmt.Errorf("sni server name required") @@ -225,7 +233,7 @@ func (m *Manager) SetAllowedHostnames(resolver dns.Resolver, hostnames map[dns.D xlog.Debug("checking ips of hosts configured for acme tls cert validation") for _, h := range added { - ips, err := resolver.LookupIP(ctx, "ip", h.ASCII+".") + ips, _, err := resolver.LookupIP(ctx, "ip", h.ASCII+".") if err != nil { xlog.Errorx("warning: acme tls cert validation for host may fail due to dns lookup error", err, mlog.Field("host", h)) continue diff --git a/autotls/autotls_test.go b/autotls/autotls_test.go index 8c5f406..6cf16d1 100644 --- a/autotls/autotls_test.go +++ b/autotls/autotls_test.go @@ -2,12 +2,14 @@ package autotls import ( "context" + "crypto" "errors" + "fmt" "os" "reflect" "testing" - "golang.org/x/crypto/acme/autocert" + "github.com/mjl-/autocert" "github.com/mjl-/mox/dns" ) @@ -17,7 +19,11 @@ func TestAutotls(t *testing.T) { os.MkdirAll("../testdata/autotls", 0770) shutdown := make(chan struct{}) - m, err := Load("test", "../testdata/autotls", "mox@localhost", "https://localhost/", shutdown) + + getPrivateKey := func(host string, keyType autocert.KeyType) (crypto.Signer, error) { + return nil, fmt.Errorf("not used") + } + m, err := Load("test", "../testdata/autotls", "mox@localhost", "https://localhost/", getPrivateKey, shutdown) if err != nil { t.Fatalf("load manager: %v", err) } @@ -74,7 +80,7 @@ func TestAutotls(t *testing.T) { key0 := m.Manager.Client.Key - m, err = Load("test", "../testdata/autotls", "mox@localhost", "https://localhost/", shutdown) + m, err = Load("test", "../testdata/autotls", "mox@localhost", "https://localhost/", getPrivateKey, shutdown) if err != nil { t.Fatalf("load manager again: %v", err) } @@ -87,7 +93,7 @@ func TestAutotls(t *testing.T) { t.Fatalf("hostpolicy, got err %v, expected no error", err) } - m2, err := Load("test2", "../testdata/autotls", "mox@localhost", "https://localhost/", shutdown) + m2, err := Load("test2", "../testdata/autotls", "mox@localhost", "https://localhost/", nil, shutdown) if err != nil { t.Fatalf("load another manager: %v", err) } diff --git a/config/config.go b/config/config.go index a8f40b4..c98108b 100644 --- a/config/config.go +++ b/config/config.go @@ -419,12 +419,15 @@ type KeyCert struct { } type TLS struct { - ACME string `sconf:"optional" sconf-doc:"Name of provider from top-level configuration to use for ACME, e.g. letsencrypt."` - KeyCerts []KeyCert `sconf:"optional" sconf-doc:"Key and certificate files are opened by the privileged root process and passed to the unprivileged mox process, so no special permissions are required."` - MinVersion string `sconf:"optional" sconf-doc:"Minimum TLS version. Default: TLSv1.2."` + ACME string `sconf:"optional" sconf-doc:"Name of provider from top-level configuration to use for ACME, e.g. letsencrypt."` + KeyCerts []KeyCert `sconf:"optional" sconf-doc:"Keys and certificates to use for this listener. The files are opened by the privileged root process and passed to the unprivileged mox process, so no special permissions are required on the files. If the private key will not be replaced when refreshing certificates, also consider adding the private key to HostPrivateKeyFiles and configuring DANE TLSA DNS records."` + MinVersion string `sconf:"optional" sconf-doc:"Minimum TLS version. Default: TLSv1.2."` + HostPrivateKeyFiles []string `sconf:"optional" sconf-doc:"Private keys used for ACME certificates. Specified explicitly so DANE TLSA DNS records can be generated, even before the certificates are requested. DANE is a mechanism to authenticate remote TLS certificates based on a public key or certificate specified in DNS, protected with DNSSEC. DANE is opportunistic and attempted when delivering SMTP with STARTTLS. The private key files must be in PEM format. PKCS8 is recommended, but PKCS1 and EC private keys are recognized as well. Only RSA 2048 bit and ECDSA P-256 keys are currently used. The first of each is used when requesting new certificates through ACME."` - Config *tls.Config `sconf:"-" json:"-"` // TLS config for non-ACME-verification connections, i.e. SMTP and IMAP, and not port 443. - ACMEConfig *tls.Config `sconf:"-" json:"-"` // TLS config that handles ACME verification, for serving on port 443. + Config *tls.Config `sconf:"-" json:"-"` // TLS config for non-ACME-verification connections, i.e. SMTP and IMAP, and not port 443. + ACMEConfig *tls.Config `sconf:"-" json:"-"` // TLS config that handles ACME verification, for serving on port 443. + HostPrivateRSA2048Keys []crypto.Signer `sconf:"-" json:"-"` // Private keys for new TLS certificates for listener host name, for new certificates with ACME, and for DANE records. + HostPrivateECDSAP256Keys []crypto.Signer `sconf:"-" json:"-"` } type WebHandler struct { diff --git a/config/doc.go b/config/doc.go index 59bffee..feead06 100644 --- a/config/doc.go +++ b/config/doc.go @@ -136,9 +136,11 @@ describe-static" and "mox config describe-domains": # (optional) ACME: - # Key and certificate files are opened by the privileged root process and passed - # to the unprivileged mox process, so no special permissions are required. - # (optional) + # Keys and certificates to use for this listener. The files are opened by the + # privileged root process and passed to the unprivileged mox process, so no + # special permissions are required on the files. If the private key will not be + # replaced when refreshing certificates, also consider adding the private key to + # HostPrivateKeyFiles and configuring DANE TLSA DNS records. (optional) KeyCerts: - @@ -152,6 +154,17 @@ describe-static" and "mox config describe-domains": # Minimum TLS version. Default: TLSv1.2. (optional) MinVersion: + # Private keys used for ACME certificates. Specified explicitly so DANE TLSA DNS + # records can be generated, even before the certificates are requested. DANE is a + # mechanism to authenticate remote TLS certificates based on a public key or + # certificate specified in DNS, protected with DNSSEC. DANE is opportunistic and + # attempted when delivering SMTP with STARTTLS. The private key files must be in + # PEM format. PKCS8 is recommended, but PKCS1 and EC private keys are recognized + # as well. Only RSA 2048 bit and ECDSA P-256 keys are currently used. The first of + # each is used when requesting new certificates through ACME. (optional) + HostPrivateKeyFiles: + - + # Maximum size in bytes for incoming and outgoing messages. Default is 100MB. # (optional) SMTPMaxMessageSize: 0 diff --git a/dane/dane.go b/dane/dane.go new file mode 100644 index 0000000..1eddde0 --- /dev/null +++ b/dane/dane.go @@ -0,0 +1,519 @@ +// Package dane verifies TLS certificates through DNSSEC-verified TLSA records. +// +// On the internet, TLS certificates are commonly verified by checking if they are +// signed by one of many commonly trusted Certificate Authorities (CAs). This is +// PKIX or WebPKI. With DANE, TLS certificates are verified through +// DNSSEC-protected DNS records of type TLSA. These TLSA records specify the rules +// for verification ("usage") and whether a full certificate ("selector" cert) is +// checked or only its "subject public key info" ("selector" spki). The (hash of) +// the certificate or "spki" is included in the TLSA record ("matchtype"). +// +// DANE SMTP connections have two allowed "usages" (verification rules): +// - DANE-EE, which only checks if the certificate or spki match, without the +// WebPKI verification of expiration, name or signed-by-trusted-party verification. +// - DANE-TA, which does verification similar to PKIX/WebPKI, but verifies against +// a certificate authority ("trust anchor", or "TA") specified in the TLSA record +// instead of the CA pool. +// +// DANE has two more "usages", that may be used with protocols other than SMTP: +// - PKIX-EE, which matches the certificate or spki, and also verifies the +// certificate against the CA pool. +// - PKIX-TA, which verifies the certificate or spki against a "trust anchor" +// specified in the TLSA record, that also has to be trusted by the CA pool. +// +// TLSA records are looked up for a specific port number, protocol (tcp/udp) and +// host name. Each port can have different TLSA records. TLSA records must be +// signed and verified with DNSSEC before they can be trusted and used. +// +// TLSA records are looked up under "TLSA candidate base domains". The domain +// where the TLSA records are found is the "TLSA base domain". If the host to +// connect to is a CNAME that can be followed with DNSSEC protection, it is the +// first TLSA candidate base domain. If no protected records are found, the +// original host name is the second TLSA candidate base domain. +// +// For TLS connections, the TLSA base domain is used with SNI during the +// handshake. +// +// For TLS certificate verification that requires PKIX/WebPKI/trusted-anchor +// verification (all except DANE-EE), the potential second TLSA candidate base +// domain name is also valid. With SMTP, additionally for hosts found in MX records +// for a "next-hop domain", the "original next-hop domain" (domain of an email +// address to deliver to) is also a valid name, as is the "CNAME-expanded original +// next-hop domain", bringing the potential total allowed names to four (if CNAMEs +// are followed for the MX hosts). +package dane + +// todo: why is https://datatracker.ietf.org/doc/html/draft-barnes-dane-uks-00 not in use? sounds reasonable. +// todo: add a DialSRV function that accepts a domain name, looks up srv records, dials the service, verifies dane certificate and returns the connection. for ../rfc/7673 + +import ( + "bytes" + "context" + "crypto/sha256" + "crypto/sha512" + "crypto/tls" + "crypto/x509" + "errors" + "fmt" + "net" + "strings" + "time" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" + + "github.com/mjl-/adns" + + "github.com/mjl-/mox/dns" + "github.com/mjl-/mox/mlog" + "github.com/mjl-/mox/mox-" +) + +var ( + metricVerify = promauto.NewCounter( + prometheus.CounterOpts{ + Name: "mox_dane_verify_total", + Help: "Total number of DANE verification attempts, including mox_dane_verify_errors_total.", + }, + ) + metricVerifyErrors = promauto.NewCounter( + prometheus.CounterOpts{ + Name: "mox_dane_verify_errors_total", + Help: "Total number of DANE verification failures, causing connections to fail.", + }, + ) +) + +var ( + // ErrNoRecords means no TLSA records were found and host has not opted into DANE. + ErrNoRecords = errors.New("dane: no tlsa records") + + // ErrInsecure indicates insecure DNS responses were encountered while looking up + // the host, CNAME records, or TLSA records. + ErrInsecure = errors.New("dane: dns lookups insecure") + + // ErrNoMatch means some TLSA records were found, but none can be verified against + // the remote TLS certificate. + ErrNoMatch = errors.New("dane: no match between certificate and tlsa records") +) + +// VerifyError is an error encountered while verifying a DANE TLSA record. For +// example, an error encountered with x509 certificate trusted-anchor verification. +// A TLSA record that does not match a TLS certificate is not a VerifyError. +type VerifyError struct { + Err error // Underlying error, possibly from crypto/x509. + Record adns.TLSA // Cause of error. +} + +// Error returns a string explaining this is a dane verify error along with the +// underlying error. +func (e VerifyError) Error() string { + return fmt.Sprintf("dane verify error: %s", e.Err) +} + +// Unwrap returns the underlying error. +func (e VerifyError) Unwrap() error { + return e.Err +} + +// Dial looks up a DNSSEC-protected DANE TLSA record for the domain name and +// port/service in address, checks for allowed usages, makes a network connection +// and verifies the remote certificate against the TLSA records. If +// verification succeeds, the verified record is returned. +// +// Different protocols require different usages. For example, SMTP with STARTTLS +// for delivery only allows usages DANE-TA and DANE-EE. If allowedUsages is +// non-nil, only the specified usages are taken into account when verifying, and +// any others ignored. +// +// Errors that can be returned, possibly in wrapped form: +// - ErrNoRecords, also in case the DNS response indicates "not found". +// - adns.DNSError, potentially wrapping adns.ExtendedError of which some can +// indicate DNSSEC errors. +// - ErrInsecure +// - VerifyError, potentially wrapping errors from crypto/x509. +func Dial(ctx context.Context, resolver dns.Resolver, network, address string, allowedUsages []adns.TLSAUsage) (net.Conn, adns.TLSA, error) { + log := mlog.New("dane").WithContext(ctx) + + // Split host and port. + host, portstr, err := net.SplitHostPort(address) + if err != nil { + return nil, adns.TLSA{}, fmt.Errorf("parsing address: %w", err) + } + port, err := resolver.LookupPort(ctx, network, portstr) + if err != nil { + return nil, adns.TLSA{}, fmt.Errorf("parsing port: %w", err) + } + + hostDom, err := dns.ParseDomain(strings.TrimSuffix(host, ".")) + if err != nil { + return nil, adns.TLSA{}, fmt.Errorf("parsing host: %w", err) + } + + // ../rfc/7671:1015 + // First follow CNAMEs for host. If the path to the final name is secure, we must + // lookup TLSA there first, then fallback to the original name. If the final name + // is secure that's also the SNI server name we must use, with the original name as + // allowed host during certificate name checks (for all TLSA usages other than + // DANE-EE). + cnameDom := hostDom + cnameAuthentic := true + for i := 0; ; i += 1 { + if i == 10 { + return nil, adns.TLSA{}, fmt.Errorf("too many cname lookups") + } + cname, cnameResult, err := resolver.LookupCNAME(ctx, cnameDom.ASCII+".") + cnameAuthentic = cnameAuthentic && cnameResult.Authentic + if !cnameResult.Authentic && i == 0 { + return nil, adns.TLSA{}, fmt.Errorf("%w: cname lookup insecure", ErrInsecure) + } else if dns.IsNotFound(err) { + break + } else if err != nil { + return nil, adns.TLSA{}, fmt.Errorf("resolving cname %s: %w", cnameDom, err) + } else if d, err := dns.ParseDomain(strings.TrimSuffix(cname, ".")); err != nil { + return nil, adns.TLSA{}, fmt.Errorf("parsing cname: %w", err) + } else { + cnameDom = d + } + } + + // We lookup the IP. + ipnetwork := "ip" + if strings.HasSuffix(network, "4") { + ipnetwork += "4" + } else if strings.HasSuffix(network, "6") { + ipnetwork += "6" + } + ips, _, err := resolver.LookupIP(ctx, ipnetwork, cnameDom.ASCII+".") + // note: For SMTP with opportunistic DANE we would stop here with an insecure + // response. But as long as long as we have a verified original tlsa base name, we + // can continue with regular DANE. + if err != nil { + return nil, adns.TLSA{}, fmt.Errorf("resolving ips: %w", err) + } else if len(ips) == 0 { + return nil, adns.TLSA{}, &adns.DNSError{Err: "no ips for host", Name: cnameDom.ASCII, IsNotFound: true} + } + + // Lookup TLSA records. If resolving CNAME was secure, we try that first. Otherwise + // we try at the secure original domain. + baseDom := hostDom + if cnameAuthentic { + baseDom = cnameDom + } + var records []adns.TLSA + var result adns.Result + for { + var err error + records, result, err = resolver.LookupTLSA(ctx, port, network, baseDom.ASCII+".") + // If no (secure) records can be found at the final cname, and there is an original + // name, try at original name. + // ../rfc/7671:1015 + if baseDom != hostDom && (dns.IsNotFound(err) || !result.Authentic) { + baseDom = hostDom + continue + } + if !result.Authentic { + return nil, adns.TLSA{}, ErrInsecure + } else if dns.IsNotFound(err) { + return nil, adns.TLSA{}, ErrNoRecords + } else if err != nil { + return nil, adns.TLSA{}, fmt.Errorf("lookup dane tlsa records: %w", err) + } + break + } + + // Keep only the allowed usages. + if allowedUsages != nil { + o := 0 + for _, r := range records { + for _, usage := range allowedUsages { + if r.Usage == usage { + records[o] = r + o++ + break + } + } + } + records = records[:o] + if len(records) == 0 { + // No point in dialing when we know we won't be able to verify the remote TLS + // certificate. + return nil, adns.TLSA{}, fmt.Errorf("no usable tlsa records remaining: %w", ErrNoMatch) + } + } + + // We use the base domain for SNI, allowing the original domain as well. + // ../rfc/7671:1021 + var moreAllowedHosts []dns.Domain + if baseDom != hostDom { + moreAllowedHosts = []dns.Domain{hostDom} + } + + // Dial the remote host. + timeout := 30 * time.Second + if deadline, ok := ctx.Deadline(); ok && len(ips) > 0 { + timeout = time.Until(deadline) / time.Duration(len(ips)) + } + dialer := &net.Dialer{Timeout: timeout} + var conn net.Conn + var dialErrs []error + for _, ip := range ips { + addr := net.JoinHostPort(ip.String(), portstr) + c, err := dialer.DialContext(ctx, network, addr) + if err != nil { + dialErrs = append(dialErrs, err) + continue + } + conn = c + break + } + if conn == nil { + return nil, adns.TLSA{}, errors.Join(dialErrs...) + } + + var verifiedRecord adns.TLSA + config := TLSClientConfig(log, records, baseDom, moreAllowedHosts, &verifiedRecord) + tlsConn := tls.Client(conn, &config) + if err := tlsConn.HandshakeContext(ctx); err != nil { + conn.Close() + return nil, adns.TLSA{}, err + } + return tlsConn, verifiedRecord, nil +} + +// TLSClientConfig returns a tls.Config to be used for dialing/handshaking a +// TLS connection with DANE verification. +// +// Callers should only pass records that are allowed for the use of DANE. DANE +// with SMTP only allows DANE-EE and DANE-TA usages, not the PKIX-usages. +// +// The config has InsecureSkipVerify set to true, with a custom VerifyConnection +// function for verifying DANE. Its VerifyConnection can return ErrNoMatch and +// additionally one or more (wrapped) errors of type VerifyError. +// +// The TLS config uses allowedHost for SNI. +// +// If verifiedRecord is not nil, it is set to the record that was successfully +// verified, if any. +func TLSClientConfig(log *mlog.Log, records []adns.TLSA, allowedHost dns.Domain, moreAllowedHosts []dns.Domain, verifiedRecord *adns.TLSA) tls.Config { + return tls.Config{ + ServerName: allowedHost.ASCII, + InsecureSkipVerify: true, + VerifyConnection: func(cs tls.ConnectionState) error { + verified, record, err := Verify(log, records, cs, allowedHost, moreAllowedHosts) + log.Debugx("dane verification", err, mlog.Field("verified", verified), mlog.Field("record", record)) + if verified { + if verifiedRecord != nil { + *verifiedRecord = record + } + return nil + } else if err == nil { + return ErrNoMatch + } + return fmt.Errorf("%w, and error(s) encountered during verification: %w", ErrNoMatch, err) + }, + MinVersion: tls.VersionTLS12, // ../rfc/8996:31 ../rfc/8997:66 + } +} + +// Verify checks if the TLS connection state can be verified against DANE TLSA +// records. +// +// allowedHost along with the optional moreAllowedHosts are the host names that are +// allowed during certificate verification (as used by PKIX-TA, PKIX-EE, DANE-TA, +// but not DANE-EE). A typical connection would allow just one name, but some uses +// of DANE allow multiple, like SMTP which allow up to four valid names for a TLS +// certificate based on MX/CNAME/TLSA/DNSSEC lookup results. +// +// When one of the records matches, Verify returns true, along with the matching +// record and a nil error. +// If there is no match, then in the typical case false, a zero record value and a +// nil error is returned. +// If an error is encountered while verifying a record, e.g. for x509 +// trusted-anchor verification, an error may be returned, typically one or more +// (wrapped) errors of type VerifyError. +func Verify(log *mlog.Log, records []adns.TLSA, cs tls.ConnectionState, allowedHost dns.Domain, moreAllowedHosts []dns.Domain) (verified bool, matching adns.TLSA, rerr error) { + metricVerify.Inc() + if len(records) == 0 { + metricVerifyErrors.Inc() + return false, adns.TLSA{}, fmt.Errorf("verify requires at least one tlsa record") + } + var errs []error + for _, r := range records { + ok, err := verifySingle(log, r, cs, allowedHost, moreAllowedHosts) + if err != nil { + errs = append(errs, VerifyError{err, r}) + } else if ok { + return true, r, nil + } + } + metricVerifyErrors.Inc() + return false, adns.TLSA{}, errors.Join(errs...) +} + +// verifySingle verifies the TLS connection against a single DANE TLSA record. +// +// If the remote TLS certificate matches with the TLSA record, true is +// returned. Errors may be encountered while verifying, e.g. when checking one +// of the allowed hosts against a TLSA record. A typical non-matching/verified +// TLSA record returns a nil error. But in some cases, e.g. when encountering +// errors while verifying certificates against a trust-anchor, an error can be +// returned with one or more underlying x509 verification errors. A nil-nil error +// is only returned when verified is false. +func verifySingle(log *mlog.Log, tlsa adns.TLSA, cs tls.ConnectionState, allowedHost dns.Domain, moreAllowedHosts []dns.Domain) (verified bool, rerr error) { + if len(cs.PeerCertificates) == 0 { + return false, fmt.Errorf("no server certificate") + } + + match := func(cert *x509.Certificate) bool { + var buf []byte + switch tlsa.Selector { + case adns.TLSASelectorCert: + buf = cert.Raw + case adns.TLSASelectorSPKI: + buf = cert.RawSubjectPublicKeyInfo + default: + return false + } + + switch tlsa.MatchType { + case adns.TLSAMatchTypeFull: + case adns.TLSAMatchTypeSHA256: + d := sha256.Sum256(buf) + buf = d[:] + case adns.TLSAMatchTypeSHA512: + d := sha512.Sum512(buf) + buf = d[:] + default: + return false + } + + return bytes.Equal(buf, tlsa.CertAssoc) + } + + pkixVerify := func(host dns.Domain) ([][]*x509.Certificate, error) { + // Default Verify checks for expiration. We pass the host name to check. And we + // configure the intermediates. The roots are filled in by the x509 package. + opts := x509.VerifyOptions{ + DNSName: host.ASCII, + Intermediates: x509.NewCertPool(), + Roots: mox.Conf.Static.TLS.CertPool, + } + for _, cert := range cs.PeerCertificates[1:] { + opts.Intermediates.AddCert(cert) + } + chains, err := cs.PeerCertificates[0].Verify(opts) + return chains, err + } + + switch tlsa.Usage { + case adns.TLSAUsagePKIXTA: + // We cannot get at the system trusted ca certificates to look for the trusted + // anchor. So we just ask Go to verify, then see if any of the chains include the + // ca certificate. + var errs []error + for _, host := range append([]dns.Domain{allowedHost}, moreAllowedHosts...) { + chains, err := pkixVerify(host) + log.Debugx("pkix-ta verify", err) + if err != nil { + errs = append(errs, err) + continue + } + // The chains by x509's Verify should include the longest possible match, so it is + // sure to include the trusted anchor. ../rfc/7671:835 + for _, chain := range chains { + // If pkix verified, check if any of the certificates match. + for i := len(chain) - 1; i >= 0; i-- { + if match(chain[i]) { + return true, nil + } + } + } + } + return false, errors.Join(errs...) + + case adns.TLSAUsagePKIXEE: + // Check for a certificate match. + if !match(cs.PeerCertificates[0]) { + return false, nil + } + // And do regular pkix checks, ../rfc/7671:799 + var errs []error + for _, host := range append([]dns.Domain{allowedHost}, moreAllowedHosts...) { + _, err := pkixVerify(host) + log.Debugx("pkix-ee verify", err) + if err == nil { + return true, nil + } + errs = append(errs, err) + } + return false, errors.Join(errs...) + + case adns.TLSAUsageDANETA: + // We set roots, so the system defaults don't get used. Verify checks the host name + // (set below) and checks for expiration. + opts := x509.VerifyOptions{ + Roots: x509.NewCertPool(), + } + + // If the full certificate was included, we must add it to the valid roots, the TLS + // server may not send it. ../rfc/7671:692 + var found bool + if tlsa.Selector == adns.TLSASelectorCert && tlsa.MatchType == adns.TLSAMatchTypeFull { + cert, err := x509.ParseCertificate(tlsa.CertAssoc) + if err != nil { + log.Debugx("parsing full exact certificate from tlsa record to use as root for usage dane-trusted-anchor", err) + // Continue anyway, perhaps the servers sends it again in a way that the tls package can parse? (unlikely) + } else { + opts.Roots.AddCert(cert) + found = true + } + } + + for _, cert := range cs.PeerCertificates { + if match(cert) { + opts.Roots.AddCert(cert) + found = true + break + } + } + if !found { + // Trusted anchor was not found in TLS certificates so we won't be able to + // verify. + return false, nil + } + + // Trusted anchor was found, still need to verify. + var errs []error + for _, host := range append([]dns.Domain{allowedHost}, moreAllowedHosts...) { + opts.DNSName = host.ASCII + _, err := cs.PeerCertificates[0].Verify(opts) + if err == nil { + return true, nil + } + errs = append(errs, err) + } + return false, errors.Join(errs...) + + case adns.TLSAUsageDANEEE: + // ../rfc/7250 is about raw public keys instead of x.509 certificates in tls + // handshakes. Go's crypto/tls does not implement the extension (see + // crypto/tls/common.go, the extensions values don't appear in the + // rfc, but have values 19 and 20 according to + // https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#tls-extensiontype-values-1 + // ../rfc/7671:1148 mentions the raw public keys are allowed. It's still + // questionable that this is commonly implemented. For now the world can probably + // live with an ignored certificate wrapped around the subject public key info. + + // We don't verify host name in certificate, ../rfc/7671:489 + // And we don't check for expiration. ../rfc/7671:527 + // The whole point of this type is to have simple secure infrastructure that + // doesn't automatically expire (at the most inconvenient times). + return match(cs.PeerCertificates[0]), nil + + default: + // Unknown, perhaps defined in the future. Not an error. + log.Debug("unrecognized tlsa usage, skipping", mlog.Field("tlsausage", tlsa.Usage)) + return false, nil + } +} diff --git a/dane/dane_test.go b/dane/dane_test.go new file mode 100644 index 0000000..753e685 --- /dev/null +++ b/dane/dane_test.go @@ -0,0 +1,479 @@ +package dane + +import ( + "context" + "crypto/ecdsa" + "crypto/elliptic" + cryptorand "crypto/rand" + "crypto/sha256" + "crypto/sha512" + "crypto/tls" + "crypto/x509" + "crypto/x509/pkix" + "errors" + "fmt" + "math/big" + "net" + "reflect" + "strconv" + "sync/atomic" + "time" + + "testing" + + "github.com/mjl-/adns" + + "github.com/mjl-/mox/dns" + "github.com/mjl-/mox/mlog" + "github.com/mjl-/mox/mox-" +) + +func tcheckf(t *testing.T, err error, format string, args ...any) { + t.Helper() + if err != nil { + t.Fatalf("%s: %s", fmt.Sprintf(format, args...), err) + } +} + +// Test dialing and DANE TLS verification. +func TestDial(t *testing.T) { + mlog.SetConfig(map[string]mlog.Level{"": mlog.LevelDebug}) + + // Create fake CA/trusted-anchor certificate. + taTempl := x509.Certificate{ + SerialNumber: big.NewInt(1), // Required field. + Subject: pkix.Name{CommonName: "fake ca"}, + Issuer: pkix.Name{CommonName: "fake ca"}, + NotBefore: time.Now().Add(-1 * time.Hour), + NotAfter: time.Now().Add(1 * time.Hour), + KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, + ExtKeyUsage: []x509.ExtKeyUsage{ + x509.ExtKeyUsageServerAuth, + x509.ExtKeyUsageClientAuth, + }, + BasicConstraintsValid: true, + IsCA: true, + MaxPathLen: 1, + } + taPriv, err := ecdsa.GenerateKey(elliptic.P256(), cryptorand.Reader) + tcheckf(t, err, "generating trusted-anchor ca private key") + taCertBuf, err := x509.CreateCertificate(cryptorand.Reader, &taTempl, &taTempl, taPriv.Public(), taPriv) + tcheckf(t, err, "create trusted-anchor ca certificate") + taCert, err := x509.ParseCertificate(taCertBuf) + tcheckf(t, err, "parsing generated trusted-anchor ca certificate") + + tacertsha256 := sha256.Sum256(taCert.Raw) + taCertSHA256 := tacertsha256[:] + + // Generate leaf private key & 2 certs, one expired and one valid, both signed by + // trusted-anchor cert. + leafPriv, err := ecdsa.GenerateKey(elliptic.P256(), cryptorand.Reader) + tcheckf(t, err, "generating leaf private key") + + makeLeaf := func(expired bool) (tls.Certificate, []byte, []byte) { + now := time.Now() + if expired { + now = now.Add(-2 * time.Hour) + } + leafTempl := x509.Certificate{ + SerialNumber: big.NewInt(1), // Required field. + Issuer: taTempl.Subject, + NotBefore: now.Add(-1 * time.Hour), + NotAfter: now.Add(1 * time.Hour), + DNSNames: []string{"localhost"}, + } + leafCertBuf, err := x509.CreateCertificate(cryptorand.Reader, &leafTempl, taCert, leafPriv.Public(), taPriv) + tcheckf(t, err, "create trusted-anchor ca certificate") + leafCert, err := x509.ParseCertificate(leafCertBuf) + tcheckf(t, err, "parsing generated trusted-anchor ca certificate") + + leafSPKISHA256 := sha256.Sum256(leafCert.RawSubjectPublicKeyInfo) + leafSPKISHA512 := sha512.Sum512(leafCert.RawSubjectPublicKeyInfo) + + tlsLeafCert := tls.Certificate{ + Certificate: [][]byte{leafCertBuf, taCertBuf}, + PrivateKey: leafPriv, // .(crypto.PrivateKey), + Leaf: leafCert, + } + return tlsLeafCert, leafSPKISHA256[:], leafSPKISHA512[:] + } + tlsLeafCert, leafSPKISHA256, leafSPKISHA512 := makeLeaf(false) + tlsLeafCertExpired, _, _ := makeLeaf(true) + + // Set up loopback tls server. + listenConn, err := net.Listen("tcp", "127.0.0.1:0") + tcheckf(t, err, "listen for test server") + addr := listenConn.Addr().String() + _, portstr, err := net.SplitHostPort(addr) + tcheckf(t, err, "get localhost port") + uport, err := strconv.ParseUint(portstr, 10, 16) + tcheckf(t, err, "parse localhost port") + port := int(uport) + + defer listenConn.Close() + + // Config for server, replaced during tests. + var tlsConfig atomic.Pointer[tls.Config] + tlsConfig.Store(&tls.Config{ + Certificates: []tls.Certificate{tlsLeafCert}, + }) + + // Loop handling incoming TLS connections. + go func() { + for { + conn, err := listenConn.Accept() + if err != nil { + return + } + + tlsConn := tls.Server(conn, tlsConfig.Load()) + tlsConn.Handshake() + tlsConn.Close() + } + }() + + dialHost := "localhost" + var allowedUsages []adns.TLSAUsage + + // Helper function for dialing with DANE. + test := func(resolver dns.Resolver, expRecord adns.TLSA, expErr any) { + t.Helper() + + conn, record, err := Dial(context.Background(), resolver, "tcp", net.JoinHostPort(dialHost, portstr), allowedUsages) + if err == nil { + conn.Close() + } + if (err == nil) != (expErr == nil) || err != nil && !errors.Is(err, expErr.(error)) && !errors.As(err, expErr) { + t.Fatalf("got err %v (%#v), expected %#v", err, err, expErr) + } + if !reflect.DeepEqual(record, expRecord) { + t.Fatalf("got verified record %v, expected %v", record, expRecord) + } + } + + tlsaName := fmt.Sprintf("_%d._tcp.localhost.", port) + + // Make all kinds of records, some invalid or non-matching. + var zeroRecord adns.TLSA + recordDANEEESPKISHA256 := adns.TLSA{ + Usage: adns.TLSAUsageDANEEE, + Selector: adns.TLSASelectorSPKI, + MatchType: adns.TLSAMatchTypeSHA256, + CertAssoc: leafSPKISHA256, + } + recordDANEEESPKISHA512 := adns.TLSA{ + Usage: adns.TLSAUsageDANEEE, + Selector: adns.TLSASelectorSPKI, + MatchType: adns.TLSAMatchTypeSHA512, + CertAssoc: leafSPKISHA512, + } + recordDANEEESPKIFull := adns.TLSA{ + Usage: adns.TLSAUsageDANEEE, + Selector: adns.TLSASelectorSPKI, + MatchType: adns.TLSAMatchTypeFull, + CertAssoc: tlsLeafCert.Leaf.RawSubjectPublicKeyInfo, + } + mismatchRecordDANEEESPKISHA256 := adns.TLSA{ + Usage: adns.TLSAUsageDANEEE, + Selector: adns.TLSASelectorSPKI, + MatchType: adns.TLSAMatchTypeSHA256, + CertAssoc: make([]byte, sha256.Size), // Zero, no match. + } + malformedRecordDANEEESPKISHA256 := adns.TLSA{ + Usage: adns.TLSAUsageDANEEE, + Selector: adns.TLSASelectorSPKI, + MatchType: adns.TLSAMatchTypeSHA256, + CertAssoc: leafSPKISHA256[:16], // Too short. + } + unknownparamRecordDANEEESPKISHA256 := adns.TLSA{ + Usage: adns.TLSAUsage(10), // Unrecognized value. + Selector: adns.TLSASelectorSPKI, + MatchType: adns.TLSAMatchTypeSHA256, + CertAssoc: leafSPKISHA256, + } + recordDANETACertSHA256 := adns.TLSA{ + Usage: adns.TLSAUsageDANETA, + Selector: adns.TLSASelectorCert, + MatchType: adns.TLSAMatchTypeSHA256, + CertAssoc: taCertSHA256, + } + recordDANETACertFull := adns.TLSA{ + Usage: adns.TLSAUsageDANETA, + Selector: adns.TLSASelectorCert, + MatchType: adns.TLSAMatchTypeFull, + CertAssoc: taCert.Raw, + } + malformedRecordDANETACertFull := adns.TLSA{ + Usage: adns.TLSAUsageDANETA, + Selector: adns.TLSASelectorCert, + MatchType: adns.TLSAMatchTypeFull, + CertAssoc: taCert.Raw[1:], // Cannot parse certificate. + } + mismatchRecordDANETACertSHA256 := adns.TLSA{ + Usage: adns.TLSAUsageDANETA, + Selector: adns.TLSASelectorCert, + MatchType: adns.TLSAMatchTypeSHA256, + CertAssoc: make([]byte, sha256.Size), // Zero, no match. + } + recordPKIXEESPKISHA256 := adns.TLSA{ + Usage: adns.TLSAUsagePKIXEE, + Selector: adns.TLSASelectorSPKI, + MatchType: adns.TLSAMatchTypeSHA256, + CertAssoc: leafSPKISHA256, + } + recordPKIXTACertSHA256 := adns.TLSA{ + Usage: adns.TLSAUsagePKIXTA, + Selector: adns.TLSASelectorCert, + MatchType: adns.TLSAMatchTypeSHA256, + CertAssoc: taCertSHA256, + } + + resolver := dns.MockResolver{ + A: map[string][]string{"localhost.": {"127.0.0.1"}}, + TLSA: map[string][]adns.TLSA{tlsaName: {recordDANEEESPKISHA256}}, + AllAuthentic: true, + } + + // DANE-EE SPKI SHA2-256 record. + test(resolver, recordDANEEESPKISHA256, nil) + + // Check that record isn't used if not allowed. + allowedUsages = []adns.TLSAUsage{adns.TLSAUsagePKIXTA} + test(resolver, zeroRecord, ErrNoMatch) + allowedUsages = nil // Restore. + + // Mixed allowed/not allowed usages are fine. + resolver = dns.MockResolver{ + A: map[string][]string{"localhost.": {"127.0.0.1"}}, + TLSA: map[string][]adns.TLSA{tlsaName: {mismatchRecordDANETACertSHA256, recordDANEEESPKISHA256}}, + AllAuthentic: true, + } + allowedUsages = []adns.TLSAUsage{adns.TLSAUsageDANEEE} + test(resolver, recordDANEEESPKISHA256, nil) + allowedUsages = nil // Restore. + + // DANE-TA CERT SHA2-256 record. + resolver.TLSA = map[string][]adns.TLSA{ + tlsaName: {recordDANETACertSHA256}, + } + test(resolver, recordDANETACertSHA256, nil) + + // No TLSA record. + resolver.TLSA = nil + test(resolver, zeroRecord, ErrNoRecords) + + // Insecure TLSA record. + resolver.TLSA = map[string][]adns.TLSA{ + tlsaName: {recordDANEEESPKISHA256}, + } + resolver.Inauthentic = []string{"tlsa " + tlsaName} + test(resolver, zeroRecord, ErrInsecure) + + // Insecure CNAME. + resolver.Inauthentic = []string{"cname localhost."} + test(resolver, zeroRecord, ErrInsecure) + + // Insecure TLSA + resolver.Inauthentic = []string{"tlsa " + tlsaName} + test(resolver, zeroRecord, ErrInsecure) + + // Insecure CNAME should not look at TLSA records under that name, only under original. + // Initial name/cname is secure. And it has secure TLSA records. But the lookup for + // example1 is not secure, though the final example2 records are. + resolver = dns.MockResolver{ + A: map[string][]string{"example2.": {"127.0.0.1"}}, + CNAME: map[string]string{"localhost.": "example1.", "example1.": "example2."}, + TLSA: map[string][]adns.TLSA{ + fmt.Sprintf("_%d._tcp.example2.", port): {mismatchRecordDANETACertSHA256}, // Should be ignored. + tlsaName: {recordDANEEESPKISHA256}, // Should match. + }, + AllAuthentic: true, + Inauthentic: []string{"cname example1."}, + } + test(resolver, recordDANEEESPKISHA256, nil) + + // Matching records after following cname. + resolver = dns.MockResolver{ + A: map[string][]string{"example.": {"127.0.0.1"}}, + CNAME: map[string]string{"localhost.": "example."}, + TLSA: map[string][]adns.TLSA{fmt.Sprintf("_%d._tcp.example.", port): {recordDANETACertSHA256}}, + AllAuthentic: true, + } + test(resolver, recordDANETACertSHA256, nil) + + // Fallback to original name for TLSA records if cname-expanded name doesn't have records. + resolver = dns.MockResolver{ + A: map[string][]string{"example.": {"127.0.0.1"}}, + CNAME: map[string]string{"localhost.": "example."}, + TLSA: map[string][]adns.TLSA{tlsaName: {recordDANETACertSHA256}}, + AllAuthentic: true, + } + test(resolver, recordDANETACertSHA256, nil) + + // Invalid DANE-EE record. + resolver = dns.MockResolver{ + A: map[string][]string{ + "localhost.": {"127.0.0.1"}, + }, + TLSA: map[string][]adns.TLSA{ + tlsaName: {mismatchRecordDANEEESPKISHA256}, + }, + AllAuthentic: true, + } + test(resolver, zeroRecord, ErrNoMatch) + + // DANE-EE SPKI SHA2-512 record. + resolver = dns.MockResolver{ + A: map[string][]string{"localhost.": {"127.0.0.1"}}, + TLSA: map[string][]adns.TLSA{tlsaName: {recordDANEEESPKISHA512}}, + AllAuthentic: true, + } + test(resolver, recordDANEEESPKISHA512, nil) + + // DANE-EE SPKI Full record. + resolver = dns.MockResolver{ + A: map[string][]string{"localhost.": {"127.0.0.1"}}, + TLSA: map[string][]adns.TLSA{tlsaName: {recordDANEEESPKIFull}}, + AllAuthentic: true, + } + test(resolver, recordDANEEESPKIFull, nil) + + // DANE-TA with full certificate. + resolver = dns.MockResolver{ + A: map[string][]string{"localhost.": {"127.0.0.1"}}, + TLSA: map[string][]adns.TLSA{tlsaName: {recordDANETACertFull}}, + AllAuthentic: true, + } + test(resolver, recordDANETACertFull, nil) + + // DANE-TA for cert not in TLS handshake. + resolver = dns.MockResolver{ + A: map[string][]string{"localhost.": {"127.0.0.1"}}, + TLSA: map[string][]adns.TLSA{tlsaName: {mismatchRecordDANETACertSHA256}}, + AllAuthentic: true, + } + test(resolver, zeroRecord, ErrNoMatch) + + // DANE-TA with leaf cert for other name. + resolver = dns.MockResolver{ + A: map[string][]string{"example.": {"127.0.0.1"}}, + TLSA: map[string][]adns.TLSA{fmt.Sprintf("_%d._tcp.example.", port): {recordDANETACertSHA256}}, + AllAuthentic: true, + } + origDialHost := dialHost + dialHost = "example." + test(resolver, zeroRecord, ErrNoMatch) + dialHost = origDialHost + + // DANE-TA with expired cert. + resolver = dns.MockResolver{ + A: map[string][]string{"localhost.": {"127.0.0.1"}}, + TLSA: map[string][]adns.TLSA{tlsaName: {recordDANETACertSHA256}}, + AllAuthentic: true, + } + tlsConfig.Store(&tls.Config{ + Certificates: []tls.Certificate{tlsLeafCertExpired}, + }) + test(resolver, zeroRecord, ErrNoMatch) + test(resolver, zeroRecord, &VerifyError{}) + test(resolver, zeroRecord, &x509.CertificateInvalidError{}) + // Restore. + tlsConfig.Store(&tls.Config{ + Certificates: []tls.Certificate{tlsLeafCert}, + }) + + // Malformed TLSA record is unusable, resulting in failure if none left. + resolver = dns.MockResolver{ + A: map[string][]string{"localhost.": {"127.0.0.1"}}, + TLSA: map[string][]adns.TLSA{tlsaName: {malformedRecordDANEEESPKISHA256}}, + AllAuthentic: true, + } + test(resolver, zeroRecord, ErrNoMatch) + + // Malformed TLSA record is unusable and skipped, other verified record causes Dial to succeed. + resolver = dns.MockResolver{ + A: map[string][]string{"localhost.": {"127.0.0.1"}}, + TLSA: map[string][]adns.TLSA{tlsaName: {malformedRecordDANEEESPKISHA256, recordDANEEESPKISHA256}}, + AllAuthentic: true, + } + test(resolver, recordDANEEESPKISHA256, nil) + + // Record with unknown parameters (usage in this case) is unusable, resulting in failure if none left. + resolver = dns.MockResolver{ + A: map[string][]string{"localhost.": {"127.0.0.1"}}, + TLSA: map[string][]adns.TLSA{tlsaName: {unknownparamRecordDANEEESPKISHA256}}, + AllAuthentic: true, + } + test(resolver, zeroRecord, ErrNoMatch) + + // Unknown parameter does not prevent other valid record to verify. + resolver = dns.MockResolver{ + A: map[string][]string{"localhost.": {"127.0.0.1"}}, + TLSA: map[string][]adns.TLSA{tlsaName: {unknownparamRecordDANEEESPKISHA256, recordDANEEESPKISHA256}}, + AllAuthentic: true, + } + test(resolver, recordDANEEESPKISHA256, nil) + + // Malformed full TA certificate. + resolver = dns.MockResolver{ + A: map[string][]string{"localhost.": {"127.0.0.1"}}, + TLSA: map[string][]adns.TLSA{tlsaName: {malformedRecordDANETACertFull}}, + AllAuthentic: true, + } + test(resolver, zeroRecord, ErrNoMatch) + + // Full TA certificate without getting it from TLS server. + resolver = dns.MockResolver{ + A: map[string][]string{"localhost.": {"127.0.0.1"}}, + TLSA: map[string][]adns.TLSA{tlsaName: {recordDANETACertFull}}, + AllAuthentic: true, + } + tlsLeafOnlyCert := tlsLeafCert + tlsLeafOnlyCert.Certificate = tlsLeafOnlyCert.Certificate[:1] + tlsConfig.Store(&tls.Config{ + Certificates: []tls.Certificate{tlsLeafOnlyCert}, + }) + test(resolver, recordDANETACertFull, nil) + // Restore. + tlsConfig.Store(&tls.Config{ + Certificates: []tls.Certificate{tlsLeafCert}, + }) + + // PKIXEE, will fail due to not being CA-signed. + resolver = dns.MockResolver{ + A: map[string][]string{"localhost.": {"127.0.0.1"}}, + TLSA: map[string][]adns.TLSA{tlsaName: {recordPKIXEESPKISHA256}}, + AllAuthentic: true, + } + test(resolver, zeroRecord, &x509.UnknownAuthorityError{}) + + // PKIXTA, will fail due to not being CA-signed. + resolver = dns.MockResolver{ + A: map[string][]string{"localhost.": {"127.0.0.1"}}, + TLSA: map[string][]adns.TLSA{tlsaName: {recordPKIXTACertSHA256}}, + AllAuthentic: true, + } + test(resolver, zeroRecord, &x509.UnknownAuthorityError{}) + + // Now we add the TA to the "system" trusted roots and try again. + pool, err := x509.SystemCertPool() + tcheckf(t, err, "get system certificate pool") + mox.Conf.Static.TLS.CertPool = pool + pool.AddCert(taCert) + + // PKIXEE, will now succeed. + resolver = dns.MockResolver{ + A: map[string][]string{"localhost.": {"127.0.0.1"}}, + TLSA: map[string][]adns.TLSA{tlsaName: {recordPKIXEESPKISHA256}}, + AllAuthentic: true, + } + test(resolver, recordPKIXEESPKISHA256, nil) + + // PKIXTA, will fail due to not being CA-signed. + resolver = dns.MockResolver{ + A: map[string][]string{"localhost.": {"127.0.0.1"}}, + TLSA: map[string][]adns.TLSA{tlsaName: {recordPKIXTACertSHA256}}, + AllAuthentic: true, + } + test(resolver, recordPKIXTACertSHA256, nil) +} diff --git a/dkim/dkim.go b/dkim/dkim.go index 1b60278..8f044b9 100644 --- a/dkim/dkim.go +++ b/dkim/dkim.go @@ -38,7 +38,7 @@ import ( var xlog = mlog.New("dkim") var ( - metricDKIMSign = promauto.NewCounterVec( + metricSign = promauto.NewCounterVec( prometheus.CounterOpts{ Name: "mox_dkim_sign_total", Help: "DKIM messages signings, label key is the type of key, rsa or ed25519.", @@ -47,7 +47,7 @@ var ( "key", }, ) - metricDKIMVerify = promauto.NewHistogramVec( + metricVerify = promauto.NewHistogramVec( prometheus.HistogramOpts{ Name: "mox_dkim_verify_duration_seconds", Help: "DKIM verify, including lookup, duration and result.", @@ -113,10 +113,11 @@ var ( // To decide what to do with a message, both the signature parameters and the DNS // TXT record have to be consulted. type Result struct { - Status Status - Sig *Sig // Parsed form of DKIM-Signature header. Can be nil for invalid DKIM-Signature header. - Record *Record // Parsed form of DKIM DNS record for selector and domain in Sig. Optional. - Err error // If Status is not StatusPass, this error holds the details and can be checked using errors.Is. + Status Status + Sig *Sig // Parsed form of DKIM-Signature header. Can be nil for invalid DKIM-Signature header. + Record *Record // Parsed form of DKIM DNS record for selector and domain in Sig. Optional. + RecordAuthentic bool // Whether DKIM DNS record was DNSSEC-protected. Only valid if Sig is non-nil. + Err error // If Status is not StatusPass, this error holds the details and can be checked using errors.Is. } // todo: use some io.Writer to hash the body and the header. @@ -157,10 +158,10 @@ func Sign(ctx context.Context, localpart smtp.Localpart, domain dns.Domain, c co switch sel.Key.(type) { case *rsa.PrivateKey: sig.AlgorithmSign = "rsa" - metricDKIMSign.WithLabelValues("rsa").Inc() + metricSign.WithLabelValues("rsa").Inc() case ed25519.PrivateKey: sig.AlgorithmSign = "ed25519" - metricDKIMSign.WithLabelValues("ed25519").Inc() + metricSign.WithLabelValues("ed25519").Inc() default: return "", fmt.Errorf("internal error, unknown pivate key %T", sel.Key) } @@ -267,7 +268,9 @@ func Sign(ctx context.Context, localpart smtp.Localpart, domain dns.Domain, c co // // A requested record is ._domainkey.. Exactly one valid DKIM // record should be present. -func Lookup(ctx context.Context, resolver dns.Resolver, selector, domain dns.Domain) (rstatus Status, rrecord *Record, rtxt string, rerr error) { +// +// authentic indicates if DNS results were DNSSEC-verified. +func Lookup(ctx context.Context, resolver dns.Resolver, selector, domain dns.Domain) (rstatus Status, rrecord *Record, rtxt string, authentic bool, rerr error) { log := xlog.WithContext(ctx) start := timeNow() defer func() { @@ -275,14 +278,14 @@ func Lookup(ctx context.Context, resolver dns.Resolver, selector, domain dns.Dom }() name := selector.ASCII + "._domainkey." + domain.ASCII + "." - records, err := dns.WithPackage(resolver, "dkim").LookupTXT(ctx, name) + records, lookupResult, err := dns.WithPackage(resolver, "dkim").LookupTXT(ctx, name) if dns.IsNotFound(err) { // ../rfc/6376:2608 // We must return StatusPermerror. We may want to return StatusTemperror because in // practice someone will start using a new key before DNS changes have propagated. - return StatusPermerror, nil, "", fmt.Errorf("%w: dns name %q", ErrNoRecord, name) + return StatusPermerror, nil, "", lookupResult.Authentic, fmt.Errorf("%w: dns name %q", ErrNoRecord, name) } else if err != nil { - return StatusTemperror, nil, "", fmt.Errorf("%w: dns name %q: %s", ErrDNS, name, err) + return StatusTemperror, nil, "", lookupResult.Authentic, fmt.Errorf("%w: dns name %q: %s", ErrDNS, name, err) } // ../rfc/6376:2612 @@ -298,7 +301,7 @@ func Lookup(ctx context.Context, resolver dns.Resolver, selector, domain dns.Dom var isdkim bool r, isdkim, err = ParseRecord(s) if err != nil && isdkim { - return StatusPermerror, nil, txt, fmt.Errorf("%w: %s", ErrSyntax, err) + return StatusPermerror, nil, txt, lookupResult.Authentic, fmt.Errorf("%w: %s", ErrSyntax, err) } else if err != nil { // Hopefully the remote MTA admin discovers the configuration error and fix it for // an upcoming delivery attempt, in case we rejected with temporary status. @@ -310,7 +313,7 @@ func Lookup(ctx context.Context, resolver dns.Resolver, selector, domain dns.Dom // ../rfc/6376:1609 // ../rfc/6376:2584 if record != nil { - return StatusTemperror, nil, "", fmt.Errorf("%w: dns name %q", ErrMultipleRecords, name) + return StatusTemperror, nil, "", lookupResult.Authentic, fmt.Errorf("%w: dns name %q", ErrMultipleRecords, name) } record = r txt = s @@ -318,9 +321,9 @@ func Lookup(ctx context.Context, resolver dns.Resolver, selector, domain dns.Dom } if record == nil { - return status, nil, "", err + return status, nil, "", lookupResult.Authentic, err } - return StatusNeutral, record, txt, nil + return StatusNeutral, record, txt, lookupResult.Authentic, nil } // Verify parses the DKIM-Signature headers in a message and verifies each of them. @@ -346,7 +349,7 @@ func Verify(ctx context.Context, resolver dns.Resolver, smtputf8 bool, policy fu alg = r.Sig.Algorithm() } status := string(r.Status) - metricDKIMVerify.WithLabelValues(alg, status).Observe(duration) + metricVerify.WithLabelValues(alg, status).Observe(duration) } if len(results) == 0 { @@ -373,26 +376,26 @@ func Verify(ctx context.Context, resolver dns.Resolver, smtputf8 bool, policy fu if err != nil { // ../rfc/6376:2503 err := fmt.Errorf("parsing DKIM-Signature header: %w", err) - results = append(results, Result{StatusPermerror, nil, nil, err}) + results = append(results, Result{StatusPermerror, nil, nil, false, err}) continue } h, canonHeaderSimple, canonDataSimple, err := checkSignatureParams(ctx, sig) if err != nil { - results = append(results, Result{StatusPermerror, nil, nil, err}) + results = append(results, Result{StatusPermerror, nil, nil, false, err}) continue } // ../rfc/6376:2560 if err := policy(sig); err != nil { err := fmt.Errorf("%w: %s", ErrPolicy, err) - results = append(results, Result{StatusPolicy, nil, nil, err}) + results = append(results, Result{StatusPolicy, nil, nil, false, err}) continue } br := bufio.NewReader(&moxio.AtReader{R: r, Offset: int64(bodyOffset)}) - status, txt, err := verifySignature(ctx, resolver, sig, h, canonHeaderSimple, canonDataSimple, hdrs, verifySig, br, ignoreTestMode) - results = append(results, Result{status, sig, txt, err}) + status, txt, authentic, err := verifySignature(ctx, resolver, sig, h, canonHeaderSimple, canonDataSimple, hdrs, verifySig, br, ignoreTestMode) + results = append(results, Result{status, sig, txt, authentic, err}) } return results, nil } @@ -477,15 +480,15 @@ func checkSignatureParams(ctx context.Context, sig *Sig) (hash crypto.Hash, cano } // lookup the public key in the DNS and verify the signature. -func verifySignature(ctx context.Context, resolver dns.Resolver, sig *Sig, hash crypto.Hash, canonHeaderSimple, canonDataSimple bool, hdrs []header, verifySig []byte, body *bufio.Reader, ignoreTestMode bool) (Status, *Record, error) { +func verifySignature(ctx context.Context, resolver dns.Resolver, sig *Sig, hash crypto.Hash, canonHeaderSimple, canonDataSimple bool, hdrs []header, verifySig []byte, body *bufio.Reader, ignoreTestMode bool) (Status, *Record, bool, error) { // ../rfc/6376:2604 - status, record, _, err := Lookup(ctx, resolver, sig.Selector, sig.Domain) + status, record, _, authentic, err := Lookup(ctx, resolver, sig.Selector, sig.Domain) if err != nil { // todo: for temporary errors, we could pass on information so caller returns a 4.7.5 ecode, ../rfc/6376:2777 - return status, nil, err + return status, nil, authentic, err } status, err = verifySignatureRecord(record, sig, hash, canonHeaderSimple, canonDataSimple, hdrs, verifySig, body, ignoreTestMode) - return status, record, err + return status, record, authentic, err } // verify a DKIM signature given the record from dns and signature from the email message. diff --git a/dmarc/dmarc.go b/dmarc/dmarc.go index 1630cda..0af56cd 100644 --- a/dmarc/dmarc.go +++ b/dmarc/dmarc.go @@ -81,6 +81,8 @@ type Result struct { Domain dns.Domain // Parsed DMARC record. Record *Record + // Whether DMARC DNS response was DNSSEC-signed, regardless of whether SPF/DKIM records were DNSSEC-signed. + RecordAuthentic bool // Details about possible error condition, e.g. when parsing the DMARC record failed. Err error } @@ -93,7 +95,9 @@ type Result struct { // domain is determined using the public suffix list. E.g. for // "sub.example.com", the organizational domain is "example.com". The returned // domain is the domain with the DMARC record. -func Lookup(ctx context.Context, resolver dns.Resolver, from dns.Domain) (status Status, domain dns.Domain, record *Record, txt string, rerr error) { +// +// rauthentic indicates if the DNS results were DNSSEC-verified. +func Lookup(ctx context.Context, resolver dns.Resolver, from dns.Domain) (status Status, domain dns.Domain, record *Record, txt string, rauthentic bool, rerr error) { log := xlog.WithContext(ctx) start := time.Now() defer func() { @@ -102,27 +106,29 @@ func Lookup(ctx context.Context, resolver dns.Resolver, from dns.Domain) (status // ../rfc/7489:859 ../rfc/7489:1370 domain = from - status, record, txt, err := lookupRecord(ctx, resolver, domain) + status, record, txt, authentic, err := lookupRecord(ctx, resolver, domain) if status != StatusNone { - return status, domain, record, txt, err + return status, domain, record, txt, authentic, err } if record == nil { // ../rfc/7489:761 ../rfc/7489:1377 domain = publicsuffix.Lookup(ctx, from) if domain == from { - return StatusNone, domain, nil, txt, err + return StatusNone, domain, nil, txt, authentic, err } - status, record, txt, err = lookupRecord(ctx, resolver, domain) + var xauth bool + status, record, txt, xauth, err = lookupRecord(ctx, resolver, domain) + authentic = authentic && xauth } - return status, domain, record, txt, err + return status, domain, record, txt, authentic, err } -func lookupRecord(ctx context.Context, resolver dns.Resolver, domain dns.Domain) (Status, *Record, string, error) { +func lookupRecord(ctx context.Context, resolver dns.Resolver, domain dns.Domain) (Status, *Record, string, bool, error) { name := "_dmarc." + domain.ASCII + "." - txts, err := dns.WithPackage(resolver, "dmarc").LookupTXT(ctx, name) + txts, result, err := dns.WithPackage(resolver, "dmarc").LookupTXT(ctx, name) if err != nil && !dns.IsNotFound(err) { - return StatusTemperror, nil, "", fmt.Errorf("%w: %s", ErrDNS, err) + return StatusTemperror, nil, "", result.Authentic, fmt.Errorf("%w: %s", ErrDNS, err) } var record *Record var text string @@ -133,24 +139,24 @@ func lookupRecord(ctx context.Context, resolver dns.Resolver, domain dns.Domain) // ../rfc/7489:1374 continue } else if err != nil { - return StatusPermerror, nil, text, fmt.Errorf("%w: %s", ErrSyntax, err) + return StatusPermerror, nil, text, result.Authentic, fmt.Errorf("%w: %s", ErrSyntax, err) } if record != nil { // ../ ../rfc/7489:1388 - return StatusNone, nil, "", ErrMultipleRecords + return StatusNone, nil, "", result.Authentic, ErrMultipleRecords } text = txt record = r rerr = nil } - return StatusNone, record, text, rerr + return StatusNone, record, text, result.Authentic, rerr } -func lookupReportsRecord(ctx context.Context, resolver dns.Resolver, dmarcDomain, extDestDomain dns.Domain) (Status, *Record, string, error) { +func lookupReportsRecord(ctx context.Context, resolver dns.Resolver, dmarcDomain, extDestDomain dns.Domain) (Status, *Record, string, bool, error) { name := dmarcDomain.ASCII + "._report._dmarc." + extDestDomain.ASCII + "." - txts, err := dns.WithPackage(resolver, "dmarc").LookupTXT(ctx, name) + txts, result, err := dns.WithPackage(resolver, "dmarc").LookupTXT(ctx, name) if err != nil && !dns.IsNotFound(err) { - return StatusTemperror, nil, "", fmt.Errorf("%w: %s", ErrDNS, err) + return StatusTemperror, nil, "", result.Authentic, fmt.Errorf("%w: %s", ErrDNS, err) } var record *Record var text string @@ -168,17 +174,17 @@ func lookupReportsRecord(ctx context.Context, resolver dns.Resolver, dmarcDomain // ../rfc/7489:1374 continue } else if err != nil { - return StatusPermerror, nil, text, fmt.Errorf("%w: %s", ErrSyntax, err) + return StatusPermerror, nil, text, result.Authentic, fmt.Errorf("%w: %s", ErrSyntax, err) } if record != nil { // ../ ../rfc/7489:1388 - return StatusNone, nil, "", ErrMultipleRecords + return StatusNone, nil, "", result.Authentic, ErrMultipleRecords } text = txt record = r rerr = nil } - return StatusNone, record, text, rerr + return StatusNone, record, text, result.Authentic, rerr } // LookupExternalReportsAccepted returns whether the extDestDomain has opted in @@ -191,16 +197,18 @@ func lookupReportsRecord(ctx context.Context, resolver dns.Resolver, dmarcDomain // // The normally invalid "v=DMARC1" record is accepted since it is used as // example in RFC 7489. -func LookupExternalReportsAccepted(ctx context.Context, resolver dns.Resolver, dmarcDomain dns.Domain, extDestDomain dns.Domain) (accepts bool, status Status, record *Record, txt string, rerr error) { +// +// authentic indicates if the DNS results were DNSSEC-verified. +func LookupExternalReportsAccepted(ctx context.Context, resolver dns.Resolver, dmarcDomain dns.Domain, extDestDomain dns.Domain) (accepts bool, status Status, record *Record, txt string, authentic bool, rerr error) { log := xlog.WithContext(ctx) start := time.Now() defer func() { log.Debugx("dmarc externalreports result", rerr, mlog.Field("accepts", accepts), mlog.Field("dmarcdomain", dmarcDomain), mlog.Field("extdestdomain", extDestDomain), mlog.Field("record", record), mlog.Field("duration", time.Since(start))) }() - status, record, txt, rerr = lookupReportsRecord(ctx, resolver, dmarcDomain, extDestDomain) + status, record, txt, authentic, rerr = lookupReportsRecord(ctx, resolver, dmarcDomain, extDestDomain) accepts = rerr == nil - return accepts, status, record, txt, rerr + return accepts, status, record, txt, authentic, rerr } // Verify evaluates the DMARC policy for the domain in the From-header of a @@ -231,12 +239,13 @@ func Verify(ctx context.Context, resolver dns.Resolver, from dns.Domain, dkimRes log.Debugx("dmarc verify result", result.Err, mlog.Field("fromdomain", from), mlog.Field("dkimresults", dkimResults), mlog.Field("spfresult", spfResult), mlog.Field("status", result.Status), mlog.Field("reject", result.Reject), mlog.Field("use", useResult), mlog.Field("duration", time.Since(start))) }() - status, recordDomain, record, _, err := Lookup(ctx, resolver, from) + status, recordDomain, record, _, authentic, err := Lookup(ctx, resolver, from) if record == nil { - return false, Result{false, status, recordDomain, record, err} + return false, Result{false, status, recordDomain, record, authentic, err} } result.Domain = recordDomain result.Record = record + result.RecordAuthentic = authentic // Record can request sampling of messages to apply policy. // See ../rfc/7489:1432 diff --git a/dmarc/dmarc_test.go b/dmarc/dmarc_test.go index a377276..623d484 100644 --- a/dmarc/dmarc_test.go +++ b/dmarc/dmarc_test.go @@ -29,7 +29,7 @@ func TestLookup(t *testing.T) { test := func(d string, expStatus Status, expDomain string, expRecord *Record, expErr error) { t.Helper() - status, dom, record, _, err := Lookup(context.Background(), resolver, dns.Domain{ASCII: d}) + status, dom, record, _, _, err := Lookup(context.Background(), resolver, dns.Domain{ASCII: d}) if (err == nil) != (expErr == nil) || err != nil && !errors.Is(err, expErr) { t.Fatalf("got err %#v, expected %#v", err, expErr) } @@ -68,7 +68,7 @@ func TestLookupExternalReportsAccepted(t *testing.T) { test := func(dom, extdom string, expStatus Status, expAccepts bool, expErr error) { t.Helper() - accepts, status, _, _, err := LookupExternalReportsAccepted(context.Background(), resolver, dns.Domain{ASCII: dom}, dns.Domain{ASCII: extdom}) + accepts, status, _, _, _, err := LookupExternalReportsAccepted(context.Background(), resolver, dns.Domain{ASCII: dom}, dns.Domain{ASCII: extdom}) if (err == nil) != (expErr == nil) || err != nil && !errors.Is(err, expErr) { t.Fatalf("got err %#v, expected %#v", err, expErr) } @@ -137,7 +137,7 @@ func TestVerify(t *testing.T) { []dkim.Result{}, spf.StatusNone, nil, - true, Result{true, StatusFail, dns.Domain{ASCII: "reject.example"}, &reject, nil}, + true, Result{true, StatusFail, dns.Domain{ASCII: "reject.example"}, &reject, false, nil}, ) // Accept with spf pass. @@ -145,7 +145,7 @@ func TestVerify(t *testing.T) { []dkim.Result{}, spf.StatusPass, &dns.Domain{ASCII: "sub.reject.example"}, - true, Result{false, StatusPass, dns.Domain{ASCII: "reject.example"}, &reject, nil}, + true, Result{false, StatusPass, dns.Domain{ASCII: "reject.example"}, &reject, false, nil}, ) // Accept with dkim pass. @@ -161,7 +161,7 @@ func TestVerify(t *testing.T) { }, spf.StatusFail, &dns.Domain{ASCII: "reject.example"}, - true, Result{false, StatusPass, dns.Domain{ASCII: "reject.example"}, &reject, nil}, + true, Result{false, StatusPass, dns.Domain{ASCII: "reject.example"}, &reject, false, nil}, ) // Reject due to spf and dkim "strict". @@ -181,7 +181,7 @@ func TestVerify(t *testing.T) { }, spf.StatusPass, &dns.Domain{ASCII: "sub.strict.example"}, - true, Result{true, StatusFail, dns.Domain{ASCII: "strict.example"}, &strict, nil}, + true, Result{true, StatusFail, dns.Domain{ASCII: "strict.example"}, &strict, false, nil}, ) // No dmarc policy, nothing to say. @@ -189,7 +189,7 @@ func TestVerify(t *testing.T) { []dkim.Result{}, spf.StatusNone, nil, - false, Result{false, StatusNone, dns.Domain{ASCII: "absent.example"}, nil, ErrNoRecord}, + false, Result{false, StatusNone, dns.Domain{ASCII: "absent.example"}, nil, false, ErrNoRecord}, ) // No dmarc policy, spf pass does nothing. @@ -197,7 +197,7 @@ func TestVerify(t *testing.T) { []dkim.Result{}, spf.StatusPass, &dns.Domain{ASCII: "absent.example"}, - false, Result{false, StatusNone, dns.Domain{ASCII: "absent.example"}, nil, ErrNoRecord}, + false, Result{false, StatusNone, dns.Domain{ASCII: "absent.example"}, nil, false, ErrNoRecord}, ) none := DefaultRecord @@ -207,7 +207,7 @@ func TestVerify(t *testing.T) { []dkim.Result{}, spf.StatusPass, &dns.Domain{ASCII: "none.example"}, - true, Result{false, StatusPass, dns.Domain{ASCII: "none.example"}, &none, nil}, + true, Result{false, StatusPass, dns.Domain{ASCII: "none.example"}, &none, false, nil}, ) // No actual reject due to pct=0. @@ -218,7 +218,7 @@ func TestVerify(t *testing.T) { []dkim.Result{}, spf.StatusNone, nil, - false, Result{true, StatusFail, dns.Domain{ASCII: "test.example"}, &testr, nil}, + false, Result{true, StatusFail, dns.Domain{ASCII: "test.example"}, &testr, false, nil}, ) // No reject if subdomain has "none" policy. @@ -229,7 +229,7 @@ func TestVerify(t *testing.T) { []dkim.Result{}, spf.StatusFail, &dns.Domain{ASCII: "sub.subnone.example"}, - true, Result{false, StatusFail, dns.Domain{ASCII: "subnone.example"}, &sub, nil}, + true, Result{false, StatusFail, dns.Domain{ASCII: "subnone.example"}, &sub, false, nil}, ) // No reject if spf temperror and no other pass. @@ -237,7 +237,7 @@ func TestVerify(t *testing.T) { []dkim.Result{}, spf.StatusTemperror, &dns.Domain{ASCII: "mail.reject.example"}, - true, Result{false, StatusTemperror, dns.Domain{ASCII: "reject.example"}, &reject, nil}, + true, Result{false, StatusTemperror, dns.Domain{ASCII: "reject.example"}, &reject, false, nil}, ) // No reject if dkim temperror and no other pass. @@ -253,7 +253,7 @@ func TestVerify(t *testing.T) { }, spf.StatusNone, nil, - true, Result{false, StatusTemperror, dns.Domain{ASCII: "reject.example"}, &reject, nil}, + true, Result{false, StatusTemperror, dns.Domain{ASCII: "reject.example"}, &reject, false, nil}, ) // No reject if spf temperror but still dkim pass. @@ -269,7 +269,7 @@ func TestVerify(t *testing.T) { }, spf.StatusTemperror, &dns.Domain{ASCII: "mail.reject.example"}, - true, Result{false, StatusPass, dns.Domain{ASCII: "reject.example"}, &reject, nil}, + true, Result{false, StatusPass, dns.Domain{ASCII: "reject.example"}, &reject, false, nil}, ) // No reject if dkim temperror but still spf pass. @@ -285,7 +285,7 @@ func TestVerify(t *testing.T) { }, spf.StatusPass, &dns.Domain{ASCII: "mail.reject.example"}, - true, Result{false, StatusPass, dns.Domain{ASCII: "reject.example"}, &reject, nil}, + true, Result{false, StatusPass, dns.Domain{ASCII: "reject.example"}, &reject, false, nil}, ) // Bad DMARC record results in permerror without reject. @@ -293,7 +293,7 @@ func TestVerify(t *testing.T) { []dkim.Result{}, spf.StatusNone, nil, - false, Result{false, StatusPermerror, dns.Domain{ASCII: "malformed.example"}, nil, ErrSyntax}, + false, Result{false, StatusPermerror, dns.Domain{ASCII: "malformed.example"}, nil, false, ErrSyntax}, ) // DKIM domain that is higher-level than organizational can not result in a pass. ../rfc/7489:525 @@ -309,6 +309,6 @@ func TestVerify(t *testing.T) { }, spf.StatusNone, nil, - true, Result{true, StatusFail, dns.Domain{ASCII: "example.com"}, &reject, nil}, + true, Result{true, StatusFail, dns.Domain{ASCII: "example.com"}, &reject, false, nil}, ) } diff --git a/dns/dns.go b/dns/dns.go index 1d41bf1..3e1fdb6 100644 --- a/dns/dns.go +++ b/dns/dns.go @@ -5,10 +5,11 @@ package dns import ( "errors" "fmt" - "net" "strings" "golang.org/x/net/idna" + + "github.com/mjl-/adns" ) var errTrailingDot = errors.New("dns name has trailing dot") @@ -100,16 +101,16 @@ func ParseDomain(s string) (Domain, error) { return Domain{ascii, unicode}, nil } -// IsNotFound returns whether an error is a net.DNSError with IsNotFound set. +// IsNotFound returns whether an error is an adns.DNSError with IsNotFound set. // IsNotFound means the requested type does not exist for the given domain (a -// nodata or nxdomain response). It doesn't not necessarily mean no other types -// for that name exist. +// nodata or nxdomain response). It doesn't not necessarily mean no other types for +// that name exist. // // A DNS server can respond to a lookup with an error "nxdomain" to indicate a // name does not exist (at all), or with a success status with an empty list. // The Go resolver returns an IsNotFound error for both cases, there is no need // to explicitly check for zero entries. func IsNotFound(err error) bool { - var dnsErr *net.DNSError + var dnsErr *adns.DNSError return err != nil && errors.As(err, &dnsErr) && dnsErr.IsNotFound } diff --git a/dns/mock.go b/dns/mock.go index b0d420c..022a2c9 100644 --- a/dns/mock.go +++ b/dns/mock.go @@ -4,139 +4,185 @@ import ( "context" "fmt" "net" + + "golang.org/x/exp/slices" + + "github.com/mjl-/adns" ) // MockResolver is a Resolver used for testing. // Set DNS records in the fields, which map FQDNs (with trailing dot) to values. type MockResolver struct { - PTR map[string][]string - A map[string][]string - AAAA map[string][]string - TXT map[string][]string - MX map[string][]*net.MX - CNAME map[string]string - Fail map[Mockreq]struct{} + PTR map[string][]string + A map[string][]string + AAAA map[string][]string + TXT map[string][]string + MX map[string][]*net.MX + TLSA map[string][]adns.TLSA // Keys are e.g. _25._tcp.. + CNAME map[string]string + Fail map[Mockreq]struct{} + AllAuthentic bool // Default value for authentic in responses. Overridden with Authentic and Inauthentic + Authentic []string // Records of the form "type name", e.g. "cname localhost." + Inauthentic []string } type Mockreq struct { Type string // E.g. "cname", "txt", "mx", "ptr", etc. - Name string + Name string // Name of request. For TLSA, the full requested DNS name, e.g. _25._tcp.. } var _ Resolver = MockResolver{} -func (r MockResolver) nxdomain(s string) *net.DNSError { - return &net.DNSError{ +func (r MockResolver) result(ctx context.Context, mr Mockreq) (string, adns.Result, error) { + result := adns.Result{Authentic: r.AllAuthentic} + + if err := ctx.Err(); err != nil { + return "", result, err + } + + updateAuthentic := func(mock string) { + if slices.Contains(r.Authentic, mock) { + result.Authentic = true + } + if slices.Contains(r.Inauthentic, mock) { + result.Authentic = false + } + } + + for { + if _, ok := r.Fail[mr]; ok { + updateAuthentic(mr.Type + " " + mr.Name) + return mr.Name, adns.Result{}, r.servfail(mr.Name) + } + + cname, ok := r.CNAME[mr.Name] + if !ok { + updateAuthentic(mr.Type + " " + mr.Name) + break + } + updateAuthentic("cname " + mr.Name) + if mr.Type == "cname" { + return mr.Name, result, nil + } + mr.Name = cname + } + return mr.Name, result, nil +} + +func (r MockResolver) nxdomain(s string) error { + return &adns.DNSError{ Err: "no record", Name: s, - Server: "localhost", + Server: "mock", IsNotFound: true, } } -func (r MockResolver) servfail(s string) *net.DNSError { - return &net.DNSError{ +func (r MockResolver) servfail(s string) error { + return &adns.DNSError{ Err: "temp error", Name: s, - Server: "localhost", + Server: "mock", IsTemporary: true, } } -func (r MockResolver) LookupCNAME(ctx context.Context, name string) (string, error) { - if err := ctx.Err(); err != nil { - return "", err - } - if _, ok := r.Fail[Mockreq{"cname", name}]; ok { - return "", r.servfail(name) - } - if cname, ok := r.CNAME[name]; ok { - return cname, nil - } - return "", r.nxdomain(name) -} - -func (r MockResolver) LookupAddr(ctx context.Context, ip string) ([]string, error) { - if err := ctx.Err(); err != nil { - return nil, err - } - if _, ok := r.Fail[Mockreq{"ptr", ip}]; ok { - return nil, r.servfail(ip) - } - l, ok := r.PTR[ip] - if !ok { - return nil, r.nxdomain(ip) - } - return l, nil -} - -func (r MockResolver) LookupNS(ctx context.Context, name string) ([]*net.NS, error) { - if err := ctx.Err(); err != nil { - return nil, err - } - return nil, r.servfail("ns not implemented") -} - func (r MockResolver) LookupPort(ctx context.Context, network, service string) (port int, err error) { if err := ctx.Err(); err != nil { return 0, err } - return 0, r.servfail("port not implemented") + return net.LookupPort(network, service) } -func (r MockResolver) LookupSRV(ctx context.Context, service, proto, name string) (string, []*net.SRV, error) { - if err := ctx.Err(); err != nil { - return "", nil, err - } - return "", nil, r.servfail("srv not implemented") -} - -func (r MockResolver) LookupIPAddr(ctx context.Context, host string) ([]net.IPAddr, error) { - if err := ctx.Err(); err != nil { - return nil, err - } - if _, ok := r.Fail[Mockreq{"ipaddr", host}]; ok { - return nil, r.servfail(host) - } - addrs, err := r.LookupHost(ctx, host) +func (r MockResolver) LookupCNAME(ctx context.Context, name string) (string, adns.Result, error) { + mr := Mockreq{"cname", name} + name, result, err := r.result(ctx, mr) if err != nil { - return nil, err + return name, result, err + } + cname, ok := r.CNAME[name] + if !ok { + return cname, result, r.nxdomain(name) + } + return cname, result, nil +} + +func (r MockResolver) LookupAddr(ctx context.Context, ip string) ([]string, adns.Result, error) { + mr := Mockreq{"ptr", ip} + _, result, err := r.result(ctx, mr) + if err != nil { + return nil, result, err + } + l, ok := r.PTR[ip] + if !ok { + return nil, result, r.nxdomain(ip) + } + return l, result, nil +} + +func (r MockResolver) LookupNS(ctx context.Context, name string) ([]*net.NS, adns.Result, error) { + mr := Mockreq{"ns", name} + _, result, err := r.result(ctx, mr) + if err != nil { + return nil, result, err + } + return nil, result, r.servfail("ns not implemented") +} + +func (r MockResolver) LookupSRV(ctx context.Context, service, proto, name string) (string, []*net.SRV, adns.Result, error) { + xname := fmt.Sprintf("_%s._%s.%s", service, proto, name) + mr := Mockreq{"srv", xname} + name, result, err := r.result(ctx, mr) + if err != nil { + return name, nil, result, err + } + return name, nil, result, r.servfail("srv not implemented") +} + +func (r MockResolver) LookupIPAddr(ctx context.Context, host string) ([]net.IPAddr, adns.Result, error) { + // todo: make closer to resolver, doing a & aaaa lookups, including their error/(in)secure status. + mr := Mockreq{"ipaddr", host} + _, result, err := r.result(ctx, mr) + if err != nil { + return nil, result, err + } + addrs, result1, err := r.LookupHost(ctx, host) + result.Authentic = result.Authentic && result1.Authentic + if err != nil { + return nil, result, err } ips := make([]net.IPAddr, len(addrs)) for i, a := range addrs { ip := net.ParseIP(a) if ip == nil { - return nil, fmt.Errorf("malformed ip %q", a) + return nil, result, fmt.Errorf("malformed ip %q", a) } ips[i] = net.IPAddr{IP: ip} } - return ips, nil + return ips, result, nil } -func (r MockResolver) LookupHost(ctx context.Context, host string) (addrs []string, err error) { - if err := ctx.Err(); err != nil { - return nil, err - } - if _, ok := r.Fail[Mockreq{"host", host}]; ok { - return nil, r.servfail(host) +func (r MockResolver) LookupHost(ctx context.Context, host string) ([]string, adns.Result, error) { + // todo: make closer to resolver, doing a & aaaa lookups, including their error/(in)secure status. + mr := Mockreq{"host", host} + _, result, err := r.result(ctx, mr) + if err != nil { + return nil, result, err } + var addrs []string addrs = append(addrs, r.A[host]...) addrs = append(addrs, r.AAAA[host]...) - if len(addrs) > 0 { - return addrs, nil + if len(addrs) == 0 { + return nil, result, r.nxdomain(host) } - if cname, ok := r.CNAME[host]; ok { - return []string{cname}, nil - } - return nil, r.nxdomain(host) + return addrs, result, nil } -func (r MockResolver) LookupIP(ctx context.Context, network, host string) ([]net.IP, error) { - if err := ctx.Err(); err != nil { - return nil, err - } - if _, ok := r.Fail[Mockreq{"ip", host}]; ok { - return nil, r.servfail(host) +func (r MockResolver) LookupIP(ctx context.Context, network, host string) ([]net.IP, adns.Result, error) { + mr := Mockreq{"ip", host} + _, result, err := r.result(ctx, mr) + if err != nil { + return nil, result, err } var ips []net.IP switch network { @@ -152,35 +198,52 @@ func (r MockResolver) LookupIP(ctx context.Context, network, host string) ([]net } } if len(ips) == 0 { - return nil, r.nxdomain(host) + return nil, result, r.nxdomain(host) } - return ips, nil + return ips, result, nil } -func (r MockResolver) LookupMX(ctx context.Context, name string) ([]*net.MX, error) { - if err := ctx.Err(); err != nil { - return nil, err - } - if _, ok := r.Fail[Mockreq{"mx", name}]; ok { - return nil, r.servfail(name) +func (r MockResolver) LookupMX(ctx context.Context, name string) ([]*net.MX, adns.Result, error) { + mr := Mockreq{"mx", name} + _, result, err := r.result(ctx, mr) + if err != nil { + return nil, result, err } l, ok := r.MX[name] if !ok { - return nil, r.nxdomain(name) + return nil, result, r.nxdomain(name) } - return l, nil + return l, result, nil } -func (r MockResolver) LookupTXT(ctx context.Context, name string) ([]string, error) { - if err := ctx.Err(); err != nil { - return nil, err - } - if _, ok := r.Fail[Mockreq{"txt", name}]; ok { - return nil, r.servfail(name) +func (r MockResolver) LookupTXT(ctx context.Context, name string) ([]string, adns.Result, error) { + mr := Mockreq{"txt", name} + _, result, err := r.result(ctx, mr) + if err != nil { + return nil, result, err } l, ok := r.TXT[name] if !ok { - return nil, r.nxdomain(name) + return nil, result, r.nxdomain(name) } - return l, nil + return l, result, nil +} + +func (r MockResolver) LookupTLSA(ctx context.Context, port int, protocol string, host string) ([]adns.TLSA, adns.Result, error) { + var name string + if port == 0 && protocol == "" { + name = host + } else { + name = fmt.Sprintf("_%d._%s.%s", port, protocol, host) + } + mr := Mockreq{"tlsa", name} + _, result, err := r.result(ctx, mr) + if err != nil { + return nil, result, err + } + l, ok := r.TLSA[name] + if !ok { + return nil, result, r.nxdomain(name) + } + return l, result, nil } diff --git a/dns/resolver.go b/dns/resolver.go index 4bcdb54..12ae375 100644 --- a/dns/resolver.go +++ b/dns/resolver.go @@ -13,6 +13,8 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" + "github.com/mjl-/adns" + "github.com/mjl-/mox/mlog" ) @@ -22,6 +24,10 @@ import ( var xlog = mlog.New("dns") +func init() { + net.DefaultResolver.StrictErrors = true +} + var ( metricLookup = promauto.NewHistogramVec( prometheus.HistogramOpts{ @@ -39,16 +45,17 @@ var ( // Resolver is the interface strict resolver implements. type Resolver interface { - LookupAddr(ctx context.Context, addr string) ([]string, error) // Always returns absolute names, with trailing dot. - LookupCNAME(ctx context.Context, host string) (string, error) // NOTE: returns an error if no CNAME record is present. - LookupHost(ctx context.Context, host string) (addrs []string, err error) - LookupIP(ctx context.Context, network, host string) ([]net.IP, error) - LookupIPAddr(ctx context.Context, host string) ([]net.IPAddr, error) - LookupMX(ctx context.Context, name string) ([]*net.MX, error) - LookupNS(ctx context.Context, name string) ([]*net.NS, error) LookupPort(ctx context.Context, network, service string) (port int, err error) - LookupSRV(ctx context.Context, service, proto, name string) (string, []*net.SRV, error) - LookupTXT(ctx context.Context, name string) ([]string, error) + LookupAddr(ctx context.Context, addr string) ([]string, adns.Result, error) // Always returns absolute names, with trailing dot. + LookupCNAME(ctx context.Context, host string) (string, adns.Result, error) // NOTE: returns an error if no CNAME record is present. + LookupHost(ctx context.Context, host string) ([]string, adns.Result, error) + LookupIP(ctx context.Context, network, host string) ([]net.IP, adns.Result, error) + LookupIPAddr(ctx context.Context, host string) ([]net.IPAddr, adns.Result, error) + LookupMX(ctx context.Context, name string) ([]*net.MX, adns.Result, error) + LookupNS(ctx context.Context, name string) ([]*net.NS, adns.Result, error) + LookupSRV(ctx context.Context, service, proto, name string) (string, []*net.SRV, adns.Result, error) + LookupTXT(ctx context.Context, name string) ([]string, adns.Result, error) + LookupTLSA(ctx context.Context, port int, protocol, host string) ([]adns.TLSA, adns.Result, error) } // WithPackage sets Pkg on resolver if it is a StrictResolve and does not have a package set yet. @@ -65,8 +72,8 @@ func WithPackage(resolver Resolver, name string) Resolver { // StrictResolver is a net.Resolver that enforces that DNS names end with a dot, // preventing "search"-relative lookups. type StrictResolver struct { - Pkg string // Name of subsystem that is making DNS requests, for metrics. - Resolver *net.Resolver // Where the actual lookups are done. If nil, net.DefaultResolver is used for lookups. + Pkg string // Name of subsystem that is making DNS requests, for metrics. + Resolver *adns.Resolver // Where the actual lookups are done. If nil, adns.DefaultResolver is used for lookups. } var _ Resolver = StrictResolver{} @@ -75,7 +82,7 @@ var ErrRelativeDNSName = errors.New("dns: host to lookup must be absolute, endin func metricLookupObserve(pkg, typ string, err error, start time.Time) { var result string - var dnsErr *net.DNSError + var dnsErr *adns.DNSError switch { case err == nil: result = "ok" @@ -101,7 +108,7 @@ func (r StrictResolver) WithPackage(name string) Resolver { func (r StrictResolver) resolver() Resolver { if r.Resolver == nil { - return net.DefaultResolver + return adns.DefaultResolver } return r.Resolver } @@ -111,26 +118,52 @@ func resolveErrorHint(err *error) { if e == nil { return } - dnserr, ok := e.(*net.DNSError) + dnserr, ok := e.(*adns.DNSError) if !ok { return } // If the dns server is not running, and it is one of the default/fallback IPs, // hint at where to look. if dnserr.IsTemporary && runtime.GOOS == "linux" && (dnserr.Server == "127.0.0.1:53" || dnserr.Server == "[::1]:53") && strings.HasSuffix(dnserr.Err, "connection refused") { - *err = fmt.Errorf("%w (hint: does /etc/resolv.conf point to a running nameserver? in case of systemd-resolved, see systemd-resolved.service(8))", *err) + *err = fmt.Errorf("%w (hint: does /etc/resolv.conf point to a running nameserver? in case of systemd-resolved, see systemd-resolved.service(8); better yet, install a proper dnssec-verifying recursive resolver like unbound)", *err) } } -func (r StrictResolver) LookupAddr(ctx context.Context, addr string) (resp []string, err error) { +func (r StrictResolver) LookupPort(ctx context.Context, network, service string) (resp int, err error) { start := time.Now() defer func() { - metricLookupObserve(r.Pkg, "addr", err, start) - xlog.WithContext(ctx).Debugx("dns lookup result", err, mlog.Field("pkg", r.Pkg), mlog.Field("type", "addr"), mlog.Field("addr", addr), mlog.Field("resp", resp), mlog.Field("duration", time.Since(start))) + metricLookupObserve(r.Pkg, "port", err, start) + xlog.WithContext(ctx).Debugx("dns lookup result", err, + mlog.Field("pkg", r.Pkg), + mlog.Field("type", "port"), + mlog.Field("network", network), + mlog.Field("service", service), + mlog.Field("resp", resp), + mlog.Field("duration", time.Since(start)), + ) }() defer resolveErrorHint(&err) - resp, err = r.resolver().LookupAddr(ctx, addr) + resp, err = r.resolver().LookupPort(ctx, network, service) + return +} + +func (r StrictResolver) LookupAddr(ctx context.Context, addr string) (resp []string, result adns.Result, err error) { + start := time.Now() + defer func() { + metricLookupObserve(r.Pkg, "addr", err, start) + xlog.WithContext(ctx).Debugx("dns lookup result", err, + mlog.Field("pkg", r.Pkg), + mlog.Field("type", "addr"), + mlog.Field("addr", addr), + mlog.Field("resp", resp), + mlog.Field("authentic", result.Authentic), + mlog.Field("duration", time.Since(start)), + ) + }() + defer resolveErrorHint(&err) + + resp, result, err = r.resolver().LookupAddr(ctx, addr) // For addresses from /etc/hosts without dot, we add the missing trailing dot. for i, s := range resp { if !strings.HasSuffix(s, ".") { @@ -142,20 +175,27 @@ func (r StrictResolver) LookupAddr(ctx context.Context, addr string) (resp []str // LookupCNAME looks up a CNAME. Unlike "net" LookupCNAME, it returns a "not found" // error if there is no CNAME record. -func (r StrictResolver) LookupCNAME(ctx context.Context, host string) (resp string, err error) { +func (r StrictResolver) LookupCNAME(ctx context.Context, host string) (resp string, result adns.Result, err error) { start := time.Now() defer func() { metricLookupObserve(r.Pkg, "cname", err, start) - xlog.WithContext(ctx).Debugx("dns lookup result", err, mlog.Field("pkg", r.Pkg), mlog.Field("type", "cname"), mlog.Field("host", host), mlog.Field("resp", resp), mlog.Field("duration", time.Since(start))) + xlog.WithContext(ctx).Debugx("dns lookup result", err, + mlog.Field("pkg", r.Pkg), + mlog.Field("type", "cname"), + mlog.Field("host", host), + mlog.Field("resp", resp), + mlog.Field("authentic", result.Authentic), + mlog.Field("duration", time.Since(start)), + ) }() defer resolveErrorHint(&err) if !strings.HasSuffix(host, ".") { - return "", ErrRelativeDNSName + return "", result, ErrRelativeDNSName } - resp, err = r.resolver().LookupCNAME(ctx, host) + resp, result, err = r.resolver().LookupCNAME(ctx, host) if err == nil && resp == host { - return "", &net.DNSError{ + return "", result, &adns.DNSError{ Err: "no cname record", Name: host, Server: "", @@ -164,119 +204,185 @@ func (r StrictResolver) LookupCNAME(ctx context.Context, host string) (resp stri } return } -func (r StrictResolver) LookupHost(ctx context.Context, host string) (resp []string, err error) { + +func (r StrictResolver) LookupHost(ctx context.Context, host string) (resp []string, result adns.Result, err error) { start := time.Now() defer func() { metricLookupObserve(r.Pkg, "host", err, start) - xlog.WithContext(ctx).Debugx("dns lookup result", err, mlog.Field("pkg", r.Pkg), mlog.Field("type", "host"), mlog.Field("host", host), mlog.Field("resp", resp), mlog.Field("duration", time.Since(start))) + xlog.WithContext(ctx).Debugx("dns lookup result", err, + mlog.Field("pkg", r.Pkg), + mlog.Field("type", "host"), + mlog.Field("host", host), + mlog.Field("resp", resp), + mlog.Field("authentic", result.Authentic), + mlog.Field("duration", time.Since(start)), + ) }() defer resolveErrorHint(&err) if !strings.HasSuffix(host, ".") { - return nil, ErrRelativeDNSName + return nil, result, ErrRelativeDNSName } - resp, err = r.resolver().LookupHost(ctx, host) + resp, result, err = r.resolver().LookupHost(ctx, host) return } -func (r StrictResolver) LookupIP(ctx context.Context, network, host string) (resp []net.IP, err error) { +func (r StrictResolver) LookupIP(ctx context.Context, network, host string) (resp []net.IP, result adns.Result, err error) { start := time.Now() defer func() { metricLookupObserve(r.Pkg, "ip", err, start) - xlog.WithContext(ctx).Debugx("dns lookup result", err, mlog.Field("pkg", r.Pkg), mlog.Field("type", "ip"), mlog.Field("network", network), mlog.Field("host", host), mlog.Field("resp", resp), mlog.Field("duration", time.Since(start))) + xlog.WithContext(ctx).Debugx("dns lookup result", err, + mlog.Field("pkg", r.Pkg), + mlog.Field("type", "ip"), + mlog.Field("network", network), + mlog.Field("host", host), + mlog.Field("resp", resp), + mlog.Field("authentic", result.Authentic), + mlog.Field("duration", time.Since(start)), + ) }() defer resolveErrorHint(&err) if !strings.HasSuffix(host, ".") { - return nil, ErrRelativeDNSName + return nil, result, ErrRelativeDNSName } - resp, err = r.resolver().LookupIP(ctx, network, host) + resp, result, err = r.resolver().LookupIP(ctx, network, host) return } -func (r StrictResolver) LookupIPAddr(ctx context.Context, host string) (resp []net.IPAddr, err error) { +func (r StrictResolver) LookupIPAddr(ctx context.Context, host string) (resp []net.IPAddr, result adns.Result, err error) { start := time.Now() defer func() { metricLookupObserve(r.Pkg, "ipaddr", err, start) - xlog.WithContext(ctx).Debugx("dns lookup result", err, mlog.Field("pkg", r.Pkg), mlog.Field("type", "ipaddr"), mlog.Field("host", host), mlog.Field("resp", resp), mlog.Field("duration", time.Since(start))) + xlog.WithContext(ctx).Debugx("dns lookup result", err, + mlog.Field("pkg", r.Pkg), + mlog.Field("type", "ipaddr"), + mlog.Field("host", host), + mlog.Field("resp", resp), + mlog.Field("authentic", result.Authentic), + mlog.Field("duration", time.Since(start)), + ) }() defer resolveErrorHint(&err) if !strings.HasSuffix(host, ".") { - return nil, ErrRelativeDNSName + return nil, result, ErrRelativeDNSName } - resp, err = r.resolver().LookupIPAddr(ctx, host) + resp, result, err = r.resolver().LookupIPAddr(ctx, host) return } -func (r StrictResolver) LookupMX(ctx context.Context, name string) (resp []*net.MX, err error) { +func (r StrictResolver) LookupMX(ctx context.Context, name string) (resp []*net.MX, result adns.Result, err error) { start := time.Now() defer func() { metricLookupObserve(r.Pkg, "mx", err, start) - xlog.WithContext(ctx).Debugx("dns lookup result", err, mlog.Field("pkg", r.Pkg), mlog.Field("type", "mx"), mlog.Field("name", name), mlog.Field("resp", resp), mlog.Field("duration", time.Since(start))) + xlog.WithContext(ctx).Debugx("dns lookup result", err, + mlog.Field("pkg", r.Pkg), + mlog.Field("type", "mx"), + mlog.Field("name", name), + mlog.Field("resp", resp), + mlog.Field("authentic", result.Authentic), + mlog.Field("duration", time.Since(start)), + ) }() defer resolveErrorHint(&err) if !strings.HasSuffix(name, ".") { - return nil, ErrRelativeDNSName + return nil, result, ErrRelativeDNSName } - resp, err = r.resolver().LookupMX(ctx, name) + resp, result, err = r.resolver().LookupMX(ctx, name) return } -func (r StrictResolver) LookupNS(ctx context.Context, name string) (resp []*net.NS, err error) { +func (r StrictResolver) LookupNS(ctx context.Context, name string) (resp []*net.NS, result adns.Result, err error) { start := time.Now() defer func() { metricLookupObserve(r.Pkg, "ns", err, start) - xlog.WithContext(ctx).Debugx("dns lookup result", err, mlog.Field("pkg", r.Pkg), mlog.Field("type", "ns"), mlog.Field("name", name), mlog.Field("resp", resp), mlog.Field("duration", time.Since(start))) + xlog.WithContext(ctx).Debugx("dns lookup result", err, + mlog.Field("pkg", r.Pkg), + mlog.Field("type", "ns"), + mlog.Field("name", name), + mlog.Field("resp", resp), + mlog.Field("authentic", result.Authentic), + mlog.Field("duration", time.Since(start)), + ) }() defer resolveErrorHint(&err) if !strings.HasSuffix(name, ".") { - return nil, ErrRelativeDNSName + return nil, result, ErrRelativeDNSName } - resp, err = r.resolver().LookupNS(ctx, name) + resp, result, err = r.resolver().LookupNS(ctx, name) return } -func (r StrictResolver) LookupPort(ctx context.Context, network, service string) (resp int, err error) { - start := time.Now() - defer func() { - metricLookupObserve(r.Pkg, "port", err, start) - xlog.WithContext(ctx).Debugx("dns lookup result", err, mlog.Field("pkg", r.Pkg), mlog.Field("type", "port"), mlog.Field("network", network), mlog.Field("service", service), mlog.Field("resp", resp), mlog.Field("duration", time.Since(start))) - }() - defer resolveErrorHint(&err) - - resp, err = r.resolver().LookupPort(ctx, network, service) - return -} - -func (r StrictResolver) LookupSRV(ctx context.Context, service, proto, name string) (resp0 string, resp1 []*net.SRV, err error) { +func (r StrictResolver) LookupSRV(ctx context.Context, service, proto, name string) (resp0 string, resp1 []*net.SRV, result adns.Result, err error) { start := time.Now() defer func() { metricLookupObserve(r.Pkg, "srv", err, start) - xlog.WithContext(ctx).Debugx("dns lookup result", err, mlog.Field("pkg", r.Pkg), mlog.Field("type", "srv"), mlog.Field("service", service), mlog.Field("proto", proto), mlog.Field("name", name), mlog.Field("resp0", resp0), mlog.Field("resp1", resp1), mlog.Field("duration", time.Since(start))) + xlog.WithContext(ctx).Debugx("dns lookup result", err, + mlog.Field("pkg", r.Pkg), + mlog.Field("type", "srv"), + mlog.Field("service", service), + mlog.Field("proto", proto), + mlog.Field("name", name), + mlog.Field("resp0", resp0), + mlog.Field("resp1", resp1), + mlog.Field("authentic", result.Authentic), + mlog.Field("duration", time.Since(start)), + ) }() defer resolveErrorHint(&err) if !strings.HasSuffix(name, ".") { - return "", nil, ErrRelativeDNSName + return "", nil, result, ErrRelativeDNSName } - resp0, resp1, err = r.resolver().LookupSRV(ctx, service, proto, name) + resp0, resp1, result, err = r.resolver().LookupSRV(ctx, service, proto, name) return } -func (r StrictResolver) LookupTXT(ctx context.Context, name string) (resp []string, err error) { +func (r StrictResolver) LookupTXT(ctx context.Context, name string) (resp []string, result adns.Result, err error) { start := time.Now() defer func() { metricLookupObserve(r.Pkg, "txt", err, start) - xlog.WithContext(ctx).Debugx("dns lookup result", err, mlog.Field("pkg", r.Pkg), mlog.Field("type", "txt"), mlog.Field("name", name), mlog.Field("resp", resp), mlog.Field("duration", time.Since(start))) + xlog.WithContext(ctx).Debugx("dns lookup result", err, + mlog.Field("pkg", r.Pkg), + mlog.Field("type", "txt"), + mlog.Field("name", name), + mlog.Field("resp", resp), + mlog.Field("authentic", result.Authentic), + mlog.Field("duration", time.Since(start)), + ) }() defer resolveErrorHint(&err) if !strings.HasSuffix(name, ".") { - return nil, ErrRelativeDNSName + return nil, result, ErrRelativeDNSName } - resp, err = r.resolver().LookupTXT(ctx, name) + resp, result, err = r.resolver().LookupTXT(ctx, name) + return +} + +func (r StrictResolver) LookupTLSA(ctx context.Context, port int, protocol, host string) (resp []adns.TLSA, result adns.Result, err error) { + start := time.Now() + defer func() { + metricLookupObserve(r.Pkg, "tlsa", err, start) + xlog.WithContext(ctx).Debugx("dns lookup result", err, + mlog.Field("pkg", r.Pkg), + mlog.Field("type", "tlsa"), + mlog.Field("port", port), + mlog.Field("protocol", protocol), + mlog.Field("host", host), + mlog.Field("resp", resp), + mlog.Field("authentic", result.Authentic), + mlog.Field("duration", time.Since(start)), + ) + }() + defer resolveErrorHint(&err) + + if !strings.HasSuffix(host, ".") { + return nil, result, ErrRelativeDNSName + } + resp, result, err = r.resolver().LookupTLSA(ctx, port, protocol, host) return } diff --git a/dnsbl/dnsbl.go b/dnsbl/dnsbl.go index 7eb708d..57dac58 100644 --- a/dnsbl/dnsbl.go +++ b/dnsbl/dnsbl.go @@ -82,14 +82,14 @@ func Lookup(ctx context.Context, resolver dns.Resolver, zone dns.Domain, ip net. addr := b.String() // ../rfc/5782:175 - _, err := dns.WithPackage(resolver, "dnsbl").LookupIP(ctx, "ip4", addr) + _, _, err := dns.WithPackage(resolver, "dnsbl").LookupIP(ctx, "ip4", addr) if dns.IsNotFound(err) { return StatusPass, "", nil } else if err != nil { return StatusTemperr, "", fmt.Errorf("%w: %s", ErrDNS, err) } - txts, err := dns.WithPackage(resolver, "dnsbl").LookupTXT(ctx, addr) + txts, _, err := dns.WithPackage(resolver, "dnsbl").LookupTXT(ctx, addr) if dns.IsNotFound(err) { return StatusFail, "", nil } else if err != nil { diff --git a/doc.go b/doc.go index 40a7647..5d79872 100644 --- a/doc.go +++ b/doc.go @@ -1,15 +1,7 @@ /* -Command mox is a modern full-featured open source secure mail server for +Command mox is a modern, secure, full-featured, open source mail server for low-maintenance self-hosted email. - - Quick and easy to set up with quickstart and automatic TLS with ACME and - Let's Encrypt. - - IMAP4 with extensions for accessing email. - - SMTP with SPF, DKIM, DMARC, DNSBL, MTA-STS, TLSRPT for exchanging email. - - Reputation-based and content-based spam filtering. - - Internationalized email. - - Admin web interface. - # Commands mox [-config config/mox.conf] [-pedantic] ... @@ -44,10 +36,15 @@ low-maintenance self-hosted email. mox config domain rm domain mox config describe-sendmail >/etc/moxsubmit.conf mox config printservice >mox.service + mox config ensureacmehostprivatekeys mox example [name] mox checkupdate mox cid cid mox clientconfig domain + mox dane dial host:port + mox dane dialmx domain [destination-host] + mox dane makerecord usage selector matchtype [certificate.pem | publickey.pem | privatekey.pem] + mox dns lookup [ptr | mx | cname | ips | a | aaaa | ns | txt | srv | tlsa] name mox dkim gened25519 >$selector._domainkey.$domain.ed25519key.pkcs8.pem mox dkim genrsa >$selector._domainkey.$domain.rsakey.pkcs8.pem mox dkim lookup selector domain @@ -541,6 +538,31 @@ date version. usage: mox config printservice >mox.service +# mox config ensureacmehostprivatekeys + +Ensure host private keys exist for TLS listeners with ACME. + +In mox.conf, each listener can have TLS configured. Long-lived private key files +can be specified, which will be used when requesting ACME certificates. +Configuring these private keys makes it feasible to publish DANE TLSA records +for the corresponding public keys in DNS, protected with DNSSEC, allowing TLS +certificate verification without depending on a list of Certificate Authorities +(CAs). Previous versions of mox did not pre-generate private keys for use with +ACME certificates, but would generate private keys on-demand. By explicitly +configuring private keys, they will not change automatedly with new +certificates, and the DNS TLSA records stay valid. + +This command looks for listeners in mox.conf with TLS with ACME configured. For +each missing host private key (of type rsa-2048 and ecdsa-p256) a key is written +to config/hostkeys/. If a certificate exists in the ACME "cache", its private +key is copied. Otherwise a new private key is generated. Snippets for manually +updating/editing mox.conf are printed. + +After running this command, and updating mox.conf, run "mox config dnsrecords" +for a domain and create the TLSA DNS records it suggests to enable DANE. + + usage: mox config ensureacmehostprivatekeys + # mox example List available examples, or print a specific example. @@ -553,7 +575,7 @@ Check if a newer version of mox is available. A single DNS TXT lookup to _updates.xmox.nl tells if a new version is available. If so, a changelog is fetched from https://updates.xmox.nl, and the -individual entries validated with a builtin public key. The changelog is +individual entries verified with a builtin public key. The changelog is printed. usage: mox checkupdate @@ -582,6 +604,80 @@ configured over otherwise secured connections, like a VPN. usage: mox clientconfig domain +# mox dane dial + +Dial the address using TLS with certificate verification using DANE. + +Data is copied between connection and stdin/stdout until either side closes the +connection. + + usage: mox dane dial host:port + -usages string + allowed usages for dane, comma-separated list (default "pkix-ta,pkix-ee,dane-ta,dane-ee") + +# mox dane dialmx + +Connect to MX server for domain using STARTTLS verified with DANE. + +If no destination host is specified, regular delivery logic is used to find the +hosts to attempt delivery too. This involves following CNAMEs for the domain, +looking up MX records, and possibly falling back to the domain name itself as +host. + +If a destination host is specified, that is the only candidate host considered +for dialing. + +With a list of destinations gathered, each is dialed until a successful SMTP +session verified with DANE has been initialized, including EHLO and STARTTLS +commands. + +Once connected, data is copied between connection and stdin/stdout, until +either side closes the connection. + +This command follows the same logic as delivery attempts made from the queue, +sharing most of its code. + + usage: mox dane dialmx domain [destination-host] + -ehlohostname string + hostname to send in smtp ehlo command (default "localhost") + +# mox dane makerecord + +Print TLSA record for given certificate/key and parameters. + +Valid values: +- usage: pkix-ta (0), pkix-ee (1), dane-ta (2), dane-ee (3) +- selector: cert (0), spki (1) +- matchtype: full (0), sha2-256 (1), sha2-512 (2) + +Common DANE TLSA record parameters are: dane-ee spki sha2-256, or 3 1 1, +followed by a sha2-256 hash of the DER-encoded "SPKI" (subject public key info) +from the certificate. An example DNS zone file entry: + + _25._tcp.example.com. IN TLSA 3 1 1 133b919c9d65d8b1488157315327334ead8d83372db57465ecabf53ee5748aee + +The first usable information from the pem file is used to compose the TLSA +record. In case of selector "cert", a certificate is required. Otherwise the +"subject public key info" (spki) of the first certificate or public or private +key (pkcs#8, pkcs#1 or ec private key) is used. + + usage: mox dane makerecord usage selector matchtype [certificate.pem | publickey.pem | privatekey.pem] + +# mox dns lookup + +Lookup DNS name of given type. + +Lookup always prints whether the response was DNSSEC-protected. + +Examples: + +mox dns lookup ptr 1.1.1.1 +mox dns lookup mx xmox.nl +mox dns lookup txt _dmarc.xmox.nl. +mox dns lookup tlsa _25._tcp.xmox.nl + + usage: mox dns lookup [ptr | mx | cname | ips | a | aaaa | ns | txt | srv | tlsa] name + # mox dkim gened25519 Generate a new ed25519 key for use with DKIM. diff --git a/gendoc.sh b/gendoc.sh index f1ab779..0bd5c06 100755 --- a/gendoc.sh +++ b/gendoc.sh @@ -3,17 +3,9 @@ ( cat < 0 + if len(created) > 0 { + tls := config.TLS{ + HostPrivateKeyFiles: append(l.TLS.HostPrivateKeyFiles, created...), + } + fmt.Printf("\nEnsure Listener %q in %s has the following in its TLS section, below \"ACME: %s\" (don't forget to indent with tabs):\n\n", listenerName, mox.ConfigStaticPath, l.TLS.ACME) + err := sconf.Write(os.Stdout, tls) + xcheckf(err, "writing new TLS.HostPrivateKeyFiles section") + fmt.Println() + } + } + if didCreate { + fmt.Printf(` +After updating mox.conf and restarting, run "mox config dnsrecords" for a +domain and create the TLSA DNS records it suggests to enable DANE. +`) + } +} + var examples = []struct { Name string Get func() string @@ -1326,6 +1570,517 @@ with DKIM, by mox. xcheckf(err, "writing rsa private key") } +func cmdDANEDial(c *cmd) { + c.params = "host:port" + var usages string + c.flag.StringVar(&usages, "usages", "pkix-ta,pkix-ee,dane-ta,dane-ee", "allowed usages for dane, comma-separated list") + c.help = `Dial the address using TLS with certificate verification using DANE. + +Data is copied between connection and stdin/stdout until either side closes the +connection. +` + args := c.Parse() + if len(args) != 1 { + c.Usage() + } + + allowedUsages := []adns.TLSAUsage{} + if usages != "" { + for _, s := range strings.Split(usages, ",") { + var usage adns.TLSAUsage + switch strings.ToLower(s) { + case "pkix-ta", strconv.Itoa(int(adns.TLSAUsagePKIXTA)): + usage = adns.TLSAUsagePKIXTA + case "pkix-ee", strconv.Itoa(int(adns.TLSAUsagePKIXEE)): + usage = adns.TLSAUsagePKIXEE + case "dane-ta", strconv.Itoa(int(adns.TLSAUsageDANETA)): + usage = adns.TLSAUsageDANETA + case "dane-ee", strconv.Itoa(int(adns.TLSAUsageDANEEE)): + usage = adns.TLSAUsageDANEEE + default: + log.Fatalf("unknown dane usage %q", s) + } + allowedUsages = append(allowedUsages, usage) + } + } + + resolver := dns.StrictResolver{Pkg: "danedial"} + conn, record, err := dane.Dial(context.Background(), resolver, "tcp", args[0], allowedUsages) + xcheckf(err, "dial") + log.Printf("(connected, verified with %s)", record) + + go func() { + _, err := io.Copy(os.Stdout, conn) + xcheckf(err, "copy from connection to stdout") + conn.Close() + }() + _, err = io.Copy(conn, os.Stdin) + xcheckf(err, "copy from stdin to connection") +} + +func cmdDANEDialmx(c *cmd) { + c.params = "domain [destination-host]" + var ehloHostname string + c.flag.StringVar(&ehloHostname, "ehlohostname", "localhost", "hostname to send in smtp ehlo command") + c.help = `Connect to MX server for domain using STARTTLS verified with DANE. + +If no destination host is specified, regular delivery logic is used to find the +hosts to attempt delivery too. This involves following CNAMEs for the domain, +looking up MX records, and possibly falling back to the domain name itself as +host. + +If a destination host is specified, that is the only candidate host considered +for dialing. + +With a list of destinations gathered, each is dialed until a successful SMTP +session verified with DANE has been initialized, including EHLO and STARTTLS +commands. + +Once connected, data is copied between connection and stdin/stdout, until +either side closes the connection. + +This command follows the same logic as delivery attempts made from the queue, +sharing most of its code. +` + args := c.Parse() + if len(args) != 1 && len(args) != 2 { + c.Usage() + } + + ehloDomain, err := dns.ParseDomain(ehloHostname) + xcheckf(err, "parsing ehlo hostname") + + origNextHop, err := dns.ParseDomain(args[0]) + xcheckf(err, "parse domain") + + clog := mlog.New("danedialmx") + + ctxbg := context.Background() + + resolver := dns.StrictResolver{} + var haveMX bool + var origNextHopAuthentic, expandedNextHopAuthentic bool + var expandedNextHop dns.Domain + var hosts []dns.IPDomain + if len(args) == 1 { + var permanent bool + haveMX, origNextHopAuthentic, expandedNextHopAuthentic, expandedNextHop, hosts, permanent, err = smtpclient.GatherDestinations(ctxbg, clog, resolver, dns.IPDomain{Domain: origNextHop}) + status := "temporary" + if permanent { + status = "permanent" + } + if err != nil { + log.Fatalf("gathering destinations: %v (%s)", err, status) + } + if expandedNextHop != origNextHop { + log.Printf("followed cnames to %s", expandedNextHop) + } + if haveMX { + log.Printf("found mx record, trying mx hosts") + } else { + log.Printf("no mx record found, will try to connect to domain directly") + } + if !origNextHopAuthentic { + log.Fatalf("error: initial domain not dnssec-secure") + } + if !expandedNextHopAuthentic { + log.Fatalf("error: expanded domain not dnssec-secure") + } + + l := []string{} + for _, h := range hosts { + l = append(l, h.String()) + } + log.Printf("destinations: %s", strings.Join(l, ", ")) + } else { + d, err := dns.ParseDomain(args[1]) + if err != nil { + log.Fatalf("parsing destination host: %v", err) + } + log.Printf("skipping domain mx/cname lookups, assuming domain is dnssec-protected") + + origNextHopAuthentic = true + expandedNextHopAuthentic = true + expandedNextHop = d + hosts = []dns.IPDomain{{Domain: d}} + } + + dialedIPs := map[string][]net.IP{} + for _, host := range hosts { + // It should not be possible for hosts to have IP addresses: They are not + // allowed by dns.ParseDomain, and MX records cannot contain them. + if host.IsIP() { + log.Fatalf("unexpected IP address for destination host") + } + + log.Printf("attempting to connect to %s", host) + + authentic, expandedAuthentic, expandedHost, ips, _, err := smtpclient.GatherIPs(ctxbg, clog, resolver, host, dialedIPs) + if err != nil { + log.Printf("resolving ips for %s: %v, skipping", host, err) + continue + } + if !authentic { + log.Printf("no dnssec for ips of %s, skipping", host) + continue + } + if !expandedAuthentic { + log.Printf("no dnssec for cname-followed ips of %s, skipping", host) + continue + } + if expandedHost != host.Domain { + log.Printf("host %s cname-expanded to %s", host, expandedHost) + } + log.Printf("host %s resolved to ips %s, looking up tlsa records", host, ips) + + daneRequired, daneRecords, tlsaBaseDomain, err := smtpclient.GatherTLSA(ctxbg, clog, resolver, host.Domain, expandedAuthentic, expandedHost) + if err != nil { + log.Printf("looking up tlsa records: %s, skipping", err) + continue + } + tlsMode := smtpclient.TLSStrictStartTLS + if len(daneRecords) == 0 { + if !daneRequired { + log.Printf("host %s has no tlsa records, skipping", expandedHost) + continue + } + log.Printf("warning: only unusable tlsa records found, continuing with required tls without certificate verification") + tlsMode = smtpclient.TLSUnverifiedStartTLS + } else { + var l []string + for _, r := range daneRecords { + l = append(l, r.String()) + } + log.Printf("tlsa records: %s", strings.Join(l, "; ")) + } + + tlsRemoteHostnames := smtpclient.GatherTLSANames(haveMX, expandedNextHopAuthentic, expandedAuthentic, origNextHop, expandedNextHop, host.Domain, tlsaBaseDomain) + var l []string + for _, name := range tlsRemoteHostnames { + l = append(l, name.String()) + } + log.Printf("gathered valid tls certificate names for potential verification with dane-ta: %s", strings.Join(l, ", ")) + + dialer := &net.Dialer{Timeout: 5 * time.Second} + conn, _, err := smtpclient.Dial(ctxbg, clog, dialer, dns.IPDomain{Domain: expandedHost}, ips, 25, dialedIPs) + if err != nil { + log.Printf("dial %s: %v, skipping", expandedHost, err) + continue + } + log.Printf("connected to %s, %s, starting smtp session with ehlo and starttls with dane verification", expandedHost, conn.RemoteAddr()) + + var verifiedRecord adns.TLSA + sc, err := smtpclient.New(ctxbg, clog, conn, tlsMode, ehloDomain, tlsRemoteHostnames[0], nil, daneRecords, tlsRemoteHostnames[1:], &verifiedRecord) + if err != nil { + log.Printf("setting up smtp session: %v, skipping", err) + conn.Close() + continue + } + + smtpConn, err := sc.Conn() + if err != nil { + log.Fatalf("error: taking over smtp connection: %s", err) + } + log.Printf("tls verified with tlsa record: %s", verifiedRecord) + log.Printf("smtp session initialized and connected to stdin/stdout") + + go func() { + _, err := io.Copy(os.Stdout, smtpConn) + xcheckf(err, "copy from connection to stdout") + smtpConn.Close() + }() + _, err = io.Copy(smtpConn, os.Stdin) + xcheckf(err, "copy from stdin to connection") + } + + log.Fatalf("no remaining destinations") +} + +func cmdDANEMakeRecord(c *cmd) { + c.params = "usage selector matchtype [certificate.pem | publickey.pem | privatekey.pem]" + c.help = `Print TLSA record for given certificate/key and parameters. + +Valid values: +- usage: pkix-ta (0), pkix-ee (1), dane-ta (2), dane-ee (3) +- selector: cert (0), spki (1) +- matchtype: full (0), sha2-256 (1), sha2-512 (2) + +Common DANE TLSA record parameters are: dane-ee spki sha2-256, or 3 1 1, +followed by a sha2-256 hash of the DER-encoded "SPKI" (subject public key info) +from the certificate. An example DNS zone file entry: + + _25._tcp.example.com. IN TLSA 3 1 1 133b919c9d65d8b1488157315327334ead8d83372db57465ecabf53ee5748aee + +The first usable information from the pem file is used to compose the TLSA +record. In case of selector "cert", a certificate is required. Otherwise the +"subject public key info" (spki) of the first certificate or public or private +key (pkcs#8, pkcs#1 or ec private key) is used. +` + + args := c.Parse() + if len(args) != 4 { + c.Usage() + } + + var usage adns.TLSAUsage + switch strings.ToLower(args[0]) { + case "pkix-ta", strconv.Itoa(int(adns.TLSAUsagePKIXTA)): + usage = adns.TLSAUsagePKIXTA + case "pkix-ee", strconv.Itoa(int(adns.TLSAUsagePKIXEE)): + usage = adns.TLSAUsagePKIXEE + case "dane-ta", strconv.Itoa(int(adns.TLSAUsageDANETA)): + usage = adns.TLSAUsageDANETA + case "dane-ee", strconv.Itoa(int(adns.TLSAUsageDANEEE)): + usage = adns.TLSAUsageDANEEE + default: + if v, err := strconv.ParseUint(args[0], 10, 16); err != nil { + log.Fatalf("bad usage %q", args[0]) + } else { + // Does not influence certificate association data, so we can accept other numbers. + log.Printf("warning: continuing with unrecognized tlsa usage %d", v) + usage = adns.TLSAUsage(v) + } + } + + var selector adns.TLSASelector + switch strings.ToLower(args[1]) { + case "cert", strconv.Itoa(int(adns.TLSASelectorCert)): + selector = adns.TLSASelectorCert + case "spki", strconv.Itoa(int(adns.TLSASelectorSPKI)): + selector = adns.TLSASelectorSPKI + default: + log.Fatalf("bad selector %q", args[1]) + } + + var matchType adns.TLSAMatchType + switch strings.ToLower(args[2]) { + case "full", strconv.Itoa(int(adns.TLSAMatchTypeFull)): + matchType = adns.TLSAMatchTypeFull + case "sha2-256", strconv.Itoa(int(adns.TLSAMatchTypeSHA256)): + matchType = adns.TLSAMatchTypeSHA256 + case "sha2-512", strconv.Itoa(int(adns.TLSAMatchTypeSHA512)): + matchType = adns.TLSAMatchTypeSHA512 + default: + log.Fatalf("bad matchtype %q", args[2]) + } + + buf, err := os.ReadFile(args[3]) + xcheckf(err, "reading certificate") + for { + var block *pem.Block + block, buf = pem.Decode(buf) + if block == nil { + extra := "" + if len(buf) > 0 { + extra = " (with leftover data from pem file)" + } + if selector == adns.TLSASelectorCert { + log.Fatalf("no certificate found in pem file%s", extra) + } else { + log.Fatalf("no certificate or public or private key found in pem file%s", extra) + } + } + var cert *x509.Certificate + var data []byte + if block.Type == "CERTIFICATE" { + cert, err = x509.ParseCertificate(block.Bytes) + xcheckf(err, "parse certificate") + switch selector { + case adns.TLSASelectorCert: + data = cert.Raw + case adns.TLSASelectorSPKI: + data = cert.RawSubjectPublicKeyInfo + } + } else if selector == adns.TLSASelectorCert { + // We need a certificate, just a public/private key won't do. + log.Printf("skipping pem type %q, certificate is required", block.Type) + continue + } else { + var privKey, pubKey any + var err error + switch block.Type { + case "PUBLIC KEY": + _, err := x509.ParsePKIXPublicKey(block.Bytes) + xcheckf(err, "parse pkix subject public key info (spki)") + data = block.Bytes + case "EC PRIVATE KEY": + privKey, err = x509.ParseECPrivateKey(block.Bytes) + xcheckf(err, "parse ec private key") + case "RSA PRIVATE KEY": + privKey, err = x509.ParsePKCS1PrivateKey(block.Bytes) + xcheckf(err, "parse pkcs#1 rsa private key") + case "RSA PUBLIC KEY": + pubKey, err = x509.ParsePKCS1PublicKey(block.Bytes) + xcheckf(err, "parse pkcs#1 rsa public key") + case "PRIVATE KEY": + // PKCS#8 private key + privKey, err = x509.ParsePKCS8PrivateKey(block.Bytes) + xcheckf(err, "parse pkcs#8 private key") + default: + log.Printf("skipping unrecognized pem type %q", block.Type) + continue + } + if data == nil { + if pubKey == nil && privKey != nil { + if signer, ok := privKey.(crypto.Signer); !ok { + log.Fatalf("private key of type %T is not a signer, cannot get public key", privKey) + } else { + pubKey = signer.Public() + } + } + if pubKey == nil { + // Should not happen. + log.Fatalf("internal error: did not find private or public key") + } + data, err = x509.MarshalPKIXPublicKey(pubKey) + xcheckf(err, "marshal pkix subject public key info (spki)") + } + } + + switch matchType { + case adns.TLSAMatchTypeFull: + case adns.TLSAMatchTypeSHA256: + p := sha256.Sum256(data) + data = p[:] + case adns.TLSAMatchTypeSHA512: + p := sha512.Sum512(data) + data = p[:] + } + fmt.Printf("%d %d %d %x\n", usage, selector, matchType, data) + break + } +} + +func cmdDNSLookup(c *cmd) { + c.params = "[ptr | mx | cname | ips | a | aaaa | ns | txt | srv | tlsa] name" + c.help = `Lookup DNS name of given type. + +Lookup always prints whether the response was DNSSEC-protected. + +Examples: + +mox dns lookup ptr 1.1.1.1 +mox dns lookup mx xmox.nl +mox dns lookup txt _dmarc.xmox.nl. +mox dns lookup tlsa _25._tcp.xmox.nl +` + args := c.Parse() + + if len(args) != 2 { + c.Usage() + } + + resolver := dns.StrictResolver{Pkg: "dns"} + + // like xparseDomain, but treat unparseable domain as an ASCII name so names with + // underscores are still looked up, e,g ._domainkey.. + xdomain := func(s string) dns.Domain { + d, err := dns.ParseDomain(s) + if err != nil { + return dns.Domain{ASCII: strings.TrimSuffix(s, ".")} + } + return d + } + + cmd, name := args[0], args[1] + + switch cmd { + case "ptr": + ip := xparseIP(name, "ip") + ptrs, result, err := resolver.LookupAddr(context.Background(), ip.String()) + if err != nil { + log.Fatalf("dns lookup: %v (%s)", err, dnssecStatus(result.Authentic)) + } + fmt.Printf("names (%d, %s):\n", len(ptrs), dnssecStatus(result.Authentic)) + for _, ptr := range ptrs { + fmt.Printf("- %s\n", ptr) + } + + case "mx": + name := xdomain(name) + mxl, result, err := resolver.LookupMX(context.Background(), name.ASCII+".") + if err != nil { + log.Printf("dns lookup: %v (%s)", err, dnssecStatus(result.Authentic)) + // We can still have valid records... + } + fmt.Printf("mx records (%d, %s):\n", len(mxl), dnssecStatus(result.Authentic)) + for _, mx := range mxl { + fmt.Printf("- %s, preference %d\n", mx.Host, mx.Pref) + } + + case "cname": + name := xdomain(name) + target, result, err := resolver.LookupCNAME(context.Background(), name.ASCII+".") + if err != nil { + log.Fatalf("dns lookup: %v (%s)", err, dnssecStatus(result.Authentic)) + } + fmt.Printf("%s (%s)\n", target, dnssecStatus(result.Authentic)) + + case "ips", "a", "aaaa": + network := "ip" + if cmd == "a" { + network = "ip4" + } else if cmd == "aaaa" { + network = "ip6" + } + name := xdomain(name) + ips, result, err := resolver.LookupIP(context.Background(), network, name.ASCII+".") + if err != nil { + log.Fatalf("dns lookup: %v (%s)", err, dnssecStatus(result.Authentic)) + } + fmt.Printf("records (%d, %s):\n", len(ips), dnssecStatus(result.Authentic)) + for _, ip := range ips { + fmt.Printf("- %s\n", ip) + } + + case "ns": + name := xdomain(name) + nsl, result, err := resolver.LookupNS(context.Background(), name.ASCII+".") + if err != nil { + log.Fatalf("dns lookup: %v (%s)", err, dnssecStatus(result.Authentic)) + } + fmt.Printf("ns records (%d, %s):\n", len(nsl), dnssecStatus(result.Authentic)) + for _, ns := range nsl { + fmt.Printf("- %s\n", ns) + } + + case "txt": + host := xdomain(name) + l, result, err := resolver.LookupTXT(context.Background(), host.ASCII+".") + if err != nil { + log.Fatalf("dns lookup: %v (%s)", err, dnssecStatus(result.Authentic)) + } + fmt.Printf("txt records (%d, %s):\n", len(l), dnssecStatus(result.Authentic)) + for _, txt := range l { + fmt.Printf("- %s\n", txt) + } + + case "srv": + host := xdomain(name) + _, l, result, err := resolver.LookupSRV(context.Background(), "", "", host.ASCII+".") + if err != nil { + log.Fatalf("dns lookup: %v (%s)", err, dnssecStatus(result.Authentic)) + } + fmt.Printf("srv records (%d, %s):\n", len(l), dnssecStatus(result.Authentic)) + for _, srv := range l { + fmt.Printf("- host %s, port %d, priority %d, weight %d\n", srv.Target, srv.Port, srv.Priority, srv.Weight) + } + + case "tlsa": + host := xdomain(name) + l, result, err := resolver.LookupTLSA(context.Background(), 0, "", host.ASCII+".") + if err != nil { + log.Fatalf("dns lookup: %v (%s)", err, dnssecStatus(result.Authentic)) + } + fmt.Printf("tlsa records (%d, %s):\n", len(l), dnssecStatus(result.Authentic)) + for _, tlsa := range l { + fmt.Printf("- usage %q (%d), selector %q (%d), matchtype %q (%d), certificate association data %x\n", tlsa.Usage, tlsa.Usage, tlsa.Selector, tlsa.Selector, tlsa.MatchType, tlsa.MatchType, tlsa.CertAssoc) + } + default: + log.Fatalf("unknown record type %q", args[0]) + } +} + func cmdDKIMGened25519(c *cmd) { c.params = ">$selector._domainkey.$domain.ed25519key.pkcs8.pem" c.help = `Generate a new ed25519 key for use with DKIM. @@ -1506,7 +2261,7 @@ func cmdDKIMLookup(c *cmd) { selector := xparseDomain(args[0], "selector") domain := xparseDomain(args[1], "domain") - status, record, txt, err := dkim.Lookup(context.Background(), dns.StrictResolver{}, selector, domain) + status, record, txt, authentic, err := dkim.Lookup(context.Background(), dns.StrictResolver{}, selector, domain) if err != nil { fmt.Printf("error: %s\n", err) } @@ -1516,6 +2271,11 @@ func cmdDKIMLookup(c *cmd) { if txt != "" { fmt.Printf("TXT record: %s\n", txt) } + if authentic { + fmt.Println("dnssec-signed: yes") + } else { + fmt.Println("dnssec-signed: no") + } if record != nil { fmt.Printf("Record:\n") pairs := []any{ @@ -1541,9 +2301,17 @@ func cmdDMARCLookup(c *cmd) { } fromdomain := xparseDomain(args[0], "domain") - _, domain, _, txt, err := dmarc.Lookup(context.Background(), dns.StrictResolver{}, fromdomain) + _, domain, _, txt, authentic, err := dmarc.Lookup(context.Background(), dns.StrictResolver{}, fromdomain) xcheckf(err, "dmarc lookup domain %s", fromdomain) fmt.Printf("dmarc record at domain %s: %s\n", domain, txt) + fmt.Printf("(%s)\n", dnssecStatus(authentic)) +} + +func dnssecStatus(v bool) string { + if v { + return "with dnssec" + } + return "without dnssec" } func cmdDMARCVerify(c *cmd) { @@ -1593,9 +2361,9 @@ can be found in message headers. if heloDomain != nil { spfArgs.HelloDomain = dns.IPDomain{Domain: *heloDomain} } - rspf, spfDomain, expl, err := spf.Verify(context.Background(), dns.StrictResolver{}, spfArgs) + rspf, spfDomain, expl, authentic, err := spf.Verify(context.Background(), dns.StrictResolver{}, spfArgs) if err != nil { - log.Printf("spf verify: %v (explanation: %q)", err, expl) + log.Printf("spf verify: %v (explanation: %q, authentic %v)", err, expl, authentic) } else { received = &rspf spfStatus = received.Result @@ -1605,7 +2373,7 @@ can be found in message headers. } else { spfIdentity = heloDomain } - fmt.Printf("spf result: %s: %s\n", spfDomain, spfStatus) + fmt.Printf("spf result: %s: %s (%s)\n", spfDomain, spfStatus, dnssecStatus(authentic)) } } @@ -1642,13 +2410,16 @@ address must opt-in to receiving DMARC reports by creating a DMARC record at } dom := xparseDomain(args[0], "domain") - _, domain, record, txt, err := dmarc.Lookup(context.Background(), dns.StrictResolver{}, dom) + _, domain, record, txt, authentic, err := dmarc.Lookup(context.Background(), dns.StrictResolver{}, dom) xcheckf(err, "dmarc lookup domain %s", dom) fmt.Printf("dmarc record at domain %s: %q\n", domain, txt) + fmt.Printf("(%s)\n", dnssecStatus(authentic)) check := func(kind, addr string) { + var authentic bool + printResult := func(format string, args ...any) { - fmt.Printf("%s %s: %s\n", kind, addr, fmt.Sprintf(format, args...)) + fmt.Printf("%s %s: %s (%s)\n", kind, addr, fmt.Sprintf(format, args...), dnssecStatus(authentic)) } u, err := url.Parse(addr) @@ -1675,7 +2446,7 @@ address must opt-in to receiving DMARC reports by creating a DMARC record at return } - accepts, status, _, txt, err := dmarc.LookupExternalReportsAccepted(context.Background(), dns.StrictResolver{}, domain, destdom) + accepts, status, _, txt, authentic, err := dmarc.LookupExternalReportsAccepted(context.Background(), dns.StrictResolver{}, domain, destdom) var txtstr string txtaddr := fmt.Sprintf("%s._report._dmarc.%s", domain.ASCII, destdom.ASCII) if txt == "" { @@ -1853,14 +2624,14 @@ printed. LocalIP: net.ParseIP("127.0.0.1"), LocalHostname: dns.Domain{ASCII: "localhost"}, } - r, _, explanation, err := spf.Verify(context.Background(), dns.StrictResolver{}, spfargs) + r, _, explanation, authentic, err := spf.Verify(context.Background(), dns.StrictResolver{}, spfargs) if err != nil { fmt.Printf("error: %s\n", err) } if explanation != "" { fmt.Printf("explanation: %s\n", explanation) } - fmt.Printf("status: %s\n", r.Result) + fmt.Printf("status: %s (%s)\n", r.Result, dnssecStatus(authentic)) if r.Mechanism != "" { fmt.Printf("mechanism: %s\n", r.Mechanism) } @@ -1887,9 +2658,10 @@ func cmdSPFLookup(c *cmd) { } domain := xparseDomain(args[0], "domain") - _, txt, _, err := spf.Lookup(context.Background(), dns.StrictResolver{}, domain) + _, txt, _, authentic, err := spf.Lookup(context.Background(), dns.StrictResolver{}, domain) xcheckf(err, "spf lookup for %s", domain) fmt.Println(txt) + fmt.Printf("(%s)\n", dnssecStatus(authentic)) } func cmdMTASTSLookup(c *cmd) { @@ -2027,7 +2799,7 @@ func cmdCheckupdate(c *cmd) { A single DNS TXT lookup to _updates.xmox.nl tells if a new version is available. If so, a changelog is fetched from https://updates.xmox.nl, and the -individual entries validated with a builtin public key. The changelog is +individual entries verified with a builtin public key. The changelog is printed. ` if len(c.Parse()) != 0 { diff --git a/message/authresults.go b/message/authresults.go index 9eded7a..aa507a9 100644 --- a/message/authresults.go +++ b/message/authresults.go @@ -41,7 +41,7 @@ type AuthProp struct { // Whether value is address-like (localpart@domain, or domain). Or another value, // which is subject to escaping. IsAddrLike bool - Comment string // If not empty, header comment withtout "()", added after Value. + Comment string // If not empty, header comment without "()", added after Value. } // MakeAuthProp is a convenient way to make an AuthProp. diff --git a/mox-/admin.go b/mox-/admin.go index 86b72e8..18778f3 100644 --- a/mox-/admin.go +++ b/mox-/admin.go @@ -3,9 +3,11 @@ package mox import ( "bytes" "context" + "crypto" "crypto/ed25519" cryptorand "crypto/rand" "crypto/rsa" + "crypto/sha256" "crypto/x509" "encoding/pem" "fmt" @@ -19,6 +21,8 @@ import ( "golang.org/x/exp/maps" + "github.com/mjl-/adns" + "github.com/mjl-/mox/config" "github.com/mjl-/mox/dkim" "github.com/mjl-/mox/dmarc" @@ -446,7 +450,7 @@ func WebserverConfigSet(ctx context.Context, domainRedirects map[string]string, // DomainRecords returns text lines describing DNS records required for configuring // a domain. -func DomainRecords(domConf config.Domain, domain dns.Domain) ([]string, error) { +func DomainRecords(domConf config.Domain, domain dns.Domain, hasDNSSEC bool) ([]string, error) { d := domain.ASCII h := Conf.Static.HostnameDomain.ASCII @@ -457,6 +461,60 @@ func DomainRecords(domConf config.Domain, domain dns.Domain) ([]string, error) { "", } + if public, ok := Conf.Static.Listeners["public"]; ok && public.TLS != nil && (len(public.TLS.HostPrivateRSA2048Keys) > 0 || len(public.TLS.HostPrivateECDSAP256Keys) > 0) { + records = append(records, + "; DANE: These records indicate that a remote mail server trying to deliver email", + "; with SMTP (TCP port 25) must verify the TLS certificate with DANE-EE (3), based", + "; on the certificate public key (\"SPKI\", 1) that is SHA2-256-hashed (1) to the", + "; hexadecimal hash. DANE-EE verification means only the certificate or public", + "; key is verified, not whether the certificate is signed by a (centralized)", + "; certificate authority (CA), is expired, or matches the host name.", + ";", + "; NOTE: Create the records below only once: They are for the machine, and apply", + "; to all hosted domains.", + ) + if !hasDNSSEC { + records = append(records, + ";", + "; WARNING: Domain does not appear to be DNSSEC-signed. To enable DANE, first", + "; enable DNSSEC on your domain, then add the TLSA records. Records below have been", + "; commented out.", + ) + } + addTLSA := func(privKey crypto.Signer) error { + spkiBuf, err := x509.MarshalPKIXPublicKey(privKey.Public()) + if err != nil { + return fmt.Errorf("marshal SubjectPublicKeyInfo for DANE record: %v", err) + } + sum := sha256.Sum256(spkiBuf) + tlsaRecord := adns.TLSA{ + Usage: adns.TLSAUsageDANEEE, + Selector: adns.TLSASelectorSPKI, + MatchType: adns.TLSAMatchTypeSHA256, + CertAssoc: sum[:], + } + var s string + if hasDNSSEC { + s = fmt.Sprintf("_25._tcp.%-*s IN TLSA %s", 20+len(d)-len("_25._tcp."), h+".", tlsaRecord.Record()) + } else { + s = fmt.Sprintf(";; _25._tcp.%-*s IN TLSA %s", 20+len(d)-len(";; _25._tcp."), h+".", tlsaRecord.Record()) + } + records = append(records, s) + return nil + } + for _, privKey := range public.TLS.HostPrivateECDSAP256Keys { + if err := addTLSA(privKey); err != nil { + return nil, err + } + } + for _, privKey := range public.TLS.HostPrivateRSA2048Keys { + if err := addTLSA(privKey); err != nil { + return nil, err + } + } + records = append(records, "") + } + if d != h { records = append(records, "; For the machine, only needs to be created once, for the first domain added.", @@ -537,7 +595,9 @@ func DomainRecords(domConf config.Domain, domain dns.Domain) ([]string, error) { if sts := domConf.MTASTS; sts != nil { records = append(records, - "; TLS must be used when delivering to us.", + "; Remote servers can use MTA-STS to verify our TLS certificate with the", + "; WebPKI pool of CA's (certificate authorities) when delivering over SMTP with", + "; STARTTLSTLS.", fmt.Sprintf(`mta-sts.%s. IN CNAME %s.`, d, h), fmt.Sprintf(`_mta-sts.%s. IN TXT "v=STSv1; id=%s"`, d, sts.PolicyID), "", diff --git a/mox-/config.go b/mox-/config.go index 3c92b0f..d930991 100644 --- a/mox-/config.go +++ b/mox-/config.go @@ -3,7 +3,11 @@ package mox import ( "bytes" "context" + "crypto" + "crypto/ecdsa" "crypto/ed25519" + "crypto/elliptic" + cryptorand "crypto/rand" "crypto/rsa" "crypto/tls" "crypto/x509" @@ -26,6 +30,8 @@ import ( "golang.org/x/text/unicode/norm" + "github.com/mjl-/autocert" + "github.com/mjl-/sconf" "github.com/mjl-/mox/autotls" @@ -434,6 +440,8 @@ func PrepareStaticConfig(ctx context.Context, configFile string, conf *Config, c errs = append(errs, fmt.Errorf(format, args...)) } + log := xlog.WithContext(ctx) + c := &conf.Static // check that mailbox is in unicode NFC normalized form. @@ -496,13 +504,77 @@ func PrepareStaticConfig(ctx context.Context, configFile string, conf *Config, c } c.HostnameDomain = hostname + // Return private key for host name for use with an ACME. Used to return the same + // private key as pre-generated for use with DANE, with its public key in DNS. + // We only use this key for Listener's that have this ACME configured, and for + // which the effective listener host name (either specific to the listener, or the + // global name) is requested. Other host names can get a fresh private key, they + // don't appear in DANE records. + // + // - run 0: only use listener with explicitly matching host name in listener + // (default quickstart config does not set it). + // - run 1: only look at public listener (and host matching mox host name) + // - run 2: all listeners (and host matching mox host name) + findACMEHostPrivateKey := func(acmeName, host string, keyType autocert.KeyType, run int) crypto.Signer { + for listenerName, l := range Conf.Static.Listeners { + if l.TLS == nil || l.TLS.ACME != acmeName { + continue + } + if run == 0 && host != l.HostnameDomain.ASCII { + continue + } + if run == 1 && listenerName != "public" || host != Conf.Static.HostnameDomain.ASCII { + continue + } + switch keyType { + case autocert.KeyRSA2048: + if len(l.TLS.HostPrivateRSA2048Keys) == 0 { + continue + } + return l.TLS.HostPrivateRSA2048Keys[0] + case autocert.KeyECDSAP256: + if len(l.TLS.HostPrivateECDSAP256Keys) == 0 { + continue + } + return l.TLS.HostPrivateECDSAP256Keys[0] + default: + return nil + } + } + return nil + } + // Make a function for an autocert.Manager.GetPrivateKey, using findACMEHostPrivateKey. + makeGetPrivateKey := func(acmeName string) func(host string, keyType autocert.KeyType) (crypto.Signer, error) { + return func(host string, keyType autocert.KeyType) (crypto.Signer, error) { + key := findACMEHostPrivateKey(acmeName, host, keyType, 0) + if key == nil { + key = findACMEHostPrivateKey(acmeName, host, keyType, 1) + } + if key == nil { + key = findACMEHostPrivateKey(acmeName, host, keyType, 2) + } + if key != nil { + log.Debug("found existing private key for certificate for host", mlog.Field("acmename", acmeName), mlog.Field("host", host), mlog.Field("keytype", keyType)) + return key, nil + } + log.Debug("generating new private key for certificate for host", mlog.Field("acmename", acmeName), mlog.Field("host", host), mlog.Field("keytype", keyType)) + switch keyType { + case autocert.KeyRSA2048: + return rsa.GenerateKey(cryptorand.Reader, 2048) + case autocert.KeyECDSAP256: + return ecdsa.GenerateKey(elliptic.P256(), cryptorand.Reader) + default: + return nil, fmt.Errorf("unrecognized requested key type %v", keyType) + } + } + } for name, acme := range c.ACME { if checkOnly { continue } acmeDir := dataDirPath(configFile, c.DataDir, "acme") os.MkdirAll(acmeDir, 0770) - manager, err := autotls.Load(name, acmeDir, acme.ContactEmail, acme.DirectoryURL, Shutdown.Done()) + manager, err := autotls.Load(name, acmeDir, acme.ContactEmail, acme.DirectoryURL, makeGetPrivateKey(name), Shutdown.Done()) if err != nil { addErrorf("loading ACME identity for %q: %s", name, err) } @@ -538,7 +610,7 @@ func PrepareStaticConfig(ctx context.Context, configFile string, conf *Config, c l.TLS.ACMEConfig = acme.Manager.ACMETLSConfig // SMTP STARTTLS connections are commonly made without SNI, because certificates - // often aren't validated. + // often aren't verified. hostname := c.HostnameDomain if l.Hostname != "" { hostname = l.HostnameDomain @@ -561,6 +633,34 @@ func PrepareStaticConfig(ctx context.Context, configFile string, conf *Config, c } else { addErrorf("listener %q: cannot have TLS config without ACME and without static keys/certificates", name) } + for _, privKeyFile := range l.TLS.HostPrivateKeyFiles { + keyPath := configDirPath(configFile, privKeyFile) + privKey, err := loadPrivateKeyFile(keyPath) + if err != nil { + addErrorf("listener %q: parsing host private key for DANE and ACME certificates: %v", name, err) + continue + } + switch k := privKey.(type) { + case *rsa.PrivateKey: + if k.N.BitLen() != 2048 { + 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())) + continue + } + l.TLS.HostPrivateRSA2048Keys = append(l.TLS.HostPrivateRSA2048Keys, k) + case *ecdsa.PrivateKey: + if k.Curve != elliptic.P256() { + log.Error("unrecognized ecdsa curve for host private key for DANE/ACME certificates, ignoring", mlog.Field("listener", name), mlog.Field("file", keyPath)) + continue + } + l.TLS.HostPrivateECDSAP256Keys = append(l.TLS.HostPrivateECDSAP256Keys, k) + default: + 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))) + continue + } + } + if l.TLS.ACME != "" && (len(l.TLS.HostPrivateRSA2048Keys) == 0) != (len(l.TLS.HostPrivateECDSAP256Keys) == 0) { + 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") + } // TLS 1.2 was introduced in 2008. TLS <1.2 was deprecated by ../rfc/8996:31 and ../rfc/8997:66 in 2021. var minVersion uint16 = tls.VersionTLS12 @@ -1395,6 +1495,35 @@ func prepareDynamicConfig(ctx context.Context, dynamicPath string, static config return } +func loadPrivateKeyFile(keyPath string) (crypto.Signer, error) { + keyBuf, err := os.ReadFile(keyPath) + if err != nil { + return nil, fmt.Errorf("reading host private key: %v", err) + } + b, _ := pem.Decode(keyBuf) + if b == nil { + return nil, fmt.Errorf("parsing pem block for private key: %v", err) + } + var privKey any + switch b.Type { + case "PRIVATE KEY": + privKey, err = x509.ParsePKCS8PrivateKey(b.Bytes) + case "RSA PRIVATE KEY": + privKey, err = x509.ParsePKCS1PrivateKey(b.Bytes) + case "EC PRIVATE KEY": + privKey, err = x509.ParseECPrivateKey(b.Bytes) + default: + err = fmt.Errorf("unknown pem type %q", b.Type) + } + if err != nil { + return nil, fmt.Errorf("parsing private key: %v", err) + } + if k, ok := privKey.(crypto.Signer); ok { + return k, nil + } + return nil, fmt.Errorf("parsed private key not a crypto.Signer, but %T", privKey) +} + func loadTLSKeyCerts(configFile, kind string, ctls *config.TLS) error { certs := []tls.Certificate{} for _, kp := range ctls.KeyCerts { diff --git a/mtasts/mtasts.go b/mtasts/mtasts.go index 24fd031..4ce0ca0 100644 --- a/mtasts/mtasts.go +++ b/mtasts/mtasts.go @@ -179,14 +179,14 @@ func LookupRecord(ctx context.Context, resolver dns.Resolver, domain dns.Domain) var txts []string for { var err error - txts, err = dns.WithPackage(resolver, "mtasts").LookupTXT(ctx, name) + txts, _, err = dns.WithPackage(resolver, "mtasts").LookupTXT(ctx, name) if dns.IsNotFound(err) { // DNS has no specified limit on how many CNAMEs to follow. Chains of 10 CNAMEs // have been seen on the internet. if len(cnames) > 16 { return nil, "", cnames, fmt.Errorf("too many cnames") } - cname, err := dns.WithPackage(resolver, "mtasts").LookupCNAME(ctx, name) + cname, _, err := dns.WithPackage(resolver, "mtasts").LookupCNAME(ctx, name) if dns.IsNotFound(err) { return nil, "", cnames, ErrNoRecord } @@ -266,7 +266,7 @@ func FetchPolicy(ctx context.Context, domain dns.Domain) (policy *Policy, policy defer cancel() // TLS requirements are what the Go standard library checks: trusted, non-expired, - // hostname validated against DNS-ID supporting wildcard. ../rfc/8461:524 + // hostname verified against DNS-ID supporting wildcard. ../rfc/8461:524 url := "https://mta-sts." + domain.Name() + "/.well-known/mta-sts.txt" req, err := http.NewRequestWithContext(ctx, "GET", url, nil) if err != nil { diff --git a/mtasts/mtasts_test.go b/mtasts/mtasts_test.go index 3d11bca..24a6851 100644 --- a/mtasts/mtasts_test.go +++ b/mtasts/mtasts_test.go @@ -18,6 +18,8 @@ import ( "testing" "time" + "github.com/mjl-/adns" + "github.com/mjl-/mox/dns" ) @@ -223,7 +225,7 @@ func TestFetch(t *testing.T) { HTTPClient.Transport = &http.Transport{ Dial: func(network, addr string) (net.Conn, error) { if strings.HasPrefix(addr, "mta-sts.doesnotexist.example") { - return nil, &net.DNSError{IsNotFound: true} + return nil, &adns.DNSError{IsNotFound: true} } return l.Dial() }, diff --git a/queue/direct.go b/queue/direct.go index bf5dbde..f5082f6 100644 --- a/queue/direct.go +++ b/queue/direct.go @@ -11,6 +11,10 @@ import ( "strings" "time" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" + + "github.com/mjl-/adns" "github.com/mjl-/bstore" "github.com/mjl-/mox/dns" @@ -23,6 +27,39 @@ import ( "github.com/mjl-/mox/store" ) +var ( + metricDestinations = promauto.NewCounter( + prometheus.CounterOpts{ + Name: "mox_queue_destinations_total", + Help: "Total destination (e.g. MX) lookups for delivery attempts, including those in mox_smtpclient_destinations_authentic_total.", + }, + ) + metricDestinationsAuthentic = promauto.NewCounter( + prometheus.CounterOpts{ + Name: "mox_queue_destinations_authentic_total", + Help: "Destination (e.g. MX) lookups for delivery attempts authenticated with DNSSEC so they are candidates for DANE verification.", + }, + ) + metricDestinationDANERequired = promauto.NewCounter( + prometheus.CounterOpts{ + Name: "mox_queue_destination_dane_required_total", + Help: "Total number of connections to hosts with valid TLSA records making DANE required.", + }, + ) + metricDestinationDANESTARTTLSUnverified = promauto.NewCounter( + prometheus.CounterOpts{ + Name: "mox_queue_destination_dane_starttlsunverified_total", + Help: "Total number of connections with required DANE where all TLSA records were unusable.", + }, + ) + metricDestinationDANEGatherTLSAErrors = promauto.NewCounter( + prometheus.CounterOpts{ + Name: "mox_queue_destination_dane_gathertlsa_errors_total", + Help: "Total number of connections where looking up TLSA records resulted in an error.", + }, + ) +) + // todo: rename function, perhaps put some of the params in a delivery struct so we don't pass all the params all the time? func fail(qlog *mlog.Log, m Msg, backoff time.Duration, permanent bool, remoteMTA dsn.NameIP, secodeOpt, errmsg string) { if permanent || m.Attempts >= 8 { @@ -53,9 +90,27 @@ func fail(qlog *mlog.Log, m Msg, backoff time.Duration, permanent bool, remoteMT } } -// Delivery by directly dialing MX hosts for destination domain. -func deliverDirect(cid int64, qlog *mlog.Log, resolver dns.Resolver, dialer contextDialer, ourHostname dns.Domain, transportName string, m Msg, backoff time.Duration) { - hosts, effectiveDomain, permanent, err := gatherHosts(resolver, m, cid, qlog) +// Delivery by directly dialing (MX) hosts for destination domain of message. +func deliverDirect(cid int64, qlog *mlog.Log, resolver dns.Resolver, dialer smtpclient.Dialer, ourHostname dns.Domain, transportName string, m Msg, backoff time.Duration) { + // High-level approach: + // - Resolve domain to deliver to (CNAME), and determine hosts to try to deliver to (MX) + // - Get MTA-STS policy for domain (optional). If present, only deliver to its + // allowlisted hosts and verify TLS against CA pool. + // - For each host, attempt delivery. If the attempt results in a permanent failure + // (as claimed by remote with a 5xx SMTP response, or perhaps decided by us), the + // attempt can be aborted. Other errors are often temporary and may result in later + // successful delivery. But hopefully the delivery just succeeds. For each host: + // - If there is an MTA-STS policy, we only connect to allow-listed hosts. + // - We try to lookup DANE records (optional) and verify them if present. + + // Resolve domain and hosts to attempt delivery to. + // These next-hop names are often the name under which we find MX records. The + // expanded name is different from the original if the original was a CNAME, + // possibly a chain. If there are no MX records, it can be an IP or the host + // directly. + origNextHop := m.RecipientDomain.Domain + ctx := context.WithValue(mox.Context, mlog.CidKey, cid) + haveMX, origNextHopAuthentic, expandedNextHopAuthentic, expandedNextHop, hosts, permanent, err := smtpclient.GatherDestinations(ctx, qlog, resolver, m.RecipientDomain) if err != nil { fail(qlog, m, backoff, permanent, dsn.NameIP{}, "", err.Error()) return @@ -63,65 +118,87 @@ func deliverDirect(cid int64, qlog *mlog.Log, resolver dns.Resolver, dialer cont // Check for MTA-STS policy and enforce it if needed. We have to check the // effective domain (found after following CNAME record(s)): there will certainly - // not be an mtasts record for the original recipient domain, because that is not + // not be an MTA-STS record for the original recipient domain, because that is not // allowed when a CNAME record is present. - var policyFresh bool var policy *mtasts.Policy tlsModeDefault := smtpclient.TLSOpportunistic - if !effectiveDomain.IsZero() { + if !expandedNextHop.IsZero() { cidctx := context.WithValue(mox.Shutdown, mlog.CidKey, cid) - policy, policyFresh, err = mtastsdb.Get(cidctx, resolver, effectiveDomain) + policy, _, err = mtastsdb.Get(cidctx, resolver, expandedNextHop) if err != nil { // No need to refuse to deliver if we have some mtasts error. - qlog.Infox("mtasts failed, continuing with strict tls requirement", err, mlog.Field("domain", effectiveDomain)) + qlog.Infox("mtasts failed, continuing with strict tls requirement", err, mlog.Field("domain", expandedNextHop)) tlsModeDefault = smtpclient.TLSStrictStartTLS } - // note: policy can be nil, if a domain does not implement MTA-STS or its the first - // time we fetch the policy and if we encountered an error. + // note: policy can be nil, if a domain does not implement MTA-STS or it's the + // first time we fetch the policy and if we encountered an error. } - // We try delivery to each record until we have success or a permanent failure. So - // for transient errors, we'll try the next MX record. For MX records pointing to a + // We try delivery to each host until we have success or a permanent failure. So + // for transient errors, we'll try the next host. For MX records pointing to a // dual stack host, we turn a permanent failure due to policy on the first delivery // attempt into a temporary failure and make sure to try the other address family // the next attempt. This should reduce issues due to one of our IPs being on a // block list. We won't try multiple IPs of the same address family. Surprisingly, - // RFC 5321 does not specify a clear algorithm, but common practicie is probably + // RFC 5321 does not specify a clear algorithm, but common practice is probably // ../rfc/3974:268. var remoteMTA dsn.NameIP var secodeOpt, errmsg string permanent = false - mtastsFailure := true // todo: should make distinction between host permanently not accepting the message, and the message not being deliverable permanently. e.g. a mx host may have a size limit, or not accept 8bitmime, while another host in the list does accept the message. same for smtputf8, ../rfc/6531:555 for _, h := range hosts { var badTLS, ok bool // ../rfc/8461:913 - if policy != nil && policy.Mode == mtasts.ModeEnforce && !policy.Matches(h.Domain) { + if policy != nil && !policy.Matches(h.Domain) { var policyHosts []string for _, mx := range policy.MX { policyHosts = append(policyHosts, mx.LogString()) } - errmsg = fmt.Sprintf("mx host %s does not match enforced mta-sts policy with hosts %s", h.Domain, strings.Join(policyHosts, ",")) - qlog.Error("mx host does not match enforce mta-sts policy, skipping", mlog.Field("host", h.Domain), mlog.Field("policyhosts", policyHosts)) - continue + if policy.Mode == mtasts.ModeEnforce { + errmsg = fmt.Sprintf("mx host %s does not match enforced mta-sts policy with hosts %s", h.Domain, strings.Join(policyHosts, ",")) + qlog.Error("mx host does not match mta-sts policy in mode enforce, skipping", mlog.Field("host", h.Domain), mlog.Field("policyhosts", policyHosts)) + continue + } + qlog.Error("mx host does not match mta-sts policy, but it is not enforced, continuing", mlog.Field("host", h.Domain), mlog.Field("policyhosts", policyHosts)) } qlog.Info("delivering to remote", mlog.Field("remote", h), mlog.Field("queuecid", cid)) cid := mox.Cid() nqlog := qlog.WithCid(cid) var remoteIP net.IP + tlsMode := tlsModeDefault if policy != nil && policy.Mode == mtasts.ModeEnforce { tlsMode = smtpclient.TLSStrictStartTLS } - permanent, badTLS, secodeOpt, remoteIP, errmsg, ok = deliverHost(nqlog, resolver, dialer, cid, ourHostname, transportName, h, &m, tlsMode) - if !ok && badTLS && tlsMode == smtpclient.TLSOpportunistic { + + // Try to deliver to host. We can get various errors back. Like permanent failure + // response codes, TCP, DNSSEC, TLS (opportunistic, i.e. optional with fallback to + // without), etc. It's a balancing act to handle these situations correctly. We + // don't want to bounce unnecessarily. But also not keep trying if there is no + // chance of success. + + // Set if there TLSA records were found. Means TLS is required for this host, + // usually with verification of the certificate. + var daneRequired bool + + enforceMTASTS := policy != nil && policy.Mode == mtasts.ModeEnforce + permanent, daneRequired, badTLS, secodeOpt, remoteIP, errmsg, ok = deliverHost(nqlog, resolver, dialer, cid, ourHostname, transportName, h, enforceMTASTS, haveMX, origNextHopAuthentic, origNextHop, expandedNextHopAuthentic, expandedNextHop, &m, tlsMode) + + // If we had a TLS-related failure when doing opportunistic (optional) TLS, and no + // DANE records were not found, we should try again without TLS. This could be an + // old server that only does ancient TLS versions, or has a misconfiguration. Note + // that opportunistic TLS does not do regular certificate verification, so that can't + // be the problem. + if !ok && badTLS && !enforceMTASTS && tlsMode == smtpclient.TLSOpportunistic && !daneRequired { // In case of failure with opportunistic TLS, try again without TLS. ../rfc/7435:459 - // todo future: revisit this decision. perhaps it should be a configuration option that defaults to not doing this? + // todo future: add a configuration option to not fall back? nqlog.Info("connecting again for delivery attempt without tls") - permanent, badTLS, secodeOpt, remoteIP, errmsg, ok = deliverHost(nqlog, resolver, dialer, cid, ourHostname, transportName, h, &m, smtpclient.TLSSkip) + tlsMode = smtpclient.TLSSkip + permanent, _, _, secodeOpt, remoteIP, errmsg, ok = deliverHost(nqlog, resolver, dialer, cid, ourHostname, transportName, h, enforceMTASTS, haveMX, origNextHopAuthentic, origNextHop, expandedNextHopAuthentic, expandedNextHop, &m, tlsMode) } + if ok { nqlog.Info("delivered from queue") if err := queueDelete(context.Background(), m.ID); err != nil { @@ -130,141 +207,54 @@ func deliverDirect(cid int64, qlog *mlog.Log, resolver dns.Resolver, dialer cont return } remoteMTA = dsn.NameIP{Name: h.XString(false), IP: remoteIP} - if !badTLS { - mtastsFailure = false - } if permanent { break } } - if mtastsFailure && policyFresh { - permanent = true - } + + // In theory, we could make a failure permanent if we didn't find any mx host + // matching the mta-sts policy AND the policy is fresh AND all DNS records leading + // to the MX targets (including CNAME) have a TTL that is beyond the latest + // possible delivery attempt. Until that time, configuration problems can be + // corrected through DNS or policy update. Not sure if worth it in practice, there + // is a good chance the MX records can still change, at least on initial delivery + // failures. + // todo: possibly detect that future deliveries will fail due to long ttl's of cached records that are preventing delivery. fail(qlog, m, backoff, permanent, remoteMTA, secodeOpt, errmsg) } -var ( - errCNAMELoop = errors.New("cname loop") - errCNAMELimit = errors.New("too many cname records") - errNoRecord = errors.New("no dns record") - errDNS = errors.New("dns lookup error") - errNoMail = errors.New("domain does not accept email as indicated with single dot for mx record") -) - -// Gather hosts to try to deliver to. We start with the straight-forward MX record. -// If that does not exist, we'll look for CNAME of the entire domain (following -// chains if needed). If a CNAME does not exist, but the domain name has an A or -// AAAA record, we'll try delivery directly to that host. -// ../rfc/5321:3824 -func gatherHosts(resolver dns.Resolver, m Msg, cid int64, qlog *mlog.Log) (hosts []dns.IPDomain, effectiveDomain dns.Domain, permanent bool, err error) { - if len(m.RecipientDomain.IP) > 0 { - return []dns.IPDomain{m.RecipientDomain}, effectiveDomain, false, nil - } - - // We start out delivering to the recipient domain. We follow CNAMEs a few times. - rcptDomain := m.RecipientDomain.Domain - // Domain we are actually delivering to, after following CNAME record(s). - effectiveDomain = rcptDomain - domainsSeen := map[string]bool{} - for i := 0; ; i++ { - if domainsSeen[effectiveDomain.ASCII] { - return nil, effectiveDomain, true, fmt.Errorf("%w: recipient domain %s: already saw %s", errCNAMELoop, rcptDomain, effectiveDomain) - } - domainsSeen[effectiveDomain.ASCII] = true - - // note: The Go resolver returns the requested name if the domain has no CNAME record but has a host record. - if i == 16 { - // We have a maximum number of CNAME records we follow. There is no hard limit for - // DNS, and you might think folks wouldn't configure CNAME chains at all, but for - // (non-mail) domains, CNAME chains of 10 records have been encountered according - // to the internet. - return nil, effectiveDomain, true, fmt.Errorf("%w: recipient domain %s, last resolved domain %s", errCNAMELimit, rcptDomain, effectiveDomain) - } - - cidctx := context.WithValue(mox.Context, mlog.CidKey, cid) - ctx, cancel := context.WithTimeout(cidctx, 30*time.Second) - defer cancel() - // Note: LookupMX can return an error and still return records: Invalid records are - // filtered out and an error returned. We must process any records that are valid. - // Only if all are unusable will we return an error. ../rfc/5321:3851 - mxl, err := resolver.LookupMX(ctx, effectiveDomain.ASCII+".") - cancel() - if err != nil && len(mxl) == 0 { - if !dns.IsNotFound(err) { - return nil, effectiveDomain, false, fmt.Errorf("%w: mx lookup for %s: %v", errDNS, effectiveDomain, err) - } - - // No MX record. First attempt CNAME lookup. ../rfc/5321:3838 ../rfc/3974:197 - ctx, cancel = context.WithTimeout(cidctx, 30*time.Second) - defer cancel() - cname, err := resolver.LookupCNAME(ctx, effectiveDomain.ASCII+".") - cancel() - if err != nil && !dns.IsNotFound(err) { - return nil, effectiveDomain, false, fmt.Errorf("%w: cname lookup for %s: %v", errDNS, effectiveDomain, err) - } - if err == nil && cname != effectiveDomain.ASCII+"." { - d, err := dns.ParseDomain(strings.TrimSuffix(cname, ".")) - if err != nil { - return nil, effectiveDomain, true, fmt.Errorf("%w: parsing cname domain %s: %v", errDNS, effectiveDomain, err) - } - effectiveDomain = d - // Start again with new domain. - continue - } - - // See if the host exists. If so, attempt delivery directly to host. ../rfc/5321:3842 - ctx, cancel = context.WithTimeout(cidctx, 30*time.Second) - defer cancel() - _, err = resolver.LookupHost(ctx, effectiveDomain.ASCII+".") - cancel() - if dns.IsNotFound(err) { - return nil, effectiveDomain, true, fmt.Errorf("%w: recipient domain/host %s", errNoRecord, effectiveDomain) - } else if err != nil { - return nil, effectiveDomain, false, fmt.Errorf("%w: looking up host %s because of no mx record: %v", errDNS, effectiveDomain, err) - } - hosts = []dns.IPDomain{{Domain: effectiveDomain}} - } else if err != nil { - qlog.Infox("partial mx failure, attempting delivery to valid mx records", err) - } - - // ../rfc/7505:122 - if err == nil && len(mxl) == 1 && mxl[0].Host == "." { - return nil, effectiveDomain, true, errNoMail - } - - // The Go resolver already sorts by preference, randomizing records of same - // preference. ../rfc/5321:3885 - for _, mx := range mxl { - host, err := dns.ParseDomain(strings.TrimSuffix(mx.Host, ".")) - if err != nil { - // note: should not happen because Go resolver already filters these out. - return nil, effectiveDomain, true, fmt.Errorf("%w: invalid host name in mx record %q: %v", errDNS, mx.Host, err) - } - hosts = append(hosts, dns.IPDomain{Domain: host}) - } - if len(hosts) > 0 { - err = nil - } - return hosts, effectiveDomain, false, err - } -} - -// deliverHost attempts to deliver m to host. -// deliverHost updated m.DialedIPs, which must be saved in case of failure to deliver. -func deliverHost(log *mlog.Log, resolver dns.Resolver, dialer contextDialer, cid int64, ourHostname dns.Domain, transportName string, host dns.IPDomain, m *Msg, tlsMode smtpclient.TLSMode) (permanent, badTLS bool, secodeOpt string, remoteIP net.IP, errmsg string, ok bool) { +// deliverHost attempts to deliver m to host. Depending on tlsMode, we'll do +// required TLS with WebPKI verification (with MTA-STS), opportunistic DANE TLS +// (opportunistic TLS) or non-verifying TLS (opportunistic TLS) deliverHost updates +// m.DialedIPs, which must be saved in case of failure to deliver. +// +// The haveMX and next-hop-authentic fields are used to determine if DANE is +// applicable. The next-hop fields themselves are used to determine valid names +// during DANE TLS certificate verification. +func deliverHost(log *mlog.Log, resolver dns.Resolver, dialer smtpclient.Dialer, cid int64, ourHostname dns.Domain, transportName string, host dns.IPDomain, enforceMTASTS, haveMX, origNextHopAuthentic bool, origNextHop dns.Domain, expandedNextHopAuthentic bool, expandedNextHop dns.Domain, m *Msg, tlsMode smtpclient.TLSMode) (permanent, daneRequired, badTLS bool, secodeOpt string, remoteIP net.IP, errmsg string, ok bool) { // About attempting delivery to multiple addresses of a host: ../rfc/5321:3898 start := time.Now() var deliveryResult string defer func() { metricDelivery.WithLabelValues(fmt.Sprintf("%d", m.Attempts), transportName, string(tlsMode), deliveryResult).Observe(float64(time.Since(start)) / float64(time.Second)) - log.Debug("queue deliverhost result", mlog.Field("host", host), mlog.Field("attempt", m.Attempts), mlog.Field("tlsmode", tlsMode), mlog.Field("permanent", permanent), mlog.Field("badtls", badTLS), mlog.Field("secodeopt", secodeOpt), mlog.Field("errmsg", errmsg), mlog.Field("ok", ok), mlog.Field("duration", time.Since(start))) + log.Debug("queue deliverhost result", + mlog.Field("host", host), + mlog.Field("attempt", m.Attempts), + mlog.Field("tlsmode", tlsMode), + mlog.Field("permanent", permanent), + mlog.Field("badtls", badTLS), + mlog.Field("secodeopt", secodeOpt), + mlog.Field("errmsg", errmsg), + mlog.Field("ok", ok), + mlog.Field("duration", time.Since(start))) }() + // Open message to deliver. f, err := os.Open(m.MessagePath()) if err != nil { - return false, false, "", nil, fmt.Sprintf("open message file: %s", err), false + return false, false, false, "", nil, fmt.Sprintf("open message file: %s", err), false } msgr := store.FileMsgReader(m.MsgPrefix, f) defer func() { @@ -276,9 +266,83 @@ func deliverHost(log *mlog.Log, resolver dns.Resolver, dialer contextDialer, cid ctx, cancel := context.WithTimeout(cidctx, 30*time.Second) defer cancel() - conn, ip, dualstack, err := dialHost(ctx, log, resolver, dialer, host, 25, m) - remoteIP = ip + // We must lookup the IPs for the host name before checking DANE TLSA records. And + // only check TLSA records for secure responses. This prevents problems with old + // name servers returning an error for TLSA requests or letting it timeout (not + // sending a response). ../rfc/7672:879 + var daneRecords []adns.TLSA + var tlsRemoteHostnames []dns.Domain + if host.IsDomain() { + tlsRemoteHostnames = []dns.Domain{host.Domain} + } + if m.DialedIPs == nil { + m.DialedIPs = map[string][]net.IP{} + } + metricDestinations.Inc() + authentic, expandedAuthentic, expandedHost, ips, dualstack, err := smtpclient.GatherIPs(ctx, log, resolver, host, m.DialedIPs) + if err == nil && authentic && origNextHopAuthentic && (!haveMX || expandedNextHopAuthentic) && host.IsDomain() { + metricDestinationsAuthentic.Inc() + + // Modes to skip and not verify aren't normally set when we get here. But in the + // future may perhaps be set on a message manually after delivery failures. We can + // handle them here. + switch tlsMode { + case smtpclient.TLSSkip: + // No TLS, so clearly no DANE. + case smtpclient.TLSUnverifiedStartTLS: + // Fallback mode for DANE without usable records, so skip DANE. + default: + // Look for TLSA records in either the expandedHost, or otherwise the original + // host. ../rfc/7672:912 + var tlsaBaseDomain dns.Domain + daneRequired, daneRecords, tlsaBaseDomain, err = smtpclient.GatherTLSA(ctx, log, resolver, host.Domain, expandedNextHopAuthentic && expandedAuthentic, expandedHost) + if daneRequired { + metricDestinationDANERequired.Inc() + } + if err != nil { + metricDestinationDANEGatherTLSAErrors.Inc() + } + if err == nil && daneRequired { + tlsMode = smtpclient.TLSStrictStartTLS + if len(daneRecords) == 0 { + // If there are no usable DANE records, we still have to use TLS, but without + // verifying its certificate. At least when there is no MTA-STS. Why? Perhaps to + // prevent ossification? The SMTP TLSA specification has different behaviour than + // the generic TLSA. "Usable" means different things in different places. + // ../rfc/7672:718 ../rfc/6698:1845 ../rfc/6698:660 + if !enforceMTASTS { + tlsMode = smtpclient.TLSUnverifiedStartTLS + log.Debug("no usable dane records, not verifying dane records, but doing required non-verifying opportunistic tls") + metricDestinationDANESTARTTLSUnverified.Inc() + } + daneRecords = nil + } else { + // Based on CNAMEs followed and DNSSEC-secure status, we must allow up to 4 host + // names. + tlsRemoteHostnames = smtpclient.GatherTLSANames(haveMX, expandedNextHopAuthentic, expandedAuthentic, origNextHop, expandedNextHop, host.Domain, tlsaBaseDomain) + log.Debug("delivery with required starttls with dane verification", mlog.Field("allowedtlshostnames", tlsRemoteHostnames)) + } + } else if !daneRequired { + log.Debugx("not doing opportunistic dane after gathering tlsa records", err) + err = nil + } + // else, err is propagated below. + } + } else { + log.Debugx("not attempting verification with dane", err, mlog.Field("authentic", authentic), mlog.Field("expandedauthentic", expandedAuthentic)) + } + + // Dial the remote host given the IPs if no error yet. + var conn net.Conn + if err == nil { + if m.DialedIPs == nil { + m.DialedIPs = map[string][]net.IP{} + } + conn, remoteIP, err = smtpclient.Dial(ctx, log, dialer, host, ips, 25, m.DialedIPs) + } cancel() + + // Set error for metrics. var result string switch { case err == nil: @@ -293,7 +357,7 @@ func deliverHost(log *mlog.Log, resolver dns.Resolver, dialer contextDialer, cid metricConnection.WithLabelValues(result).Inc() if err != nil { log.Debugx("connecting to remote smtp", err, mlog.Field("host", host)) - return false, false, "", ip, fmt.Sprintf("dialing smtp server: %v", err), false + return false, daneRequired, false, "", remoteIP, fmt.Sprintf("dialing smtp server: %v", err), false } var mailFrom string @@ -303,11 +367,21 @@ func deliverHost(log *mlog.Log, resolver dns.Resolver, dialer contextDialer, cid rcptTo := m.Recipient().XString(m.SMTPUTF8) // todo future: get closer to timeouts specified in rfc? ../rfc/5321:3610 - log = log.Fields(mlog.Field("remoteip", ip)) + log = log.Fields(mlog.Field("remoteip", remoteIP)) ctx, cancel = context.WithTimeout(cidctx, 30*time.Minute) defer cancel() mox.Connections.Register(conn, "smtpclient", "queue") - sc, err := smtpclient.New(ctx, log, conn, tlsMode, ourHostname, host.Domain, nil) + + // Initialize SMTP session, sending EHLO/HELO and STARTTLS with specified tls mode. + var firstHost dns.Domain + var moreHosts []dns.Domain + if len(tlsRemoteHostnames) > 0 { + // For use with DANE-TA. + firstHost = tlsRemoteHostnames[0] + moreHosts = tlsRemoteHostnames[1:] + } + var verifiedRecord adns.TLSA + sc, err := smtpclient.New(ctx, log, conn, tlsMode, ourHostname, firstHost, nil, daneRecords, moreHosts, &verifiedRecord) defer func() { if sc == nil { conn.Close() @@ -317,6 +391,7 @@ func deliverHost(log *mlog.Log, resolver dns.Resolver, dialer contextDialer, cid mox.Connections.Unregister(conn) }() if err == nil { + // SMTP session is ready. Finally try to actually deliver. has8bit := m.Has8bit smtputf8 := m.SMTPUTF8 var msg io.Reader = msgr @@ -349,7 +424,7 @@ func deliverHost(log *mlog.Log, resolver dns.Resolver, dialer contextDialer, cid deliveryResult = "error" } if err == nil { - return false, false, "", ip, "", true + return false, daneRequired, false, "", remoteIP, "", true } else if cerr, ok := err.(smtpclient.Error); ok { // If we are being rejected due to policy reasons on the first // attempt and remote has both IPv4 and IPv6, we'll give it @@ -359,8 +434,8 @@ func deliverHost(log *mlog.Log, resolver dns.Resolver, dialer contextDialer, cid if permanent && m.Attempts == 1 && dualstack && strings.HasPrefix(cerr.Secode, "7.") { permanent = false } - return permanent, errors.Is(cerr, smtpclient.ErrTLS), cerr.Secode, ip, cerr.Error(), false + return permanent, daneRequired, errors.Is(cerr, smtpclient.ErrTLS), cerr.Secode, remoteIP, cerr.Error(), false } else { - return false, errors.Is(cerr, smtpclient.ErrTLS), "", ip, err.Error(), false + return false, daneRequired, errors.Is(cerr, smtpclient.ErrTLS), "", remoteIP, err.Error(), false } } diff --git a/queue/queue.go b/queue/queue.go index 89c4846..dec30fe 100644 --- a/queue/queue.go +++ b/queue/queue.go @@ -30,6 +30,7 @@ import ( "github.com/mjl-/mox/mox-" "github.com/mjl-/mox/moxio" "github.com/mjl-/mox/smtp" + "github.com/mjl-/mox/smtpclient" "github.com/mjl-/mox/store" ) @@ -60,24 +61,6 @@ var ( ) ) -type contextDialer interface { - DialContext(ctx context.Context, network, addr string) (c net.Conn, err error) -} - -// Used to dial remote SMTP servers. -// Overridden for tests. -var dial = func(ctx context.Context, dialer contextDialer, timeout time.Duration, addr string, laddr net.Addr) (net.Conn, error) { - // If this is a net.Dialer, use its settings and add the timeout and localaddr. - // This is the typical case, but SOCKS5 support can use a different dialer. - if d, ok := dialer.(*net.Dialer); ok { - nd := *d - nd.Timeout = timeout - nd.LocalAddr = laddr - return nd.DialContext(ctx, "tcp", addr) - } - return dialer.DialContext(ctx, "tcp", addr) -} - var jitter = mox.NewRand() var DBTypes = []any{Msg{}} // Types stored in DB. @@ -547,7 +530,7 @@ func deliver(resolver dns.Resolver, m Msg) { qlog.Debug("delivering with transport", mlog.Field("transport", transportName)) } - var dialer contextDialer = &net.Dialer{} + var dialer smtpclient.Dialer = &net.Dialer{} if transport.Submissions != nil { deliverSubmit(cid, qlog, resolver, dialer, m, backoff, transportName, transport.Submissions, true, 465) } else if transport.Submission != nil { @@ -561,7 +544,7 @@ func deliver(resolver dns.Resolver, m Msg) { if err != nil { fail(qlog, m, backoff, false, dsn.NameIP{}, "", fmt.Sprintf("socks dialer: %v", err)) return - } else if d, ok := socksdialer.(contextDialer); !ok { + } else if d, ok := socksdialer.(smtpclient.Dialer); !ok { fail(qlog, m, backoff, false, dsn.NameIP{}, "", "socks dialer is not a contextdialer") return } else { @@ -611,98 +594,3 @@ func routeMatchDomain(l []string, d dns.Domain) bool { } return false } - -// dialHost dials host for delivering Msg, taking previous attempts into accounts. -// If the previous attempt used IPv4, this attempt will use IPv6 (in case one of the IPs is in a DNSBL). -// The second attempt for an address family we prefer the same IP as earlier, to increase our chances if remote is doing greylisting. -// dialHost updates m with the dialed IP and m should be saved in case of failure. -// If we have fully specified local smtp listen IPs, we set those for the outgoing -// connection. The admin probably configured these same IPs in SPF, but others -// possibly not. -func dialHost(ctx context.Context, log *mlog.Log, resolver dns.Resolver, dialer contextDialer, host dns.IPDomain, port int, m *Msg) (conn net.Conn, ip net.IP, dualstack bool, rerr error) { - var ips []net.IP - if len(host.IP) > 0 { - ips = []net.IP{host.IP} - } else { - // todo: The Go resolver automatically follows CNAMEs, which is not allowed for - // host names in MX records. ../rfc/5321:3861 ../rfc/2181:661 - name := host.Domain.ASCII + "." - ipaddrs, err := resolver.LookupIPAddr(ctx, name) - if err != nil || len(ipaddrs) == 0 { - return nil, nil, false, fmt.Errorf("looking up %q: %v", name, err) - } - var have4, have6 bool - for _, ipaddr := range ipaddrs { - ips = append(ips, ipaddr.IP) - if ipaddr.IP.To4() == nil { - have6 = true - } else { - have4 = true - } - } - dualstack = have4 && have6 - prevIPs := m.DialedIPs[host.String()] - if len(prevIPs) > 0 { - prevIP := prevIPs[len(prevIPs)-1] - prevIs4 := prevIP.To4() != nil - sameFamily := 0 - for _, ip := range prevIPs { - is4 := ip.To4() != nil - if prevIs4 == is4 { - sameFamily++ - } - } - preferPrev := sameFamily == 1 - // We use stable sort so any preferred/randomized listing from DNS is kept intact. - sort.SliceStable(ips, func(i, j int) bool { - aIs4 := ips[i].To4() != nil - bIs4 := ips[j].To4() != nil - if aIs4 != bIs4 { - // Prefer "i" if it is not same address family. - return aIs4 != prevIs4 - } - // Prefer "i" if it is the same as last and we should be preferring it. - return preferPrev && ips[i].Equal(prevIP) - }) - log.Debug("ordered ips for dialing", mlog.Field("ips", ips)) - } - } - - var timeout time.Duration - deadline, ok := ctx.Deadline() - if !ok { - timeout = 30 * time.Second - } else { - timeout = time.Until(deadline) / time.Duration(len(ips)) - } - - var lastErr error - var lastIP net.IP - for _, ip := range ips { - addr := net.JoinHostPort(ip.String(), fmt.Sprintf("%d", port)) - log.Debug("dialing remote host for delivery", mlog.Field("addr", addr)) - var laddr net.Addr - for _, lip := range mox.Conf.Static.SpecifiedSMTPListenIPs { - ipIs4 := ip.To4() != nil - lipIs4 := lip.To4() != nil - if ipIs4 == lipIs4 { - laddr = &net.TCPAddr{IP: lip} - break - } - } - conn, err := dial(ctx, dialer, timeout, addr, laddr) - if err == nil { - log.Debug("connected for smtp delivery", mlog.Field("host", host), mlog.Field("addr", addr), mlog.Field("laddr", laddr)) - if m.DialedIPs == nil { - m.DialedIPs = map[string][]net.IP{} - } - name := host.String() - m.DialedIPs[name] = append(m.DialedIPs[name], ip) - return conn, ip, dualstack, nil - } - log.Debugx("connection attempt for smtp delivery", err, mlog.Field("host", host), mlog.Field("addr", addr), mlog.Field("laddr", laddr)) - lastErr = err - lastIP = ip - } - return nil, lastIP, dualstack, lastErr -} diff --git a/queue/queue_test.go b/queue/queue_test.go index 5960245..1d7e86e 100644 --- a/queue/queue_test.go +++ b/queue/queue_test.go @@ -5,24 +5,25 @@ import ( "context" "crypto/ed25519" cryptorand "crypto/rand" + "crypto/sha256" "crypto/tls" "crypto/x509" - "errors" "fmt" "io" "math/big" "net" "os" - "reflect" "strings" "testing" "time" + "github.com/mjl-/adns" "github.com/mjl-/bstore" "github.com/mjl-/mox/dns" "github.com/mjl-/mox/mox-" "github.com/mjl-/mox/smtp" + "github.com/mjl-/mox/smtpclient" "github.com/mjl-/mox/store" ) @@ -132,13 +133,18 @@ func TestQueue(t *testing.T) { MX: map[string][]*net.MX{"mox.example.": {{Host: "mox.example", Pref: 10}}}, } dialed := make(chan struct{}, 1) - dial = func(ctx context.Context, dialer contextDialer, timeout time.Duration, addr string, laddr net.Addr) (net.Conn, error) { + smtpclient.DialHook = func(ctx context.Context, dialer smtpclient.Dialer, timeout time.Duration, addr string, laddr net.Addr) (net.Conn, error) { dialed <- struct{}{} return nil, fmt.Errorf("failure from test") } + defer func() { + smtpclient.DialHook = nil + }() launchWork(resolver, map[string]struct{}{}) + moxCert := fakeCert(t, "mox.example", false) + // Wait until we see the dial and the failed attempt. timer := time.NewTimer(time.Second) defer timer.Stop() @@ -192,23 +198,92 @@ func TestQueue(t *testing.T) { // We do a minimal fake smtp server. We cannot import smtpserver.Serve due to cyclic dependencies. fmt.Fprintf(server, "220 mox.example\r\n") br := bufio.NewReader(server) - br.ReadString('\n') // Should be EHLO. - fmt.Fprintf(server, "250 ok\r\n") - br.ReadString('\n') // Should be MAIL FROM. - fmt.Fprintf(server, "250 ok\r\n") - br.ReadString('\n') // Should be RCPT TO. - fmt.Fprintf(server, "250 ok\r\n") - br.ReadString('\n') // Should be DATA. - fmt.Fprintf(server, "354 continue\r\n") + + readline := func(cmd string) { + line, err := br.ReadString('\n') + if err == nil && !strings.HasPrefix(strings.ToLower(line), cmd) { + panic(fmt.Sprintf("unexpected line %q, expected %q", line, cmd)) + } + } + writeline := func(s string) { + fmt.Fprintf(server, "%s\r\n", s) + } + + readline("ehlo") + writeline("250 mox.example") + readline("mail") + writeline("250 ok") + readline("rcpt") + writeline("250 ok") + readline("data") + writeline("354 continue") reader := smtp.NewDataReader(br) io.Copy(io.Discard, reader) - fmt.Fprintf(server, "250 ok\r\n") - br.ReadString('\n') // Should be QUIT. - fmt.Fprintf(server, "221 ok\r\n") + writeline("250 ok") + readline("quit") + writeline("221 ok") smtpdone <- struct{}{} } + goodTLSConfig := tls.Config{Certificates: []tls.Certificate{moxCert}} + makeFakeSMTPSTARTTLSServer := func(tlsConfig *tls.Config, nstarttls int) func(server net.Conn) { + attempt := 0 + return func(server net.Conn) { + attempt++ + + // We do a minimal fake smtp server. We cannot import smtpserver.Serve due to cyclic dependencies. + fmt.Fprintf(server, "220 mox.example\r\n") + br := bufio.NewReader(server) + + readline := func(cmd string) { + line, err := br.ReadString('\n') + if err == nil && !strings.HasPrefix(strings.ToLower(line), cmd) { + panic(fmt.Sprintf("unexpected line %q, expected %q", line, cmd)) + } + } + writeline := func(s string) { + fmt.Fprintf(server, "%s\r\n", s) + } + + readline("ehlo") + writeline("250-mox.example") + writeline("250 starttls") + if nstarttls == 0 || attempt <= nstarttls { + readline("starttls") + writeline("220 ok") + tlsConn := tls.Server(server, tlsConfig) + err := tlsConn.Handshake() + if err != nil { + return + } + server = tlsConn + br = bufio.NewReader(server) + + readline("ehlo") + writeline("250 mox.example") + } + readline("mail") + writeline("250 ok") + readline("rcpt") + writeline("250 ok") + readline("data") + writeline("354 continue") + reader := smtp.NewDataReader(br) + io.Copy(io.Discard, reader) + writeline("250 ok") + readline("quit") + writeline("221 ok") + + smtpdone <- struct{}{} + } + } + + fakeSMTPSTARTTLSServer := makeFakeSMTPSTARTTLSServer(&goodTLSConfig, 0) + makeBadFakeSMTPSTARTTLSServer := func() func(server net.Conn) { + return makeFakeSMTPSTARTTLSServer(&tls.Config{MaxVersion: tls.VersionTLS10, Certificates: []tls.Certificate{moxCert}}, 1) + } + fakeSubmitServer := func(server net.Conn) { // We do a minimal fake smtp server. We cannot import smtpserver.Serve due to cyclic dependencies. fmt.Fprintf(server, "220 mox.example\r\n") @@ -236,19 +311,37 @@ func TestQueue(t *testing.T) { testDeliver := func(fakeServer func(conn net.Conn)) bool { t.Helper() - // Setting up a pipe. We'll start a fake smtp server on the server-side. And return the - // client-side to the invocation dial, for the attempted delivery from the queue. - // The delivery should succeed. - server, client := net.Pipe() - defer server.Close() - defer client.Close() + var pipes []net.Conn + defer func() { + for _, conn := range pipes { + conn.Close() + } + }() var wasNetDialer bool - dial = func(ctx context.Context, dialer contextDialer, timeout time.Duration, addr string, laddr net.Addr) (net.Conn, error) { + smtpclient.DialHook = func(ctx context.Context, dialer smtpclient.Dialer, timeout time.Duration, addr string, laddr net.Addr) (net.Conn, error) { + // Setting up a pipe. We'll start a fake smtp server on the server-side. And return the + // client-side to the invocation dial, for the attempted delivery from the queue. + server, client := net.Pipe() + for _, c := range pipes { + c.Close() + } + pipes = []net.Conn{server, client} + go fakeServer(server) + _, wasNetDialer = dialer.(*net.Dialer) - dialed <- struct{}{} + + // For reconnects, we are already waiting for delivery below. + select { + case dialed <- struct{}{}: + default: + } + return client, nil } + defer func() { + smtpclient.DialHook = nil + }() waitDeliver := func() { t.Helper() @@ -279,7 +372,6 @@ func TestQueue(t *testing.T) { <-deliveryResult // Deliver sends here. } - go fakeServer(server) launchWork(resolver, map[string]struct{}{}) waitDeliver() return wasNetDialer @@ -325,7 +417,7 @@ func TestQueue(t *testing.T) { } // Add a message to be delivered with socks. - msgID, err = Add(ctxbg, xlog, "mjl", path, path, false, false, int64(len(testmsg)), "", nil, prepareFile(t), nil, true) + msgID, err = Add(ctxbg, xlog, "mjl", path, path, false, false, int64(len(testmsg)), "", nil, prepareFile(t), nil, true) tcheck(t, err, "add message to queue for delivery") transportSocks := "socks" n, err = Kick(ctxbg, msgID, "", "", &transportSocks) @@ -338,6 +430,79 @@ func TestQueue(t *testing.T) { t.Fatalf("expected non-net.Dialer as dialer") // SOCKS5 dialer is a private type, we cannot check for it. } + // Add message to be delivered with opportunistic TLS verification. + msgID, err = Add(ctxbg, xlog, "mjl", path, path, false, false, int64(len(testmsg)), "", nil, prepareFile(t), nil, true) + tcheck(t, err, "add message to queue for delivery") + n, err = Kick(ctxbg, msgID, "", "", nil) + tcheck(t, err, "kick queue") + if n != 1 { + t.Fatalf("kick changed %d messages, expected 1", n) + } + testDeliver(fakeSMTPSTARTTLSServer) + + // Test fallback to plain text with TLS handshake fails. + msgID, err = Add(ctxbg, xlog, "mjl", path, path, false, false, int64(len(testmsg)), "", nil, prepareFile(t), nil, true) + tcheck(t, err, "add message to queue for delivery") + n, err = Kick(ctxbg, msgID, "", "", nil) + tcheck(t, err, "kick queue") + if n != 1 { + t.Fatalf("kick changed %d messages, expected 1", n) + } + testDeliver(makeBadFakeSMTPSTARTTLSServer()) + + // Add message to be delivered with DANE verification. + resolver.AllAuthentic = true + resolver.TLSA = map[string][]adns.TLSA{ + "_25._tcp.mox.example.": { + {Usage: adns.TLSAUsageDANEEE, Selector: adns.TLSASelectorSPKI, MatchType: adns.TLSAMatchTypeFull, CertAssoc: moxCert.Leaf.RawSubjectPublicKeyInfo}, + }, + } + msgID, err = Add(ctxbg, xlog, "mjl", path, path, false, false, int64(len(testmsg)), "", nil, prepareFile(t), nil, true) + tcheck(t, err, "add message to queue for delivery") + n, err = Kick(ctxbg, msgID, "", "", nil) + tcheck(t, err, "kick queue") + if n != 1 { + t.Fatalf("kick changed %d messages, expected 1", n) + } + testDeliver(fakeSMTPSTARTTLSServer) + + // Check that message is delivered with all unusable DANE records. + resolver.TLSA = map[string][]adns.TLSA{ + "_25._tcp.mox.example.": { + {}, + }, + } + msgID, err = Add(ctxbg, xlog, "mjl", path, path, false, false, int64(len(testmsg)), "", nil, prepareFile(t), nil, true) + tcheck(t, err, "add message to queue for delivery") + n, err = Kick(ctxbg, msgID, "", "", nil) + tcheck(t, err, "kick queue") + if n != 1 { + t.Fatalf("kick changed %d messages, expected 1", n) + } + testDeliver(fakeSMTPSTARTTLSServer) + + // Check that message is delivered with insecure TLSA records. They should be + // ignored and regular STARTTLS tried. + resolver.Inauthentic = []string{"tlsa _25._tcp.mox.example."} + resolver.TLSA = map[string][]adns.TLSA{ + "_25._tcp.mox.example.": { + {Usage: adns.TLSAUsageDANEEE, Selector: adns.TLSASelectorSPKI, MatchType: adns.TLSAMatchTypeFull, CertAssoc: make([]byte, sha256.Size)}, + }, + } + msgID, err = Add(ctxbg, xlog, "mjl", path, path, false, false, int64(len(testmsg)), "", nil, prepareFile(t), nil, true) + tcheck(t, err, "add message to queue for delivery") + n, err = Kick(ctxbg, msgID, "", "", nil) + tcheck(t, err, "kick queue") + if n != 1 { + t.Fatalf("kick changed %d messages, expected 1", n) + } + testDeliver(makeBadFakeSMTPSTARTTLSServer()) + resolver.Inauthentic = nil + + // Restore pre-DANE behaviour. + resolver.AllAuthentic = false + resolver.TLSA = nil + // Add another message that we'll fail to deliver entirely. _, err = Add(ctxbg, xlog, "mjl", path, path, false, false, int64(len(testmsg)), "", nil, prepareFile(t), nil, true) tcheck(t, err, "add message to queue for delivery") @@ -349,10 +514,10 @@ func TestQueue(t *testing.T) { } msg = msgs[0] - prepServer := func(code string) (net.Conn, func()) { + prepServer := func(fn func(c net.Conn)) (net.Conn, func()) { server, client := net.Pipe() go func() { - fmt.Fprintf(server, "%s mox.example\r\n", code) + fn(server) server.Close() }() return client, func() { @@ -361,15 +526,17 @@ func TestQueue(t *testing.T) { } } - conn2, cleanup2 := prepServer("220") - conn3, cleanup3 := prepServer("451") + conn2, cleanup2 := prepServer(func(conn net.Conn) { fmt.Fprintf(conn, "220 mox.example\r\n") }) + conn3, cleanup3 := prepServer(func(conn net.Conn) { fmt.Fprintf(conn, "451 mox.example\r\n") }) + conn4, cleanup4 := prepServer(fakeSMTPSTARTTLSServer) defer func() { cleanup2() cleanup3() + cleanup4() }() seq := 0 - dial = func(ctx context.Context, dialer contextDialer, timeout time.Duration, addr string, laddr net.Addr) (net.Conn, error) { + smtpclient.DialHook = func(ctx context.Context, dialer smtpclient.Dialer, timeout time.Duration, addr string, laddr net.Addr) (net.Conn, error) { seq++ switch seq { default: @@ -378,14 +545,31 @@ func TestQueue(t *testing.T) { return conn2, nil case 3: return conn3, nil + case 4: + return conn4, nil } } + defer func() { + smtpclient.DialHook = nil + }() comm := store.RegisterComm(acc) defer comm.Unregister() for i := 1; i < 8; i++ { go func() { <-deliveryResult }() // Deliver sends here. + if i == 4 { + resolver.AllAuthentic = true + resolver.TLSA = map[string][]adns.TLSA{ + "_25._tcp.mox.example.": { + // Non-matching zero CertAssoc, should cause failure. + {Usage: adns.TLSAUsageDANEEE, Selector: adns.TLSASelectorSPKI, MatchType: adns.TLSAMatchTypeSHA256, CertAssoc: make([]byte, sha256.Size)}, + }, + } + } else { + resolver.AllAuthentic = false + resolver.TLSA = nil + } deliver(resolver, msg) err = DB.Get(ctxbg, &msg) tcheck(t, err, "get msg") @@ -436,10 +620,13 @@ func TestQueueStart(t *testing.T) { MX: map[string][]*net.MX{"mox.example.": {{Host: "mox.example", Pref: 10}}}, } dialed := make(chan struct{}, 1) - dial = func(ctx context.Context, dialer contextDialer, timeout time.Duration, addr string, laddr net.Addr) (net.Conn, error) { + smtpclient.DialHook = func(ctx context.Context, dialer smtpclient.Dialer, timeout time.Duration, addr string, laddr net.Addr) (net.Conn, error) { dialed <- struct{}{} return nil, fmt.Errorf("failure from test") } + defer func() { + smtpclient.DialHook = nil + }() _, cleanup := setup(t) defer cleanup() @@ -491,139 +678,6 @@ func TestQueueStart(t *testing.T) { time.Sleep(100 * time.Millisecond) // Racy... we won't get notified when work is done... } -func TestGatherHosts(t *testing.T) { - mox.Context = ctxbg - - // Test basic MX lookup case, but also following CNAME, detecting CNAME loops and - // having a CNAME limit, connecting directly to a host, and domain that does not - // exist or has temporary error. - - resolver := dns.MockResolver{ - MX: map[string][]*net.MX{ - "basic.example.": {{Host: "mail.basic.example.", Pref: 10}}, - "multimx.example.": {{Host: "mail1.multimx.example.", Pref: 10}, {Host: "mail2.multimx.example.", Pref: 10}}, - "nullmx.example.": {{Host: ".", Pref: 10}}, - "temperror-mx.example.": {{Host: "absent.example.", Pref: 10}}, - }, - A: map[string][]string{ - "mail.basic.example": {"10.0.0.1"}, - "justhost.example.": {"10.0.0.1"}, // No MX record for domain, only an A record. - "temperror-a.example.": {"10.0.0.1"}, - }, - AAAA: map[string][]string{ - "justhost6.example.": {"2001:db8::1"}, // No MX record for domain, only an AAAA record. - }, - CNAME: map[string]string{ - "cname.example.": "basic.example.", - "cnameloop.example.": "cnameloop2.example.", - "cnameloop2.example.": "cnameloop.example.", - "danglingcname.example.": "absent.example.", // Points to missing name. - "temperror-cname.example.": "absent.example.", - }, - Fail: map[dns.Mockreq]struct{}{ - {Type: "mx", Name: "temperror-mx.example."}: {}, - {Type: "host", Name: "temperror-a.example."}: {}, - {Type: "cname", Name: "temperror-cname.example."}: {}, - }, - } - for i := 0; i <= 16; i++ { - s := fmt.Sprintf("cnamelimit%d.example.", i) - next := fmt.Sprintf("cnamelimit%d.example.", i+1) - resolver.CNAME[s] = next - } - - test := func(ipd dns.IPDomain, expHosts []dns.IPDomain, expDomain dns.Domain, expPerm bool, expErr error) { - t.Helper() - - m := Msg{RecipientDomain: ipd} - hosts, ed, perm, err := gatherHosts(resolver, m, 1, xlog) - if (err == nil) != (expErr == nil) || err != nil && !errors.Is(err, expErr) { - // todo: could also check the individual errors? code currently does not have structured errors. - t.Fatalf("gather hosts: %v", err) - } - if err != nil { - return - } - if !reflect.DeepEqual(hosts, expHosts) || ed != expDomain || perm != expPerm { - t.Fatalf("got hosts %#v, effectiveDomain %#v, permanent %#v, expected %#v %#v %#v", hosts, ed, perm, expHosts, expDomain, expPerm) - } - } - - domain := func(s string) dns.Domain { - d, err := dns.ParseDomain(s) - if err != nil { - t.Fatalf("parse domain: %v", err) - } - return d - } - ipdomain := func(s string) dns.IPDomain { - ip := net.ParseIP(s) - if ip != nil { - return dns.IPDomain{IP: ip} - } - d, err := dns.ParseDomain(s) - if err != nil { - t.Fatalf("parse domain %q: %v", s, err) - } - return dns.IPDomain{Domain: d} - } - - ipdomains := func(s ...string) (l []dns.IPDomain) { - for _, e := range s { - l = append(l, ipdomain(e)) - } - return - } - - var zerodom dns.Domain - - test(ipdomain("10.0.0.1"), ipdomains("10.0.0.1"), zerodom, false, nil) - test(ipdomain("basic.example"), ipdomains("mail.basic.example"), domain("basic.example"), false, nil) // Basic with simple MX. - test(ipdomain("multimx.example"), ipdomains("mail1.multimx.example", "mail2.multimx.example"), domain("multimx.example"), false, nil) // Basic with simple MX. - test(ipdomain("justhost.example"), ipdomains("justhost.example"), domain("justhost.example"), false, nil) // Only an A record. - test(ipdomain("justhost6.example"), ipdomains("justhost6.example"), domain("justhost6.example"), false, nil) // Only an AAAA record. - test(ipdomain("cname.example"), ipdomains("mail.basic.example"), domain("basic.example"), false, nil) // Follow CNAME. - test(ipdomain("cnamelimit1.example"), nil, zerodom, true, errCNAMELimit) - test(ipdomain("cnameloop.example"), nil, zerodom, true, errCNAMELoop) - test(ipdomain("absent.example"), nil, zerodom, true, errNoRecord) - test(ipdomain("danglingcname.example"), nil, zerodom, true, errNoRecord) - test(ipdomain("nullmx.example"), nil, zerodom, true, errNoMail) - test(ipdomain("temperror-mx.example"), nil, zerodom, false, errDNS) - test(ipdomain("temperror-cname.example"), nil, zerodom, false, errDNS) - test(ipdomain("temperror-a.example"), nil, zerodom, false, errDNS) -} - -func TestDialHost(t *testing.T) { - // We mostly want to test that dialing a second time switches to the other address family. - - resolver := dns.MockResolver{ - A: map[string][]string{ - "dualstack.example.": {"10.0.0.1"}, - }, - AAAA: map[string][]string{ - "dualstack.example.": {"2001:db8::1"}, - }, - } - - dial = func(ctx context.Context, dialer contextDialer, timeout time.Duration, addr string, laddr net.Addr) (net.Conn, error) { - return nil, nil // No error, nil connection isn't used. - } - - ipdomain := func(s string) dns.IPDomain { - return dns.IPDomain{Domain: dns.Domain{ASCII: s}} - } - - m := Msg{DialedIPs: map[string][]net.IP{}} - _, ip, dualstack, err := dialHost(ctxbg, xlog, resolver, nil, ipdomain("dualstack.example"), 25, &m) - if err != nil || ip.String() != "10.0.0.1" || !dualstack { - t.Fatalf("expected err nil, address 10.0.0.1, dualstack true, got %v %v %v", err, ip, dualstack) - } - _, ip, dualstack, err = dialHost(ctxbg, xlog, resolver, nil, ipdomain("dualstack.example"), 25, &m) - if err != nil || ip.String() != "2001:db8::1" || !dualstack { - t.Fatalf("expected err nil, address 2001:db8::1, dualstack true, got %v %v %v", err, ip, dualstack) - } -} - // Just a cert that appears valid. func fakeCert(t *testing.T, name string, expired bool) tls.Certificate { notAfter := time.Now() diff --git a/queue/submit.go b/queue/submit.go index 1d0c1af..346e9f7 100644 --- a/queue/submit.go +++ b/queue/submit.go @@ -24,7 +24,7 @@ import ( // deliver via another SMTP server, e.g. relaying to a smart host, possibly // with authentication (submission). -func deliverSubmit(cid int64, qlog *mlog.Log, resolver dns.Resolver, dialer contextDialer, m Msg, backoff time.Duration, transportName string, transport *config.TransportSMTP, dialTLS bool, defaultPort int) { +func deliverSubmit(cid int64, qlog *mlog.Log, resolver dns.Resolver, dialer smtpclient.Dialer, m Msg, backoff time.Duration, transportName string, transport *config.TransportSMTP, dialTLS bool, defaultPort int) { // todo: configurable timeouts port := transport.Port @@ -51,10 +51,25 @@ func deliverSubmit(cid int64, qlog *mlog.Log, resolver dns.Resolver, dialer cont qlog.Debug("queue deliversubmit result", mlog.Field("host", transport.DNSHost), mlog.Field("port", port), mlog.Field("attempt", m.Attempts), mlog.Field("permanent", permanent), mlog.Field("secodeopt", secodeOpt), mlog.Field("errmsg", errmsg), mlog.Field("ok", success), mlog.Field("duration", time.Since(start))) }() + // We don't have to attempt SMTP-DANE for submission, since it only applies to SMTP + // relaying on port 25. ../rfc/7672:1261 + + // todo: for submission, understand SRV records, and even DANE. + dialctx, dialcancel := context.WithTimeout(context.Background(), 30*time.Second) defer dialcancel() + if m.DialedIPs == nil { + m.DialedIPs = map[string][]net.IP{} + } + _, _, _, ips, _, err := smtpclient.GatherIPs(dialctx, qlog, resolver, dns.IPDomain{Domain: transport.DNSHost}, m.DialedIPs) + var conn net.Conn + if err == nil { + if m.DialedIPs == nil { + m.DialedIPs = map[string][]net.IP{} + } + conn, _, err = smtpclient.Dial(dialctx, qlog, dialer, dns.IPDomain{Domain: transport.DNSHost}, ips, port, m.DialedIPs) + } addr := net.JoinHostPort(transport.Host, fmt.Sprintf("%d", port)) - conn, _, _, err := dialHost(dialctx, qlog, resolver, dialer, dns.IPDomain{Domain: transport.DNSHost}, port, &m) var result string switch { case err == nil: @@ -103,7 +118,7 @@ func deliverSubmit(cid int64, qlog *mlog.Log, resolver dns.Resolver, dialer cont } clientctx, clientcancel := context.WithTimeout(context.Background(), 60*time.Second) defer clientcancel() - client, err := smtpclient.New(clientctx, qlog, conn, tlsMode, mox.Conf.Static.HostnameDomain, transport.DNSHost, auth) + client, err := smtpclient.New(clientctx, qlog, conn, tlsMode, mox.Conf.Static.HostnameDomain, transport.DNSHost, auth, nil, nil, nil) if err != nil { smtperr, ok := err.(smtpclient.Error) var remoteMTA dsn.NameIP diff --git a/quickstart.go b/quickstart.go index 432e72b..873afca 100644 --- a/quickstart.go +++ b/quickstart.go @@ -3,6 +3,13 @@ package main import ( "bytes" "context" + "crypto" + "crypto/ecdsa" + "crypto/elliptic" + cryptorand "crypto/rand" + "crypto/rsa" + "crypto/x509" + "encoding/pem" "errors" "fmt" "log" @@ -146,6 +153,36 @@ logging in with IMAP. resolveCtx, resolveCancel := context.WithTimeout(context.Background(), 10*time.Second) defer resolveCancel() + fmt.Printf("Checking if DNS resolvers are DNSSEC-verifying...") + _, resolverDNSSECResult, err := resolver.LookupNS(resolveCtx, ".") + if err != nil { + fmt.Println("") + fatalf("checking dnssec support in resolver: %v", err) + } else if !resolverDNSSECResult.Authentic { + fmt.Printf(` + +WARNING: It looks like the DNS resolvers configured on your system do not +verify DNSSEC, or aren't trusted (by having loopback IPs or through "options +trust-ad" in /etc/resolv.conf). Without DNSSEC, outbound delivery with SMTP +used unprotected MX records, and SMTP STARTTLS connections cannot verify the TLS +certificate with DANE (based on a public key in DNS), and will fallback to +either MTA-STS for verification, or use "opportunistic TLS" with no certificate +verification. + +Recommended action: Install unbound, a DNSSEC-verifying recursive DNS resolver, +and enable support for "extended dns errors" (EDE): + +cat </etc/unbound/unbound.conf.d/ede.conf +server: + ede: yes + val-log-level: 2 +EOF + +`) + } else { + fmt.Println(" OK") + } + // We are going to find the (public) IPs to listen on and possibly the host name. // Start with reasonable defaults. We'll replace them specific IPs, if we can find them. @@ -244,7 +281,7 @@ logging in with IMAP. for _, ip := range publicIPs { revctx, revcancel := context.WithTimeout(resolveCtx, 5*time.Second) defer revcancel() - l, err := resolver.LookupAddr(revctx, ip) + l, _, err := resolver.LookupAddr(revctx, ip) if err != nil { warnf("WARNING: looking up reverse name(s) for %s: %v", ip, err) } @@ -306,7 +343,7 @@ again with the -hostname flag. fmt.Printf("Looking up IPs for hostname %s...", dnshostname) ipctx, ipcancel := context.WithTimeout(resolveCtx, 5*time.Second) defer ipcancel() - ips, err := resolver.LookupIPAddr(ipctx, dnshostname.ASCII+".") + ips, domainDNSSECResult, err := resolver.LookupIPAddr(ipctx, dnshostname.ASCII+".") ipcancel() var xips []net.IPAddr var hostIPs []string @@ -403,6 +440,23 @@ This likely means one of two things: `, dnshostname, err) + } else if !domainDNSSECResult.Authentic { + if !dnswarned { + fmt.Printf("\n") + } + dnswarned = true + fmt.Printf(` +NOTE: It looks like the DNS records of your domain (zone) are not DNSSEC-signed. +Mail servers that send email to your domain, or receive email from your domain, +cannot verify that the MX/SPF/DKIM/DMARC/MTA-STS records they receive are +authentic. DANE, for authenticated delivery without relying on a pool of +certificate authorities, requires DNSSEC, so will not be configured at this +time. +Recommended action: Continue now, but consider enabling DNSSEC for your domain +later at your DNS operator, and adding DANE records for protecting incoming +messages over SMTP. + +`) } if !dnswarned { @@ -421,7 +475,7 @@ This likely means one of two things: go func() { revctx, revcancel := context.WithTimeout(resolveCtx, 5*time.Second) defer revcancel() - addrs, err := resolver.LookupAddr(revctx, s) + addrs, _, err := resolver.LookupAddr(revctx, s) results <- result{s, addrs, err} }() } @@ -581,9 +635,57 @@ many authentication failures). {CertFile: autoconfigbase + "-chain.crt.pem", KeyFile: autoconfigbase + ".key.pem"}, }, } + + fmt.Println( + `Placeholder paths to TLS certificates to be provided by the existing webserver +have been placed in config/mox.conf and need to be edited. + +No private keys for the public listener have been generated for use with DANE. +To configure DANE (which requires DNSSEC), set config field HostPrivateKeyFiles +in the "public" Listener to both RSA 2048-bit and ECDSA P-256 private key files +and check the admin page for the needed DNS records.`) + } else { + // todo: we may want to generate a second set of keys, make the user already add it to the DNS, but keep the private key offline. would require config option to specify a public key only, so the dane records can be generated. + hostRSAPrivateKey, err := rsa.GenerateKey(cryptorand.Reader, 2048) + if err != nil { + fatalf("generating rsa private key for host: %s", err) + } + hostECDSAPrivateKey, err := ecdsa.GenerateKey(elliptic.P256(), cryptorand.Reader) + if err != nil { + fatalf("generating ecsa private key for host: %s", err) + } + now := time.Now() + timestamp := now.Format("20060102T150405") + hostRSAPrivateKeyFile := fmt.Sprintf("hostkeys/%s.%s.%s.privatekey.pkcs8.pem", dnshostname.Name(), timestamp, "rsa2048") + hostECDSAPrivateKeyFile := fmt.Sprintf("hostkeys/%s.%s.%s.privatekey.pkcs8.pem", dnshostname.Name(), timestamp, "ecdsap256") + xwritehostkeyfile := func(path string, key crypto.Signer) { + buf, err := x509.MarshalPKCS8PrivateKey(key) + if err != nil { + fatalf("marshaling host private key to pkcs8 for %s: %s", path, err) + } + var b bytes.Buffer + block := pem.Block{ + Type: "PRIVATE KEY", + Bytes: buf, + } + err = pem.Encode(&b, &block) + if err != nil { + fatalf("pem-encoding host private key file for %s: %s", path, err) + } + xwritefile(path, b.Bytes(), 0600) + } + xwritehostkeyfile(filepath.Join("config", hostRSAPrivateKeyFile), hostRSAPrivateKey) + xwritehostkeyfile(filepath.Join("config", hostECDSAPrivateKeyFile), hostECDSAPrivateKey) + public.TLS = &config.TLS{ ACME: "letsencrypt", + HostPrivateKeyFiles: []string{ + hostRSAPrivateKeyFile, + hostECDSAPrivateKeyFile, + }, + HostPrivateRSA2048Keys: []crypto.Signer{hostRSAPrivateKey}, + HostPrivateECDSAP256Keys: []crypto.Signer{hostECDSAPrivateKey}, } public.AutoconfigHTTPS.Enabled = true public.MTASTSHTTPS.Enabled = true @@ -780,7 +882,7 @@ configured correctly. // priming dns caches with negative/absent records, causing our "quick setup" to // appear to fail or take longer than "quick". - records, err := mox.DomainRecords(confDomain, domain) + records, err := mox.DomainRecords(confDomain, domain, domainDNSSECResult.Authentic) if err != nil { fatalf("making required DNS records") } @@ -837,9 +939,6 @@ To access these from your browser, run "ssh -L 8080:localhost:80 you@yourmachine" locally and open http://localhost:8080/[...]. -For secure email exchange you should have a strictly validating DNSSEC -resolver. An easy and the recommended way is to install unbound. - If you run into problem, have questions/feedback or found a bug, please let us know. Mox needs your help! diff --git a/rfc/index.md b/rfc/index.md index b84fdbe..b442808 100644 --- a/rfc/index.md +++ b/rfc/index.md @@ -122,10 +122,14 @@ https://www.iana.org/assignments/message-headers/message-headers.xhtml 8904 DNS Whitelist (DNSWL) Email Authentication Method Extension # DANE +6394 Use Cases and Requirements for DNS-Based Authentication of Named Entities (DANE) 6698 The DNS-Based Authentication of Named Entities (DANE) Transport Layer Security (TLS) Protocol: TLSA 7218 Adding Acronyms to Simplify Conversations about DNS-Based Authentication of Named Entities (DANE) 7671 The DNS-Based Authentication of Named Entities (DANE) Protocol: Updates and Operational Guidance 7672 SMTP Security via Opportunistic DNS-Based Authentication of Named Entities (DANE) Transport Layer Security (TLS) +7673 Using DNS-Based Authentication of Named Entities (DANE) TLSA Records with SRV Records +7929 DNS-Based Authentication of Named Entities (DANE) Bindings for OpenPGP +8162 Using Secure DNS to Associate Certificates with Domain Names for S/MIME # TLS-RPT 8460 SMTP TLS Reporting @@ -283,6 +287,7 @@ See implementation guide, https://jmap.io/server.html # TLS 6125 Representation and Verification of Domain-Based Application Service Identity within Internet Public Key Infrastructure Using X.509 (PKIX) Certificates in the Context of Transport Layer Security (TLS) +7250 Using Raw Public Keys in Transport Layer Security (TLS) and Datagram Transport Layer Security (DTLS) 7525 Recommendations for Secure Use of Transport Layer Security (TLS) and Datagram Transport Layer Security (DTLS) 8314 Cleartext Considered Obsolete: Use of Transport Layer Security (TLS) for Email Submission and Access 8996 Deprecating TLS 1.0 and TLS 1.1 @@ -321,21 +326,31 @@ See implementation guide, https://jmap.io/server.html 1536 Common DNS Implementation Errors and Suggested Fixes 2181 Clarifications to the DNS Specification 2308 Negative Caching of DNS Queries (DNS NCACHE) +2672 (obsoleted by RFC 6672) Non-Terminal DNS Name Redirection +3226 DNSSEC and IPv6 A6 aware server/resolver message size requirements 3363 Representing Internet Protocol version 6 (IPv6) Addresses in the Domain Name System (DNS) 3596 DNS Extensions to Support IP Version 6 3597 Handling of Unknown DNS Resource Record (RR) Types +3833 Threat Analysis of the Domain Name System (DNS) 4343 Domain Name System (DNS) Case Insensitivity Clarification 4592 The Role of Wildcards in the Domain Name System +5001 DNS Name Server Identifier (NSID) Option 5452 Measures for Making DNS More Resilient against Forged Answers 6604 xNAME RCODE and Status Bits Clarification 6672 DNAME Redirection in the DNS 6891 Extension Mechanisms for DNS (EDNS(0)) 6895 Domain Name System (DNS) IANA Considerations +7686 The ".onion" Special-Use Domain Name 7766 DNS Transport over TCP - Implementation Requirements +7828 The edns-tcp-keepalive EDNS0 Option +7873 Domain Name System (DNS) Cookies 8020 NXDOMAIN: There Really Is Nothing Underneath 8482 Providing Minimal-Sized Responses to DNS Queries That Have QTYPE=ANY 8490 DNS Stateful Operations +8499 DNS Terminology 8767 Serving Stale Data to Improve DNS Resiliency +8914 Extended DNS Errors +9018 Interoperable Domain Name System (DNS) Server Cookies 9210 DNS Transport over TCP - Operational Requirements # DNSSEC @@ -352,6 +367,7 @@ See implementation guide, https://jmap.io/server.html 6014 Cryptographic Algorithm Identifier Allocation for DNSSEC 6781 DNSSEC Operational Practices, Version 2 6840 Clarifications and Implementation Notes for DNS Security (DNSSEC) +7901 CHAIN Query Requests in DNS 8198 Aggressive Use of DNSSEC-Validated Cache 8624 Algorithm Implementation Requirements and Usage Guidance for DNSSEC 8749 Moving DNSSEC Lookaside Validation (DLV) to Historic Status diff --git a/sendmail.go b/sendmail.go index f73fa64..e04eca8 100644 --- a/sendmail.go +++ b/sendmail.go @@ -279,7 +279,8 @@ binary should be setgid that group: xsavecheckf(err, "parsing remote hostname") } - client, err := smtpclient.New(ctx, mlog.New("sendmail"), conn, tlsMode, ourHostname, remoteHostname, auth) + // todo: implement SRV and DANE, allowing for a simpler config file (just the email address & password) + client, err := smtpclient.New(ctx, mlog.New("sendmail"), conn, tlsMode, ourHostname, remoteHostname, auth, nil, nil, nil) xsavecheckf(err, "open smtp session") err = client.Deliver(ctx, submitconf.From, recipient, int64(len(msg)), strings.NewReader(msg), true, false) diff --git a/smtpclient/client.go b/smtpclient/client.go index 3430350..9ff8629 100644 --- a/smtpclient/client.go +++ b/smtpclient/client.go @@ -17,6 +17,9 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" + "github.com/mjl-/adns" + + "github.com/mjl-/mox/dane" "github.com/mjl-/mox/dns" "github.com/mjl-/mox/metrics" "github.com/mjl-/mox/mlog" @@ -49,7 +52,7 @@ var ( ErrSMTPUTF8Unsupported = errors.New("remote smtp server does not implement smtputf8 extension, required by message") ErrStatus = errors.New("remote smtp server sent unexpected response status code") // Relatively common, e.g. when a 250 OK was expected and server sent 451 temporary error. ErrProtocol = errors.New("smtp protocol error") // After a malformed SMTP response or inconsistent multi-line response. - ErrTLS = errors.New("tls error") // E.g. handshake failure, or hostname validation was required and failed. + ErrTLS = errors.New("tls error") // E.g. handshake failure, or hostname verification was required and failed. ErrBotched = errors.New("smtp connection is botched") // Set on a client, and returned for new operations, after an i/o error or malformed SMTP response. ErrClosed = errors.New("client is closed") ) @@ -58,13 +61,20 @@ var ( type TLSMode string const ( - // TLS with STARTTLS for MX SMTP servers, with validated certificate is required: matching name, not expired, trusted by CA. + // Required TLS with STARTTLS for SMTP servers, with either verified DANE TLSA + // record, or a WebPKI-verified certificate (with matching name, not expired, etc). TLSStrictStartTLS TLSMode = "strictstarttls" - // TLS immediately ("implicit TLS"), with validated certificate is required: matching name, not expired, trusted by CA. + // Required TLS with STARTTLS for SMTP servers, without verifiying the certificate. + // This mode is needed to fallback after only unusable DANE records were found + // (e.g. with unknown parameters in the TLSA records). + TLSUnverifiedStartTLS TLSMode = "unverifiedstarttls" + + // TLS immediately ("implicit TLS"), with either verified DANE TLSA records or a + // verified certificate: matching name, not expired, trusted by CA. TLSStrictImmediate TLSMode = "strictimmediate" - // Use TLS if remote claims to support it, but do not validate the certificate + // Use TLS if remote claims to support it, but do not verify the certificate // (not trusted by CA, different host name or expired certificate is accepted). TLSOpportunistic TLSMode = "opportunistic" @@ -80,8 +90,12 @@ type Client struct { // can be wrapped in a tls.Client. We close origConn instead of conn because // closing the TLS connection would send a TLS close notification, which may block // for 5s if the server isn't reading it (because it is also sending it). - origConn net.Conn - conn net.Conn + origConn net.Conn + conn net.Conn + remoteHostname dns.Domain // TLS with SNI and name verification. + daneRecords []adns.TLSA // For authenticating (START)TLS connection. + moreRemoteHostnames []dns.Domain // Additional allowed names in TLS certificate. + verifiedRecord *adns.TLSA // If non-nil, then will be set to verified DANE record if any. r *bufio.Reader w *bufio.Writer @@ -164,21 +178,27 @@ func (e Error) Error() string { // records with preferences, other DNS records, MTA-STS, retries and special // cases into account. // -// tlsMode indicates if TLS is required, optional or should not be used. A -// certificate is only validated (trusted, match remoteHostname and not expired) -// for the strict tls modes. By default, SMTP does not verify TLS for -// interopability reasons, but MTA-STS or DANE can require it. If opportunistic TLS -// is used, and a TLS error is encountered, the caller may want to try again (on a -// new connection) without TLS. +// tlsMode indicates if TLS is required, optional or should not be used. Only for +// strict TLS modes is the certificate verified: Either with DANE, or through +// the trusted CA pool with matching remoteHostname and not expired. For DANE, +// additional host names in moreRemoteHostnames are allowed during TLS certificate +// verification. By default, SMTP does not verify TLS for interopability reasons, +// but MTA-STS or DANE can require it. If opportunistic TLS is used, and a TLS +// error is encountered, the caller may want to try again (on a new connection) +// without TLS. // // If auth is non-empty, authentication will be done with the first algorithm // supported by the server. If none of the algorithms are supported, an error is // returned. -func New(ctx context.Context, log *mlog.Log, conn net.Conn, tlsMode TLSMode, ourHostname, remoteHostname dns.Domain, auth []sasl.Client) (*Client, error) { +func New(ctx context.Context, log *mlog.Log, conn net.Conn, tlsMode TLSMode, ehloHostname, remoteHostname dns.Domain, auth []sasl.Client, daneRecords []adns.TLSA, moreRemoteHostnames []dns.Domain, verifiedRecord *adns.TLSA) (*Client, error) { c := &Client{ - origConn: conn, - lastlog: time.Now(), - cmds: []string{"(none)"}, + origConn: conn, + remoteHostname: remoteHostname, + daneRecords: daneRecords, + moreRemoteHostnames: moreRemoteHostnames, + verifiedRecord: verifiedRecord, + lastlog: time.Now(), + cmds: []string{"(none)"}, } c.log = log.Fields(mlog.Field("smtpclient", "")).MoreFields(func() []mlog.Pair { now := time.Now() @@ -190,12 +210,9 @@ func New(ctx context.Context, log *mlog.Log, conn net.Conn, tlsMode TLSMode, our }) if tlsMode == TLSStrictImmediate { - tlsconfig := tls.Config{ - ServerName: remoteHostname.ASCII, - RootCAs: mox.Conf.Static.TLS.CertPool, - MinVersion: tls.VersionTLS12, // ../rfc/8996:31 ../rfc/8997:66 - } - tlsconn := tls.Client(conn, &tlsconfig) + // todo: we could also verify DANE here. not applicable to SMTP delivery. + config := c.tlsConfig(tlsMode) + tlsconn := tls.Client(conn, &config) if err := tlsconn.HandshakeContext(ctx); err != nil { return nil, err } @@ -216,12 +233,25 @@ func New(ctx context.Context, log *mlog.Log, conn net.Conn, tlsMode TLSMode, our c.tw = moxio.NewTraceWriter(c.log, "LC: ", timeoutWriter{c.conn, 30 * time.Second, c.log}) c.w = bufio.NewWriter(c.tw) - if err := c.hello(ctx, tlsMode, ourHostname, remoteHostname, auth); err != nil { + if err := c.hello(ctx, tlsMode, ehloHostname, auth); err != nil { return nil, err } return c, nil } +func (c *Client) tlsConfig(tlsMode TLSMode) tls.Config { + if c.daneRecords != nil { + return dane.TLSClientConfig(c.log, c.daneRecords, c.remoteHostname, c.moreRemoteHostnames, c.verifiedRecord) + } + // todo: possibly accept older TLS versions for TLSOpportunistic? + return tls.Config{ + ServerName: c.remoteHostname.ASCII, + RootCAs: mox.Conf.Static.TLS.CertPool, + InsecureSkipVerify: tlsMode == TLSOpportunistic || tlsMode == TLSUnverifiedStartTLS, + MinVersion: tls.VersionTLS12, // ../rfc/8996:31 ../rfc/8997:66 + } +} + // xbotchf generates a temporary error and marks the client as botched. e.g. for // i/o errors or invalid protocol messages. func (c *Client) xbotchf(code int, secode string, lastLine, format string, args ...any) { @@ -463,7 +493,7 @@ func (c *Client) recover(rerr *error) { *rerr = cerr } -func (c *Client) hello(ctx context.Context, tlsMode TLSMode, ourHostname, remoteHostname dns.Domain, auth []sasl.Client) (rerr error) { +func (c *Client) hello(ctx context.Context, tlsMode TLSMode, ehloHostname dns.Domain, auth []sasl.Client) (rerr error) { defer c.recover(&rerr) // perform EHLO handshake, falling back to HELO if server does not appear to @@ -474,7 +504,7 @@ func (c *Client) hello(ctx context.Context, tlsMode TLSMode, ourHostname, remote c.cmds[0] = "ehlo" c.cmdStart = time.Now() // Syntax: ../rfc/5321:1827 - c.xwritelinef("EHLO %s", ourHostname.ASCII) + c.xwritelinef("EHLO %s", ehloHostname.ASCII) code, _, lastLine, remains := c.xreadecode(false) switch code { // ../rfc/5321:997 @@ -486,7 +516,7 @@ func (c *Client) hello(ctx context.Context, tlsMode TLSMode, ourHostname, remote // ../rfc/5321:996 c.cmds[0] = "helo" c.cmdStart = time.Now() - c.xwritelinef("HELO %s", ourHostname.ASCII) + c.xwritelinef("HELO %s", ehloHostname.ASCII) code, _, lastLine, _ = c.xreadecode(false) if code != smtp.C250Completed { c.xerrorf(code/100 == 5, code, "", lastLine, "%w: expected 250 to HELO, got %d", ErrStatus, code) @@ -536,8 +566,8 @@ func (c *Client) hello(ctx context.Context, tlsMode TLSMode, ourHostname, remote hello(true) // Attempt TLS if remote understands STARTTLS and we aren't doing immediate TLS or if caller requires it. - if c.extStartTLS && (tlsMode != TLSSkip && tlsMode != TLSStrictImmediate) || tlsMode == TLSStrictStartTLS { - c.log.Debug("starting tls client", mlog.Field("tlsmode", tlsMode), mlog.Field("servername", remoteHostname)) + if c.extStartTLS && (tlsMode != TLSSkip && tlsMode != TLSStrictImmediate) || tlsMode == TLSStrictStartTLS || tlsMode == TLSUnverifiedStartTLS { + c.log.Debug("starting tls client", mlog.Field("tlsmode", tlsMode), mlog.Field("servername", c.remoteHostname)) c.cmds[0] = "starttls" c.cmdStart = time.Now() c.xwritelinef("STARTTLS") @@ -561,14 +591,8 @@ func (c *Client) hello(ctx context.Context, tlsMode TLSMode, ourHostname, remote // For TLSStrictStartTLS, the Go TLS library performs the checks needed for MTA-STS. // ../rfc/8461:646 - // todo: possibly accept older TLS versions for TLSOpportunistic? - tlsConfig := &tls.Config{ - ServerName: remoteHostname.ASCII, - RootCAs: mox.Conf.Static.TLS.CertPool, - InsecureSkipVerify: tlsMode != TLSStrictStartTLS, - MinVersion: tls.VersionTLS12, // ../rfc/8996:31 ../rfc/8997:66 - } - nconn := tls.Client(conn, tlsConfig) + tlsConfig := c.tlsConfig(tlsMode) + nconn := tls.Client(conn, &tlsConfig) c.conn = nconn nctx, cancel := context.WithTimeout(ctx, time.Minute) @@ -584,7 +608,7 @@ func (c *Client) hello(ctx context.Context, tlsMode TLSMode, ourHostname, remote c.w = bufio.NewWriter(c.tw) tlsversion, ciphersuite := mox.TLSInfo(nconn) - c.log.Debug("starttls client handshake done", mlog.Field("tls", tlsversion), mlog.Field("ciphersuite", ciphersuite), mlog.Field("servername", remoteHostname), mlog.Field("insecureskipverify", tlsConfig.InsecureSkipVerify)) + c.log.Debug("starttls client handshake done", mlog.Field("tlsmode", tlsMode), mlog.Field("tls", tlsversion), mlog.Field("ciphersuite", ciphersuite), mlog.Field("servername", c.remoteHostname), mlog.Field("danerecord", c.verifiedRecord)) hello(false) } @@ -916,3 +940,13 @@ func (c *Client) Close() (rerr error) { } return } + +// Conn returns the connection with initialized SMTP session. Once the caller uses +// this connection it is in control, and responsible for closing the connection, +// and other functions on the client must not be called anymore. +func (c *Client) Conn() (net.Conn, error) { + if err := c.conn.SetDeadline(time.Time{}); err != nil { + return nil, fmt.Errorf("clearing io deadlines: %w", err) + } + return c.conn, nil +} diff --git a/smtpclient/client_test.go b/smtpclient/client_test.go index e9c024c..8fdd359 100644 --- a/smtpclient/client_test.go +++ b/smtpclient/client_test.go @@ -271,7 +271,7 @@ func TestClient(t *testing.T) { result <- fmt.Errorf("client: %w", fmt.Errorf(format, args...)) panic("stop") } - c, err := New(ctx, log, clientConn, opts.tlsMode, localhost, opts.tlsHostname, auths) + c, err := New(ctx, log, clientConn, opts.tlsMode, localhost, opts.tlsHostname, auths, nil, nil, nil) if (err == nil) != (expClientErr == nil) || err != nil && !errors.As(err, reflect.New(reflect.ValueOf(expClientErr).Type()).Interface()) && !errors.Is(err, expClientErr) { fail("new client: got err %v, expected %#v", err, expClientErr) } @@ -373,7 +373,7 @@ func TestErrors(t *testing.T) { run(t, func(s xserver) { s.writeline("bogus") // Invalid, should be "220 ". }, func(conn net.Conn) { - _, err := New(ctx, log, conn, TLSOpportunistic, localhost, zerohost, nil) + _, err := New(ctx, log, conn, TLSOpportunistic, localhost, zerohost, nil, nil, nil, nil) var xerr Error if err == nil || !errors.Is(err, ErrProtocol) || !errors.As(err, &xerr) || xerr.Permanent { panic(fmt.Errorf("got %#v, expected ErrProtocol without Permanent", err)) @@ -384,7 +384,7 @@ func TestErrors(t *testing.T) { run(t, func(s xserver) { s.conn.Close() }, func(conn net.Conn) { - _, err := New(ctx, log, conn, TLSOpportunistic, localhost, zerohost, nil) + _, err := New(ctx, log, conn, TLSOpportunistic, localhost, zerohost, nil, nil, nil, nil) var xerr Error if err == nil || !errors.Is(err, io.ErrUnexpectedEOF) || !errors.As(err, &xerr) || xerr.Permanent { panic(fmt.Errorf("got %#v (%v), expected ErrUnexpectedEOF without Permanent", err, err)) @@ -395,7 +395,7 @@ func TestErrors(t *testing.T) { run(t, func(s xserver) { s.writeline("521 not accepting connections") }, func(conn net.Conn) { - _, err := New(ctx, log, conn, TLSOpportunistic, localhost, zerohost, nil) + _, err := New(ctx, log, conn, TLSOpportunistic, localhost, zerohost, nil, nil, nil, nil) var xerr Error if err == nil || !errors.Is(err, ErrStatus) || !errors.As(err, &xerr) || !xerr.Permanent { panic(fmt.Errorf("got %#v, expected ErrStatus with Permanent", err)) @@ -406,7 +406,7 @@ func TestErrors(t *testing.T) { run(t, func(s xserver) { s.writeline("2200 mox.example") // Invalid, too many digits. }, func(conn net.Conn) { - _, err := New(ctx, log, conn, TLSOpportunistic, localhost, zerohost, nil) + _, err := New(ctx, log, conn, TLSOpportunistic, localhost, zerohost, nil, nil, nil, nil) var xerr Error if err == nil || !errors.Is(err, ErrProtocol) || !errors.As(err, &xerr) || xerr.Permanent { panic(fmt.Errorf("got %#v, expected ErrProtocol without Permanent", err)) @@ -420,7 +420,7 @@ func TestErrors(t *testing.T) { s.writeline("250-mox.example") s.writeline("500 different code") // Invalid. }, func(conn net.Conn) { - _, err := New(ctx, log, conn, TLSOpportunistic, localhost, zerohost, nil) + _, err := New(ctx, log, conn, TLSOpportunistic, localhost, zerohost, nil, nil, nil, nil) var xerr Error if err == nil || !errors.Is(err, ErrProtocol) || !errors.As(err, &xerr) || xerr.Permanent { panic(fmt.Errorf("got %#v, expected ErrProtocol without Permanent", err)) @@ -436,7 +436,7 @@ func TestErrors(t *testing.T) { s.readline("MAIL FROM:") s.writeline("550 5.7.0 not allowed") }, func(conn net.Conn) { - c, err := New(ctx, log, conn, TLSOpportunistic, localhost, zerohost, nil) + c, err := New(ctx, log, conn, TLSOpportunistic, localhost, zerohost, nil, nil, nil, nil) if err != nil { panic(err) } @@ -456,7 +456,7 @@ func TestErrors(t *testing.T) { s.readline("MAIL FROM:") s.writeline("451 bad sender") }, func(conn net.Conn) { - c, err := New(ctx, log, conn, TLSOpportunistic, localhost, zerohost, nil) + c, err := New(ctx, log, conn, TLSOpportunistic, localhost, zerohost, nil, nil, nil, nil) if err != nil { panic(err) } @@ -478,7 +478,7 @@ func TestErrors(t *testing.T) { s.readline("RCPT TO:") s.writeline("451") }, func(conn net.Conn) { - c, err := New(ctx, log, conn, TLSOpportunistic, localhost, zerohost, nil) + c, err := New(ctx, log, conn, TLSOpportunistic, localhost, zerohost, nil, nil, nil, nil) if err != nil { panic(err) } @@ -502,7 +502,7 @@ func TestErrors(t *testing.T) { s.readline("DATA") s.writeline("550 no!") }, func(conn net.Conn) { - c, err := New(ctx, log, conn, TLSOpportunistic, localhost, zerohost, nil) + c, err := New(ctx, log, conn, TLSOpportunistic, localhost, zerohost, nil, nil, nil, nil) if err != nil { panic(err) } @@ -522,7 +522,7 @@ func TestErrors(t *testing.T) { s.readline("STARTTLS") s.writeline("502 command not implemented") }, func(conn net.Conn) { - _, err := New(ctx, log, conn, TLSStrictStartTLS, localhost, dns.Domain{ASCII: "mox.example"}, nil) + _, err := New(ctx, log, conn, TLSStrictStartTLS, localhost, dns.Domain{ASCII: "mox.example"}, nil, nil, nil, nil) var xerr Error if err == nil || !errors.Is(err, ErrTLS) || !errors.As(err, &xerr) || !xerr.Permanent { panic(fmt.Errorf("got %#v, expected ErrTLS with Permanent", err)) @@ -538,7 +538,7 @@ func TestErrors(t *testing.T) { s.readline("MAIL FROM:") s.writeline("451 enough") }, func(conn net.Conn) { - c, err := New(ctx, log, conn, TLSSkip, localhost, dns.Domain{ASCII: "mox.example"}, nil) + c, err := New(ctx, log, conn, TLSSkip, localhost, dns.Domain{ASCII: "mox.example"}, nil, nil, nil, nil) if err != nil { panic(err) } @@ -568,7 +568,7 @@ func TestErrors(t *testing.T) { s.readline("DATA") s.writeline("550 not now") }, func(conn net.Conn) { - c, err := New(ctx, log, conn, TLSOpportunistic, localhost, zerohost, nil) + c, err := New(ctx, log, conn, TLSOpportunistic, localhost, zerohost, nil, nil, nil, nil) if err != nil { panic(err) } @@ -598,7 +598,7 @@ func TestErrors(t *testing.T) { s.readline("MAIL FROM:") s.writeline("550 ok") }, func(conn net.Conn) { - c, err := New(ctx, log, conn, TLSOpportunistic, localhost, zerohost, nil) + c, err := New(ctx, log, conn, TLSOpportunistic, localhost, zerohost, nil, nil, nil, nil) if err != nil { panic(err) } diff --git a/smtpclient/dial.go b/smtpclient/dial.go new file mode 100644 index 0000000..95626a4 --- /dev/null +++ b/smtpclient/dial.go @@ -0,0 +1,87 @@ +package smtpclient + +import ( + "context" + "fmt" + "net" + "time" + + "github.com/mjl-/mox/dns" + "github.com/mjl-/mox/mlog" + "github.com/mjl-/mox/mox-" +) + +// DialHook can be used during tests to override the regular dialer from being used. +var DialHook func(ctx context.Context, dialer Dialer, timeout time.Duration, addr string, laddr net.Addr) (net.Conn, error) + +func dial(ctx context.Context, dialer Dialer, timeout time.Duration, addr string, laddr net.Addr) (net.Conn, error) { + // todo: see if we can remove this function and DialHook in favor of the Dialer interface. + + if DialHook != nil { + return DialHook(ctx, dialer, timeout, addr, laddr) + } + + // If this is a net.Dialer, use its settings and add the timeout and localaddr. + // This is the typical case, but SOCKS5 support can use a different dialer. + if d, ok := dialer.(*net.Dialer); ok { + nd := *d + nd.Timeout = timeout + nd.LocalAddr = laddr + return nd.DialContext(ctx, "tcp", addr) + } + return dialer.DialContext(ctx, "tcp", addr) +} + +// Dialer is used to dial mail servers, an interface to facilitate testing. +type Dialer interface { + DialContext(ctx context.Context, network, addr string) (c net.Conn, err error) +} + +// Dial connects to host by dialing ips, taking previous attempts in dialedIPs into +// accounts (for greylisting, blocklisting and ipv4/ipv6). +// +// If the previous attempt used IPv4, this attempt will use IPv6 (in case one of +// the IPs is in a DNSBL). +// The second attempt for an address family we prefer the same IP as earlier, to +// increase our chances if remote is doing greylisting. +// +// Dial updates dialedIPs, callers may want to save it so it can be taken into +// account for future delivery attempts. +// +// If we have fully specified local SMTP listener IPs, we set those for the +// outgoing connection. The admin probably configured these same IPs in SPF, but +// others possibly not. +func Dial(ctx context.Context, log *mlog.Log, dialer Dialer, host dns.IPDomain, ips []net.IP, port int, dialedIPs map[string][]net.IP) (conn net.Conn, ip net.IP, rerr error) { + timeout := 30 * time.Second + if deadline, ok := ctx.Deadline(); ok && len(ips) > 0 { + timeout = time.Until(deadline) / time.Duration(len(ips)) + } + + var lastErr error + var lastIP net.IP + for _, ip := range ips { + addr := net.JoinHostPort(ip.String(), fmt.Sprintf("%d", port)) + log.Debug("dialing host", mlog.Field("addr", addr)) + var laddr net.Addr + for _, lip := range mox.Conf.Static.SpecifiedSMTPListenIPs { + ipIs4 := ip.To4() != nil + lipIs4 := lip.To4() != nil + if ipIs4 == lipIs4 { + laddr = &net.TCPAddr{IP: lip} + break + } + } + conn, err := dial(ctx, dialer, timeout, addr, laddr) + if err == nil { + log.Debug("connected to host", mlog.Field("host", host), mlog.Field("addr", addr), mlog.Field("laddr", laddr)) + name := host.String() + dialedIPs[name] = append(dialedIPs[name], ip) + return conn, ip, nil + } + log.Debugx("connection attempt", err, mlog.Field("host", host), mlog.Field("addr", addr), mlog.Field("laddr", laddr)) + lastErr = err + lastIP = ip + } + // todo: possibly return all errors joined? + return nil, lastIP, lastErr +} diff --git a/smtpclient/dial_test.go b/smtpclient/dial_test.go new file mode 100644 index 0000000..97f3eae --- /dev/null +++ b/smtpclient/dial_test.go @@ -0,0 +1,57 @@ +package smtpclient + +import ( + "context" + "net" + "reflect" + "testing" + "time" + + "github.com/mjl-/mox/dns" + "github.com/mjl-/mox/mlog" +) + +func TestDialHost(t *testing.T) { + // We mostly want to test that dialing a second time switches to the other address family. + ctxbg := context.Background() + log := mlog.New("smtpclient") + + resolver := dns.MockResolver{ + A: map[string][]string{ + "dualstack.example.": {"10.0.0.1"}, + }, + AAAA: map[string][]string{ + "dualstack.example.": {"2001:db8::1"}, + }, + } + + DialHook = func(ctx context.Context, dialer Dialer, timeout time.Duration, addr string, laddr net.Addr) (net.Conn, error) { + return nil, nil // No error, nil connection isn't used. + } + defer func() { + DialHook = nil + }() + + ipdomain := func(s string) dns.IPDomain { + return dns.IPDomain{Domain: dns.Domain{ASCII: s}} + } + + dialedIPs := map[string][]net.IP{} + _, _, _, ips, dualstack, err := GatherIPs(ctxbg, log, resolver, ipdomain("dualstack.example"), dialedIPs) + if err != nil || !reflect.DeepEqual(ips, []net.IP{net.ParseIP("10.0.0.1"), net.ParseIP("2001:db8::1")}) || !dualstack { + t.Fatalf("expected err nil, address 10.0.0.1,2001:db8::1, dualstack true, got %v %v %v", err, ips, dualstack) + } + _, ip, err := Dial(ctxbg, log, nil, ipdomain("dualstack.example"), ips, 25, dialedIPs) + if err != nil || ip.String() != "10.0.0.1" { + t.Fatalf("expected err nil, address 10.0.0.1, dualstack true, got %v %v %v", err, ip, dualstack) + } + + _, _, _, ips, dualstack, err = GatherIPs(ctxbg, log, resolver, ipdomain("dualstack.example"), dialedIPs) + if err != nil || !reflect.DeepEqual(ips, []net.IP{net.ParseIP("2001:db8::1"), net.ParseIP("10.0.0.1")}) || !dualstack { + t.Fatalf("expected err nil, address 2001:db8::1,10.0.0.1, dualstack true, got %v %v %v", err, ips, dualstack) + } + _, ip, err = Dial(ctxbg, log, nil, ipdomain("dualstack.example"), ips, 25, dialedIPs) + if err != nil || ip.String() != "2001:db8::1" { + t.Fatalf("expected err nil, address 2001:db8::1, dualstack true, got %v %v %v", err, ip, dualstack) + } +} diff --git a/smtpclient/gather.go b/smtpclient/gather.go new file mode 100644 index 0000000..55f468a --- /dev/null +++ b/smtpclient/gather.go @@ -0,0 +1,419 @@ +package smtpclient + +import ( + "context" + "crypto/sha256" + "crypto/sha512" + "crypto/x509" + "errors" + "fmt" + "net" + "sort" + "strings" + "time" + + "github.com/mjl-/adns" + + "github.com/mjl-/mox/dns" + "github.com/mjl-/mox/mlog" +) + +var ( + errCNAMELoop = errors.New("cname loop") + errCNAMELimit = errors.New("too many cname records") + errDNS = errors.New("dns lookup error") + errNoMail = errors.New("domain does not accept email as indicated with single dot for mx record") +) + +// GatherDestinations looks up the hosts to deliver email to a domain ("next-hop"). +// If it is an IP address, it is the only destination to try. Otherwise CNAMEs of +// the domain are followed. Then MX records for the expanded CNAME are looked up. +// If no MX record is present, the original domain is returned. If an MX record is +// present but indicates the domain does not accept email, ErrNoMail is returned. +// If valid MX records were found, the MX target hosts are returned. +// +// haveMX indicates if an MX record was found. +// +// origNextHopAuthentic indicates if the DNS record for the initial domain name was +// DNSSEC secure (CNAME, MX). +// +// expandedNextHopAuthentic indicates if the DNS records after following CNAMEs were +// DNSSEC secure. +// +// These authentic flags are used by DANE, to determine where to look up TLSA +// records, and which names to allow in the remote TLS certificate. If MX records +// were found, both the original and expanded next-hops must be authentic for DANE +// to apply. For a non-IP with no MX records found, the authentic result can be +// used to decide which of the names to use as TLSA base domain. +func GatherDestinations(ctx context.Context, log *mlog.Log, resolver dns.Resolver, origNextHop dns.IPDomain) (haveMX, origNextHopAuthentic, expandedNextHopAuthentic bool, expandedNextHop dns.Domain, hosts []dns.IPDomain, permanent bool, err error) { + // ../rfc/5321:3824 + + // IP addresses are dialed directly, and don't have TLSA records. + if len(origNextHop.IP) > 0 { + return false, false, false, expandedNextHop, []dns.IPDomain{origNextHop}, false, nil + } + + // We start out assuming the result is authentic. Updated with each lookup. + origNextHopAuthentic = true + expandedNextHopAuthentic = true + + // We start out delivering to the recipient domain. We follow CNAMEs. + rcptDomain := origNextHop.Domain + // Domain we are actually delivering to, after following CNAME record(s). + expandedNextHop = rcptDomain + // Keep track of CNAMEs we have followed, to detect loops. + domainsSeen := map[string]bool{} + for i := 0; ; i++ { + if domainsSeen[expandedNextHop.ASCII] { + // todo: only mark as permanent failure if TTLs for all records are beyond latest possibly delivery retry we would do. + err := fmt.Errorf("%w: recipient domain %s: already saw %s", errCNAMELoop, rcptDomain, expandedNextHop) + return false, origNextHopAuthentic, expandedNextHopAuthentic, expandedNextHop, nil, false, err + } + domainsSeen[expandedNextHop.ASCII] = true + + // note: The Go resolver returns the requested name if the domain has no CNAME + // record but has a host record. + if i == 16 { + // We have a maximum number of CNAME records we follow. There is no hard limit for + // DNS, and you might think folks wouldn't configure CNAME chains at all, but for + // (non-mail) domains, CNAME chains of 10 records have been encountered according + // to the internet. + // todo: only mark as permanent failure if TTLs for all records are beyond latest possibly delivery retry we would do. + err := fmt.Errorf("%w: recipient domain %s, last resolved domain %s", errCNAMELimit, rcptDomain, expandedNextHop) + return false, origNextHopAuthentic, expandedNextHopAuthentic, expandedNextHop, nil, false, err + } + + // Do explicit CNAME lookup. Go's LookupMX also resolves CNAMEs, but we want to + // know the final name, and we're interested in learning if the first vs later + // results were DNSSEC-(in)secure. + // ../rfc/5321:3838 ../rfc/3974:197 + cctx, ccancel := context.WithTimeout(ctx, 30*time.Second) + defer ccancel() + cname, cnameResult, err := resolver.LookupCNAME(cctx, expandedNextHop.ASCII+".") + ccancel() + if i == 0 { + origNextHopAuthentic = origNextHopAuthentic && cnameResult.Authentic + } + expandedNextHopAuthentic = expandedNextHopAuthentic && cnameResult.Authentic + if err != nil && !dns.IsNotFound(err) { + err = fmt.Errorf("%w: cname lookup for %s: %v", errDNS, expandedNextHop, err) + return false, origNextHopAuthentic, expandedNextHopAuthentic, expandedNextHop, nil, false, err + } + if err == nil && cname != expandedNextHop.ASCII+"." { + d, err := dns.ParseDomain(strings.TrimSuffix(cname, ".")) + if err != nil { + // todo: only mark as permanent failure if TTLs for all records are beyond latest possibly delivery retry we would do. + err = fmt.Errorf("%w: parsing cname domain %s: %v", errDNS, expandedNextHop, err) + return false, origNextHopAuthentic, expandedNextHopAuthentic, expandedNextHop, nil, false, err + } + expandedNextHop = d + // Start again with new domain. + continue + } + + // Not a CNAME, so lookup MX record. + mctx, mcancel := context.WithTimeout(ctx, 30*time.Second) + defer mcancel() + // Note: LookupMX can return an error and still return records: Invalid records are + // filtered out and an error returned. We must process any records that are valid. + // Only if all are unusable will we return an error. ../rfc/5321:3851 + mxl, mxResult, err := resolver.LookupMX(mctx, expandedNextHop.ASCII+".") + mcancel() + if i == 0 { + origNextHopAuthentic = origNextHopAuthentic && mxResult.Authentic + } + expandedNextHopAuthentic = expandedNextHopAuthentic && mxResult.Authentic + if err != nil && len(mxl) == 0 { + if !dns.IsNotFound(err) { + err = fmt.Errorf("%w: mx lookup for %s: %v", errDNS, expandedNextHop, err) + return false, origNextHopAuthentic, expandedNextHopAuthentic, expandedNextHop, nil, false, err + } + + // No MX record, attempt delivery directly to host. ../rfc/5321:3842 + hosts = []dns.IPDomain{{Domain: expandedNextHop}} + return false, origNextHopAuthentic, expandedNextHopAuthentic, expandedNextHop, hosts, false, nil + } else if err != nil { + log.Infox("mx record has some invalid records, keeping only the valid mx records", err) + } + + // ../rfc/7505:122 + if err == nil && len(mxl) == 1 && mxl[0].Host == "." { + // Note: Depending on MX record TTL, this record may be replaced with a more + // receptive MX record before our final delivery attempt. But it's clearly the + // explicit desire not to be bothered with email delivery attempts, so mark failure + // as permanent. + return true, origNextHopAuthentic, expandedNextHopAuthentic, expandedNextHop, nil, true, errNoMail + } + + // The Go resolver already sorts by preference, randomizing records of same + // preference. ../rfc/5321:3885 + for _, mx := range mxl { + host, err := dns.ParseDomain(strings.TrimSuffix(mx.Host, ".")) + if err != nil { + // note: should not happen because Go resolver already filters these out. + err = fmt.Errorf("%w: invalid host name in mx record %q: %v", errDNS, mx.Host, err) + return true, origNextHopAuthentic, expandedNextHopAuthentic, expandedNextHop, nil, true, err + } + hosts = append(hosts, dns.IPDomain{Domain: host}) + } + if len(hosts) > 0 { + err = nil + } + return true, origNextHopAuthentic, expandedNextHopAuthentic, expandedNextHop, hosts, false, err + } +} + +// GatherIPs looks up the IPs to try for connecting to host, with the IPs ordered +// to take previous attempts into account. For use with DANE, the CNAME-expanded +// name is returned, and whether the DNS responses were authentic. +func GatherIPs(ctx context.Context, log *mlog.Log, resolver dns.Resolver, host dns.IPDomain, dialedIPs map[string][]net.IP) (authentic bool, expandedAuthentic bool, expandedHost dns.Domain, ips []net.IP, dualstack bool, rerr error) { + if len(host.IP) > 0 { + return false, false, dns.Domain{}, []net.IP{host.IP}, false, nil + } + + authentic = true + expandedAuthentic = true + + // The Go resolver automatically follows CNAMEs, which is not allowed for host + // names in MX records, but seems to be accepted and is documented for DANE SMTP + // behaviour. We resolve CNAMEs explicitly, so we can return the final name, which + // DANE needs. ../rfc/7671:246 + // ../rfc/5321:3861 ../rfc/2181:661 ../rfc/7672:1382 ../rfc/7671:1030 + name := host.Domain.ASCII + "." + + for i := 0; ; i++ { + cname, result, err := resolver.LookupCNAME(ctx, name) + if i == 0 { + authentic = result.Authentic + } + expandedAuthentic = expandedAuthentic && result.Authentic + if dns.IsNotFound(err) { + break + } else if err != nil { + return authentic, expandedAuthentic, dns.Domain{}, nil, dualstack, err + } else if strings.TrimSuffix(cname, ".") == strings.TrimSuffix(name, ".") { + break + } + if i > 10 { + return authentic, expandedAuthentic, dns.Domain{}, nil, dualstack, fmt.Errorf("mx lookup: %w", errCNAMELimit) + } + name = strings.TrimSuffix(cname, ".") + "." + } + + if name == host.Domain.ASCII+"." { + expandedHost = host.Domain + } else { + var err error + expandedHost, err = dns.ParseDomain(strings.TrimSuffix(name, ".")) + if err != nil { + return authentic, expandedAuthentic, dns.Domain{}, nil, dualstack, fmt.Errorf("parsing cname-resolved domain: %w", err) + } + } + + ipaddrs, result, err := resolver.LookupIPAddr(ctx, name) + authentic = authentic && result.Authentic + expandedAuthentic = expandedAuthentic && result.Authentic + if err != nil || len(ipaddrs) == 0 { + return authentic, expandedAuthentic, expandedHost, nil, false, fmt.Errorf("looking up %q: %w", name, err) + } + var have4, have6 bool + for _, ipaddr := range ipaddrs { + ips = append(ips, ipaddr.IP) + if ipaddr.IP.To4() == nil { + have6 = true + } else { + have4 = true + } + } + dualstack = have4 && have6 + prevIPs := dialedIPs[host.String()] + if len(prevIPs) > 0 { + prevIP := prevIPs[len(prevIPs)-1] + prevIs4 := prevIP.To4() != nil + sameFamily := 0 + for _, ip := range prevIPs { + is4 := ip.To4() != nil + if prevIs4 == is4 { + sameFamily++ + } + } + preferPrev := sameFamily == 1 + // We use stable sort so any preferred/randomized listing from DNS is kept intact. + sort.SliceStable(ips, func(i, j int) bool { + aIs4 := ips[i].To4() != nil + bIs4 := ips[j].To4() != nil + if aIs4 != bIs4 { + // Prefer "i" if it is not same address family. + return aIs4 != prevIs4 + } + // Prefer "i" if it is the same as last and we should be preferring it. + return preferPrev && ips[i].Equal(prevIP) + }) + log.Debug("ordered ips for dialing", mlog.Field("ips", ips)) + } + return +} + +// GatherTLSA looks up TLSA record for either expandedHost or host, and returns +// records usable for DANE with SMTP, and host names to allow in DANE-TA +// certificate name verification. +// +// If no records are found, this isn't necessarily an error. It can just indicate +// the domain/host does not opt-in to DANE, and nil records and a nil error are +// returned. +// +// Only usable records are returned. If any record was found, DANE is required and +// this is indicated with daneRequired. If no usable records remain, the caller +// must do TLS, but not verify the remote TLS certificate. +func GatherTLSA(ctx context.Context, log *mlog.Log, resolver dns.Resolver, host dns.Domain, expandedAuthentic bool, expandedHost dns.Domain) (daneRequired bool, daneRecords []adns.TLSA, tlsaBaseDomain dns.Domain, err error) { + // ../rfc/7672:912 + // This function is only called when the lookup of host was authentic. + + var l []adns.TLSA + + if host == expandedHost || !expandedAuthentic { + tlsaBaseDomain = host + l, err = lookupTLSACNAME(ctx, log, resolver, 25, "tcp", host) + } else if expandedAuthentic { + // ../rfc/7672:934 + tlsaBaseDomain = expandedHost + l, err = lookupTLSACNAME(ctx, log, resolver, 25, "tcp", expandedHost) + if err == nil && len(l) == 0 { + tlsaBaseDomain = host + l, err = lookupTLSACNAME(ctx, log, resolver, 25, "tcp", host) + } + } + if len(l) == 0 || err != nil { + daneRequired = err != nil + log.Debugx("gathering tlsa records failed", err, mlog.Field("danerequired", daneRequired)) + return daneRequired, nil, dns.Domain{}, err + } + daneRequired = len(l) > 0 + l = filterUsableTLSARecords(log, l) + log.Debug("tlsa records exist", mlog.Field("danerequired", daneRequired), mlog.Field("records", l), mlog.Field("basedomain", tlsaBaseDomain)) + return daneRequired, l, tlsaBaseDomain, err +} + +// lookupTLSACNAME composes a TLSA domain name to lookup, follows CNAMEs and looks +// up TLSA records. no TLSA records exist, a nil error is returned as it means +// the host does not opt-in to DANE. +func lookupTLSACNAME(ctx context.Context, log *mlog.Log, resolver dns.Resolver, port int, protocol string, host dns.Domain) (l []adns.TLSA, rerr error) { + name := fmt.Sprintf("_%d._%s.%s", port, protocol, host.ASCII+".") + for i := 0; ; i++ { + cname, result, err := resolver.LookupCNAME(ctx, name) + if dns.IsNotFound(err) { + if !result.Authentic { + log.Debugx("cname nxdomain result during tlsa lookup not authentic, not doing dane for host", err, mlog.Field("host", host), mlog.Field("name", name)) + return nil, nil + } + break + } else if err != nil { + return nil, fmt.Errorf("looking up cname for tlsa candidate base domain: %w", err) + } else if !result.Authentic { + log.Debugx("cname result during tlsa lookup not authentic, not doing dane for host", err, mlog.Field("host", host), mlog.Field("name", name)) + return nil, nil + } + if i == 10 { + return nil, fmt.Errorf("looking up cname for tlsa candidate base domain: %w", errCNAMELimit) + } + name = strings.TrimSuffix(cname, ".") + "." + } + var result adns.Result + var err error + l, result, err = resolver.LookupTLSA(ctx, 0, "", name) + if dns.IsNotFound(err) || err == nil && len(l) == 0 { + log.Debugx("no tlsa records for host, not doing dane", err, mlog.Field("host", host), mlog.Field("name", name), mlog.Field("authentic", result.Authentic)) + return nil, nil + } else if err != nil { + return nil, fmt.Errorf("looking up tlsa records for tlsa candidate base domain: %w", err) + } else if !result.Authentic { + log.Debugx("tlsa lookup not authentic, not doing dane for host", err, mlog.Field("host", host), mlog.Field("name", name)) + return nil, err + } + return l, nil +} + +func filterUsableTLSARecords(log *mlog.Log, l []adns.TLSA) []adns.TLSA { + // Gather "usable" records. ../rfc/7672:708 + o := 0 + for _, r := range l { + // A record is not usable when we don't recognize parameters. ../rfc/6698:649 + + switch r.Usage { + case adns.TLSAUsageDANETA, adns.TLSAUsageDANEEE: + default: + // We can regard PKIX-TA and PKIX-EE as "unusable" with SMTP DANE. ../rfc/7672:1304 + continue + } + switch r.Selector { + case adns.TLSASelectorCert, adns.TLSASelectorSPKI: + default: + continue + } + switch r.MatchType { + case adns.TLSAMatchTypeFull: + if r.Selector == adns.TLSASelectorCert { + if _, err := x509.ParseCertificate(r.CertAssoc); err != nil { + log.Debugx("parsing certificate in dane tlsa record, ignoring", err) + continue + } + } else if r.Selector == adns.TLSASelectorSPKI { + if _, err := x509.ParsePKIXPublicKey(r.CertAssoc); err != nil { + log.Debugx("parsing certificate in dane tlsa record, ignoring", err) + continue + } + } + case adns.TLSAMatchTypeSHA256: + if len(r.CertAssoc) != sha256.Size { + log.Debug("dane tlsa record with wrong data size for sha2-256", mlog.Field("got", len(r.CertAssoc)), mlog.Field("expect", sha256.Size)) + continue + } + case adns.TLSAMatchTypeSHA512: + if len(r.CertAssoc) != sha512.Size { + log.Debug("dane tlsa record with wrong data size for sha2-512", mlog.Field("got", len(r.CertAssoc)), mlog.Field("expect", sha512.Size)) + continue + } + default: + continue + } + + l[o] = r + o++ + } + return l[:o] +} + +// GatherTLSANames returns the allowed names in TLS certificates for verification +// with PKIX-* or DANE-TA. The first name should be used for SNI. +// +// If there was no MX record, the next-hop domain parameters (i.e. the original +// email destination host, and its CNAME-expanded host, that has MX records) are +// ignored and only the base domain parameters are taken into account. +func GatherTLSANames(haveMX, expandedNextHopAuthentic, expandedTLSABaseDomainAuthentic bool, origNextHop, expandedNextHop, origTLSABaseDomain, expandedTLSABaseDomain dns.Domain) []dns.Domain { + // Gather the names to check against TLS certificate. ../rfc/7672:1318 + if !haveMX { + // ../rfc/7672:1336 + if !expandedTLSABaseDomainAuthentic || origTLSABaseDomain == expandedTLSABaseDomain { + return []dns.Domain{origTLSABaseDomain} + } + return []dns.Domain{expandedTLSABaseDomain, origTLSABaseDomain} + } else if expandedNextHopAuthentic { + // ../rfc/7672:1326 + var l []dns.Domain + if expandedTLSABaseDomainAuthentic { + l = []dns.Domain{expandedTLSABaseDomain} + } + if expandedTLSABaseDomain != origTLSABaseDomain { + l = append(l, origTLSABaseDomain) + } + l = append(l, origNextHop) + if origNextHop != expandedNextHop { + l = append(l, expandedNextHop) + } + return l + } else { + // We don't attempt DANE after insecure MX, but behaviour for it is specified. + // ../rfc/7672:1332 + return []dns.Domain{origNextHop} + } +} diff --git a/smtpclient/gather_test.go b/smtpclient/gather_test.go new file mode 100644 index 0000000..043a129 --- /dev/null +++ b/smtpclient/gather_test.go @@ -0,0 +1,309 @@ +package smtpclient + +import ( + "context" + "crypto/sha256" + "errors" + "fmt" + "net" + "reflect" + "testing" + + "github.com/mjl-/adns" + + "github.com/mjl-/mox/dns" + "github.com/mjl-/mox/mlog" +) + +func domain(s string) dns.Domain { + d, err := dns.ParseDomain(s) + if err != nil { + panic("parse domain: " + err.Error()) + } + return d +} + +func ipdomain(s string) dns.IPDomain { + ip := net.ParseIP(s) + if ip != nil { + return dns.IPDomain{IP: ip} + } + d, err := dns.ParseDomain(s) + if err != nil { + panic(fmt.Sprintf("parse domain %q: %v", s, err)) + } + return dns.IPDomain{Domain: d} +} + +func ipdomains(s ...string) (l []dns.IPDomain) { + for _, e := range s { + l = append(l, ipdomain(e)) + } + return +} + +// Test basic MX lookup case, but also following CNAME, detecting CNAME loops and +// having a CNAME limit, connecting directly to a host, and domain that does not +// exist or has temporary error. +func TestGatherDestinations(t *testing.T) { + ctxbg := context.Background() + log := mlog.New("smtpclient") + + resolver := dns.MockResolver{ + MX: map[string][]*net.MX{ + "basic.example.": {{Host: "mail.basic.example.", Pref: 10}}, + "multimx.example.": {{Host: "mail1.multimx.example.", Pref: 10}, {Host: "mail2.multimx.example.", Pref: 10}}, + "nullmx.example.": {{Host: ".", Pref: 10}}, + "temperror-mx.example.": {{Host: "absent.example.", Pref: 10}}, + }, + A: map[string][]string{ + "mail.basic.example": {"10.0.0.1"}, + "justhost.example.": {"10.0.0.1"}, // No MX record for domain, only an A record. + "temperror-a.example.": {"10.0.0.1"}, + }, + AAAA: map[string][]string{ + "justhost6.example.": {"2001:db8::1"}, // No MX record for domain, only an AAAA record. + }, + CNAME: map[string]string{ + "cname.example.": "basic.example.", + "cname-to-inauthentic.example.": "cnameinauthentic.example.", + "cnameinauthentic.example.": "basic.example.", + "cnameloop.example.": "cnameloop2.example.", + "cnameloop2.example.": "cnameloop.example.", + "danglingcname.example.": "absent.example.", // Points to missing name. + "temperror-cname.example.": "absent.example.", + }, + Fail: map[dns.Mockreq]struct{}{ + {Type: "mx", Name: "temperror-mx.example."}: {}, + {Type: "host", Name: "temperror-a.example."}: {}, + {Type: "cname", Name: "temperror-cname.example."}: {}, + }, + Inauthentic: []string{"cname cnameinauthentic.example."}, + } + for i := 0; i <= 16; i++ { + s := fmt.Sprintf("cnamelimit%d.example.", i) + next := fmt.Sprintf("cnamelimit%d.example.", i+1) + resolver.CNAME[s] = next + } + + test := func(ipd dns.IPDomain, expHosts []dns.IPDomain, expDomain dns.Domain, expPerm, expAuthic, expExpAuthic bool, expErr error) { + t.Helper() + + _, authic, authicExp, ed, hosts, perm, err := GatherDestinations(ctxbg, log, resolver, ipd) + if (err == nil) != (expErr == nil) || err != nil && !errors.Is(err, expErr) { + // todo: could also check the individual errors? code currently does not have structured errors. + t.Fatalf("gather hosts: %v, expected %v", err, expErr) + } + if err != nil { + return + } + if !reflect.DeepEqual(hosts, expHosts) || ed != expDomain || perm != expPerm || authic != expAuthic || authicExp != expExpAuthic { + t.Fatalf("got hosts %#v, effectiveDomain %#v, permanent %#v, authic %v %v, expected %#v %#v %#v %v %v", hosts, ed, perm, authic, authicExp, expHosts, expDomain, expPerm, expAuthic, expExpAuthic) + } + } + + var zerodom dns.Domain + + for i := 0; i < 2; i++ { + authic := i == 1 + resolver.AllAuthentic = authic + // Basic with simple MX. + test(ipdomain("basic.example"), ipdomains("mail.basic.example"), domain("basic.example"), false, authic, authic, nil) + test(ipdomain("multimx.example"), ipdomains("mail1.multimx.example", "mail2.multimx.example"), domain("multimx.example"), false, authic, authic, nil) + // Only an A record. + test(ipdomain("justhost.example"), ipdomains("justhost.example"), domain("justhost.example"), false, authic, authic, nil) + // Only an AAAA record. + test(ipdomain("justhost6.example"), ipdomains("justhost6.example"), domain("justhost6.example"), false, authic, authic, nil) + // Follow CNAME. + test(ipdomain("cname.example"), ipdomains("mail.basic.example"), domain("basic.example"), false, authic, authic, nil) + // No MX/CNAME, non-existence of host will be found out later. + test(ipdomain("absent.example"), ipdomains("absent.example"), domain("absent.example"), false, authic, authic, nil) + // Followed CNAME, has no MX, non-existence of host will be found out later. + test(ipdomain("danglingcname.example"), ipdomains("absent.example"), domain("absent.example"), false, authic, authic, nil) + test(ipdomain("cnamelimit1.example"), nil, zerodom, true, authic, authic, errCNAMELimit) + test(ipdomain("cnameloop.example"), nil, zerodom, true, authic, authic, errCNAMELoop) + test(ipdomain("nullmx.example"), nil, zerodom, true, authic, authic, errNoMail) + test(ipdomain("temperror-mx.example"), nil, zerodom, false, authic, authic, errDNS) + test(ipdomain("temperror-cname.example"), nil, zerodom, false, authic, authic, errDNS) + } + + test(ipdomain("10.0.0.1"), ipdomains("10.0.0.1"), zerodom, false, false, false, nil) + test(ipdomain("cnameinauthentic.example"), ipdomains("mail.basic.example"), domain("basic.example"), false, false, false, nil) + test(ipdomain("cname-to-inauthentic.example"), ipdomains("mail.basic.example"), domain("basic.example"), false, true, false, nil) +} + +func TestGatherIPs(t *testing.T) { + ctxbg := context.Background() + log := mlog.New("smtpclient") + + resolver := dns.MockResolver{ + A: map[string][]string{ + "host1.example.": {"10.0.0.1"}, + "host2.example.": {"10.0.0.2"}, + "temperror-a.example.": {"10.0.0.3"}, + }, + AAAA: map[string][]string{ + "host2.example.": {"2001:db8::1"}, + }, + CNAME: map[string]string{ + "cname1.example.": "host1.example.", + "cname-to-inauthentic.example.": "cnameinauthentic.example.", + "cnameinauthentic.example.": "host1.example.", + "cnameloop.example.": "cnameloop2.example.", + "cnameloop2.example.": "cnameloop.example.", + "danglingcname.example.": "absent.example.", // Points to missing name. + "temperror-cname.example.": "absent.example.", + }, + Fail: map[dns.Mockreq]struct{}{ + {Type: "host", Name: "temperror-a.example."}: {}, + {Type: "cname", Name: "temperror-cname.example."}: {}, + }, + Inauthentic: []string{"cname cnameinauthentic.example."}, + } + + test := func(host dns.IPDomain, expAuthic, expAuthicExp bool, expHostExp dns.Domain, expIPs []net.IP, expErr any) { + t.Helper() + + authic, authicExp, hostExp, ips, _, err := GatherIPs(ctxbg, log, resolver, host, nil) + if (err == nil) != (expErr == nil) || err != nil && !(errors.Is(err, expErr.(error)) || errors.As(err, &expErr)) { + // todo: could also check the individual errors? + t.Fatalf("gather hosts: %v, expected %v", err, expErr) + } + if err != nil { + return + } + if expHostExp == zerohost { + expHostExp = host.Domain + } + if authic != expAuthic || authicExp != expAuthicExp || hostExp != expHostExp || !reflect.DeepEqual(ips, expIPs) { + t.Fatalf("got authic %v %v, host %v, ips %v, expected %v %v %v %v", authic, authicExp, hostExp, ips, expAuthic, expAuthicExp, expHostExp, expIPs) + } + } + + ips := func(l ...string) (r []net.IP) { + for _, s := range l { + r = append(r, net.ParseIP(s)) + } + return r + } + + for i := 0; i < 2; i++ { + authic := i == 1 + resolver.AllAuthentic = authic + + test(ipdomain("host1.example"), authic, authic, zerohost, ips("10.0.0.1"), nil) + test(ipdomain("host2.example"), authic, authic, zerohost, ips("10.0.0.2", "2001:db8::1"), nil) + test(ipdomain("cname-to-inauthentic.example"), authic, false, domain("host1.example"), ips("10.0.0.1"), nil) + test(ipdomain("cnameloop.example"), authic, authic, zerohost, nil, errCNAMELimit) + test(ipdomain("bogus.example"), authic, authic, zerohost, nil, &adns.DNSError{}) + test(ipdomain("danglingcname.example"), authic, authic, zerohost, nil, &adns.DNSError{}) + test(ipdomain("temperror-a.example"), authic, authic, zerohost, nil, &adns.DNSError{}) + test(ipdomain("temperror-cname.example"), authic, authic, zerohost, nil, &adns.DNSError{}) + + } + test(ipdomain("cnameinauthentic.example"), false, false, domain("host1.example"), ips("10.0.0.1"), nil) + test(ipdomain("cname-to-inauthentic.example"), true, false, domain("host1.example"), ips("10.0.0.1"), nil) +} + +func TestGatherTLSA(t *testing.T) { + ctxbg := context.Background() + log := mlog.New("smtpclient") + + record := func(usage, selector, matchType uint8) adns.TLSA { + return adns.TLSA{ + Usage: adns.TLSAUsage(usage), + Selector: adns.TLSASelector(selector), + MatchType: adns.TLSAMatchType(matchType), + CertAssoc: make([]byte, sha256.Size), // Assume sha256. + } + } + records := func(l ...adns.TLSA) []adns.TLSA { + return l + } + + record0 := record(3, 1, 1) + list0 := records(record0) + record1 := record(3, 0, 1) + list1 := records(record1) + + resolver := dns.MockResolver{ + TLSA: map[string][]adns.TLSA{ + "_25._tcp.host0.example.": list0, + "_25._tcp.host1.example.": list1, + "_25._tcp.inauthentic.example.": list1, + "_25._tcp.temperror-cname.example.": list1, + }, + CNAME: map[string]string{ + "_25._tcp.cname.example.": "_25._tcp.host1.example.", + "_25._tcp.cnameloop.example.": "_25._tcp.cnameloop2.example.", + "_25._tcp.cnameloop2.example.": "_25._tcp.cnameloop.example.", + "_25._tcp.cname-to-inauthentic.example.": "_25._tcp.cnameinauthentic.example.", + "_25._tcp.cnameinauthentic.example.": "_25._tcp.host1.example.", + "_25._tcp.danglingcname.example.": "_25._tcp.absent.example.", // Points to missing name. + }, + Fail: map[dns.Mockreq]struct{}{ + {Type: "cname", Name: "_25._tcp.temperror-cname.example."}: {}, + }, + Inauthentic: []string{ + "cname _25._tcp.cnameinauthentic.example.", + "tlsa _25._tcp.inauthentic.example.", + }, + } + + test := func(host dns.Domain, expandedAuthentic bool, expandedHost dns.Domain, expDANERequired bool, expRecords []adns.TLSA, expBaseDom dns.Domain, expErr any) { + t.Helper() + + daneReq, records, baseDom, err := GatherTLSA(ctxbg, log, resolver, host, expandedAuthentic, expandedHost) + if (err == nil) != (expErr == nil) || err != nil && !(errors.Is(err, expErr.(error)) || errors.As(err, &expErr)) { + // todo: could also check the individual errors? + t.Fatalf("gather tlsa: %v, expected %v", err, expErr) + } + if daneReq != expDANERequired { + t.Fatalf("got daneRequired %v, expected %v", daneReq, expDANERequired) + } + if err != nil { + return + } + if !reflect.DeepEqual(records, expRecords) || baseDom != expBaseDom { + t.Fatalf("got records, baseDomain %v %v, expected %v %v", records, baseDom, expRecords, expBaseDom) + } + } + + resolver.AllAuthentic = true + test(domain("host1.example"), false, domain("host1.example"), true, list1, domain("host1.example"), nil) + test(domain("host1.example"), true, domain("host1.example"), true, list1, domain("host1.example"), nil) + test(domain("host0.example"), true, domain("host1.example"), true, list1, domain("host1.example"), nil) + test(domain("host0.example"), false, domain("host1.example"), true, list0, domain("host0.example"), nil) + + // CNAME for TLSA at cname.example should be followed. + test(domain("host0.example"), true, domain("cname.example"), true, list1, domain("cname.example"), nil) + // TLSA records at original domain should be followed. + test(domain("host0.example"), false, domain("cname.example"), true, list0, domain("host0.example"), nil) + + test(domain("cnameloop.example"), false, domain("cnameloop.example"), true, nil, zerohost, errCNAMELimit) + + test(domain("host0.example"), false, domain("inauthentic.example"), true, list0, domain("host0.example"), nil) + test(domain("inauthentic.example"), false, domain("inauthentic.example"), false, nil, zerohost, nil) + test(domain("temperror-cname.example"), false, domain("temperror-cname.example"), true, nil, zerohost, &adns.DNSError{}) + + test(domain("host1.example"), true, domain("cname-to-inauthentic.example"), true, list1, domain("host1.example"), nil) + test(domain("host1.example"), true, domain("danglingcname.example"), true, list1, domain("host1.example"), nil) + test(domain("danglingcname.example"), true, domain("danglingcname.example"), false, nil, zerohost, nil) +} + +func TestGatherTLSANames(t *testing.T) { + a, b, c, d := domain("nexthop.example"), domain("nexthopexpanded.example"), domain("base.example"), domain("baseexpanded.example") + test := func(haveMX, nexthopExpAuth, tlsabaseExpAuth bool, expDoms ...dns.Domain) { + t.Helper() + doms := GatherTLSANames(haveMX, nexthopExpAuth, tlsabaseExpAuth, a, b, c, d) + if !reflect.DeepEqual(doms, expDoms) { + t.Fatalf("got domains %v, expected %v", doms, expDoms) + } + } + + test(false, false, false, c) + test(false, false, true, d, c) + test(true, true, true, d, c, a, b) + test(true, true, false, c, a, b) + test(true, false, false, a) +} diff --git a/smtpserver/mx.go b/smtpserver/mx.go index d41a168..4f7d701 100644 --- a/smtpserver/mx.go +++ b/smtpserver/mx.go @@ -11,7 +11,7 @@ import ( // i.e. if it has no null mx record, regular mx records or resolve to an address. func checkMXRecords(ctx context.Context, resolver dns.Resolver, d dns.Domain) (bool, error) { // Note: LookupMX can return an error and still return records. - mx, err := resolver.LookupMX(ctx, d.ASCII+".") + mx, _, err := resolver.LookupMX(ctx, d.ASCII+".") if err == nil && len(mx) == 1 && mx[0].Host == "." { // Null MX record, explicit signal that remote does not accept email. return false, nil @@ -25,7 +25,7 @@ func checkMXRecords(ctx context.Context, resolver dns.Resolver, d dns.Domain) (b } var lastErr error for _, x := range mx { - ips, err := resolver.LookupIPAddr(ctx, x.Host) + ips, _, err := resolver.LookupIPAddr(ctx, x.Host) if len(ips) > 0 { return true, nil } diff --git a/smtpserver/server.go b/smtpserver/server.go index 8008ff4..8dd55e6 100644 --- a/smtpserver/server.go +++ b/smtpserver/server.go @@ -789,7 +789,7 @@ func (c *conn) cmdHello(p *parser, ehlo bool) { // Verify a remote domain name has an A or AAAA record, CNAME not allowed. ../rfc/5321:722 cidctx := context.WithValue(mox.Context, mlog.CidKey, c.cid) ctx, cancel := context.WithTimeout(cidctx, time.Minute) - _, err := c.resolver.LookupIPAddr(ctx, remote.Domain.ASCII+".") + _, _, err := c.resolver.LookupIPAddr(ctx, remote.Domain.ASCII+".") cancel() if dns.IsNotFound(err) { xsmtpUserErrorf(smtp.C550MailboxUnavail, smtp.SeProto5Other0, "your ehlo domain does not resolve to an IP address") @@ -1413,7 +1413,7 @@ func (c *conn) cmdRcpt(p *parser) { cidctx := context.WithValue(mox.Context, mlog.CidKey, c.cid) spfctx, spfcancel := context.WithTimeout(cidctx, time.Minute) defer spfcancel() - receivedSPF, _, _, err := spf.Verify(spfctx, c.resolver, spfArgs) + receivedSPF, _, _, _, err := spf.Verify(spfctx, c.resolver, spfArgs) spfcancel() if err != nil { c.log.Errorx("spf verify for multiple recipients", err) @@ -1573,6 +1573,7 @@ func (c *conn) cmdData(p *parser) { // ../rfc/5321:3311 ../rfc/6531:578 var recvFrom string var iprevStatus iprev.Status // Only for delivery, not submission. + var iprevAuthentic bool if c.submission { // Hide internal hosts. // todo future: make this a config option, where admins specify ip ranges that they don't want exposed. also see ../rfc/5321:4321 @@ -1588,7 +1589,7 @@ func (c *conn) cmdData(p *parser) { iprevctx, iprevcancel := context.WithTimeout(cmdctx, time.Minute) var revName string var revNames []string - iprevStatus, revName, revNames, err = iprev.Lookup(iprevctx, c.resolver, c.remoteIP) + iprevStatus, revName, revNames, iprevAuthentic, err = iprev.Lookup(iprevctx, c.resolver, c.remoteIP) iprevcancel() if err != nil { c.log.Infox("reverse-forward lookup", err, mlog.Field("remoteip", c.remoteIP)) @@ -1655,7 +1656,7 @@ func (c *conn) cmdData(p *parser) { if c.submission { c.submit(cmdctx, recvHdrFor, msgWriter, &dataFile) } else { - c.deliver(cmdctx, recvHdrFor, msgWriter, iprevStatus, &dataFile) + c.deliver(cmdctx, recvHdrFor, msgWriter, iprevStatus, iprevAuthentic, &dataFile) } } @@ -1859,7 +1860,7 @@ func (c *conn) xlocalserveError(lp smtp.Localpart) { // deliver is called for incoming messages from external, typically untrusted // sources. i.e. not submitted by authenticated users. -func (c *conn) deliver(ctx context.Context, recvHdrFor func(string) string, msgWriter *message.Writer, iprevStatus iprev.Status, pdataFile **os.File) { +func (c *conn) deliver(ctx context.Context, recvHdrFor func(string) string, msgWriter *message.Writer, iprevStatus iprev.Status, iprevAuthentic bool, pdataFile **os.File) { dataFile := *pdataFile // todo: in decision making process, if we run into (some) temporary errors, attempt to continue. if we decide to accept, all good. if we decide to reject, we'll make it a temporary reject. @@ -1879,12 +1880,20 @@ func (c *conn) deliver(ctx context.Context, recvHdrFor func(string) string, msgW Hostname: mox.Conf.Static.HostnameDomain.XName(c.smtputf8), } + commentAuthentic := func(v bool) string { + if v { + return "with dnssec" + } + return "without dnssec" + } + // Reverse IP lookup results. // todo future: how useful is this? // ../rfc/5321:2481 authResults.Methods = append(authResults.Methods, message.AuthMethod{ - Method: "iprev", - Result: string(iprevStatus), + Method: "iprev", + Result: string(iprevStatus), + Comment: commentAuthentic(iprevAuthentic), Props: []message.AuthProp{ message.MakeAuthProp("policy", "iprev", c.remoteIP.String(), false, ""), }, @@ -1923,6 +1932,7 @@ func (c *conn) deliver(ctx context.Context, recvHdrFor func(string) string, msgW var receivedSPF spf.Received var spfDomain dns.Domain var spfExpl string + var spfAuthentic bool var spfErr error spfArgs := spf.Args{ RemoteIP: c.remoteIP, @@ -1945,7 +1955,7 @@ func (c *conn) deliver(ctx context.Context, recvHdrFor func(string) string, msgW defer wg.Done() spfctx, spfcancel := context.WithTimeout(ctx, time.Minute) defer spfcancel() - receivedSPF, spfDomain, spfExpl, spfErr = spf.Verify(spfctx, c.resolver, spfArgs) + receivedSPF, spfDomain, spfExpl, spfAuthentic, spfErr = spf.Verify(spfctx, c.resolver, spfArgs) spfcancel() if spfErr != nil { c.log.Infox("spf verify", spfErr) @@ -2003,7 +2013,7 @@ func (c *conn) deliver(ctx context.Context, recvHdrFor func(string) string, msgW // todo future: also specify whether dns record was dnssec-signed. if r.Record != nil && r.Record.PublicKey != nil { if pubkey, ok := r.Record.PublicKey.(*rsa.PublicKey); ok { - comment = fmt.Sprintf("%d bit rsa", pubkey.N.BitLen()) + comment = fmt.Sprintf("%d bit rsa, ", pubkey.N.BitLen()) } } @@ -2021,6 +2031,11 @@ func (c *conn) deliver(ctx context.Context, recvHdrFor func(string) string, msgW props = append(props, message.MakeAuthProp("header", "i", r.Sig.Identity.String(), true, "")) identity = r.Sig.Identity } + if r.RecordAuthentic { + comment += "with dnssec" + } else { + comment += "without dnssec" + } } var errmsg string if r.Err != nil { @@ -2048,10 +2063,17 @@ func (c *conn) deliver(ctx context.Context, recvHdrFor func(string) string, msgW if spfIdentity != nil { props = []message.AuthProp{message.MakeAuthProp("smtp", string(receivedSPF.Identity), spfIdentity.XName(c.smtputf8), true, spfIdentity.ASCIIExtra(c.smtputf8))} } + var spfComment string + if spfAuthentic { + spfComment = "with dnssec" + } else { + spfComment = "without dnssec" + } authResults.Methods = append(authResults.Methods, message.AuthMethod{ - Method: "spf", - Result: string(receivedSPF.Result), - Props: props, + Method: "spf", + Result: string(receivedSPF.Result), + Comment: spfComment, + Props: props, }) switch receivedSPF.Result { case spf.StatusPass: @@ -2105,9 +2127,16 @@ func (c *conn) deliver(ctx context.Context, recvHdrFor func(string) string, msgW defer dmarccancel() dmarcUse, dmarcResult = dmarc.Verify(dmarcctx, c.resolver, msgFrom.Domain, dkimResults, receivedSPF.Result, spfIdentity, applyRandomPercentage) dmarccancel() + var comment string + if dmarcResult.RecordAuthentic { + comment = "with dnssec" + } else { + comment = "without dnssec" + } dmarcMethod = message.AuthMethod{ - Method: "dmarc", - Result: string(dmarcResult.Status), + Method: "dmarc", + Result: string(dmarcResult.Status), + Comment: comment, Props: []message.AuthProp{ // ../rfc/7489:1489 message.MakeAuthProp("header", "from", msgFrom.Domain.ASCII, true, msgFrom.Domain.ASCIIExtra(c.smtputf8)), diff --git a/smtpserver/server_test.go b/smtpserver/server_test.go index f61383e..537b097 100644 --- a/smtpserver/server_test.go +++ b/smtpserver/server_test.go @@ -158,7 +158,7 @@ func (ts *testserver) run(fn func(helloErr error, client *smtpclient.Client)) { ourHostname := mox.Conf.Static.HostnameDomain remoteHostname := dns.Domain{ASCII: "mox.example"} - client, err := smtpclient.New(ctxbg, xlog.WithCid(ts.cid-1), clientConn, ts.tlsmode, ourHostname, remoteHostname, auth) + client, err := smtpclient.New(ctxbg, xlog.WithCid(ts.cid-1), clientConn, ts.tlsmode, ourHostname, remoteHostname, auth, nil, nil, nil) if err != nil { clientConn.Close() } else { diff --git a/spf/spf.go b/spf/spf.go index aa09864..2a4ca78 100644 --- a/spf/spf.go +++ b/spf/spf.go @@ -127,7 +127,9 @@ type Args struct { var timeNow = time.Now // Lookup looks up and parses an SPF TXT record for domain. -func Lookup(ctx context.Context, resolver dns.Resolver, domain dns.Domain) (rstatus Status, rtxt string, rrecord *Record, rerr error) { +// +// authentic indicates if the DNS results were DNSSEC-verified. +func Lookup(ctx context.Context, resolver dns.Resolver, domain dns.Domain) (rstatus Status, rtxt string, rrecord *Record, authentic bool, rerr error) { log := xlog.WithContext(ctx) start := time.Now() defer func() { @@ -137,15 +139,15 @@ func Lookup(ctx context.Context, resolver dns.Resolver, domain dns.Domain) (rsta // ../rfc/7208:586 host := domain.ASCII + "." if err := validateDNS(host); err != nil { - return StatusNone, "", nil, fmt.Errorf("%w: %s: %s", ErrName, domain, err) + return StatusNone, "", nil, false, fmt.Errorf("%w: %s: %s", ErrName, domain, err) } // Lookup spf record. - txts, err := dns.WithPackage(resolver, "spf").LookupTXT(ctx, host) + txts, result, err := dns.WithPackage(resolver, "spf").LookupTXT(ctx, host) if dns.IsNotFound(err) { - return StatusNone, "", nil, fmt.Errorf("%w for %s", ErrNoRecord, host) + return StatusNone, "", nil, result.Authentic, fmt.Errorf("%w for %s", ErrNoRecord, host) } else if err != nil { - return StatusTemperror, "", nil, fmt.Errorf("%w: %s: %s", ErrDNS, host, err) + return StatusTemperror, "", nil, result.Authentic, fmt.Errorf("%w: %s: %s", ErrDNS, host, err) } // Parse the records. We only handle those that look like spf records. @@ -159,20 +161,20 @@ func Lookup(ctx context.Context, resolver dns.Resolver, domain dns.Domain) (rsta continue } else if err != nil { // ../rfc/7208:852 - return StatusPermerror, txt, nil, fmt.Errorf("%w: %s", ErrRecordSyntax, err) + return StatusPermerror, txt, nil, result.Authentic, fmt.Errorf("%w: %s", ErrRecordSyntax, err) } if record != nil { // ../rfc/7208:576 - return StatusPermerror, "", nil, ErrMultipleRecords + return StatusPermerror, "", nil, result.Authentic, ErrMultipleRecords } text = txt record = r } if record == nil { // ../rfc/7208:837 - return StatusNone, "", nil, ErrNoRecord + return StatusNone, "", nil, result.Authentic, ErrNoRecord } - return StatusNone, text, record, nil + return StatusNone, text, record, result.Authentic, nil } // Verify checks if a remote IP is allowed to send email for a domain. @@ -190,7 +192,9 @@ func Lookup(ctx context.Context, resolver dns.Resolver, domain dns.Domain) (rsta // // Verify takes the maximum number of 10 DNS requests into account, and the maximum // of 2 lookups resulting in no records ("void lookups"). -func Verify(ctx context.Context, resolver dns.Resolver, args Args) (received Received, domain dns.Domain, explanation string, rerr error) { +// +// authentic indicates if the DNS results were DNSSEC-verified. +func Verify(ctx context.Context, resolver dns.Resolver, args Args) (received Received, domain dns.Domain, explanation string, authentic bool, rerr error) { log := xlog.WithContext(ctx) start := time.Now() defer func() { @@ -208,10 +212,10 @@ func Verify(ctx context.Context, resolver dns.Resolver, args Args) (received Rec Helo: args.HelloDomain, Receiver: args.LocalHostname.ASCII, } - return received, dns.Domain{}, "", nil + return received, dns.Domain{}, "", false, nil } - status, mechanism, expl, err := checkHost(ctx, resolver, args) + status, mechanism, expl, authentic, err := checkHost(ctx, resolver, args) comment := fmt.Sprintf("domain %s", args.domain.ASCII) if isHello { comment += ", from ehlo because mailfrom is empty" @@ -233,7 +237,7 @@ func Verify(ctx context.Context, resolver dns.Resolver, args Args) (received Rec } else { received.Identity = "mailfrom" } - return received, args.domain, expl, err + return received, args.domain, expl, authentic, err } // prepare args, setting fields sender* and domain as required for checkHost. @@ -268,26 +272,29 @@ func prepare(args *Args) (isHello bool, ok bool) { } // lookup spf record, then evaluate args against it. -func checkHost(ctx context.Context, resolver dns.Resolver, args Args) (rstatus Status, mechanism, rexplanation string, rerr error) { - status, _, record, err := Lookup(ctx, resolver, args.domain) +func checkHost(ctx context.Context, resolver dns.Resolver, args Args) (rstatus Status, mechanism, rexplanation string, rauthentic bool, rerr error) { + status, _, record, rauthentic, err := Lookup(ctx, resolver, args.domain) if err != nil { - return status, "", "", err + return status, "", "", rauthentic, err } - return evaluate(ctx, record, resolver, args) + var evalAuthentic bool + rstatus, mechanism, rexplanation, evalAuthentic, rerr = evaluate(ctx, record, resolver, args) + rauthentic = rauthentic && evalAuthentic + return } // Evaluate evaluates the IP and names from args against the SPF DNS record for the domain. -func Evaluate(ctx context.Context, record *Record, resolver dns.Resolver, args Args) (rstatus Status, mechanism, rexplanation string, rerr error) { +func Evaluate(ctx context.Context, record *Record, resolver dns.Resolver, args Args) (rstatus Status, mechanism, rexplanation string, rauthentic bool, rerr error) { _, ok := prepare(&args) if !ok { - return StatusNone, "default", "", fmt.Errorf("no domain name to validate") + return StatusNone, "default", "", false, fmt.Errorf("no domain name to validate") } return evaluate(ctx, record, resolver, args) } // evaluate RemoteIP against domain from args, given record. -func evaluate(ctx context.Context, record *Record, resolver dns.Resolver, args Args) (rstatus Status, mechanism, rexplanation string, rerr error) { +func evaluate(ctx context.Context, record *Record, resolver dns.Resolver, args Args) (rstatus Status, mechanism, rexplanation string, rauthentic bool, rerr error) { log := xlog.WithContext(ctx) start := time.Now() defer func() { @@ -301,6 +308,9 @@ func evaluate(ctx context.Context, record *Record, resolver dns.Resolver, args A args.voidLookups = new(int) } + // Response is authentic until we find a non-authentic DNS response. + rauthentic = true + // To4 returns nil for an IPv6 address. To16 will return an IPv4-to-IPv6-mapped address. var remote6 net.IP remote4 := args.RemoteIP.To4() @@ -338,7 +348,8 @@ func evaluate(ctx context.Context, record *Record, resolver dns.Resolver, args A // Used for "a" and "mx". checkHostIP := func(domain dns.Domain, d Directive, args *Args) (bool, Status, error) { - ips, err := resolver.LookupIP(ctx, "ip", domain.ASCII+".") + ips, result, err := resolver.LookupIP(ctx, "ip", domain.ASCII+".") + rauthentic = rauthentic && result.Authentic trackVoidLookup(err, args) // If "not found", we must ignore the error and treat as zero records in answer. ../rfc/7208:1116 if err != nil && !dns.IsNotFound(err) { @@ -358,7 +369,7 @@ func evaluate(ctx context.Context, record *Record, resolver dns.Resolver, args A switch d.Mechanism { case "include", "a", "mx", "ptr", "exists": if err := trackLookupLimits(&args); err != nil { - return StatusPermerror, d.MechanismString(), "", err + return StatusPermerror, d.MechanismString(), "", rauthentic, err } } @@ -369,22 +380,24 @@ func evaluate(ctx context.Context, record *Record, resolver dns.Resolver, args A case "include": // ../rfc/7208:1143 - name, err := expandDomainSpecDNS(ctx, resolver, d.DomainSpec, args) + name, authentic, err := expandDomainSpecDNS(ctx, resolver, d.DomainSpec, args) + rauthentic = rauthentic && authentic if err != nil { - return StatusPermerror, d.MechanismString(), "", fmt.Errorf("expanding domain-spec for include: %w", err) + return StatusPermerror, d.MechanismString(), "", rauthentic, fmt.Errorf("expanding domain-spec for include: %w", err) } nargs := args nargs.domain = dns.Domain{ASCII: strings.TrimSuffix(name, ".")} nargs.explanation = &record.Explanation // ../rfc/7208:1548 - status, _, _, err := checkHost(ctx, resolver, nargs) + status, _, _, authentic, err := checkHost(ctx, resolver, nargs) + rauthentic = rauthentic && authentic // ../rfc/7208:1202 switch status { case StatusPass: match = true case StatusTemperror: - return StatusTemperror, d.MechanismString(), "", fmt.Errorf("include %q: %w", name, err) + return StatusTemperror, d.MechanismString(), "", rauthentic, fmt.Errorf("include %q: %w", name, err) case StatusPermerror, StatusNone: - return StatusPermerror, d.MechanismString(), "", fmt.Errorf("include %q resulted in status %q: %w", name, status, err) + return StatusPermerror, d.MechanismString(), "", rauthentic, fmt.Errorf("include %q resulted in status %q: %w", name, status, err) } case "a": @@ -396,11 +409,11 @@ func evaluate(ctx context.Context, record *Record, resolver dns.Resolver, args A // mechanism for which it isn't specified. host, err := evaluateDomainSpec(d.DomainSpec, args.domain) if err != nil { - return StatusPermerror, d.MechanismString(), "", err + return StatusPermerror, d.MechanismString(), "", rauthentic, err } hmatch, status, err := checkHostIP(host, d, &args) if err != nil { - return status, d.MechanismString(), "", err + return status, d.MechanismString(), "", rauthentic, err } match = hmatch @@ -408,14 +421,15 @@ func evaluate(ctx context.Context, record *Record, resolver dns.Resolver, args A // ../rfc/7208:1262 host, err := evaluateDomainSpec(d.DomainSpec, args.domain) if err != nil { - return StatusPermerror, d.MechanismString(), "", err + return StatusPermerror, d.MechanismString(), "", rauthentic, err } // Note: LookupMX can return an error and still return MX records. - mxs, err := resolver.LookupMX(ctx, host.ASCII+".") + mxs, result, err := resolver.LookupMX(ctx, host.ASCII+".") + rauthentic = rauthentic && result.Authentic trackVoidLookup(err, &args) // note: we handle "not found" simply as a result of zero mx records. if err != nil && !dns.IsNotFound(err) { - return StatusTemperror, d.MechanismString(), "", err + return StatusTemperror, d.MechanismString(), "", rauthentic, err } if err == nil && len(mxs) == 1 && mxs[0].Host == "." { // Explicitly no MX. @@ -428,15 +442,15 @@ func evaluate(ctx context.Context, record *Record, resolver dns.Resolver, args A // found no match before the 11th name. // ../rfc/7208:945 if i >= 10 { - return StatusPermerror, d.MechanismString(), "", ErrTooManyDNSRequests + return StatusPermerror, d.MechanismString(), "", rauthentic, ErrTooManyDNSRequests } mxd, err := dns.ParseDomain(strings.TrimSuffix(mx.Host, ".")) if err != nil { - return StatusPermerror, d.MechanismString(), "", err + return StatusPermerror, d.MechanismString(), "", rauthentic, err } hmatch, status, err := checkHostIP(mxd, d, &args) if err != nil { - return status, d.MechanismString(), "", err + return status, d.MechanismString(), "", rauthentic, err } if hmatch { match = hmatch @@ -448,13 +462,14 @@ func evaluate(ctx context.Context, record *Record, resolver dns.Resolver, args A // ../rfc/7208:1281 host, err := evaluateDomainSpec(d.DomainSpec, args.domain) if err != nil { - return StatusPermerror, d.MechanismString(), "", err + return StatusPermerror, d.MechanismString(), "", rauthentic, err } - rnames, err := resolver.LookupAddr(ctx, args.RemoteIP.String()) + rnames, result, err := resolver.LookupAddr(ctx, args.RemoteIP.String()) + rauthentic = rauthentic && result.Authentic trackVoidLookup(err, &args) if err != nil && !dns.IsNotFound(err) { - return StatusTemperror, d.MechanismString(), "", err + return StatusTemperror, d.MechanismString(), "", rauthentic, err } lookups := 0 ptrnames: @@ -474,7 +489,8 @@ func evaluate(ctx context.Context, record *Record, resolver dns.Resolver, args A break } lookups++ - ips, err := resolver.LookupIP(ctx, "ip", rd.ASCII+".") + ips, result, err := resolver.LookupIP(ctx, "ip", rd.ASCII+".") + rauthentic = rauthentic && result.Authentic trackVoidLookup(err, &args) for _, ip := range ips { if checkIP(ip, d) { @@ -496,22 +512,24 @@ func evaluate(ctx context.Context, record *Record, resolver dns.Resolver, args A case "exists": // ../rfc/7208:1382 - name, err := expandDomainSpecDNS(ctx, resolver, d.DomainSpec, args) + name, authentic, err := expandDomainSpecDNS(ctx, resolver, d.DomainSpec, args) + rauthentic = rauthentic && authentic if err != nil { - return StatusPermerror, d.MechanismString(), "", fmt.Errorf("expanding domain-spec for exists: %w", err) + return StatusPermerror, d.MechanismString(), "", rauthentic, fmt.Errorf("expanding domain-spec for exists: %w", err) } - ips, err := resolver.LookupIP(ctx, "ip4", ensureAbsDNS(name)) + ips, result, err := resolver.LookupIP(ctx, "ip4", ensureAbsDNS(name)) + rauthentic = rauthentic && result.Authentic // Note: we do count this for void lookups, as that is an anti-abuse mechanism. // ../rfc/7208:1382 does not say anything special, so ../rfc/7208:984 applies. trackVoidLookup(err, &args) if err != nil && !dns.IsNotFound(err) { - return StatusTemperror, d.MechanismString(), "", err + return StatusTemperror, d.MechanismString(), "", rauthentic, err } match = len(ips) > 0 default: - return StatusNone, d.MechanismString(), "", fmt.Errorf("internal error, unexpected mechanism %q", d.Mechanism) + return StatusNone, d.MechanismString(), "", rauthentic, fmt.Errorf("internal error, unexpected mechanism %q", d.Mechanism) } if !match { @@ -519,40 +537,43 @@ func evaluate(ctx context.Context, record *Record, resolver dns.Resolver, args A } switch d.Qualifier { case "", "+": - return StatusPass, d.MechanismString(), "", nil + return StatusPass, d.MechanismString(), "", rauthentic, nil case "?": - return StatusNeutral, d.MechanismString(), "", nil + return StatusNeutral, d.MechanismString(), "", rauthentic, nil case "-": nargs := args // ../rfc/7208:1489 - expl := explanation(ctx, resolver, record, nargs) - return StatusFail, d.MechanismString(), expl, nil + authentic, expl := explanation(ctx, resolver, record, nargs) + rauthentic = rauthentic && authentic + return StatusFail, d.MechanismString(), expl, rauthentic, nil case "~": - return StatusSoftfail, d.MechanismString(), "", nil + return StatusSoftfail, d.MechanismString(), "", rauthentic, nil } - return StatusNone, d.MechanismString(), "", fmt.Errorf("internal error, unexpected qualifier %q", d.Qualifier) + return StatusNone, d.MechanismString(), "", rauthentic, fmt.Errorf("internal error, unexpected qualifier %q", d.Qualifier) } if record.Redirect != "" { // We only know "redirect" for evaluating purposes, ignoring any others. ../rfc/7208:1423 // ../rfc/7208:1440 - name, err := expandDomainSpecDNS(ctx, resolver, record.Redirect, args) + name, authentic, err := expandDomainSpecDNS(ctx, resolver, record.Redirect, args) + rauthentic = rauthentic && authentic if err != nil { - return StatusPermerror, "", "", fmt.Errorf("expanding domain-spec: %w", err) + return StatusPermerror, "", "", rauthentic, fmt.Errorf("expanding domain-spec: %w", err) } nargs := args nargs.domain = dns.Domain{ASCII: strings.TrimSuffix(name, ".")} nargs.explanation = nil // ../rfc/7208:1548 - status, mechanism, expl, err := checkHost(ctx, resolver, nargs) + status, mechanism, expl, authentic, err := checkHost(ctx, resolver, nargs) + rauthentic = rauthentic && authentic if status == StatusNone { - return StatusPermerror, mechanism, "", err + return StatusPermerror, mechanism, "", rauthentic, err } - return status, mechanism, expl, err + return status, mechanism, expl, rauthentic, err } // ../rfc/7208:996 ../rfc/7208:2095 - return StatusNeutral, "default", "", nil + return StatusNeutral, "default", "", rauthentic, nil } // evaluateDomainSpec returns the parsed dns domain for spec if non-empty, and @@ -569,11 +590,11 @@ func evaluateDomainSpec(spec string, d dns.Domain) (dns.Domain, error) { return d, nil } -func expandDomainSpecDNS(ctx context.Context, resolver dns.Resolver, domainSpec string, args Args) (string, error) { +func expandDomainSpecDNS(ctx context.Context, resolver dns.Resolver, domainSpec string, args Args) (string, bool, error) { return expandDomainSpec(ctx, resolver, domainSpec, args, true) } -func expandDomainSpecExp(ctx context.Context, resolver dns.Resolver, domainSpec string, args Args) (string, error) { +func expandDomainSpecExp(ctx context.Context, resolver dns.Resolver, domainSpec string, args Args) (string, bool, error) { return expandDomainSpec(ctx, resolver, domainSpec, args, false) } @@ -582,9 +603,11 @@ func expandDomainSpecExp(ctx context.Context, resolver dns.Resolver, domainSpec // Caller should typically treat failures as StatusPermerror. ../rfc/7208:1641 // ../rfc/7208:1639 // ../rfc/7208:1047 -func expandDomainSpec(ctx context.Context, resolver dns.Resolver, domainSpec string, args Args, dns bool) (string, error) { +func expandDomainSpec(ctx context.Context, resolver dns.Resolver, domainSpec string, args Args, dns bool) (string, bool, error) { exp := !dns + rauthentic := true // Until non-authentic record is found. + s := domainSpec b := &strings.Builder{} @@ -599,7 +622,7 @@ func expandDomainSpec(ctx context.Context, resolver dns.Resolver, domainSpec str } if i >= n { - return "", fmt.Errorf("%w: trailing bare %%", ErrMacroSyntax) + return "", rauthentic, fmt.Errorf("%w: trailing bare %%", ErrMacroSyntax) } c = s[i] i++ @@ -613,11 +636,11 @@ func expandDomainSpec(ctx context.Context, resolver dns.Resolver, domainSpec str b.WriteString("%20") continue } else if c != '{' { - return "", fmt.Errorf("%w: invalid macro opening %%%c", ErrMacroSyntax, c) + return "", rauthentic, fmt.Errorf("%w: invalid macro opening %%%c", ErrMacroSyntax, c) } if i >= n { - return "", fmt.Errorf("%w: missing macro ending }", ErrMacroSyntax) + return "", rauthentic, fmt.Errorf("%w: missing macro ending }", ErrMacroSyntax) } c = s[i] i++ @@ -645,9 +668,10 @@ func expandDomainSpec(ctx context.Context, resolver dns.Resolver, domainSpec str case 'p': // ../rfc/7208:937 if err := trackLookupLimits(&args); err != nil { - return "", err + return "", rauthentic, err } - names, err := resolver.LookupAddr(ctx, args.RemoteIP.String()) + names, result, err := resolver.LookupAddr(ctx, args.RemoteIP.String()) + rauthentic = rauthentic && result.Authentic trackVoidLookup(err, &args) if len(names) == 0 || err != nil { // ../rfc/7208:1709 @@ -661,7 +685,8 @@ func expandDomainSpec(ctx context.Context, resolver dns.Resolver, domainSpec str if !matchfn(name) { continue } - ips, err := resolver.LookupIP(ctx, "ip", name) + ips, result, err := resolver.LookupIP(ctx, "ip", name) + rauthentic = rauthentic && result.Authentic trackVoidLookup(err, &args) // ../rfc/7208:1714, we don't have to check other errors. for _, ip := range ips { @@ -678,18 +703,18 @@ func expandDomainSpec(ctx context.Context, resolver dns.Resolver, domainSpec str dotdomain := "." + domain v, err = verify(func(name string) bool { return name == domain }) if err != nil { - return "", err + return "", rauthentic, err } if v == "" { v, err = verify(func(name string) bool { return strings.HasSuffix(name, dotdomain) }) if err != nil { - return "", err + return "", rauthentic, err } } if v == "" { v, err = verify(func(name string) bool { return name != domain && !strings.HasSuffix(name, dotdomain) }) if err != nil { - return "", err + return "", rauthentic, err } } if v == "" { @@ -712,7 +737,7 @@ func expandDomainSpec(ctx context.Context, resolver dns.Resolver, domainSpec str } case 'c', 'r', 't': if !exp { - return "", fmt.Errorf("%w: macro letter %c only allowed in exp", ErrMacroSyntax, c) + return "", rauthentic, fmt.Errorf("%w: macro letter %c only allowed in exp", ErrMacroSyntax, c) } switch c { case 'c': @@ -723,7 +748,7 @@ func expandDomainSpec(ctx context.Context, resolver dns.Resolver, domainSpec str v = fmt.Sprintf("%d", timeNow().Unix()) } default: - return "", fmt.Errorf("%w: unknown macro letter %c", ErrMacroSyntax, c) + return "", rauthentic, fmt.Errorf("%w: unknown macro letter %c", ErrMacroSyntax, c) } digits := "" @@ -735,11 +760,11 @@ func expandDomainSpec(ctx context.Context, resolver dns.Resolver, domainSpec str if digits != "" { v, err := strconv.Atoi(digits) if err != nil { - return "", fmt.Errorf("%w: bad macro transformer digits %q: %s", ErrMacroSyntax, digits, err) + return "", rauthentic, fmt.Errorf("%w: bad macro transformer digits %q: %s", ErrMacroSyntax, digits, err) } nlabels = v if nlabels == 0 { - return "", fmt.Errorf("%w: zero labels for digits transformer", ErrMacroSyntax) + return "", rauthentic, fmt.Errorf("%w: zero labels for digits transformer", ErrMacroSyntax) } } @@ -764,7 +789,7 @@ func expandDomainSpec(ctx context.Context, resolver dns.Resolver, domainSpec str } if i >= n || s[i] != '}' { - return "", fmt.Errorf("%w: missing closing } for macro", ErrMacroSyntax) + return "", rauthentic, fmt.Errorf("%w: missing closing } for macro", ErrMacroSyntax) } i++ @@ -801,14 +826,14 @@ func expandDomainSpec(ctx context.Context, resolver dns.Resolver, domainSpec str isAbs := strings.HasSuffix(r, ".") r = ensureAbsDNS(r) if err := validateDNS(r); err != nil { - return "", fmt.Errorf("invalid dns name: %s", err) + return "", rauthentic, fmt.Errorf("invalid dns name: %s", err) } // If resulting name is too large, cut off labels on the left until it fits. ../rfc/7208:1749 if len(r) > 253+1 { labels := strings.Split(r, ".") for i := range labels { if i == len(labels)-1 { - return "", fmt.Errorf("expanded dns name too long") + return "", rauthentic, fmt.Errorf("expanded dns name too long") } s := strings.Join(labels[i+1:], ".") if len(s) <= 254 { @@ -821,7 +846,7 @@ func expandDomainSpec(ctx context.Context, resolver dns.Resolver, domainSpec str r = r[:len(r)-1] } } - return r, nil + return r, rauthentic, nil } func expandIP(ip net.IP) string { @@ -883,7 +908,7 @@ func split(v, delim string) (r []string) { // explanation does a best-effort attempt to fetch an explanation for a StatusFail response. // If no explanation could be composed, an empty string is returned. -func explanation(ctx context.Context, resolver dns.Resolver, r *Record, args Args) string { +func explanation(ctx context.Context, resolver dns.Resolver, r *Record, args Args) (bool, string) { // ../rfc/7208:1485 // If this record is the result of an "include", we have to use the explanation @@ -896,27 +921,29 @@ func explanation(ctx context.Context, resolver dns.Resolver, r *Record, args Arg // ../rfc/7208:1491 if expl == "" { - return "" + return true, "" } // Limits for dns requests and void lookups should not be taken into account. // Starting with zero ensures they aren't triggered. args.dnsRequests = new(int) args.voidLookups = new(int) - name, err := expandDomainSpecDNS(ctx, resolver, r.Explanation, args) + name, authentic, err := expandDomainSpecDNS(ctx, resolver, r.Explanation, args) if err != nil || name == "" { - return "" + return authentic, "" } - txts, err := resolver.LookupTXT(ctx, ensureAbsDNS(name)) + txts, result, err := resolver.LookupTXT(ctx, ensureAbsDNS(name)) + authentic = authentic && result.Authentic if err != nil || len(txts) == 0 { - return "" + return authentic, "" } txt := strings.Join(txts, "") - s, err := expandDomainSpecExp(ctx, resolver, txt, args) + s, exauthentic, err := expandDomainSpecExp(ctx, resolver, txt, args) + authentic = authentic && exauthentic if err != nil { - return "" + return authentic, "" } - return s + return authentic, s } func ensureAbsDNS(s string) string { diff --git a/spf/spf_test.go b/spf/spf_test.go index a390623..6f69237 100644 --- a/spf/spf_test.go +++ b/spf/spf_test.go @@ -31,7 +31,7 @@ func TestLookup(t *testing.T) { t.Helper() d := dns.Domain{ASCII: domain} - status, txt, record, err := Lookup(context.Background(), resolver, d) + status, txt, record, _, err := Lookup(context.Background(), resolver, d) if (err == nil) != (expErr == nil) || err != nil && !errors.Is(err, expErr) { t.Fatalf("got err %v, expected err %v", err, expErr) } @@ -99,7 +99,7 @@ func TestExpand(t *testing.T) { args.RemoteIP = mustParseIP(ip) } - r, err := expandDomainSpec(ctx, resolver, macro, args, dns) + r, _, err := expandDomainSpec(ctx, resolver, macro, args, dns) if (err == nil) != (exp != "") { t.Fatalf("got err %v, expected expansion %q, for macro %q", err, exp, macro) } @@ -260,7 +260,7 @@ func TestVerify(t *testing.T) { LocalIP: xip("127.0.0.1"), LocalHostname: dns.Domain{ASCII: "localhost"}, } - received, _, _, err := Verify(ctx, r, args) + received, _, _, _, err := Verify(ctx, r, args) if received.Result != status { t.Fatalf("got status %q, expected %q, for ip %q (err %v)", received.Result, status, ip, err) } @@ -346,7 +346,7 @@ func TestVerifyMultipleDomain(t *testing.T) { LocalIP: net.ParseIP("127.0.0.1"), LocalHostname: dns.Domain{ASCII: "localhost"}, } - received, _, _, err := Verify(context.Background(), resolver, args) + received, _, _, _, err := Verify(context.Background(), resolver, args) if err != nil { t.Fatalf("unexpected error: %s", err) } @@ -371,7 +371,7 @@ func TestVerifyScenarios(t *testing.T) { test := func(resolver dns.Resolver, args Args, expStatus Status, expDomain string, expExpl string, expErr error) { t.Helper() - recv, d, expl, err := Verify(context.Background(), resolver, args) + recv, d, expl, _, err := Verify(context.Background(), resolver, args) if (err == nil) != (expErr == nil) || err != nil && !errors.Is(err, expErr) { t.Fatalf("got err %v, expected %v", err, expErr) } @@ -506,7 +506,7 @@ func TestEvaluate(t *testing.T) { record := &Record{} resolver := dns.MockResolver{} args := Args{} - status, _, _, _ := Evaluate(context.Background(), record, resolver, args) + status, _, _, _, _ := Evaluate(context.Background(), record, resolver, args) if status != StatusNone { t.Fatalf("got status %q, expected none", status) } @@ -514,7 +514,7 @@ func TestEvaluate(t *testing.T) { args = Args{ HelloDomain: dns.IPDomain{Domain: dns.Domain{ASCII: "test.example"}}, } - status, mechanism, _, err := Evaluate(context.Background(), record, resolver, args) + status, mechanism, _, _, err := Evaluate(context.Background(), record, resolver, args) if status != StatusNeutral || mechanism != "default" || err != nil { t.Fatalf("got status %q, mechanism %q, err %v, expected neutral, default, no error", status, mechanism, err) } diff --git a/tlsrpt/lookup.go b/tlsrpt/lookup.go index 3158299..7bfecf4 100644 --- a/tlsrpt/lookup.go +++ b/tlsrpt/lookup.go @@ -58,7 +58,7 @@ func Lookup(ctx context.Context, resolver dns.Resolver, domain dns.Domain) (rrec }() name := "_smtp._tls." + domain.ASCII + "." - txts, err := dns.WithPackage(resolver, "tlsrpt").LookupTXT(ctx, name) + txts, _, err := dns.WithPackage(resolver, "tlsrpt").LookupTXT(ctx, name) if dns.IsNotFound(err) { return nil, "", ErrNoRecord } else if err != nil { diff --git a/updates/updates.go b/updates/updates.go index 0198e1b..6287b57 100644 --- a/updates/updates.go +++ b/updates/updates.go @@ -96,7 +96,7 @@ func Lookup(ctx context.Context, resolver dns.Resolver, domain dns.Domain) (rver nctx, cancel := context.WithTimeout(ctx, 30*time.Second) defer cancel() name := "_updates." + domain.ASCII + "." - txts, err := dns.WithPackage(resolver, "updates").LookupTXT(nctx, name) + txts, _, err := dns.WithPackage(resolver, "updates").LookupTXT(nctx, name) if dns.IsNotFound(err) { return Version{}, nil, ErrNoRecord } else if err != nil { diff --git a/vendor/github.com/mjl-/adns/.gitignore b/vendor/github.com/mjl-/adns/.gitignore new file mode 100644 index 0000000..56dd0b6 --- /dev/null +++ b/vendor/github.com/mjl-/adns/.gitignore @@ -0,0 +1 @@ +/cover.* diff --git a/vendor/github.com/mjl-/adns/LICENSE b/vendor/github.com/mjl-/adns/LICENSE new file mode 100644 index 0000000..6a66aea --- /dev/null +++ b/vendor/github.com/mjl-/adns/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/mjl-/adns/Makefile b/vendor/github.com/mjl-/adns/Makefile new file mode 100644 index 0000000..13cb4a6 --- /dev/null +++ b/vendor/github.com/mjl-/adns/Makefile @@ -0,0 +1,33 @@ +build: + go build + go vet ./... + +test: + go test -race -shuffle=on -coverprofile cover.out -covermode atomic + go tool cover -html=cover.out -o cover.html + +check: + GOARCH=386 go vet + staticcheck ./... + +# having "err" shadowed is common, best to not have others +check-shadow: + go vet -vettool=$$(which shadow) ./... 2>&1 | grep -v '"err"' + +buildall: + GOOS=linux GOARCH=arm go build + GOOS=linux GOARCH=arm64 go build + GOOS=linux GOARCH=amd64 go build + GOOS=linux GOARCH=386 go build + GOOS=openbsd GOARCH=amd64 go build + GOOS=freebsd GOARCH=amd64 go build + GOOS=netbsd GOARCH=amd64 go build + GOOS=darwin GOARCH=amd64 go build + GOOS=dragonfly GOARCH=amd64 go build + GOOS=illumos GOARCH=amd64 go build + GOOS=solaris GOARCH=amd64 go build + GOOS=aix GOARCH=ppc64 go build + # no windows or plan9 for now + +fmt: + gofmt -w -s *.go */*/*.go diff --git a/vendor/github.com/mjl-/adns/README.md b/vendor/github.com/mjl-/adns/README.md new file mode 100644 index 0000000..53bab97 --- /dev/null +++ b/vendor/github.com/mjl-/adns/README.md @@ -0,0 +1,5 @@ +adns - copy of pure Go resolver from Go standard library, with modifications to facilitate use with DNSSEC. + +Documentation: https://pkg.go.dev/github.com/mjl-/adns + +License: Go's BSD license, see LICENSE. diff --git a/vendor/github.com/mjl-/adns/addrselect.go b/vendor/github.com/mjl-/adns/addrselect.go new file mode 100644 index 0000000..1a9a9ad --- /dev/null +++ b/vendor/github.com/mjl-/adns/addrselect.go @@ -0,0 +1,377 @@ +// 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. + +// Minimal RFC 6724 address selection. + +package adns + +import ( + "net" + "net/netip" + "sort" +) + +func sortByRFC6724(addrs []net.IPAddr) { + if len(addrs) < 2 { + return + } + sortByRFC6724withSrcs(addrs, srcAddrs(addrs)) +} + +func sortByRFC6724withSrcs(addrs []net.IPAddr, srcs []netip.Addr) { + if len(addrs) != len(srcs) { + panic("internal error") + } + addrAttr := make([]ipAttr, len(addrs)) + srcAttr := make([]ipAttr, len(srcs)) + for i, v := range addrs { + addrAttrIP, _ := netip.AddrFromSlice(v.IP) + addrAttr[i] = ipAttrOf(addrAttrIP) + srcAttr[i] = ipAttrOf(srcs[i]) + } + sort.Stable(&byRFC6724{ + addrs: addrs, + addrAttr: addrAttr, + srcs: srcs, + srcAttr: srcAttr, + }) +} + +// srcAddrs tries to UDP-connect to each address to see if it has a +// route. (This doesn't send any packets). The destination port +// number is irrelevant. +func srcAddrs(addrs []net.IPAddr) []netip.Addr { + srcs := make([]netip.Addr, len(addrs)) + dst := net.UDPAddr{Port: 9} + for i := range addrs { + dst.IP = addrs[i].IP + dst.Zone = addrs[i].Zone + c, err := net.DialUDP("udp", nil, &dst) + if err == nil { + if src, ok := c.LocalAddr().(*net.UDPAddr); ok { + srcs[i], _ = netip.AddrFromSlice(src.IP) + } + c.Close() + } + } + return srcs +} + +type ipAttr struct { + Scope scope + Precedence uint8 + Label uint8 +} + +func ipAttrOf(ip netip.Addr) ipAttr { + if !ip.IsValid() { + return ipAttr{} + } + match := rfc6724policyTable.Classify(ip) + return ipAttr{ + Scope: classifyScope(ip), + Precedence: match.Precedence, + Label: match.Label, + } +} + +type byRFC6724 struct { + addrs []net.IPAddr // addrs to sort + addrAttr []ipAttr + srcs []netip.Addr // or not valid addr if unreachable + srcAttr []ipAttr +} + +func (s *byRFC6724) Len() int { return len(s.addrs) } + +func (s *byRFC6724) Swap(i, j int) { + s.addrs[i], s.addrs[j] = s.addrs[j], s.addrs[i] + s.srcs[i], s.srcs[j] = s.srcs[j], s.srcs[i] + s.addrAttr[i], s.addrAttr[j] = s.addrAttr[j], s.addrAttr[i] + s.srcAttr[i], s.srcAttr[j] = s.srcAttr[j], s.srcAttr[i] +} + +// Less reports whether i is a better destination address for this +// host than j. +// +// The algorithm and variable names comes from RFC 6724 section 6. +func (s *byRFC6724) Less(i, j int) bool { + DA := s.addrs[i].IP + DB := s.addrs[j].IP + SourceDA := s.srcs[i] + SourceDB := s.srcs[j] + attrDA := &s.addrAttr[i] + attrDB := &s.addrAttr[j] + attrSourceDA := &s.srcAttr[i] + attrSourceDB := &s.srcAttr[j] + + const preferDA = true + const preferDB = false + + // Rule 1: Avoid unusable destinations. + // If DB is known to be unreachable or if Source(DB) is undefined, then + // prefer DA. Similarly, if DA is known to be unreachable or if + // Source(DA) is undefined, then prefer DB. + if !SourceDA.IsValid() && !SourceDB.IsValid() { + return false // "equal" + } + if !SourceDB.IsValid() { + return preferDA + } + if !SourceDA.IsValid() { + return preferDB + } + + // Rule 2: Prefer matching scope. + // If Scope(DA) = Scope(Source(DA)) and Scope(DB) <> Scope(Source(DB)), + // then prefer DA. Similarly, if Scope(DA) <> Scope(Source(DA)) and + // Scope(DB) = Scope(Source(DB)), then prefer DB. + if attrDA.Scope == attrSourceDA.Scope && attrDB.Scope != attrSourceDB.Scope { + return preferDA + } + if attrDA.Scope != attrSourceDA.Scope && attrDB.Scope == attrSourceDB.Scope { + return preferDB + } + + // Rule 3: Avoid deprecated addresses. + // If Source(DA) is deprecated and Source(DB) is not, then prefer DB. + // Similarly, if Source(DA) is not deprecated and Source(DB) is + // deprecated, then prefer DA. + + // TODO(bradfitz): implement? low priority for now. + + // Rule 4: Prefer home addresses. + // If Source(DA) is simultaneously a home address and care-of address + // and Source(DB) is not, then prefer DA. Similarly, if Source(DB) is + // simultaneously a home address and care-of address and Source(DA) is + // not, then prefer DB. + + // TODO(bradfitz): implement? low priority for now. + + // Rule 5: Prefer matching label. + // If Label(Source(DA)) = Label(DA) and Label(Source(DB)) <> Label(DB), + // then prefer DA. Similarly, if Label(Source(DA)) <> Label(DA) and + // Label(Source(DB)) = Label(DB), then prefer DB. + if attrSourceDA.Label == attrDA.Label && + attrSourceDB.Label != attrDB.Label { + return preferDA + } + if attrSourceDA.Label != attrDA.Label && + attrSourceDB.Label == attrDB.Label { + return preferDB + } + + // Rule 6: Prefer higher precedence. + // If Precedence(DA) > Precedence(DB), then prefer DA. Similarly, if + // Precedence(DA) < Precedence(DB), then prefer DB. + if attrDA.Precedence > attrDB.Precedence { + return preferDA + } + if attrDA.Precedence < attrDB.Precedence { + return preferDB + } + + // Rule 7: Prefer native transport. + // If DA is reached via an encapsulating transition mechanism (e.g., + // IPv6 in IPv4) and DB is not, then prefer DB. Similarly, if DB is + // reached via encapsulation and DA is not, then prefer DA. + + // TODO(bradfitz): implement? low priority for now. + + // Rule 8: Prefer smaller scope. + // If Scope(DA) < Scope(DB), then prefer DA. Similarly, if Scope(DA) > + // Scope(DB), then prefer DB. + if attrDA.Scope < attrDB.Scope { + return preferDA + } + if attrDA.Scope > attrDB.Scope { + return preferDB + } + + // Rule 9: Use the longest matching prefix. + // When DA and DB belong to the same address family (both are IPv6 or + // both are IPv4 [but see below]): If CommonPrefixLen(Source(DA), DA) > + // CommonPrefixLen(Source(DB), DB), then prefer DA. Similarly, if + // CommonPrefixLen(Source(DA), DA) < CommonPrefixLen(Source(DB), DB), + // then prefer DB. + // + // However, applying this rule to IPv4 addresses causes + // problems (see issues 13283 and 18518), so limit to IPv6. + if DA.To4() == nil && DB.To4() == nil { + commonA := commonPrefixLen(SourceDA, DA) + commonB := commonPrefixLen(SourceDB, DB) + + if commonA > commonB { + return preferDA + } + if commonA < commonB { + return preferDB + } + } + + // Rule 10: Otherwise, leave the order unchanged. + // If DA preceded DB in the original list, prefer DA. + // Otherwise, prefer DB. + return false // "equal" +} + +type policyTableEntry struct { + Prefix netip.Prefix + Precedence uint8 + Label uint8 +} + +type policyTable []policyTableEntry + +// RFC 6724 section 2.1. +// Items are sorted by the size of their Prefix.Mask.Size, +var rfc6724policyTable = policyTable{ + { + // "::1/128" + Prefix: netip.PrefixFrom(netip.AddrFrom16([16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x01}), 128), + Precedence: 50, + Label: 0, + }, + { + // "::ffff:0:0/96" + // IPv4-compatible, etc. + Prefix: netip.PrefixFrom(netip.AddrFrom16([16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff}), 96), + Precedence: 35, + Label: 4, + }, + { + // "::/96" + Prefix: netip.PrefixFrom(netip.AddrFrom16([16]byte{}), 96), + Precedence: 1, + Label: 3, + }, + { + // "2001::/32" + // Teredo + Prefix: netip.PrefixFrom(netip.AddrFrom16([16]byte{0x20, 0x01}), 32), + Precedence: 5, + Label: 5, + }, + { + // "2002::/16" + // 6to4 + Prefix: netip.PrefixFrom(netip.AddrFrom16([16]byte{0x20, 0x02}), 16), + Precedence: 30, + Label: 2, + }, + { + // "3ffe::/16" + Prefix: netip.PrefixFrom(netip.AddrFrom16([16]byte{0x3f, 0xfe}), 16), + Precedence: 1, + Label: 12, + }, + { + // "fec0::/10" + Prefix: netip.PrefixFrom(netip.AddrFrom16([16]byte{0xfe, 0xc0}), 10), + Precedence: 1, + Label: 11, + }, + { + // "fc00::/7" + Prefix: netip.PrefixFrom(netip.AddrFrom16([16]byte{0xfc}), 7), + Precedence: 3, + Label: 13, + }, + { + // "::/0" + Prefix: netip.PrefixFrom(netip.AddrFrom16([16]byte{}), 0), + Precedence: 40, + Label: 1, + }, +} + +// Classify returns the policyTableEntry of the entry with the longest +// matching prefix that contains ip. +// The table t must be sorted from largest mask size to smallest. +func (t policyTable) Classify(ip netip.Addr) policyTableEntry { + // Prefix.Contains() will not match an IPv6 prefix for an IPv4 address. + if ip.Is4() { + ip = netip.AddrFrom16(ip.As16()) + } + for _, ent := range t { + if ent.Prefix.Contains(ip) { + return ent + } + } + return policyTableEntry{} +} + +// RFC 6724 section 3.1. +type scope uint8 + +const ( + scopeInterfaceLocal scope = 0x1 + scopeLinkLocal scope = 0x2 + scopeAdminLocal scope = 0x4 + scopeSiteLocal scope = 0x5 + scopeOrgLocal scope = 0x8 + scopeGlobal scope = 0xe +) + +func classifyScope(ip netip.Addr) scope { + if ip.IsLoopback() || ip.IsLinkLocalUnicast() { + return scopeLinkLocal + } + ipv6 := ip.Is6() && !ip.Is4In6() + ipv6AsBytes := ip.As16() + if ipv6 && ip.IsMulticast() { + return scope(ipv6AsBytes[1] & 0xf) + } + // Site-local addresses are defined in RFC 3513 section 2.5.6 + // (and deprecated in RFC 3879). + if ipv6 && ipv6AsBytes[0] == 0xfe && ipv6AsBytes[1]&0xc0 == 0xc0 { + return scopeSiteLocal + } + return scopeGlobal +} + +// commonPrefixLen reports the length of the longest prefix (looking +// at the most significant, or leftmost, bits) that the +// two addresses have in common, up to the length of a's prefix (i.e., +// the portion of the address not including the interface ID). +// +// If a or b is an IPv4 address as an IPv6 address, the IPv4 addresses +// are compared (with max common prefix length of 32). +// If a and b are different IP versions, 0 is returned. +// +// See https://tools.ietf.org/html/rfc6724#section-2.2 +func commonPrefixLen(a netip.Addr, b net.IP) (cpl int) { + if b4 := b.To4(); b4 != nil { + b = b4 + } + aAsSlice := a.AsSlice() + if len(aAsSlice) != len(b) { + return 0 + } + // If IPv6, only up to the prefix (first 64 bits) + if len(aAsSlice) > 8 { + aAsSlice = aAsSlice[:8] + b = b[:8] + } + for len(aAsSlice) > 0 { + if aAsSlice[0] == b[0] { + cpl += 8 + aAsSlice = aAsSlice[1:] + b = b[1:] + continue + } + bits := 8 + ab, bb := aAsSlice[0], b[0] + for { + ab >>= 1 + bb >>= 1 + bits-- + if ab == bb { + cpl += bits + return + } + } + } + return +} diff --git a/vendor/github.com/mjl-/adns/authentic.go b/vendor/github.com/mjl-/adns/authentic.go new file mode 100644 index 0000000..abac1d5 --- /dev/null +++ b/vendor/github.com/mjl-/adns/authentic.go @@ -0,0 +1,17 @@ +package adns + +// Result has additional information about a DNS lookup. +type Result struct { + // Authentic indicates whether the response was DNSSEC-signed and verified. + // This package is a security-aware non-validating stub-resolver, sending requests + // with the "authentic data" bit set to its recursive resolvers, but only if the + // resolvers are trusted. Resolvers are trusted either if explicitly marked with + // "options trust-ad" in /etc/resolv.conf, or if all resolver IP addresses are + // loopback IP's. If the response from the resolver has the "authentic data" bit + // set, the DNS name and all indirections towards the name, were signed and the + // recursive resolver has verified them. + Authentic bool + + // todo: possibly add followed cname's + // todo: possibly add lowest TTL encountered in lookup (gathered after following cname's) +} diff --git a/vendor/github.com/mjl-/adns/conf.go b/vendor/github.com/mjl-/adns/conf.go new file mode 100644 index 0000000..d9679c0 --- /dev/null +++ b/vendor/github.com/mjl-/adns/conf.go @@ -0,0 +1,508 @@ +// 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 + } +} + +// goosPreferCgo reports whether the GOOS value passed in prefers +// 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. + switch c.goos { + case "windows": + // TODO(bradfitz): implement files-based + // lookup on Windows too? I guess /etc/hosts + // kinda exists on Windows. But for now, only + // do DNS. + fallbackOrder = hostLookupDNS + default: + fallbackOrder = hostLookupFilesDNS + } + 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") +} diff --git a/vendor/github.com/mjl-/adns/dial.go b/vendor/github.com/mjl-/adns/dial.go new file mode 100644 index 0000000..115e8e4 --- /dev/null +++ b/vendor/github.com/mjl-/adns/dial.go @@ -0,0 +1,42 @@ +// Copyright 2010 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. + +package adns + +import ( + "context" + "net" +) + +func parseNetwork(ctx context.Context, network string, needsProto bool) (afnet string, proto int, err error) { + i := last(network, ':') + if i < 0 { // no colon + switch network { + case "tcp", "tcp4", "tcp6": + case "udp", "udp4", "udp6": + case "ip", "ip4", "ip6": + if needsProto { + return "", 0, net.UnknownNetworkError(network) + } + case "unix", "unixgram", "unixpacket": + default: + return "", 0, net.UnknownNetworkError(network) + } + return network, 0, nil + } + afnet = network[:i] + switch afnet { + case "ip", "ip4", "ip6": + protostr := network[i+1:] + proto, i, ok := dtoi(protostr) + if !ok || i != len(protostr) { + proto, err = lookupProtocol(ctx, protostr) + if err != nil { + return "", 0, err + } + } + return afnet, proto, nil + } + return "", 0, net.UnknownNetworkError(network) +} diff --git a/vendor/github.com/mjl-/adns/dnsclient.go b/vendor/github.com/mjl-/adns/dnsclient.go new file mode 100644 index 0000000..0d48607 --- /dev/null +++ b/vendor/github.com/mjl-/adns/dnsclient.go @@ -0,0 +1,209 @@ +// Copyright 2009 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. + +package adns + +import ( + mathrand "math/rand" + "net" + "sort" + + "golang.org/x/net/dns/dnsmessage" + + "github.com/mjl-/adns/internal/bytealg" + "github.com/mjl-/adns/internal/itoa" +) + +func randInt() int { + return mathrand.Int() +} + +func randIntn(n int) int { + return randInt() % n +} + +// reverseaddr returns the in-addr.arpa. or ip6.arpa. hostname of the IP +// address addr suitable for rDNS (PTR) record lookup or an error if it fails +// to parse the IP address. +func reverseaddr(addr string) (arpa string, err error) { + ip := net.ParseIP(addr) + if ip == nil { + return "", &DNSError{Err: "unrecognized address", Name: addr} + } + if ip.To4() != nil { + return itoa.Uitoa(uint(ip[15])) + "." + itoa.Uitoa(uint(ip[14])) + "." + itoa.Uitoa(uint(ip[13])) + "." + itoa.Uitoa(uint(ip[12])) + ".in-addr.arpa.", nil + } + // Must be IPv6 + buf := make([]byte, 0, len(ip)*4+len("ip6.arpa.")) + // Add it, in reverse, to the buffer + for i := len(ip) - 1; i >= 0; i-- { + v := ip[i] + buf = append(buf, hexDigit[v&0xF], + '.', + hexDigit[v>>4], + '.') + } + // Append "ip6.arpa." and return (buf already has the final .) + buf = append(buf, "ip6.arpa."...) + return string(buf), nil +} + +func equalASCIIName(x, y dnsmessage.Name) bool { + if x.Length != y.Length { + return false + } + for i := 0; i < int(x.Length); i++ { + a := x.Data[i] + b := y.Data[i] + if 'A' <= a && a <= 'Z' { + a += 0x20 + } + if 'A' <= b && b <= 'Z' { + b += 0x20 + } + if a != b { + return false + } + } + return true +} + +// isDomainName checks if a string is a presentation-format domain name +// (currently restricted to hostname-compatible "preferred name" LDH labels and +// SRV-like "underscore labels"; see golang.org/issue/12421). +func isDomainName(s string) bool { + // The root domain name is valid. See golang.org/issue/45715. + if s == "." { + return true + } + + // See RFC 1035, RFC 3696. + // Presentation format has dots before every label except the first, and the + // terminal empty label is optional here because we assume fully-qualified + // (absolute) input. We must therefore reserve space for the first and last + // labels' length octets in wire format, where they are necessary and the + // maximum total length is 255. + // So our _effective_ maximum is 253, but 254 is not rejected if the last + // character is a dot. + l := len(s) + if l == 0 || l > 254 || l == 254 && s[l-1] != '.' { + return false + } + + last := byte('.') + nonNumeric := false // true once we've seen a letter or hyphen + partlen := 0 + for i := 0; i < len(s); i++ { + c := s[i] + switch { + default: + return false + case 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || c == '_': + nonNumeric = true + partlen++ + case '0' <= c && c <= '9': + // fine + partlen++ + case c == '-': + // Byte before dash cannot be dot. + if last == '.' { + return false + } + partlen++ + nonNumeric = true + case c == '.': + // Byte before dot cannot be dot, dash. + if last == '.' || last == '-' { + return false + } + if partlen > 63 || partlen == 0 { + return false + } + partlen = 0 + } + last = c + } + if last == '-' || partlen > 63 { + return false + } + + return nonNumeric +} + +// absDomainName returns an absolute domain name which ends with a +// trailing dot to match pure Go reverse resolver and all other lookup +// routines. +// See golang.org/issue/12189. +// But we don't want to add dots for local names from /etc/hosts. +// It's hard to tell so we settle on the heuristic that names without dots +// (like "localhost" or "myhost") do not get trailing dots, but any other +// names do. +func absDomainName(s string) string { + if bytealg.IndexByteString(s, '.') != -1 && s[len(s)-1] != '.' { + s += "." + } + return s +} + +// byPriorityWeight sorts SRV records by ascending priority and weight. +type byPriorityWeight []*net.SRV + +func (s byPriorityWeight) Len() int { return len(s) } +func (s byPriorityWeight) Less(i, j int) bool { + return s[i].Priority < s[j].Priority || (s[i].Priority == s[j].Priority && s[i].Weight < s[j].Weight) +} +func (s byPriorityWeight) Swap(i, j int) { s[i], s[j] = s[j], s[i] } + +// shuffleByWeight shuffles SRV records by weight using the algorithm +// described in RFC 2782. +func (addrs byPriorityWeight) shuffleByWeight() { + sum := 0 + for _, addr := range addrs { + sum += int(addr.Weight) + } + for sum > 0 && len(addrs) > 1 { + s := 0 + n := randIntn(sum) + for i := range addrs { + s += int(addrs[i].Weight) + if s > n { + if i > 0 { + addrs[0], addrs[i] = addrs[i], addrs[0] + } + break + } + } + sum -= int(addrs[0].Weight) + addrs = addrs[1:] + } +} + +// sort reorders SRV records as specified in RFC 2782. +func (addrs byPriorityWeight) sort() { + sort.Sort(addrs) + i := 0 + for j := 1; j < len(addrs); j++ { + if addrs[i].Priority != addrs[j].Priority { + addrs[i:j].shuffleByWeight() + i = j + } + } + addrs[i:].shuffleByWeight() +} + +// byPref implements sort.Interface to sort MX records by preference +type byPref []*net.MX + +func (s byPref) Len() int { return len(s) } +func (s byPref) Less(i, j int) bool { return s[i].Pref < s[j].Pref } +func (s byPref) Swap(i, j int) { s[i], s[j] = s[j], s[i] } + +// sort reorders MX records as specified in RFC 5321. +func (s byPref) sort() { + for i := range s { + j := randIntn(i + 1) + s[i], s[j] = s[j], s[i] + } + sort.Sort(s) +} diff --git a/vendor/github.com/mjl-/adns/dnsclient_unix.go b/vendor/github.com/mjl-/adns/dnsclient_unix.go new file mode 100644 index 0000000..4dbfb00 --- /dev/null +++ b/vendor/github.com/mjl-/adns/dnsclient_unix.go @@ -0,0 +1,947 @@ +// Copyright 2009 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 + +// DNS client: see RFC 1035. +// Has to be linked into package net for Dial. + +// TODO(rsc): +// Could potentially handle many outstanding lookups faster. +// Random UDP source port (net.Dial should do that for us). +// Random request IDs. + +package adns + +import ( + "bytes" + "context" + cryptorand "crypto/rand" + "errors" + "io" + "net" + "os" + "runtime" + "sync" + "sync/atomic" + "time" + + "golang.org/x/net/dns/dnsmessage" + + "github.com/mjl-/adns/internal/itoa" +) + +const ( + // to be used as a useTCP parameter to exchange + useTCPOnly = true + useUDPOrTCP = false + + // Maximum DNS packet size. + // Value taken from https://dnsflagday.net/2020/. + maxDNSPacketSize = 1232 +) + +var ( + errLameReferral = errors.New("lame referral") + errCannotUnmarshalDNSMessage = errors.New("cannot unmarshal DNS message") + errCannotMarshalDNSMessage = errors.New("cannot marshal DNS message") + errServerMisbehaving = errors.New("server misbehaving") + errInvalidDNSResponse = errors.New("invalid DNS response") + errNoAnswerFromDNSServer = errors.New("no answer from DNS server") + + // errServerTemporarilyMisbehaving is like errServerMisbehaving, except + // that when it gets translated to a DNSError, the IsTemporary field + // gets set to true. + errServerTemporarilyMisbehaving = errors.New("server misbehaving") +) + +func newRequest(q dnsmessage.Question, ad bool) (id uint16, udpReq, tcpReq []byte, err error) { + var idbuf [2]byte + _, err = cryptorand.Read(idbuf[:]) + if err != nil { + return 0, nil, nil, err + } + id = uint16(idbuf[0])<<8 | uint16(idbuf[1]) + + b := dnsmessage.NewBuilder(make([]byte, 2, 514), dnsmessage.Header{ID: id, RecursionDesired: true, AuthenticData: ad}) + if err := b.StartQuestions(); err != nil { + return 0, nil, nil, err + } + if err := b.Question(q); err != nil { + return 0, nil, nil, err + } + + // Accept packets up to maxDNSPacketSize. RFC 6891. + if err := b.StartAdditionals(); err != nil { + return 0, nil, nil, err + } + var rh dnsmessage.ResourceHeader + if err := rh.SetEDNS0(maxDNSPacketSize, dnsmessage.RCodeSuccess, false); err != nil { + return 0, nil, nil, err + } + if err := b.OPTResource(rh, dnsmessage.OPTResource{}); err != nil { + return 0, nil, nil, err + } + + tcpReq, err = b.Finish() + if err != nil { + return 0, nil, nil, err + } + udpReq = tcpReq[2:] + l := len(tcpReq) - 2 + tcpReq[0] = byte(l >> 8) + tcpReq[1] = byte(l) + return id, udpReq, tcpReq, nil +} + +func checkResponse(reqID uint16, reqQues dnsmessage.Question, respHdr dnsmessage.Header, respQues dnsmessage.Question) bool { + if !respHdr.Response { + return false + } + if reqID != respHdr.ID { + return false + } + if reqQues.Type != respQues.Type || reqQues.Class != respQues.Class || !equalASCIIName(reqQues.Name, respQues.Name) { + return false + } + return true +} + +func dnsPacketRoundTrip(c net.Conn, id uint16, query dnsmessage.Question, b []byte) (dnsmessage.Parser, dnsmessage.Header, error) { + if _, err := c.Write(b); err != nil { + return dnsmessage.Parser{}, dnsmessage.Header{}, err + } + + b = make([]byte, maxDNSPacketSize) + for { + n, err := c.Read(b) + if err != nil { + return dnsmessage.Parser{}, dnsmessage.Header{}, err + } + var p dnsmessage.Parser + // Ignore invalid responses as they may be malicious + // forgery attempts. Instead continue waiting until + // timeout. See golang.org/issue/13281. + h, err := p.Start(b[:n]) + if err != nil { + continue + } + q, err := p.Question() + if err != nil || !checkResponse(id, query, h, q) { + continue + } + return p, h, nil + } +} + +func dnsStreamRoundTrip(c net.Conn, id uint16, query dnsmessage.Question, b []byte) (dnsmessage.Parser, dnsmessage.Header, error) { + if _, err := c.Write(b); err != nil { + return dnsmessage.Parser{}, dnsmessage.Header{}, err + } + + b = make([]byte, 1280) // 1280 is a reasonable initial size for IP over Ethernet, see RFC 4035 + if _, err := io.ReadFull(c, b[:2]); err != nil { + return dnsmessage.Parser{}, dnsmessage.Header{}, err + } + l := int(b[0])<<8 | int(b[1]) + if l > len(b) { + b = make([]byte, l) + } + n, err := io.ReadFull(c, b[:l]) + if err != nil { + return dnsmessage.Parser{}, dnsmessage.Header{}, err + } + var p dnsmessage.Parser + h, err := p.Start(b[:n]) + if err != nil { + return dnsmessage.Parser{}, dnsmessage.Header{}, errCannotUnmarshalDNSMessage + } + q, err := p.Question() + if err != nil { + return dnsmessage.Parser{}, dnsmessage.Header{}, errCannotUnmarshalDNSMessage + } + if !checkResponse(id, query, h, q) { + return dnsmessage.Parser{}, dnsmessage.Header{}, errInvalidDNSResponse + } + return p, h, nil +} + +// exchange sends a query on the connection and hopes for a response. +func (r *Resolver) exchange(ctx context.Context, server string, q dnsmessage.Question, timeout time.Duration, useTCP, ad bool) (dnsmessage.Parser, dnsmessage.Header, error) { + q.Class = dnsmessage.ClassINET + id, udpReq, tcpReq, err := newRequest(q, ad) + if err != nil { + return dnsmessage.Parser{}, dnsmessage.Header{}, errCannotMarshalDNSMessage + } + var networks []string + if useTCP { + networks = []string{"tcp"} + } else { + networks = []string{"udp", "tcp"} + } + for _, network := range networks { + nctx, cancel := context.WithDeadline(ctx, time.Now().Add(timeout)) + defer cancel() + + c, err := r.dial(nctx, network, server) + if err != nil { + return dnsmessage.Parser{}, dnsmessage.Header{}, err + } + if d, ok := nctx.Deadline(); ok && !d.IsZero() { + c.SetDeadline(d) + } + var p dnsmessage.Parser + var h dnsmessage.Header + if _, ok := c.(net.PacketConn); ok { + p, h, err = dnsPacketRoundTrip(c, id, q, udpReq) + } else { + p, h, err = dnsStreamRoundTrip(c, id, q, tcpReq) + } + c.Close() + if err != nil { + return dnsmessage.Parser{}, dnsmessage.Header{}, mapErr(err) + } + if err := p.SkipQuestion(); err != dnsmessage.ErrSectionDone { + return dnsmessage.Parser{}, dnsmessage.Header{}, errInvalidDNSResponse + } + if h.Truncated { // see RFC 5966 + continue + } + return p, h, nil + } + return dnsmessage.Parser{}, dnsmessage.Header{}, errNoAnswerFromDNSServer +} + +// checkHeader performs basic sanity checks on the header. +func checkHeader(p *dnsmessage.Parser, h dnsmessage.Header) error { + if h.RCode == dnsmessage.RCodeNameError { + return errNoSuchHost + } + + _, err := p.AnswerHeader() + if err != nil && err != dnsmessage.ErrSectionDone { + return errCannotUnmarshalDNSMessage + } + + // libresolv continues to the next server when it receives + // an invalid referral response. See golang.org/issue/15434. + if h.RCode == dnsmessage.RCodeSuccess && !h.Authoritative && !h.RecursionAvailable && err == dnsmessage.ErrSectionDone { + return errLameReferral + } + + if h.RCode != dnsmessage.RCodeSuccess && h.RCode != dnsmessage.RCodeNameError { + // None of the error codes make sense + // for the query we sent. If we didn't get + // a name error and we didn't get success, + // the server is behaving incorrectly or + // having temporary trouble. + if h.RCode == dnsmessage.RCodeServerFailure { + // Look for Extended DNS Error (EDE), RFC 8914. + + if p.SkipAllAnswers() != nil || p.SkipAllAuthorities() != nil { + return errServerTemporarilyMisbehaving + } + + var haveOPT bool + for { + rh, err := p.AdditionalHeader() + if err == dnsmessage.ErrSectionDone { + break + } else if err != nil { + return errServerTemporarilyMisbehaving + } + if rh.Type != dnsmessage.TypeOPT { + p.SkipAdditional() + continue + } + // Only one OPT record is allowed. With multiple we MUST return an error. See RFC + // 6891, section 6.1.1, page 6, last paragraph. + if haveOPT { + return errInvalidDNSResponse + } + haveOPT = true + opt, err := p.OPTResource() + if err != nil { + return errInvalidDNSResponse + } + for _, o := range opt.Options { + if o.Code == 15 { + if len(o.Data) < 2 { + return errInvalidDNSResponse + } + infoCode := ErrorCode(uint16(o.Data[0])<<8 | uint16(o.Data[1]<<0)) + extraText := string(bytes.TrimRight(o.Data[2:], "\u0000")) + return ExtendedError{infoCode, extraText} + } + } + } + return errServerTemporarilyMisbehaving + } + return errServerMisbehaving + } + + return nil +} + +func skipToAnswer(p *dnsmessage.Parser, qtype dnsmessage.Type) error { + for { + h, err := p.AnswerHeader() + if err == dnsmessage.ErrSectionDone { + return errNoSuchHost + } + if err != nil { + return errCannotUnmarshalDNSMessage + } + if h.Type == qtype { + return nil + } + if err := p.SkipAnswer(); err != nil { + return errCannotUnmarshalDNSMessage + } + } +} + +// Do a lookup for a single name, which must be rooted +// (otherwise answer will not find the answers). +func (r *Resolver) tryOneName(ctx context.Context, cfg *dnsConfig, name string, qtype dnsmessage.Type) (dnsmessage.Parser, string, Result, error) { + var lastErr error + var lastResult Result + serverOffset := cfg.serverOffset() + sLen := uint32(len(cfg.servers)) + + n, err := dnsmessage.NewName(name) + if err != nil { + return dnsmessage.Parser{}, "", Result{}, errCannotMarshalDNSMessage + } + q := dnsmessage.Question{ + Name: n, + Type: qtype, + Class: dnsmessage.ClassINET, + } + + for i := 0; i < cfg.attempts; i++ { + for j := uint32(0); j < sLen; j++ { + server := cfg.servers[(serverOffset+j)%sLen] + + p, h, err := r.exchange(ctx, server, q, cfg.timeout, cfg.useTCP, cfg.trustAD) + if err != nil { + dnsErr := &DNSError{ + Err: err.Error(), + Name: name, + Server: server, + } + if nerr, ok := err.(net.Error); ok && nerr.Timeout() { + dnsErr.IsTimeout = true + } + // Set IsTemporary for socket-level errors. Note that this flag + // may also be used to indicate a SERVFAIL response. + if _, ok := err.(*net.OpError); ok { + dnsErr.IsTemporary = true + } + lastErr = dnsErr + lastResult = Result{} + continue + } + useAD := h.RCode == dnsmessage.RCodeSuccess || h.RCode == dnsmessage.RCodeNameError + result := Result{Authentic: cfg.trustAD && h.AuthenticData && useAD} + + if err := checkHeader(&p, h); err != nil { + dnsErr := &DNSError{ + Underlying: err, + Err: err.Error(), + Name: name, + Server: server, + } + if err == errServerTemporarilyMisbehaving { + dnsErr.IsTemporary = true + } else if edeErr, isEDE := err.(ExtendedError); isEDE && edeErr.IsTemporary() { + dnsErr.IsTemporary = true + } else if isEDE { + // Something wrong with the zone, no point asking another server or retrying. + return p, server, result, dnsErr + } + if err == errNoSuchHost { + // The name does not exist, so trying + // another server won't help. + + dnsErr.IsNotFound = true + return p, server, result, dnsErr + } + lastErr = dnsErr + lastResult = result + continue + } + + err = skipToAnswer(&p, qtype) + if err == nil { + return p, server, result, nil + } + lastErr = &DNSError{ + Err: err.Error(), + Name: name, + Server: server, + } + lastResult = result + if err == errNoSuchHost { + // The name does not exist, so trying another + // server won't help. + + lastErr.(*DNSError).IsNotFound = true + return p, server, lastResult, lastErr + } + } + } + return dnsmessage.Parser{}, "", lastResult, lastErr +} + +// A resolverConfig represents a DNS stub resolver configuration. +type resolverConfig struct { + initOnce sync.Once // guards init of resolverConfig + + // ch is used as a semaphore that only allows one lookup at a + // time to recheck resolv.conf. + ch chan struct{} // guards lastChecked and modTime + lastChecked time.Time // last time resolv.conf was checked + + dnsConfig atomic.Pointer[dnsConfig] // parsed resolv.conf structure used in lookups +} + +var resolvConf resolverConfig + +func getSystemDNSConfig() *dnsConfig { + resolvConf.tryUpdate("/etc/resolv.conf") + return resolvConf.dnsConfig.Load() +} + +// init initializes conf and is only called via conf.initOnce. +func (conf *resolverConfig) init() { + // Set dnsConfig and lastChecked so we don't parse + // resolv.conf twice the first time. + conf.dnsConfig.Store(dnsReadConfig("/etc/resolv.conf")) + conf.lastChecked = time.Now() + + // Prepare ch so that only one update of resolverConfig may + // run at once. + conf.ch = make(chan struct{}, 1) +} + +// tryUpdate tries to update conf with the named resolv.conf file. +// The name variable only exists for testing. It is otherwise always +// "/etc/resolv.conf". +func (conf *resolverConfig) tryUpdate(name string) { + conf.initOnce.Do(conf.init) + + if conf.dnsConfig.Load().noReload { + return + } + + // Ensure only one update at a time checks resolv.conf. + if !conf.tryAcquireSema() { + return + } + defer conf.releaseSema() + + now := time.Now() + if conf.lastChecked.After(now.Add(-5 * time.Second)) { + return + } + conf.lastChecked = now + + switch runtime.GOOS { + case "windows": + // There's no file on disk, so don't bother checking + // and failing. + // + // The Windows implementation of dnsReadConfig (called + // below) ignores the name. + default: + var mtime time.Time + if fi, err := os.Stat(name); err == nil { + mtime = fi.ModTime() + } + if mtime.Equal(conf.dnsConfig.Load().mtime) { + return + } + } + + dnsConf := dnsReadConfig(name) + conf.dnsConfig.Store(dnsConf) +} + +func (conf *resolverConfig) tryAcquireSema() bool { + select { + case conf.ch <- struct{}{}: + return true + default: + return false + } +} + +func (conf *resolverConfig) releaseSema() { + <-conf.ch +} + +func (r *Resolver) lookup(ctx context.Context, name string, qtype dnsmessage.Type, conf *dnsConfig) (dnsmessage.Parser, string, Result, error) { + if !isDomainName(name) { + // We used to use "invalid domain name" as the error, + // but that is a detail of the specific lookup mechanism. + // Other lookups might allow broader name syntax + // (for example Multicast DNS allows UTF-8; see RFC 6762). + // For consistency with libc resolvers, report no such host. + return dnsmessage.Parser{}, "", Result{}, &DNSError{Err: errNoSuchHost.Error(), Name: name, IsNotFound: true} + } + + if conf == nil { + conf = getSystemDNSConfig() + } + + var ( + p dnsmessage.Parser + server string + result Result + err error + ) + for _, fqdn := range conf.nameList(name) { + p, server, result, err = r.tryOneName(ctx, conf, fqdn, qtype) + if err == nil { + break + } + var edeErr ExtendedError + if nerr, ok := err.(net.Error); ok && nerr.Temporary() && r.strictErrors() || errors.As(err, &edeErr) && !edeErr.IsTemporary() { + // If we hit a temporary error with StrictErrors enabled, + // stop immediately instead of trying more names. + break + } + } + if err == nil { + return p, server, result, nil + } + if err, ok := err.(*DNSError); ok { + // Show original name passed to lookup, not suffixed one. + // In general we might have tried many suffixes; showing + // just one is misleading. See also golang.org/issue/6324. + err.Name = name + } + return dnsmessage.Parser{}, "", result, err +} + +// avoidDNS reports whether this is a hostname for which we should not +// use DNS. Currently this includes only .onion, per RFC 7686. See +// golang.org/issue/13705. Does not cover .local names (RFC 6762), +// see golang.org/issue/16739. +func avoidDNS(name string) bool { + if name == "" { + return true + } + if name[len(name)-1] == '.' { + name = name[:len(name)-1] + } + return stringsHasSuffixFold(name, ".onion") +} + +// nameList returns a list of names for sequential DNS queries. +func (conf *dnsConfig) nameList(name string) []string { + if avoidDNS(name) { + return nil + } + + // Check name length (see isDomainName). + l := len(name) + rooted := l > 0 && name[l-1] == '.' + if l > 254 || l == 254 && !rooted { + return nil + } + + // If name is rooted (trailing dot), try only that name. + if rooted { + return []string{name} + } + + hasNdots := count(name, '.') >= conf.ndots + name += "." + l++ + + // Build list of search choices. + names := make([]string, 0, 1+len(conf.search)) + // If name has enough dots, try unsuffixed first. + if hasNdots { + names = append(names, name) + } + // Try suffixes that are not too long (see isDomainName). + for _, suffix := range conf.search { + if l+len(suffix) <= 254 { + names = append(names, name+suffix) + } + } + // Try unsuffixed, if not tried first above. + if !hasNdots { + names = append(names, name) + } + return names +} + +// hostLookupOrder specifies the order of LookupHost lookup strategies. +// It is basically a simplified representation of nsswitch.conf. +// "files" means /etc/hosts. +type hostLookupOrder int + +const ( + // hostLookupCgo means defer to cgo. + hostLookupCgo hostLookupOrder = iota + hostLookupFilesDNS // files first + hostLookupDNSFiles // dns first + hostLookupFiles // only files + hostLookupDNS // only DNS +) + +var lookupOrderName = map[hostLookupOrder]string{ + hostLookupCgo: "cgo", + hostLookupFilesDNS: "files,dns", + hostLookupDNSFiles: "dns,files", + hostLookupFiles: "files", + hostLookupDNS: "dns", +} + +func (o hostLookupOrder) String() string { + if s, ok := lookupOrderName[o]; ok { + return s + } + return "hostLookupOrder=" + itoa.Itoa(int(o)) + "??" +} + +func (r *Resolver) goLookupHostOrder(ctx context.Context, name string, order hostLookupOrder, conf *dnsConfig) (addrs []string, result Result, err error) { + if order == hostLookupFilesDNS || order == hostLookupFiles { + // Use entries from /etc/hosts if they match. + addrs, _ = lookupStaticHost(name) + if len(addrs) > 0 { + return + } + + if order == hostLookupFiles { + return nil, result, &DNSError{Err: errNoSuchHost.Error(), Name: name, IsNotFound: true} + } + } + ips, _, result, err := r.goLookupIPCNAMEOrder(ctx, "ip", name, order, conf) + if err != nil { + return + } + addrs = make([]string, 0, len(ips)) + for _, ip := range ips { + addrs = append(addrs, ip.String()) + } + return +} + +// lookup entries from /etc/hosts +func goLookupIPFiles(name string) (addrs []net.IPAddr, canonical string) { + addr, canonical := lookupStaticHost(name) + for _, haddr := range addr { + xhaddr, zone := splitHostZone(haddr) + if ip := net.ParseIP(xhaddr); ip != nil { + addr := net.IPAddr{IP: ip, Zone: zone} + addrs = append(addrs, addr) + } + } + sortByRFC6724(addrs) + return addrs, canonical +} + +// goLookupIP is the native Go implementation of LookupIP. +// The libc versions are in cgo_*.go. +func (r *Resolver) goLookupIP(ctx context.Context, network, host string) (addrs []net.IPAddr, result Result, err error) { + order, conf := systemConf().hostLookupOrder(r, host) + addrs, _, result, err = r.goLookupIPCNAMEOrder(ctx, network, host, order, conf) + return +} + +func (r *Resolver) goLookupIPCNAMEOrder(ctx context.Context, network, name string, order hostLookupOrder, conf *dnsConfig) (addrs []net.IPAddr, cname dnsmessage.Name, result Result, err error) { + if order == hostLookupFilesDNS || order == hostLookupFiles { + var canonical string + addrs, canonical = goLookupIPFiles(name) + + if len(addrs) > 0 { + var err error + cname, err = dnsmessage.NewName(canonical) + if err != nil { + return nil, dnsmessage.Name{}, result, err + } + return addrs, cname, result, nil + } + + if order == hostLookupFiles { + return nil, dnsmessage.Name{}, result, &DNSError{Err: errNoSuchHost.Error(), Name: name, IsNotFound: true} + } + } + + if !isDomainName(name) { + // See comment in func lookup above about use of errNoSuchHost. + return nil, dnsmessage.Name{}, result, &DNSError{Err: errNoSuchHost.Error(), Name: name, IsNotFound: true} + } + type result0 struct { + p dnsmessage.Parser + server string + result Result + error + } + + if conf == nil { + conf = getSystemDNSConfig() + } + + lane := make(chan result0, 1) + qtypes := []dnsmessage.Type{dnsmessage.TypeA, dnsmessage.TypeAAAA} + if network == "CNAME" { + qtypes = append(qtypes, dnsmessage.TypeCNAME) + } + switch ipVersion(network) { + case '4': + qtypes = []dnsmessage.Type{dnsmessage.TypeA} + case '6': + qtypes = []dnsmessage.Type{dnsmessage.TypeAAAA} + } + var queryFn func(fqdn string, qtype dnsmessage.Type) + var responseFn func(fqdn string, qtype dnsmessage.Type) result0 + if conf.singleRequest { + queryFn = func(fqdn string, qtype dnsmessage.Type) {} + responseFn = func(fqdn string, qtype dnsmessage.Type) result0 { + dnsWaitGroup.Add(1) + defer dnsWaitGroup.Done() + p, server, res, err := r.tryOneName(ctx, conf, fqdn, qtype) + return result0{p, server, res, err} + } + } else { + queryFn = func(fqdn string, qtype dnsmessage.Type) { + dnsWaitGroup.Add(1) + go func(qtype dnsmessage.Type) { + p, server, res, err := r.tryOneName(ctx, conf, fqdn, qtype) + lane <- result0{p, server, res, err} + dnsWaitGroup.Done() + }(qtype) + } + responseFn = func(fqdn string, qtype dnsmessage.Type) result0 { + return <-lane + } + } + var lastErr error + var lastResult Result + for _, fqdn := range conf.nameList(name) { + for _, qtype := range qtypes { + queryFn(fqdn, qtype) + } + hitStrictError := false + for _, qtype := range qtypes { + result0 := responseFn(fqdn, qtype) + if result0.error != nil { + if nerr, ok := result0.error.(net.Error); ok && nerr.Temporary() && r.strictErrors() { + // This error will abort the nameList loop. + hitStrictError = true + lastErr = result0.error + lastResult = result0.result + } else if lastErr == nil || fqdn == name+"." { + // Prefer error for original name. + lastErr = result0.error + lastResult = result0.result + } + continue + } + result = result0.result + + // Presotto says it's okay to assume that servers listed in + // /etc/resolv.conf are recursive resolvers. + // + // We asked for recursion, so it should have included all the + // answers we need in this one packet. + // + // Further, RFC 1034 section 4.3.1 says that "the recursive + // response to a query will be... The answer to the query, + // possibly preface by one or more CNAME RRs that specify + // aliases encountered on the way to an answer." + // + // Therefore, we should be able to assume that we can ignore + // CNAMEs and that the A and AAAA records we requested are + // for the canonical name. + + loop: + for { + h, err := result0.p.AnswerHeader() + if err != nil && err != dnsmessage.ErrSectionDone { + lastErr = &DNSError{ + Err: "cannot marshal DNS message", + Name: name, + Server: result0.server, + } + } + if err != nil { + break + } + switch h.Type { + case dnsmessage.TypeA: + a, err := result0.p.AResource() + if err != nil { + lastErr = &DNSError{ + Err: "cannot marshal DNS message", + Name: name, + Server: result0.server, + } + break loop + } + addrs = append(addrs, net.IPAddr{IP: net.IP(a.A[:])}) + if cname.Length == 0 && h.Name.Length != 0 { + cname = h.Name + } + + case dnsmessage.TypeAAAA: + aaaa, err := result0.p.AAAAResource() + if err != nil { + lastErr = &DNSError{ + Err: "cannot marshal DNS message", + Name: name, + Server: result0.server, + } + break loop + } + addrs = append(addrs, net.IPAddr{IP: net.IP(aaaa.AAAA[:])}) + if cname.Length == 0 && h.Name.Length != 0 { + cname = h.Name + } + + case dnsmessage.TypeCNAME: + c, err := result0.p.CNAMEResource() + if err != nil { + lastErr = &DNSError{ + Err: "cannot marshal DNS message", + Name: name, + Server: result0.server, + } + break loop + } + if cname.Length == 0 && c.CNAME.Length > 0 { + cname = c.CNAME + } + + default: + if err := result0.p.SkipAnswer(); err != nil { + lastErr = &DNSError{ + Err: "cannot marshal DNS message", + Name: name, + Server: result0.server, + } + break loop + } + continue + } + } + } + if hitStrictError { + // If either family hit an error with StrictErrors enabled, + // discard all addresses. This ensures that network flakiness + // cannot turn a dualstack hostname IPv4/IPv6-only. + addrs = nil + break + } + if len(addrs) > 0 || network == "CNAME" && cname.Length > 0 { + break + } + } + if lastErr, ok := lastErr.(*DNSError); ok { + // Show original name passed to lookup, not suffixed one. + // In general we might have tried many suffixes; showing + // just one is misleading. See also golang.org/issue/6324. + lastErr.Name = name + } + sortByRFC6724(addrs) + if len(addrs) == 0 && !(network == "CNAME" && cname.Length > 0) { + if order == hostLookupDNSFiles { + var canonical string + addrs, canonical = goLookupIPFiles(name) + if len(addrs) > 0 { + var err error + cname, err = dnsmessage.NewName(canonical) + if err != nil { + return nil, dnsmessage.Name{}, result, err + } + return addrs, cname, result, nil + } + } + if lastErr != nil { + return nil, dnsmessage.Name{}, lastResult, lastErr + } + } + return addrs, cname, result, nil +} + +// goLookupCNAME is the native Go (non-cgo) implementation of LookupCNAME. +func (r *Resolver) goLookupCNAME(ctx context.Context, host string, order hostLookupOrder, conf *dnsConfig) (string, Result, error) { + _, cname, result, err := r.goLookupIPCNAMEOrder(ctx, "CNAME", host, order, conf) + return cname.String(), result, err +} + +// goLookupPTR is the native Go implementation of LookupAddr. +func (r *Resolver) goLookupPTR(ctx context.Context, addr string, order hostLookupOrder, conf *dnsConfig) ([]string, Result, error) { + if order == hostLookupFiles || order == hostLookupFilesDNS { + names := lookupStaticAddr(addr) + if len(names) > 0 { + return names, Result{}, nil + } + + if order == hostLookupFiles { + return nil, Result{}, &DNSError{Err: errNoSuchHost.Error(), Name: addr, IsNotFound: true} + } + } + + arpa, err := reverseaddr(addr) + if err != nil { + return nil, Result{}, err + } + p, server, result, err := r.lookup(ctx, arpa, dnsmessage.TypePTR, conf) + if err != nil { + var dnsErr *DNSError + if errors.As(err, &dnsErr) && dnsErr.IsNotFound { + if order == hostLookupDNSFiles { + names := lookupStaticAddr(addr) + if len(names) > 0 { + return names, result, nil + } + } + } + return nil, result, err + } + var ptrs []string + for { + h, err := p.AnswerHeader() + if err == dnsmessage.ErrSectionDone { + break + } + if err != nil { + return nil, result, &DNSError{ + Err: "cannot marshal DNS message", + Name: addr, + Server: server, + } + } + if h.Type != dnsmessage.TypePTR { + err := p.SkipAnswer() + if err != nil { + return nil, result, &DNSError{ + Err: "cannot marshal DNS message", + Name: addr, + Server: server, + } + } + continue + } + ptr, err := p.PTRResource() + if err != nil { + return nil, result, &DNSError{ + Err: "cannot marshal DNS message", + Name: addr, + Server: server, + } + } + ptrs = append(ptrs, ptr.PTR.String()) + + } + + return ptrs, result, nil +} diff --git a/vendor/github.com/mjl-/adns/dnsconfig.go b/vendor/github.com/mjl-/adns/dnsconfig.go new file mode 100644 index 0000000..5d0fb19 --- /dev/null +++ b/vendor/github.com/mjl-/adns/dnsconfig.go @@ -0,0 +1,45 @@ +// Copyright 2022 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. + +package adns + +import ( + "os" + "sync/atomic" + "time" +) + +var ( + defaultNS = []string{"127.0.0.1:53", "[::1]:53"} + getHostname = os.Hostname // variable for testing +) + +type dnsConfig struct { + servers []string // server addresses (in host:port form) to use + search []string // rooted suffixes to append to local name + ndots int // number of dots in name to trigger absolute lookup + timeout time.Duration // wait before giving up on a query, including retries + attempts int // lost packets before giving up on server + rotate bool // round robin among servers + unknownOpt bool // anything unknown was encountered + lookup []string // OpenBSD top-level database "lookup" order + err error // any error that occurs during open of resolv.conf + mtime time.Time // time of resolv.conf modification + soffset uint32 // used by serverOffset + singleRequest bool // use sequential A and AAAA queries instead of parallel queries + useTCP bool // force usage of TCP for DNS resolutions + trustAD bool // add AD flag to queries + noReload bool // do not check for config file updates +} + +// serverOffset returns an offset that can be used to determine +// indices of servers in c.servers when making queries. +// When the rotate option is enabled, this offset increases. +// Otherwise it is always 0. +func (c *dnsConfig) serverOffset() uint32 { + if c.rotate { + return atomic.AddUint32(&c.soffset, 1) - 1 // return 0 to start + } + return 0 +} diff --git a/vendor/github.com/mjl-/adns/dnsconfig_unix.go b/vendor/github.com/mjl-/adns/dnsconfig_unix.go new file mode 100644 index 0000000..4a2f7ee --- /dev/null +++ b/vendor/github.com/mjl-/adns/dnsconfig_unix.go @@ -0,0 +1,188 @@ +// Copyright 2009 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 && !windows + +// Read system DNS config from /etc/resolv.conf + +package adns + +import ( + "net" + "net/netip" + "time" + + "github.com/mjl-/adns/internal/bytealg" +) + +// See resolv.conf(5) on a Linux machine. +func dnsReadConfig(filename string) *dnsConfig { + conf := &dnsConfig{ + ndots: 1, + timeout: 5 * time.Second, + attempts: 2, + } + file, err := open(filename) + if err != nil { + conf.servers = defaultNS + conf.trustAD = true + conf.search = dnsDefaultSearch() + conf.err = err + return conf + } + defer file.close() + if fi, err := file.file.Stat(); err == nil { + conf.mtime = fi.ModTime() + } else { + conf.servers = defaultNS + conf.trustAD = true + conf.search = dnsDefaultSearch() + conf.err = err + return conf + } + for line, ok := file.readLine(); ok; line, ok = file.readLine() { + if len(line) > 0 && (line[0] == ';' || line[0] == '#') { + // comment. + continue + } + f := getFields(line) + if len(f) < 1 { + continue + } + switch f[0] { + case "nameserver": // add one name server + if len(f) > 1 && len(conf.servers) < 3 { // small, but the standard limit + // One more check: make sure server name is + // just an IP address. Otherwise we need DNS + // to look it up. + if _, err := netip.ParseAddr(f[1]); err == nil { + conf.servers = append(conf.servers, JoinHostPort(f[1], "53")) + } + } + + case "domain": // set search path to just this domain + if len(f) > 1 { + conf.search = []string{ensureRooted(f[1])} + } + + case "search": // set search path to given servers + conf.search = make([]string, 0, len(f)-1) + for i := 1; i < len(f); i++ { + name := ensureRooted(f[i]) + if name == "." { + continue + } + conf.search = append(conf.search, name) + } + + case "options": // magic options + for _, s := range f[1:] { + switch { + case hasPrefix(s, "ndots:"): + n, _, _ := dtoi(s[6:]) + if n < 0 { + n = 0 + } else if n > 15 { + n = 15 + } + conf.ndots = n + case hasPrefix(s, "timeout:"): + n, _, _ := dtoi(s[8:]) + if n < 1 { + n = 1 + } + conf.timeout = time.Duration(n) * time.Second + case hasPrefix(s, "attempts:"): + n, _, _ := dtoi(s[9:]) + if n < 1 { + n = 1 + } + conf.attempts = n + case s == "rotate": + conf.rotate = true + case s == "single-request" || s == "single-request-reopen": + // Linux option: + // http://man7.org/linux/man-pages/man5/resolv.conf.5.html + // "By default, glibc performs IPv4 and IPv6 lookups in parallel [...] + // This option disables the behavior and makes glibc + // perform the IPv6 and IPv4 requests sequentially." + conf.singleRequest = true + case s == "use-vc" || s == "usevc" || s == "tcp": + // Linux (use-vc), FreeBSD (usevc) and OpenBSD (tcp) option: + // http://man7.org/linux/man-pages/man5/resolv.conf.5.html + // "Sets RES_USEVC in _res.options. + // This option forces the use of TCP for DNS resolutions." + // https://www.freebsd.org/cgi/man.cgi?query=resolv.conf&sektion=5&manpath=freebsd-release-ports + // https://man.openbsd.org/resolv.conf.5 + conf.useTCP = true + case s == "trust-ad": + conf.trustAD = true + case s == "edns0": + // We use EDNS by default. + // Ignore this option. + case s == "no-reload": + conf.noReload = true + default: + conf.unknownOpt = true + } + } + + case "lookup": + // OpenBSD option: + // https://www.openbsd.org/cgi-bin/man.cgi/OpenBSD-current/man5/resolv.conf.5 + // "the legal space-separated values are: bind, file, yp" + conf.lookup = f[1:] + + default: + conf.unknownOpt = true + } + } + if len(conf.servers) == 0 { + conf.servers = defaultNS + } + if !conf.trustAD { + // If we only have name servers on loopback IP, we trust them. + // As mentioned in RFC 6698, section A.3 point 2 (line 1693). + conf.trustAD = true + for _, addr := range conf.servers { + host, _, err := net.SplitHostPort(addr) + if err != nil { + conf.trustAD = false + break + } + ip := net.ParseIP(host) + if ip == nil || !ip.IsLoopback() { + conf.trustAD = false + break + } + } + } + if len(conf.search) == 0 { + conf.search = dnsDefaultSearch() + } + return conf +} + +func dnsDefaultSearch() []string { + hn, err := getHostname() + if err != nil { + // best effort + return nil + } + if i := bytealg.IndexByteString(hn, '.'); i >= 0 && i < len(hn)-1 { + return []string{ensureRooted(hn[i+1:])} + } + return nil +} + +func hasPrefix(s, prefix string) bool { + return len(s) >= len(prefix) && s[:len(prefix)] == prefix +} + +func ensureRooted(s string) string { + if len(s) > 0 && s[len(s)-1] == '.' { + return s + } + return s + "." +} diff --git a/vendor/github.com/mjl-/adns/doc.go b/vendor/github.com/mjl-/adns/doc.go new file mode 100644 index 0000000..f4dd410 --- /dev/null +++ b/vendor/github.com/mjl-/adns/doc.go @@ -0,0 +1,20 @@ +/* +adns is a copy of the Go standard library, modified to provide details about +the DNSSEC status of responses. + +The MX, NS, SRV types from the "net" package are used to make to prevent churn +when switching from net to adns. + +Modifications + + - Each Lookup* also returns a Result with the "Authentic" field representing if + the response had the "authentic data" bit (and is trusted), i.e. was + DNSSEC-signed according to the recursive resolver. + - Resolver are also trusted if all name servers have loopback IPs. Resolvers + are still also trusted if /etc/resolv.conf has "trust-ad" in the "options". + - New function LookupTLSA, to support DANE which uses DNS records of type TLSA. + - Support Extended DNS Errors (EDE) for details about DNSSEC errors. + - adns uses its own DNSError type, with an additional "Underlying error" field + and Unwrap function, so callers can check for the new ExtendedError type. +*/ +package adns diff --git a/vendor/github.com/mjl-/adns/ede.go b/vendor/github.com/mjl-/adns/ede.go new file mode 100644 index 0000000..1a7f13e --- /dev/null +++ b/vendor/github.com/mjl-/adns/ede.go @@ -0,0 +1,168 @@ +package adns + +import ( + "fmt" +) + +// ExtendedError is an RFC 8914 Extended DNS Error (EDE). +type ExtendedError struct { + InfoCode ErrorCode + ExtraText string // Human-readable error message, optional. +} + +// IsTemporary indicates whether an error is a temporary server error, and +// retries might give a different result. +func (e ExtendedError) IsTemporary() bool { + return e.InfoCode.IsTemporary() +} + +// Unwrap returns the underlying ErrorCode error. +func (e ExtendedError) Unwrap() error { + return e.InfoCode +} + +// Error returns a string representing the InfoCode, and either the extra text or +// more details for the info code. +func (e ExtendedError) Error() string { + s := e.InfoCode.Error() + if e.ExtraText != "" { + return s + ": " + e.ExtraText + } + if int(e.InfoCode) >= len(errorCodeDetails) { + return s + } + return s + ": " + errorCodeDetails[e.InfoCode] +} + +// ErrorCode is an InfoCode from Extended DNS Errors, RFC 8914. +type ErrorCode uint16 + +const ( + ErrOtherErrorCode ErrorCode = 0 + ErrUnsupportedDNSKEYAlgorithm ErrorCode = 1 + ErrUnsupportedDSDigestType ErrorCode = 2 + ErrStaleAnswer ErrorCode = 3 + ErrForgedAnswer ErrorCode = 4 + ErrDNSSECIndeterminate ErrorCode = 5 + ErrDNSSECBogus ErrorCode = 6 + ErrSignatureExpired ErrorCode = 7 + ErrSignatureNotYetValid ErrorCode = 8 + ErrDNSKEYMissing ErrorCode = 9 + ErrRRSIGMissing ErrorCode = 10 + ErrNoZoneKeyBitSet ErrorCode = 11 + ErrNSECMissing ErrorCode = 12 + ErrCachedError ErrorCode = 13 + ErrNotReady ErrorCode = 14 + ErrBlocked ErrorCode = 15 + ErrCensored ErrorCode = 16 + ErrFiltered ErrorCode = 17 + ErrProhibited ErrorCode = 18 + ErrStaleNXDOMAINAnswer ErrorCode = 19 + ErrNotAuthoritative ErrorCode = 20 + ErrNotSupported ErrorCode = 21 + ErrNoReachableAuthority ErrorCode = 22 + ErrNetworkError ErrorCode = 23 + ErrInvalidData ErrorCode = 24 +) + +// IsTemporary returns whether the error is temporary and has a chance of +// succeeding on a retry. +func (e ErrorCode) IsTemporary() bool { + switch e { + case ErrOtherErrorCode, + ErrStaleAnswer, + ErrCachedError, + ErrNotReady, + ErrStaleNXDOMAINAnswer, + ErrNoReachableAuthority, + ErrNetworkError: + return true + } + return false +} + +// IsAuthentication returns whether the error is related to authentication, +// e.g. bogus DNSSEC, missing DS/DNSKEY/RRSIG records, etc, or an other +// DNSSEC-related error. +func (e ErrorCode) IsAuthentication() bool { + switch e { + case ErrUnsupportedDNSKEYAlgorithm, + ErrUnsupportedDSDigestType, + ErrDNSSECIndeterminate, + ErrDNSSECBogus, + ErrSignatureExpired, + ErrSignatureNotYetValid, + ErrDNSKEYMissing, + ErrRRSIGMissing, + ErrNoZoneKeyBitSet, + ErrNSECMissing: + return true + } + return false +} + +// Error includes a human-readable short string for the info code. +func (e ErrorCode) Error() string { + if int(e) >= len(errorCodeStrings) { + return fmt.Sprintf("unknown error code from name server: %d", e) + } + return fmt.Sprintf("error from name server: %s", errorCodeStrings[e]) +} + +// short strings, always included in error messages. +var errorCodeStrings = []string{ + "other", + "unsupported dnskey algorithm", + "unsupported ds digest type", + "stale answer", + "forged answer", + "dnssec indeterminate", + "dnssec bogus", + "signature expired", + "signature not yet valid", + "dnskey missing", + "rrsigs missing", + "no zone key bit set", + "nsec missing", + "cached error", + "not ready", + "blocked", + "censored", + "filtered", + "prohibited", + "stale nxdomain answer", + "not authoritative", + "not supported", + "no reachable authority", + "network error", + "invalid data", +} + +// more detailed string, only included if there is no detail text in the response. +var errorCodeDetails = []string{ + "unspecified error", + "only found unsupported algorithms in DNSKEY records", + "only found unsupported types in DS records", + "unable to resolve within deadline, stale data served", + "answer was forged for policy reason", + "dnssec validation ended in interderminate state", + "dnssec validation ended in bogus status", + "only expired dnssec signatures found", + "only signatures found that are not yet valid", + "ds key exists at a parent, but no supported matching dnskey found", + "dnssec validation attempted, but no rrsig found", + "no zone key bit found in a dnskey", + "dnssec validation found missing data without nsec/nsec3 record", + "failure served from cache", + "not yet fully functional to resolve query", + "domain is on blocklist due to internal security policy", + "domain is on blocklist due to external entity", + "domain is on client-requested blocklist", + "refusing to serve request", + "stale nxdomain served from cache", + "unexpected authoritativeness of query", + "query or operation not supported", + "no authoritative name server could be reached", + "unrecoverable network error", + "zone data not valid", +} diff --git a/vendor/github.com/mjl-/adns/hook.go b/vendor/github.com/mjl-/adns/hook.go new file mode 100644 index 0000000..4c49fcc --- /dev/null +++ b/vendor/github.com/mjl-/adns/hook.go @@ -0,0 +1,22 @@ +// 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. + +package adns + +import ( + "context" + "net" +) + +var ( + testHookHostsPath = "/etc/hosts" + testHookLookupIP = func( + ctx context.Context, + fn func(context.Context, string, string) ([]net.IPAddr, Result, error), + network string, + host string, + ) ([]net.IPAddr, Result, error) { + return fn(ctx, network, host) + } +) diff --git a/vendor/github.com/mjl-/adns/hosts.go b/vendor/github.com/mjl-/adns/hosts.go new file mode 100644 index 0000000..c81cac9 --- /dev/null +++ b/vendor/github.com/mjl-/adns/hosts.go @@ -0,0 +1,166 @@ +// Copyright 2009 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. + +package adns + +import ( + "errors" + "io/fs" + "net/netip" + "sync" + "time" + + "github.com/mjl-/adns/internal/bytealg" +) + +const cacheMaxAge = 5 * time.Second + +func parseLiteralIP(addr string) string { + ip, err := netip.ParseAddr(addr) + if err != nil { + return "" + } + return ip.String() +} + +type byName struct { + addrs []string + canonicalName string +} + +// hosts contains known host entries. +var hosts struct { + sync.Mutex + + // Key for the list of literal IP addresses must be a host + // name. It would be part of DNS labels, a FQDN or an absolute + // FQDN. + // For now the key is converted to lower case for convenience. + byName map[string]byName + + // Key for the list of host names must be a literal IP address + // including IPv6 address with zone identifier. + // We don't support old-classful IP address notation. + byAddr map[string][]string + + expire time.Time + path string + mtime time.Time + size int64 +} + +func readHosts() { + now := time.Now() + hp := testHookHostsPath + + if now.Before(hosts.expire) && hosts.path == hp && len(hosts.byName) > 0 { + return + } + mtime, size, err := stat(hp) + if err == nil && hosts.path == hp && hosts.mtime.Equal(mtime) && hosts.size == size { + hosts.expire = now.Add(cacheMaxAge) + return + } + + hs := make(map[string]byName) + is := make(map[string][]string) + + file, err := open(hp) + if err != nil { + if !errors.Is(err, fs.ErrNotExist) && !errors.Is(err, fs.ErrPermission) { + return + } + } + + if file != nil { + defer file.close() + for line, ok := file.readLine(); ok; line, ok = file.readLine() { + if i := bytealg.IndexByteString(line, '#'); i >= 0 { + // Discard comments. + line = line[0:i] + } + f := getFields(line) + if len(f) < 2 { + continue + } + addr := parseLiteralIP(f[0]) + if addr == "" { + continue + } + + var canonical string + for i := 1; i < len(f); i++ { + name := absDomainName(f[i]) + h := []byte(f[i]) + lowerASCIIBytes(h) + key := absDomainName(string(h)) + + if i == 1 { + canonical = key + } + + is[addr] = append(is[addr], name) + + if v, ok := hs[key]; ok { + hs[key] = byName{ + addrs: append(v.addrs, addr), + canonicalName: v.canonicalName, + } + continue + } + + hs[key] = byName{ + addrs: []string{addr}, + canonicalName: canonical, + } + } + } + } + // Update the data cache. + hosts.expire = now.Add(cacheMaxAge) + hosts.path = hp + hosts.byName = hs + hosts.byAddr = is + hosts.mtime = mtime + hosts.size = size +} + +// lookupStaticHost looks up the addresses and the canonical name for the given host from /etc/hosts. +func lookupStaticHost(host string) ([]string, string) { + hosts.Lock() + defer hosts.Unlock() + readHosts() + if len(hosts.byName) != 0 { + if hasUpperCase(host) { + lowerHost := []byte(host) + lowerASCIIBytes(lowerHost) + host = string(lowerHost) + } + if byName, ok := hosts.byName[absDomainName(host)]; ok { + ipsCp := make([]string, len(byName.addrs)) + copy(ipsCp, byName.addrs) + return ipsCp, byName.canonicalName + } + } + return nil, "" +} + +// lookupStaticAddr looks up the hosts for the given address from /etc/hosts. +func lookupStaticAddr(addr string) []string { + hosts.Lock() + defer hosts.Unlock() + readHosts() + addr = parseLiteralIP(addr) + if addr == "" { + return nil + } + if len(hosts.byAddr) != 0 { + if hosts, ok := hosts.byAddr[addr]; ok { + hostsCp := make([]string, len(hosts)) + copy(hostsCp, hosts) + return hostsCp + } + } + return nil +} diff --git a/vendor/github.com/mjl-/adns/internal/bytealg/index.go b/vendor/github.com/mjl-/adns/internal/bytealg/index.go new file mode 100644 index 0000000..dd3ca1b --- /dev/null +++ b/vendor/github.com/mjl-/adns/internal/bytealg/index.go @@ -0,0 +1,9 @@ +package bytealg + +import ( + "strings" +) + +func IndexByteString(s string, b byte) int { + return strings.IndexByte(s, b) +} diff --git a/vendor/github.com/mjl-/adns/internal/itoa/itoa.go b/vendor/github.com/mjl-/adns/internal/itoa/itoa.go new file mode 100644 index 0000000..c6062d9 --- /dev/null +++ b/vendor/github.com/mjl-/adns/internal/itoa/itoa.go @@ -0,0 +1,33 @@ +// Copyright 2021 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. + +// Simple conversions to avoid depending on strconv. + +package itoa + +// Itoa converts val to a decimal string. +func Itoa(val int) string { + if val < 0 { + return "-" + Uitoa(uint(-val)) + } + return Uitoa(uint(val)) +} + +// Uitoa converts val to a decimal string. +func Uitoa(val uint) string { + if val == 0 { // avoid string allocation + return "0" + } + var buf [20]byte // big enough for 64bit value base 10 + i := len(buf) - 1 + for val >= 10 { + q := val / 10 + buf[i] = byte('0' + val - q*10) + i-- + val = q + } + // val < 10 + buf[i] = byte('0' + val) + return string(buf[i:]) +} diff --git a/vendor/github.com/mjl-/adns/internal/singleflight/singleflight.go b/vendor/github.com/mjl-/adns/internal/singleflight/singleflight.go new file mode 100644 index 0000000..d0e6d2f --- /dev/null +++ b/vendor/github.com/mjl-/adns/internal/singleflight/singleflight.go @@ -0,0 +1,123 @@ +// Copyright 2013 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. + +// Package singleflight provides a duplicate function call suppression +// mechanism. +package singleflight + +import "sync" + +// call is an in-flight or completed singleflight.Do call +type call struct { + wg sync.WaitGroup + + // These fields are written once before the WaitGroup is done + // and are only read after the WaitGroup is done. + val any + err error + + // These fields are read and written with the singleflight + // mutex held before the WaitGroup is done, and are read but + // not written after the WaitGroup is done. + dups int + chans []chan<- Result +} + +// Group represents a class of work and forms a namespace in +// which units of work can be executed with duplicate suppression. +type Group struct { + mu sync.Mutex // protects m + m map[string]*call // lazily initialized +} + +// Result holds the results of Do, so they can be passed +// on a channel. +type Result struct { + Val any + Err error + Shared bool +} + +// Do executes and returns the results of the given function, making +// sure that only one execution is in-flight for a given key at a +// time. If a duplicate comes in, the duplicate caller waits for the +// original to complete and receives the same results. +// The return value shared indicates whether v was given to multiple callers. +func (g *Group) Do(key string, fn func() (any, error)) (v any, err error, shared bool) { + g.mu.Lock() + if g.m == nil { + g.m = make(map[string]*call) + } + if c, ok := g.m[key]; ok { + c.dups++ + g.mu.Unlock() + c.wg.Wait() + return c.val, c.err, true + } + c := new(call) + c.wg.Add(1) + g.m[key] = c + g.mu.Unlock() + + g.doCall(c, key, fn) + return c.val, c.err, c.dups > 0 +} + +// DoChan is like Do but returns a channel that will receive the +// results when they are ready. +func (g *Group) DoChan(key string, fn func() (any, error)) <-chan Result { + ch := make(chan Result, 1) + g.mu.Lock() + if g.m == nil { + g.m = make(map[string]*call) + } + if c, ok := g.m[key]; ok { + c.dups++ + c.chans = append(c.chans, ch) + g.mu.Unlock() + return ch + } + c := &call{chans: []chan<- Result{ch}} + c.wg.Add(1) + g.m[key] = c + g.mu.Unlock() + + go g.doCall(c, key, fn) + + return ch +} + +// doCall handles the single call for a key. +func (g *Group) doCall(c *call, key string, fn func() (any, error)) { + c.val, c.err = fn() + + g.mu.Lock() + c.wg.Done() + if g.m[key] == c { + delete(g.m, key) + } + for _, ch := range c.chans { + ch <- Result{c.val, c.err, c.dups > 0} + } + g.mu.Unlock() +} + +// ForgetUnshared tells the singleflight to forget about a key if it is not +// shared with any other goroutines. Future calls to Do for a forgotten key +// will call the function rather than waiting for an earlier call to complete. +// Returns whether the key was forgotten or unknown--that is, whether no +// other goroutines are waiting for the result. +func (g *Group) ForgetUnshared(key string) bool { + g.mu.Lock() + defer g.mu.Unlock() + c, ok := g.m[key] + if !ok { + return true + } + if c.dups == 0 { + delete(g.m, key) + return true + } + return false +} diff --git a/vendor/github.com/mjl-/adns/ipsock.go b/vendor/github.com/mjl-/adns/ipsock.go new file mode 100644 index 0000000..87d75c1 --- /dev/null +++ b/vendor/github.com/mjl-/adns/ipsock.go @@ -0,0 +1,205 @@ +// Copyright 2009 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. + +package adns + +import ( + "context" + "net" + + "github.com/mjl-/adns/internal/bytealg" +) + +// BUG(rsc,mikio): On DragonFly BSD and OpenBSD, listening on the +// "tcp" and "udp" networks does not listen for both IPv4 and IPv6 +// connections. This is due to the fact that IPv4 traffic will not be +// routed to an IPv6 socket - two separate sockets are required if +// both address families are to be supported. +// See inet6(4) for details. + +// An addrList represents a list of network endpoint addresses. +type addrList []net.Addr + +// filterAddrList applies a filter to a list of IP addresses, +// yielding a list of Addr objects. Known filters are nil, ipv4only, +// and ipv6only. It returns every address when the filter is nil. +// The result contains at least one address when error is nil. +func filterAddrList(filter func(net.IPAddr) bool, ips []net.IPAddr, inetaddr func(net.IPAddr) net.Addr, originalAddr string) (addrList, error) { + var addrs addrList + for _, ip := range ips { + if filter == nil || filter(ip) { + addrs = append(addrs, inetaddr(ip)) + } + } + if len(addrs) == 0 { + return nil, &net.AddrError{Err: errNoSuitableAddress.Error(), Addr: originalAddr} + } + return addrs, nil +} + +// ipv4only reports whether addr is an IPv4 address. +func ipv4only(addr net.IPAddr) bool { + return addr.IP.To4() != nil +} + +// ipv6only reports whether addr is an IPv6 address except IPv4-mapped IPv6 address. +func ipv6only(addr net.IPAddr) bool { + return len(addr.IP) == net.IPv6len && addr.IP.To4() == nil +} + +// SplitHostPort splits a network address of the form "host:port", +// "host%zone:port", "[host]:port" or "[host%zone]:port" into host or +// host%zone and port. +// +// A literal IPv6 address in hostport must be enclosed in square +// brackets, as in "[::1]:80", "[::1%lo0]:80". +// +// See func Dial for a description of the hostport parameter, and host +// and port results. +func SplitHostPort(hostport string) (host, port string, err error) { + const ( + missingPort = "missing port in address" + tooManyColons = "too many colons in address" + ) + addrErr := func(addr, why string) (host, port string, err error) { + return "", "", &net.AddrError{Err: why, Addr: addr} + } + j, k := 0, 0 + + // The port starts after the last colon. + i := last(hostport, ':') + if i < 0 { + return addrErr(hostport, missingPort) + } + + if hostport[0] == '[' { + // Expect the first ']' just before the last ':'. + end := bytealg.IndexByteString(hostport, ']') + if end < 0 { + return addrErr(hostport, "missing ']' in address") + } + switch end + 1 { + case len(hostport): + // There can't be a ':' behind the ']' now. + return addrErr(hostport, missingPort) + case i: + // The expected result. + default: + // Either ']' isn't followed by a colon, or it is + // followed by a colon that is not the last one. + if hostport[end+1] == ':' { + return addrErr(hostport, tooManyColons) + } + return addrErr(hostport, missingPort) + } + host = hostport[1:end] + j, k = 1, end+1 // there can't be a '[' resp. ']' before these positions + } else { + host = hostport[:i] + if bytealg.IndexByteString(host, ':') >= 0 { + return addrErr(hostport, tooManyColons) + } + } + if bytealg.IndexByteString(hostport[j:], '[') >= 0 { + return addrErr(hostport, "unexpected '[' in address") + } + if bytealg.IndexByteString(hostport[k:], ']') >= 0 { + return addrErr(hostport, "unexpected ']' in address") + } + + port = hostport[i+1:] + return host, port, nil +} + +func splitHostZone(s string) (host, zone string) { + // The IPv6 scoped addressing zone identifier starts after the + // last percent sign. + if i := last(s, '%'); i > 0 { + host, zone = s[:i], s[i+1:] + } else { + host = s + } + return +} + +// JoinHostPort combines host and port into a network address of the +// form "host:port". If host contains a colon, as found in literal +// IPv6 addresses, then JoinHostPort returns "[host]:port". +// +// See func Dial for a description of the host and port parameters. +func JoinHostPort(host, port string) string { + // We assume that host is a literal IPv6 address if host has + // colons. + if bytealg.IndexByteString(host, ':') >= 0 { + return "[" + host + "]:" + port + } + return host + ":" + port +} + +// internetAddrList resolves addr, which may be a literal IP +// address or a DNS name, and returns a list of internet protocol +// family addresses. The result contains at least one address when +// error is nil. +func (r *Resolver) internetAddrList(ctx context.Context, network, addr string) (addrList, Result, error) { + var ( + err error + host, port string + portnum int + ) + switch network { + case "tcp", "tcp4", "tcp6", "udp", "udp4", "udp6": + if addr != "" { + if host, port, err = SplitHostPort(addr); err != nil { + return nil, Result{}, err + } + if portnum, err = r.LookupPort(ctx, network, port); err != nil { + return nil, Result{}, err + } + } + case "ip", "ip4", "ip6": + if addr != "" { + host = addr + } + default: + return nil, Result{}, net.UnknownNetworkError(network) + } + inetaddr := func(ip net.IPAddr) net.Addr { + switch network { + case "tcp", "tcp4", "tcp6": + return &net.TCPAddr{IP: ip.IP, Port: portnum, Zone: ip.Zone} + case "udp", "udp4", "udp6": + return &net.UDPAddr{IP: ip.IP, Port: portnum, Zone: ip.Zone} + case "ip", "ip4", "ip6": + return &net.IPAddr{IP: ip.IP, Zone: ip.Zone} + default: + panic("unexpected network: " + network) + } + } + if host == "" { + return addrList{inetaddr(net.IPAddr{})}, Result{}, nil + } + + // Try as a literal IP address, then as a DNS name. + ips, result, err := r.lookupIPAddr(ctx, network, host) + if err != nil { + return nil, result, err + } + // Issue 18806: if the machine has halfway configured + // IPv6 such that it can bind on "::" (IPv6unspecified) + // but not connect back to that same address, fall + // back to dialing 0.0.0.0. + if len(ips) == 1 && ips[0].IP.Equal(net.IPv6unspecified) { + ips = append(ips, net.IPAddr{IP: net.IPv4zero}) + } + + var filter func(net.IPAddr) bool + if network != "" && network[len(network)-1] == '4' { + filter = ipv4only + } + if network != "" && network[len(network)-1] == '6' { + filter = ipv6only + } + addrs, err := filterAddrList(filter, ips, inetaddr, host) + return addrs, result, err +} diff --git a/vendor/github.com/mjl-/adns/lookup.go b/vendor/github.com/mjl-/adns/lookup.go new file mode 100644 index 0000000..4ff320c --- /dev/null +++ b/vendor/github.com/mjl-/adns/lookup.go @@ -0,0 +1,973 @@ +// Copyright 2012 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. + +package adns + +import ( + "context" + "fmt" + "net" + "net/netip" + "sync" + + "golang.org/x/net/dns/dnsmessage" + + "github.com/mjl-/adns/internal/singleflight" +) + +// protocols contains minimal mappings between internet protocol +// names and numbers for platforms that don't have a complete list of +// protocol numbers. +// +// See https://www.iana.org/assignments/protocol-numbers +// +// On Unix, this map is augmented by readProtocols via lookupProtocol. +var protocols = map[string]int{ + "icmp": 1, + "igmp": 2, + "tcp": 6, + "udp": 17, + "ipv6-icmp": 58, +} + +// services contains minimal mappings between services names and port +// numbers for platforms that don't have a complete list of port numbers. +// +// See https://www.iana.org/assignments/service-names-port-numbers +// +// On Unix, this map is augmented by readServices via goLookupPort. +var services = map[string]map[string]int{ + "udp": { + "domain": 53, + }, + "tcp": { + "ftp": 21, + "ftps": 990, + "gopher": 70, // ʕ◔ϖ◔ʔ + "http": 80, + "https": 443, + "imap2": 143, + "imap3": 220, + "imaps": 993, + "pop3": 110, + "pop3s": 995, + "smtp": 25, + "ssh": 22, + "telnet": 23, + }, +} + +// dnsWaitGroup can be used by tests to wait for all DNS goroutines to +// complete. This avoids races on the test hooks. +var dnsWaitGroup sync.WaitGroup + +const maxProtoLength = len("RSVP-E2E-IGNORE") + 10 // with room to grow + +func lookupProtocolMap(name string) (int, error) { + var lowerProtocol [maxProtoLength]byte + n := copy(lowerProtocol[:], name) + lowerASCIIBytes(lowerProtocol[:n]) + proto, found := protocols[string(lowerProtocol[:n])] + if !found || n != len(name) { + return 0, &net.AddrError{Err: "unknown IP protocol specified", Addr: name} + } + return proto, nil +} + +// maxPortBufSize is the longest reasonable name of a service +// (non-numeric port). +// Currently the longest known IANA-unregistered name is +// "mobility-header", so we use that length, plus some slop in case +// something longer is added in the future. +const maxPortBufSize = len("mobility-header") + 10 + +func lookupPortMap(network, service string) (port int, error error) { + switch network { + case "tcp4", "tcp6": + network = "tcp" + case "udp4", "udp6": + network = "udp" + } + + if m, ok := services[network]; ok { + var lowerService [maxPortBufSize]byte + n := copy(lowerService[:], service) + lowerASCIIBytes(lowerService[:n]) + if port, ok := m[string(lowerService[:n])]; ok && n == len(service) { + return port, nil + } + } + return 0, &net.AddrError{Err: "unknown port", Addr: network + "/" + service} +} + +// ipVersion returns the provided network's IP version: '4', '6' or 0 +// if network does not end in a '4' or '6' byte. +func ipVersion(network string) byte { + if network == "" { + return 0 + } + n := network[len(network)-1] + if n != '4' && n != '6' { + n = 0 + } + return n +} + +// DefaultResolver is the resolver used by the package-level Lookup +// functions and by Dialers without a specified Resolver. +var DefaultResolver = &Resolver{} + +// A Resolver looks up names and numbers. +// +// A nil *Resolver is equivalent to a zero Resolver. +type Resolver struct { + // PreferGo controls whether Go's built-in DNS resolver is preferred + // on platforms where it's available. It is equivalent to setting + // GODEBUG=netdns=go, but scoped to just this resolver. + PreferGo bool + + // StrictErrors controls the behavior of temporary errors + // (including timeout, socket errors, and SERVFAIL) when using + // Go's built-in resolver. For a query composed of multiple + // sub-queries (such as an A+AAAA address lookup, or walking the + // DNS search list), this option causes such errors to abort the + // whole query instead of returning a partial result. This is + // not enabled by default because it may affect compatibility + // with resolvers that process AAAA queries incorrectly. + StrictErrors bool + + // Dial optionally specifies an alternate dialer for use by + // Go's built-in DNS resolver to make TCP and UDP connections + // to DNS services. The host in the address parameter will + // always be a literal IP address and not a host name, and the + // port in the address parameter will be a literal port number + // and not a service name. + // If the Conn returned is also a PacketConn, sent and received DNS + // messages must adhere to RFC 1035 section 4.2.1, "UDP usage". + // Otherwise, DNS messages transmitted over Conn must adhere + // to RFC 7766 section 5, "Transport Protocol Selection". + // If nil, the default dialer is used. + Dial func(ctx context.Context, network, address string) (net.Conn, error) + + // lookupGroup merges LookupIPAddr calls together for lookups for the same + // host. The lookupGroup key is the LookupIPAddr.host argument. + // The return values are ([]IPAddr, error). + lookupGroup singleflight.Group + + // TODO(bradfitz): optional interface impl override hook + // TODO(bradfitz): Timeout time.Duration? +} + +func (r *Resolver) preferGo() bool { return r != nil && r.PreferGo } +func (r *Resolver) strictErrors() bool { return r != nil && r.StrictErrors } + +func (r *Resolver) getLookupGroup() *singleflight.Group { + if r == nil { + return &DefaultResolver.lookupGroup + } + return &r.lookupGroup +} + +// LookupHost looks up the given host using the local resolver. +// It returns a slice of that host's addresses. +// +// LookupHost uses context.Background internally; to specify the context, use +// Resolver.LookupHost. +func LookupHost(host string) (addrs []string, result Result, err error) { + return DefaultResolver.LookupHost(context.Background(), host) +} + +// LookupHost looks up the given host using the local resolver. +// It returns a slice of that host's addresses. +func (r *Resolver) LookupHost(ctx context.Context, host string) (addrs []string, result Result, err error) { + // Make sure that no matter what we do later, host=="" is rejected. + if host == "" { + return nil, result, &DNSError{Err: errNoSuchHost.Error(), Name: host, IsNotFound: true} + } + if _, err := netip.ParseAddr(host); err == nil { + return []string{host}, result, nil + } + return r.lookupHost(ctx, host) +} + +// LookupIP looks up host using the local resolver. +// It returns a slice of that host's IPv4 and IPv6 addresses. +func LookupIP(host string) ([]net.IP, Result, error) { + addrs, result, err := DefaultResolver.LookupIPAddr(context.Background(), host) + if err != nil { + return nil, result, err + } + ips := make([]net.IP, len(addrs)) + for i, ia := range addrs { + ips[i] = ia.IP + } + return ips, result, nil +} + +// LookupIPAddr looks up host using the local resolver. +// It returns a slice of that host's IPv4 and IPv6 addresses. +func (r *Resolver) LookupIPAddr(ctx context.Context, host string) ([]net.IPAddr, Result, error) { + return r.lookupIPAddr(ctx, "ip", host) +} + +// LookupIP looks up host for the given network using the local resolver. +// It returns a slice of that host's IP addresses of the type specified by +// network. +// network must be one of "ip", "ip4" or "ip6". +func (r *Resolver) LookupIP(ctx context.Context, network, host string) ([]net.IP, Result, error) { + afnet, _, err := parseNetwork(ctx, network, false) + if err != nil { + return nil, Result{}, err + } + switch afnet { + case "ip", "ip4", "ip6": + default: + return nil, Result{}, net.UnknownNetworkError(network) + } + + if host == "" { + return nil, Result{}, &DNSError{Err: errNoSuchHost.Error(), Name: host, IsNotFound: true} + } + addrs, result, err := r.internetAddrList(ctx, afnet, host) + if err != nil { + return nil, result, err + } + + ips := make([]net.IP, 0, len(addrs)) + for _, addr := range addrs { + ips = append(ips, addr.(*net.IPAddr).IP) + } + return ips, result, nil +} + +// LookupNetIP looks up host using the local resolver. +// It returns a slice of that host's IP addresses of the type specified by +// network. +// The network must be one of "ip", "ip4" or "ip6". +func (r *Resolver) LookupNetIP(ctx context.Context, network, host string) ([]netip.Addr, Result, error) { + // TODO(bradfitz): make this efficient, making the internal net package + // type throughout be netip.Addr and only converting to the net.IP slice + // version at the edge. But for now (2021-10-20), this is a wrapper around + // the old way. + ips, result, err := r.LookupIP(ctx, network, host) + if err != nil { + return nil, result, err + } + ret := make([]netip.Addr, 0, len(ips)) + for _, ip := range ips { + if a, ok := netip.AddrFromSlice(ip); ok { + ret = append(ret, a) + } + } + return ret, result, nil +} + +// onlyValuesCtx is a context that uses an underlying context +// for value lookup if the underlying context hasn't yet expired. +type onlyValuesCtx struct { + context.Context + lookupValues context.Context +} + +var _ context.Context = (*onlyValuesCtx)(nil) + +// Value performs a lookup if the original context hasn't expired. +func (ovc *onlyValuesCtx) Value(key any) any { + select { + case <-ovc.lookupValues.Done(): + return nil + default: + return ovc.lookupValues.Value(key) + } +} + +// withUnexpiredValuesPreserved returns a context.Context that only uses lookupCtx +// for its values, otherwise it is never canceled and has no deadline. +// If the lookup context expires, any looked up values will return nil. +// See Issue 28600. +func withUnexpiredValuesPreserved(lookupCtx context.Context) context.Context { + return &onlyValuesCtx{Context: context.Background(), lookupValues: lookupCtx} +} + +// lookupIPAddr looks up host using the local resolver and particular network. +// It returns a slice of that host's IPv4 and IPv6 addresses. +func (r *Resolver) lookupIPAddr(ctx context.Context, network, host string) ([]net.IPAddr, Result, error) { + // Make sure that no matter what we do later, host=="" is rejected. + if host == "" { + return nil, Result{}, &DNSError{Err: errNoSuchHost.Error(), Name: host, IsNotFound: true} + } + if ip, err := netip.ParseAddr(host); err == nil { + return []net.IPAddr{{IP: net.IP(ip.AsSlice()).To16(), Zone: ip.Zone()}}, Result{}, nil + } + // The underlying resolver func is lookupIP by default but it + // can be overridden by tests. This is needed by net/http, so it + // uses a context key instead of unexported variables. + resolverFunc := r.lookupIP + + // We don't want a cancellation of ctx to affect the + // lookupGroup operation. Otherwise if our context gets + // canceled it might cause an error to be returned to a lookup + // using a completely different context. However we need to preserve + // only the values in context. See Issue 28600. + lookupGroupCtx, lookupGroupCancel := context.WithCancel(withUnexpiredValuesPreserved(ctx)) + + type Tuple struct { + ips []net.IPAddr + result Result + } + + lookupKey := network + "\000" + host + dnsWaitGroup.Add(1) + ch := r.getLookupGroup().DoChan(lookupKey, func() (any, error) { + ips, result, err := testHookLookupIP(lookupGroupCtx, resolverFunc, network, host) + return Tuple{ips, result}, err + }) + + dnsWaitGroupDone := func(ch <-chan singleflight.Result, cancelFn context.CancelFunc) { + <-ch + dnsWaitGroup.Done() + cancelFn() + } + select { + case <-ctx.Done(): + // Our context was canceled. If we are the only + // goroutine looking up this key, then drop the key + // from the lookupGroup and cancel the lookup. + // If there are other goroutines looking up this key, + // let the lookup continue uncanceled, and let later + // lookups with the same key share the result. + // See issues 8602, 20703, 22724. + if r.getLookupGroup().ForgetUnshared(lookupKey) { + lookupGroupCancel() + go dnsWaitGroupDone(ch, func() {}) + } else { + go dnsWaitGroupDone(ch, lookupGroupCancel) + } + ctxErr := ctx.Err() + err := &DNSError{ + Err: mapErr(ctxErr).Error(), + Name: host, + IsTimeout: ctxErr == context.DeadlineExceeded, + } + return nil, Result{}, err + case r := <-ch: + dnsWaitGroup.Done() + lookupGroupCancel() + err := r.Err + if err != nil { + if _, ok := err.(*DNSError); !ok { + isTimeout := false + if err == context.DeadlineExceeded { + isTimeout = true + } else if terr, ok := err.(timeout); ok { + isTimeout = terr.Timeout() + } + err = &DNSError{ + Err: err.Error(), + Name: host, + IsTimeout: isTimeout, + } + } + } + tuple := r.Val.(Tuple) + if err != nil { + return nil, tuple.result, err + } + ips := lookupIPReturn(tuple.ips, r.Shared) + return ips, tuple.result, nil + } +} + +// lookupIPReturn turns the return values from singleflight.Do into +// the return values from LookupIP. +func lookupIPReturn(addrs []net.IPAddr, shared bool) []net.IPAddr { + if shared { + clone := make([]net.IPAddr, len(addrs)) + copy(clone, addrs) + addrs = clone + } + return addrs +} + +// LookupPort looks up the port for the given network and service. +// +// LookupPort uses context.Background internally; to specify the context, use +// Resolver.LookupPort. +func LookupPort(network, service string) (port int, err error) { + return DefaultResolver.LookupPort(context.Background(), network, service) +} + +// LookupPort looks up the port for the given network and service. +func (r *Resolver) LookupPort(ctx context.Context, network, service string) (port int, err error) { + port, needsLookup := parsePort(service) + if needsLookup { + switch network { + case "tcp", "tcp4", "tcp6", "udp", "udp4", "udp6": + case "": // a hint wildcard for Go 1.0 undocumented behavior + network = "ip" + default: + return 0, &net.AddrError{Err: "unknown network", Addr: network} + } + port, err = r.lookupPort(ctx, network, service) + if err != nil { + return 0, err + } + } + if 0 > port || port > 65535 { + return 0, &net.AddrError{Err: "invalid port", Addr: service} + } + return port, nil +} + +// LookupCNAME returns the canonical name for the given host. +// Callers that do not care about the canonical name can call +// LookupHost or LookupIP directly; both take care of resolving +// the canonical name as part of the lookup. +// +// A canonical name is the final name after following zero +// or more CNAME records. +// LookupCNAME does not return an error if host does not +// contain DNS "CNAME" records, as long as host resolves to +// address records. +// +// The returned canonical name is validated to be a properly +// formatted presentation-format domain name. +// +// LookupCNAME uses context.Background internally; to specify the context, use +// Resolver.LookupCNAME. +func LookupCNAME(host string) (cname string, result Result, err error) { + return DefaultResolver.LookupCNAME(context.Background(), host) +} + +// LookupCNAME returns the canonical name for the given host. +// Callers that do not care about the canonical name can call +// LookupHost or LookupIP directly; both take care of resolving +// the canonical name as part of the lookup. +// +// A canonical name is the final name after following zero +// or more CNAME records. +// LookupCNAME does not return an error if host does not +// contain DNS "CNAME" records, as long as host resolves to +// address records. +// +// The returned canonical name is validated to be a properly +// formatted presentation-format domain name. +func (r *Resolver) LookupCNAME(ctx context.Context, host string) (string, Result, error) { + cname, result, err := r.lookupCNAME(ctx, host) + if err != nil { + return "", result, err + } + if !isDomainName(cname) { + return "", result, &DNSError{Err: errMalformedDNSRecordsDetail, Name: host} + } + return cname, result, nil +} + +// LookupSRV tries to resolve an SRV query of the given service, +// protocol, and domain name. The proto is "tcp" or "udp". +// The returned records are sorted by priority and randomized +// by weight within a priority. +// +// LookupSRV constructs the DNS name to look up following RFC 2782. +// That is, it looks up _service._proto.name. To accommodate services +// publishing SRV records under non-standard names, if both service +// and proto are empty strings, LookupSRV looks up name directly. +// +// The returned service names are validated to be properly +// formatted presentation-format domain names. If the response contains +// invalid names, those records are filtered out and an error +// will be returned alongside the remaining results, if any. +func LookupSRV(service, proto, name string) (cname string, addrs []*net.SRV, result Result, err error) { + return DefaultResolver.LookupSRV(context.Background(), service, proto, name) +} + +// LookupSRV tries to resolve an SRV query of the given service, +// protocol, and domain name. The proto is "tcp" or "udp". +// The returned records are sorted by priority and randomized +// by weight within a priority. +// +// LookupSRV constructs the DNS name to look up following RFC 2782. +// That is, it looks up _service._proto.name. To accommodate services +// publishing SRV records under non-standard names, if both service +// and proto are empty strings, LookupSRV looks up name directly. +// +// The returned service names are validated to be properly +// formatted presentation-format domain names. If the response contains +// invalid names, those records are filtered out and an error +// will be returned alongside the remaining results, if any. +func (r *Resolver) LookupSRV(ctx context.Context, service, proto, name string) (string, []*net.SRV, Result, error) { + cname, addrs, result, err := r.lookupSRV(ctx, service, proto, name) + if err != nil { + return "", nil, result, err + } + if cname != "" && !isDomainName(cname) { + return "", nil, result, &DNSError{Err: "SRV header name is invalid", Name: name} + } + filteredAddrs := make([]*net.SRV, 0, len(addrs)) + for _, addr := range addrs { + if addr == nil { + continue + } + if !isDomainName(addr.Target) { + continue + } + filteredAddrs = append(filteredAddrs, addr) + } + if len(addrs) != len(filteredAddrs) { + return cname, filteredAddrs, result, &DNSError{Err: errMalformedDNSRecordsDetail, Name: name} + } + return cname, filteredAddrs, result, nil +} + +// LookupMX returns the DNS MX records for the given domain name sorted by preference. +// +// The returned mail server names are validated to be properly +// formatted presentation-format domain names. If the response contains +// invalid names, those records are filtered out and an error +// will be returned alongside the remaining results, if any. +// +// LookupMX uses context.Background internally; to specify the context, use +// Resolver.LookupMX. +func LookupMX(name string) ([]*net.MX, Result, error) { + return DefaultResolver.LookupMX(context.Background(), name) +} + +// LookupMX returns the DNS MX records for the given domain name sorted by preference. +// +// The returned mail server names are validated to be properly +// formatted presentation-format domain names. If the response contains +// invalid names, those records are filtered out and an error +// will be returned alongside the remaining results, if any. +func (r *Resolver) LookupMX(ctx context.Context, name string) ([]*net.MX, Result, error) { + records, result, err := r.lookupMX(ctx, name) + if err != nil { + return nil, result, err + } + filteredMX := make([]*net.MX, 0, len(records)) + for _, mx := range records { + if mx == nil { + continue + } + if !isDomainName(mx.Host) { + continue + } + filteredMX = append(filteredMX, mx) + } + if len(records) != len(filteredMX) { + return filteredMX, result, &DNSError{Err: errMalformedDNSRecordsDetail, Name: name} + } + return filteredMX, result, nil +} + +// LookupNS returns the DNS NS records for the given domain name. +// +// The returned name server names are validated to be properly +// formatted presentation-format domain names. If the response contains +// invalid names, those records are filtered out and an error +// will be returned alongside the remaining results, if any. +// +// LookupNS uses context.Background internally; to specify the context, use +// Resolver.LookupNS. +func LookupNS(name string) ([]*net.NS, Result, error) { + return DefaultResolver.LookupNS(context.Background(), name) +} + +// LookupNS returns the DNS NS records for the given domain name. +// +// The returned name server names are validated to be properly +// formatted presentation-format domain names. If the response contains +// invalid names, those records are filtered out and an error +// will be returned alongside the remaining results, if any. +func (r *Resolver) LookupNS(ctx context.Context, name string) ([]*net.NS, Result, error) { + records, result, err := r.lookupNS(ctx, name) + if err != nil { + return nil, result, err + } + filteredNS := make([]*net.NS, 0, len(records)) + for _, ns := range records { + if ns == nil { + continue + } + if !isDomainName(ns.Host) { + continue + } + filteredNS = append(filteredNS, ns) + } + if len(records) != len(filteredNS) { + return filteredNS, result, &DNSError{Err: errMalformedDNSRecordsDetail, Name: name} + } + return filteredNS, result, nil +} + +// LookupTXT returns the DNS TXT records for the given domain name. +// +// LookupTXT uses context.Background internally; to specify the context, use +// Resolver.LookupTXT. +func LookupTXT(name string) ([]string, Result, error) { + return DefaultResolver.lookupTXT(context.Background(), name) +} + +// LookupTXT returns the DNS TXT records for the given domain name. +func (r *Resolver) LookupTXT(ctx context.Context, name string) ([]string, Result, error) { + return r.lookupTXT(ctx, name) +} + +// LookupAddr performs a reverse lookup for the given address, returning a list +// of names mapping to that address. +// +// The returned names are validated to be properly formatted presentation-format +// domain names. If the response contains invalid names, those records are filtered +// out and an error will be returned alongside the remaining results, if any. +// +// When using the host C library resolver, at most one result will be +// returned. To bypass the host resolver, use a custom Resolver. +// +// LookupAddr uses context.Background internally; to specify the context, use +// Resolver.LookupAddr. +func LookupAddr(addr string) (names []string, result Result, err error) { + return DefaultResolver.LookupAddr(context.Background(), addr) +} + +// LookupAddr performs a reverse lookup for the given address, returning a list +// of names mapping to that address. +// +// The returned names are validated to be properly formatted presentation-format +// domain names. If the response contains invalid names, those records are filtered +// out and an error will be returned alongside the remaining results, if any. +func (r *Resolver) LookupAddr(ctx context.Context, addr string) ([]string, Result, error) { + names, result, err := r.lookupAddr(ctx, addr) + if err != nil { + return nil, result, err + } + filteredNames := make([]string, 0, len(names)) + for _, name := range names { + if isDomainName(name) { + filteredNames = append(filteredNames, name) + } + } + if len(names) != len(filteredNames) { + return filteredNames, result, &DNSError{Err: errMalformedDNSRecordsDetail, Name: addr} + } + return filteredNames, result, nil +} + +// LookupTLSA calls LookupTLSA on the DefaultResolver. +func LookupTLSA(port int, protocol, host string) ([]TLSA, Result, error) { + return DefaultResolver.LookupTLSA(context.Background(), port, protocol, host) +} + +// LookupTLSA looks up a TLSA (TLS association) record for the port (service) +// and protocol (e.g. tcp, udp) at the host. +// +// LookupTLSA looks up DNS name "_._.host". Except when port is 0 +// and protocol the empty string, then host is directly used to look up the TLSA +// record. +// +// Callers must check the Authentic field of the Result before using a TLSA +// record. +// +// Callers may want to handle DNSError with NotFound set to true (i.e. "nxdomain") +// differently from other errors. DANE support is often optional, with +// protocol-specific fallback behaviour. +// +// LookupTLSA follows CNAME records. For DANE, the secure/insecure DNSSEC +// response must be taken into account when following CNAMEs to determine the +// TLSA base domains. Callers should probably first resolve CNAMEs explicitly +// for their (in)secure status. +func (r *Resolver) LookupTLSA(ctx context.Context, port int, protocol, host string) (records []TLSA, result Result, err error) { + return r.lookupTLSA(ctx, port, protocol, host) +} + +// errMalformedDNSRecordsDetail is the DNSError detail which is returned when a Resolver.Lookup... +// method receives DNS records which contain invalid DNS names. This may be returned alongside +// results which have had the malformed records filtered out. +var errMalformedDNSRecordsDetail = "DNS response contained records which contain invalid names" + +// dial makes a new connection to the provided server (which must be +// an IP address) with the provided network type, using either r.Dial +// (if both r and r.Dial are non-nil) or else Dialer.DialContext. +func (r *Resolver) dial(ctx context.Context, network, server string) (net.Conn, error) { + // Calling Dial here is scary -- we have to be sure not to + // dial a name that will require a DNS lookup, or Dial will + // call back here to translate it. The DNS config parser has + // already checked that all the cfg.servers are IP + // addresses, which Dial will use without a DNS lookup. + var c net.Conn + var err error + if r != nil && r.Dial != nil { + c, err = r.Dial(ctx, network, server) + } else { + var d net.Dialer + c, err = d.DialContext(ctx, network, server) + } + if err != nil { + return nil, mapErr(err) + } + return c, nil +} + +// goLookupSRV returns the SRV records for a target name, built either +// from its component service ("sip"), protocol ("tcp"), and name +// ("example.com."), or from name directly (if service and proto are +// both empty). +// +// In either case, the returned target name ("_sip._tcp.example.com.") +// is also returned on success. +// +// The records are sorted by weight. +func (r *Resolver) goLookupSRV(ctx context.Context, service, proto, name string) (target string, srvs []*net.SRV, result Result, err error) { + if service == "" && proto == "" { + target = name + } else { + target = "_" + service + "._" + proto + "." + name + } + p, server, result, err := r.lookup(ctx, target, dnsmessage.TypeSRV, nil) + if err != nil { + return "", nil, result, err + } + var cname dnsmessage.Name + for { + h, err := p.AnswerHeader() + if err == dnsmessage.ErrSectionDone { + break + } + if err != nil { + return "", nil, result, &DNSError{ + Err: "cannot unmarshal DNS message", + Name: name, + Server: server, + } + } + if h.Type != dnsmessage.TypeSRV { + if err := p.SkipAnswer(); err != nil { + return "", nil, result, &DNSError{ + Err: "cannot unmarshal DNS message", + Name: name, + Server: server, + } + } + continue + } + if cname.Length == 0 && h.Name.Length != 0 { + cname = h.Name + } + srv, err := p.SRVResource() + if err != nil { + return "", nil, result, &DNSError{ + Err: "cannot unmarshal DNS message", + Name: name, + Server: server, + } + } + srvs = append(srvs, &net.SRV{Target: srv.Target.String(), Port: srv.Port, Priority: srv.Priority, Weight: srv.Weight}) + } + byPriorityWeight(srvs).sort() + return cname.String(), srvs, result, nil +} + +// goLookupMX returns the MX records for name. +func (r *Resolver) goLookupMX(ctx context.Context, name string) ([]*net.MX, Result, error) { + p, server, result, err := r.lookup(ctx, name, dnsmessage.TypeMX, nil) + if err != nil { + return nil, result, err + } + var mxs []*net.MX + for { + h, err := p.AnswerHeader() + if err == dnsmessage.ErrSectionDone { + break + } + if err != nil { + return nil, result, &DNSError{ + Err: "cannot unmarshal DNS message", + Name: name, + Server: server, + } + } + if h.Type != dnsmessage.TypeMX { + if err := p.SkipAnswer(); err != nil { + return nil, result, &DNSError{ + Err: "cannot unmarshal DNS message", + Name: name, + Server: server, + } + } + continue + } + mx, err := p.MXResource() + if err != nil { + return nil, result, &DNSError{ + Err: "cannot unmarshal DNS message", + Name: name, + Server: server, + } + } + mxs = append(mxs, &net.MX{Host: mx.MX.String(), Pref: mx.Pref}) + + } + byPref(mxs).sort() + return mxs, result, nil +} + +// goLookupNS returns the NS records for name. +func (r *Resolver) goLookupNS(ctx context.Context, name string) ([]*net.NS, Result, error) { + p, server, result, err := r.lookup(ctx, name, dnsmessage.TypeNS, nil) + if err != nil { + return nil, result, err + } + var nss []*net.NS + for { + h, err := p.AnswerHeader() + if err == dnsmessage.ErrSectionDone { + break + } + if err != nil { + return nil, result, &DNSError{ + Err: "cannot unmarshal DNS message", + Name: name, + Server: server, + } + } + if h.Type != dnsmessage.TypeNS { + if err := p.SkipAnswer(); err != nil { + return nil, result, &DNSError{ + Err: "cannot unmarshal DNS message", + Name: name, + Server: server, + } + } + continue + } + ns, err := p.NSResource() + if err != nil { + return nil, result, &DNSError{ + Err: "cannot unmarshal DNS message", + Name: name, + Server: server, + } + } + nss = append(nss, &net.NS{Host: ns.NS.String()}) + } + return nss, result, nil +} + +// goLookupTXT returns the TXT records from name. +func (r *Resolver) goLookupTXT(ctx context.Context, name string) ([]string, Result, error) { + p, server, result, err := r.lookup(ctx, name, dnsmessage.TypeTXT, nil) + if err != nil { + return nil, result, err + } + var txts []string + for { + h, err := p.AnswerHeader() + if err == dnsmessage.ErrSectionDone { + break + } + if err != nil { + return nil, result, &DNSError{ + Err: "cannot unmarshal DNS message", + Name: name, + Server: server, + } + } + if h.Type != dnsmessage.TypeTXT { + if err := p.SkipAnswer(); err != nil { + return nil, result, &DNSError{ + Err: "cannot unmarshal DNS message", + Name: name, + Server: server, + } + } + continue + } + txt, err := p.TXTResource() + if err != nil { + return nil, result, &DNSError{ + Err: "cannot unmarshal DNS message", + Name: name, + Server: server, + } + } + // Multiple strings in one TXT record need to be + // concatenated without separator to be consistent + // with previous Go resolver. + n := 0 + for _, s := range txt.TXT { + n += len(s) + } + txtJoin := make([]byte, 0, n) + for _, s := range txt.TXT { + txtJoin = append(txtJoin, s...) + } + if len(txts) == 0 { + txts = make([]string, 0, 1) + } + txts = append(txts, string(txtJoin)) + } + return txts, result, nil +} + +const typeTLSA = dnsmessage.Type(52) + +// goLookupTLSA is the native Go implementation of LookupTLSA. +func (r *Resolver) goLookupTLSA(ctx context.Context, port int, protocol, host string) ([]TLSA, Result, error) { + var name string + if port == 0 && protocol == "" { + name = host + } else { + name = fmt.Sprintf("_%d._%s.%s", port, protocol, host) + } + p, server, result, err := r.lookup(ctx, name, typeTLSA, nil) + if err != nil { + return nil, result, err + } + var l []TLSA + for { + h, err := p.AnswerHeader() + if err == dnsmessage.ErrSectionDone { + break + } + if err != nil { + return nil, result, &DNSError{ + Err: "cannot unmarshal DNS message", + Name: name, + Server: server, + } + } + if h.Type != typeTLSA { + if err := p.SkipAnswer(); err != nil { + return nil, result, &DNSError{ + Err: "cannot unmarshal DNS message", + Name: name, + Server: server, + } + } + continue + } + + r, err := p.UnknownResource() + if err != nil || len(r.Data) < 3 { + return nil, result, &DNSError{ + Err: "cannot unmarshal DNS message", + Name: name, + Server: server, + } + } + record := TLSA{ + TLSAUsage(r.Data[0]), + TLSASelector(r.Data[1]), + TLSAMatchType(r.Data[2]), + nil, + } + // We do not verify the contents/size of the data. We don't want to filter out + // values we don't understand. We'll leave it to the callers to see if a record is + // usable. Also because special behaviour may be required if records were found but + // all unusable. + buf := make([]byte, len(r.Data)-3) + copy(buf, r.Data[3:]) + record.CertAssoc = buf + l = append(l, record) + } + return l, result, nil +} diff --git a/vendor/github.com/mjl-/adns/lookup_unix.go b/vendor/github.com/mjl-/adns/lookup_unix.go new file mode 100644 index 0000000..6fd616e --- /dev/null +++ b/vendor/github.com/mjl-/adns/lookup_unix.go @@ -0,0 +1,105 @@ +// Copyright 2011 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 unix || wasip1 + +package adns + +import ( + "context" + "net" + "sync" + + "github.com/mjl-/adns/internal/bytealg" +) + +var onceReadProtocols sync.Once + +// readProtocols loads contents of /etc/protocols into protocols map +// for quick access. +func readProtocols() { + file, err := open("/etc/protocols") + if err != nil { + return + } + defer file.close() + + for line, ok := file.readLine(); ok; line, ok = file.readLine() { + // tcp 6 TCP # transmission control protocol + if i := bytealg.IndexByteString(line, '#'); i >= 0 { + line = line[0:i] + } + f := getFields(line) + if len(f) < 2 { + continue + } + if proto, _, ok := dtoi(f[1]); ok { + if _, ok := protocols[f[0]]; !ok { + protocols[f[0]] = proto + } + for _, alias := range f[2:] { + if _, ok := protocols[alias]; !ok { + protocols[alias] = proto + } + } + } + } +} + +// lookupProtocol looks up IP protocol name in /etc/protocols and +// returns correspondent protocol number. +func lookupProtocol(_ context.Context, name string) (int, error) { + onceReadProtocols.Do(readProtocols) + return lookupProtocolMap(name) +} + +func (r *Resolver) lookupHost(ctx context.Context, host string) (addrs []string, result Result, err error) { + order, conf := systemConf().hostLookupOrder(r, host) + return r.goLookupHostOrder(ctx, host, order, conf) +} + +func (r *Resolver) lookupIP(ctx context.Context, network, host string) (addrs []net.IPAddr, result Result, err error) { + if r.preferGo() { + return r.goLookupIP(ctx, network, host) + } + order, conf := systemConf().hostLookupOrder(r, host) + ips, _, result, err := r.goLookupIPCNAMEOrder(ctx, network, host, order, conf) + return ips, result, err +} + +func (r *Resolver) lookupPort(ctx context.Context, network, service string) (int, error) { + // Port lookup is not a DNS operation. + // Prefer the cgo resolver if possible. + return goLookupPort(network, service) +} + +func (r *Resolver) lookupCNAME(ctx context.Context, name string) (string, Result, error) { + order, conf := systemConf().hostLookupOrder(r, name) + return r.goLookupCNAME(ctx, name, order, conf) +} + +func (r *Resolver) lookupSRV(ctx context.Context, service, proto, name string) (string, []*net.SRV, Result, error) { + return r.goLookupSRV(ctx, service, proto, name) +} + +func (r *Resolver) lookupMX(ctx context.Context, name string) ([]*net.MX, Result, error) { + return r.goLookupMX(ctx, name) +} + +func (r *Resolver) lookupNS(ctx context.Context, name string) ([]*net.NS, Result, error) { + return r.goLookupNS(ctx, name) +} + +func (r *Resolver) lookupTXT(ctx context.Context, name string) ([]string, Result, error) { + return r.goLookupTXT(ctx, name) +} + +func (r *Resolver) lookupAddr(ctx context.Context, addr string) ([]string, Result, error) { + order, conf := systemConf().addrLookupOrder(r, addr) + return r.goLookupPTR(ctx, addr, order, conf) +} + +func (r *Resolver) lookupTLSA(ctx context.Context, port int, protocol, host string) ([]TLSA, Result, error) { + return r.goLookupTLSA(ctx, port, protocol, host) +} diff --git a/vendor/github.com/mjl-/adns/mac.go b/vendor/github.com/mjl-/adns/mac.go new file mode 100644 index 0000000..e70b314 --- /dev/null +++ b/vendor/github.com/mjl-/adns/mac.go @@ -0,0 +1,7 @@ +// Copyright 2011 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. + +package adns + +const hexDigit = "0123456789abcdef" diff --git a/vendor/github.com/mjl-/adns/net.go b/vendor/github.com/mjl-/adns/net.go new file mode 100644 index 0000000..d586a5f --- /dev/null +++ b/vendor/github.com/mjl-/adns/net.go @@ -0,0 +1,110 @@ +// Copyright 2009 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. + +package adns + +import ( + "context" + "errors" +) + +// Various errors contained in OpError. +var ( + // For connection setup operations. + errNoSuitableAddress = errors.New("no suitable address found") + + // For both read and write operations. + errCanceled = canceledError{} +) + +// canceledError lets us return the same error string we have always +// returned, while still being Is context.Canceled. +type canceledError struct{} + +func (canceledError) Error() string { return "operation was canceled" } + +func (canceledError) Is(err error) bool { return err == context.Canceled } + +// mapErr maps from the context errors to the historical internal net +// error values. +func mapErr(err error) error { + switch err { + case context.Canceled: + return errCanceled + case context.DeadlineExceeded: + return errTimeout + default: + return err + } +} + +type timeout interface { + Timeout() bool +} + +// Various errors contained in DNSError. +var ( + errNoSuchHost = errors.New("no such host") +) + +// errTimeout exists to return the historical "i/o timeout" string +// for context.DeadlineExceeded. See mapErr. +// It is also used when Dialer.Deadline is exceeded. +// error.Is(errTimeout, context.DeadlineExceeded) returns true. +// +// TODO(iant): We could consider changing this to os.ErrDeadlineExceeded +// in the future, if we make +// +// errors.Is(os.ErrDeadlineExceeded, context.DeadlineExceeded) +// +// return true. +var errTimeout error = &timeoutError{} + +type timeoutError struct{} + +func (e *timeoutError) Error() string { return "i/o timeout" } +func (e *timeoutError) Timeout() bool { return true } +func (e *timeoutError) Temporary() bool { return true } + +func (e *timeoutError) Is(err error) bool { + return err == context.DeadlineExceeded +} + +// DNSError represents a DNS lookup error. +type DNSError struct { + Underlying error // Underlying error, could be an ExtendedError. + Err string // description of the error + Name string // name looked for + Server string // server used + IsTimeout bool // if true, timed out; not all timeouts set this + IsTemporary bool // if true, error is temporary; not all errors set this + IsNotFound bool // if true, host could not be found +} + +// Unwrap returns the underlying error, which could be an ExtendedError. +func (e *DNSError) Unwrap() error { + return e.Underlying +} + +func (e *DNSError) Error() string { + if e == nil { + return "" + } + s := "lookup " + e.Name + if e.Server != "" { + s += " on " + e.Server + } + s += ": " + e.Err + return s +} + +// Timeout reports whether the DNS lookup is known to have timed out. +// This is not always known; a DNS lookup may fail due to a timeout +// and return a DNSError for which Timeout returns false. +func (e *DNSError) Timeout() bool { return e.IsTimeout } + +// Temporary reports whether the DNS error is known to be temporary. +// This is not always known; a DNS lookup may fail due to a temporary +// error and return a DNSError for which Temporary returns false. +func (e *DNSError) Temporary() bool { return e.IsTimeout || e.IsTemporary } diff --git a/vendor/github.com/mjl-/adns/nss.go b/vendor/github.com/mjl-/adns/nss.go new file mode 100644 index 0000000..d0ce034 --- /dev/null +++ b/vendor/github.com/mjl-/adns/nss.go @@ -0,0 +1,250 @@ +// 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. + +package adns + +import ( + "errors" + "os" + "sync" + "time" + + "github.com/mjl-/adns/internal/bytealg" +) + +const ( + nssConfigPath = "/etc/nsswitch.conf" +) + +var nssConfig nsswitchConfig + +type nsswitchConfig struct { + initOnce sync.Once // guards init of nsswitchConfig + + // ch is used as a semaphore that only allows one lookup at a + // time to recheck nsswitch.conf + ch chan struct{} // guards lastChecked and modTime + lastChecked time.Time // last time nsswitch.conf was checked + + mu sync.Mutex // protects nssConf + nssConf *nssConf +} + +func getSystemNSS() *nssConf { + nssConfig.tryUpdate() + nssConfig.mu.Lock() + conf := nssConfig.nssConf + nssConfig.mu.Unlock() + return conf +} + +// init initializes conf and is only called via conf.initOnce. +func (conf *nsswitchConfig) init() { + conf.nssConf = parseNSSConfFile("/etc/nsswitch.conf") + conf.lastChecked = time.Now() + conf.ch = make(chan struct{}, 1) +} + +// tryUpdate tries to update conf. +func (conf *nsswitchConfig) tryUpdate() { + conf.initOnce.Do(conf.init) + + // Ensure only one update at a time checks nsswitch.conf + if !conf.tryAcquireSema() { + return + } + defer conf.releaseSema() + + now := time.Now() + if conf.lastChecked.After(now.Add(-5 * time.Second)) { + return + } + conf.lastChecked = now + + var mtime time.Time + if fi, err := os.Stat(nssConfigPath); err == nil { + mtime = fi.ModTime() + } + if mtime.Equal(conf.nssConf.mtime) { + return + } + + nssConf := parseNSSConfFile(nssConfigPath) + conf.mu.Lock() + conf.nssConf = nssConf + conf.mu.Unlock() +} + +func (conf *nsswitchConfig) acquireSema() { + conf.ch <- struct{}{} +} + +func (conf *nsswitchConfig) tryAcquireSema() bool { + select { + case conf.ch <- struct{}{}: + return true + default: + return false + } +} + +func (conf *nsswitchConfig) releaseSema() { + <-conf.ch +} + +// nssConf represents the state of the machine's /etc/nsswitch.conf file. +type nssConf struct { + mtime time.Time // time of nsswitch.conf modification + err error // any error encountered opening or parsing the file + sources map[string][]nssSource // keyed by database (e.g. "hosts") +} + +type nssSource struct { + source string // e.g. "compat", "files", "mdns4_minimal" + criteria []nssCriterion +} + +// standardCriteria reports all specified criteria have the default +// status actions. +func (s nssSource) standardCriteria() bool { + for i, crit := range s.criteria { + if !crit.standardStatusAction(i == len(s.criteria)-1) { + return false + } + } + return true +} + +// nssCriterion is the parsed structure of one of the criteria in brackets +// after an NSS source name. +type nssCriterion struct { + negate bool // if "!" was present + status string // e.g. "success", "unavail" (lowercase) + action string // e.g. "return", "continue" (lowercase) +} + +// standardStatusAction reports whether c is equivalent to not +// specifying the criterion at all. last is whether this criteria is the +// last in the list. +func (c nssCriterion) standardStatusAction(last bool) bool { + if c.negate { + return false + } + var def string + switch c.status { + case "success": + def = "return" + case "notfound", "unavail", "tryagain": + def = "continue" + default: + // Unknown status + return false + } + if last && c.action == "return" { + return true + } + return c.action == def +} + +func parseNSSConfFile(file string) *nssConf { + f, err := open(file) + if err != nil { + return &nssConf{err: err} + } + defer f.close() + mtime, _, err := f.stat() + if err != nil { + return &nssConf{err: err} + } + + conf := parseNSSConf(f) + conf.mtime = mtime + return conf +} + +func parseNSSConf(f *file) *nssConf { + conf := new(nssConf) + for line, ok := f.readLine(); ok; line, ok = f.readLine() { + line = trimSpace(removeComment(line)) + if len(line) == 0 { + continue + } + colon := bytealg.IndexByteString(line, ':') + if colon == -1 { + conf.err = errors.New("no colon on line") + return conf + } + db := trimSpace(line[:colon]) + srcs := line[colon+1:] + for { + srcs = trimSpace(srcs) + if len(srcs) == 0 { + break + } + sp := bytealg.IndexByteString(srcs, ' ') + var src string + if sp == -1 { + src = srcs + srcs = "" // done + } else { + src = srcs[:sp] + srcs = trimSpace(srcs[sp+1:]) + } + var criteria []nssCriterion + // See if there's a criteria block in brackets. + if len(srcs) > 0 && srcs[0] == '[' { + bclose := bytealg.IndexByteString(srcs, ']') + if bclose == -1 { + conf.err = errors.New("unclosed criterion bracket") + return conf + } + var err error + criteria, err = parseCriteria(srcs[1:bclose]) + if err != nil { + conf.err = errors.New("invalid criteria: " + srcs[1:bclose]) + return conf + } + srcs = srcs[bclose+1:] + } + if conf.sources == nil { + conf.sources = make(map[string][]nssSource) + } + conf.sources[db] = append(conf.sources[db], nssSource{ + source: src, + criteria: criteria, + }) + } + } + return conf +} + +// parses "foo=bar !foo=bar" +func parseCriteria(x string) (c []nssCriterion, err error) { + err = foreachField(x, func(f string) error { + not := false + if len(f) > 0 && f[0] == '!' { + not = true + f = f[1:] + } + if len(f) < 3 { + return errors.New("criterion too short") + } + eq := bytealg.IndexByteString(f, '=') + if eq == -1 { + return errors.New("criterion lacks equal sign") + } + if hasUpperCase(f) { + lower := []byte(f) + lowerASCIIBytes(lower) + f = string(lower) + } + c = append(c, nssCriterion{ + negate: not, + status: f[:eq], + action: f[eq+1:], + }) + return nil + }) + return +} diff --git a/vendor/github.com/mjl-/adns/parse.go b/vendor/github.com/mjl-/adns/parse.go new file mode 100644 index 0000000..ad3d6c8 --- /dev/null +++ b/vendor/github.com/mjl-/adns/parse.go @@ -0,0 +1,267 @@ +// Copyright 2009 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. + +// Simple file i/o and string manipulation, to avoid +// depending on strconv and bufio and strings. + +package adns + +import ( + "io" + "os" + "time" + + "github.com/mjl-/adns/internal/bytealg" +) + +type file struct { + file *os.File + data []byte + atEOF bool +} + +func (f *file) close() { f.file.Close() } + +func (f *file) getLineFromData() (s string, ok bool) { + data := f.data + i := 0 + for i = 0; i < len(data); i++ { + if data[i] == '\n' { + s = string(data[0:i]) + ok = true + // move data + i++ + n := len(data) - i + copy(data[0:], data[i:]) + f.data = data[0:n] + return + } + } + if f.atEOF && len(f.data) > 0 { + // EOF, return all we have + s = string(data) + f.data = f.data[0:0] + ok = true + } + return +} + +func (f *file) readLine() (s string, ok bool) { + if s, ok = f.getLineFromData(); ok { + return + } + if len(f.data) < cap(f.data) { + ln := len(f.data) + n, err := io.ReadFull(f.file, f.data[ln:cap(f.data)]) + if n >= 0 { + f.data = f.data[0 : ln+n] + } + if err == io.EOF || err == io.ErrUnexpectedEOF { + f.atEOF = true + } + } + s, ok = f.getLineFromData() + return +} + +func (f *file) stat() (mtime time.Time, size int64, err error) { + st, err := f.file.Stat() + if err != nil { + return time.Time{}, 0, err + } + return st.ModTime(), st.Size(), nil +} + +func open(name string) (*file, error) { + fd, err := os.Open(name) + if err != nil { + return nil, err + } + return &file{fd, make([]byte, 0, 64*1024), false}, nil +} + +func stat(name string) (mtime time.Time, size int64, err error) { + st, err := os.Stat(name) + if err != nil { + return time.Time{}, 0, err + } + return st.ModTime(), st.Size(), nil +} + +// Count occurrences in s of any bytes in t. +func countAnyByte(s string, t string) int { + n := 0 + for i := 0; i < len(s); i++ { + if bytealg.IndexByteString(t, s[i]) >= 0 { + n++ + } + } + return n +} + +// Split s at any bytes in t. +func splitAtBytes(s string, t string) []string { + a := make([]string, 1+countAnyByte(s, t)) + n := 0 + last := 0 + for i := 0; i < len(s); i++ { + if bytealg.IndexByteString(t, s[i]) >= 0 { + if last < i { + a[n] = s[last:i] + n++ + } + last = i + 1 + } + } + if last < len(s) { + a[n] = s[last:] + n++ + } + return a[0:n] +} + +func getFields(s string) []string { return splitAtBytes(s, " \r\t\n") } + +// Bigger than we need, not too big to worry about overflow +const big = 0xFFFFFF + +// Decimal to integer. +// Returns number, characters consumed, success. +func dtoi(s string) (n int, i int, ok bool) { + n = 0 + for i = 0; i < len(s) && '0' <= s[i] && s[i] <= '9'; i++ { + n = n*10 + int(s[i]-'0') + if n >= big { + return big, i, false + } + } + if i == 0 { + return 0, 0, false + } + return n, i, true +} + +// Number of occurrences of b in s. +func count(s string, b byte) int { + n := 0 + for i := 0; i < len(s); i++ { + if s[i] == b { + n++ + } + } + return n +} + +// Index of rightmost occurrence of b in s. +func last(s string, b byte) int { + i := len(s) + for i--; i >= 0; i-- { + if s[i] == b { + break + } + } + return i +} + +// hasUpperCase tells whether the given string contains at least one upper-case. +func hasUpperCase(s string) bool { + for i := range s { + if 'A' <= s[i] && s[i] <= 'Z' { + return true + } + } + return false +} + +// lowerASCIIBytes makes x ASCII lowercase in-place. +func lowerASCIIBytes(x []byte) { + for i, b := range x { + if 'A' <= b && b <= 'Z' { + x[i] += 'a' - 'A' + } + } +} + +// lowerASCII returns the ASCII lowercase version of b. +func lowerASCII(b byte) byte { + if 'A' <= b && b <= 'Z' { + return b + ('a' - 'A') + } + return b +} + +// trimSpace returns x without any leading or trailing ASCII whitespace. +func trimSpace(x string) string { + for len(x) > 0 && isSpace(x[0]) { + x = x[1:] + } + for len(x) > 0 && isSpace(x[len(x)-1]) { + x = x[:len(x)-1] + } + return x +} + +// isSpace reports whether b is an ASCII space character. +func isSpace(b byte) bool { + return b == ' ' || b == '\t' || b == '\n' || b == '\r' +} + +// removeComment returns line, removing any '#' byte and any following +// bytes. +func removeComment(line string) string { + if i := bytealg.IndexByteString(line, '#'); i != -1 { + return line[:i] + } + return line +} + +// foreachField runs fn on each non-empty run of non-space bytes in x. +// It returns the first non-nil error returned by fn. +func foreachField(x string, fn func(field string) error) error { + x = trimSpace(x) + for len(x) > 0 { + sp := bytealg.IndexByteString(x, ' ') + if sp == -1 { + return fn(x) + } + if field := trimSpace(x[:sp]); len(field) > 0 { + if err := fn(field); err != nil { + return err + } + } + x = trimSpace(x[sp+1:]) + } + return nil +} + +// stringsHasSuffix is strings.HasSuffix. It reports whether s ends in +// suffix. +func stringsHasSuffix(s, suffix string) bool { + return len(s) >= len(suffix) && s[len(s)-len(suffix):] == suffix +} + +// stringsHasSuffixFold reports whether s ends in suffix, +// ASCII-case-insensitively. +func stringsHasSuffixFold(s, suffix string) bool { + return len(s) >= len(suffix) && stringsEqualFold(s[len(s)-len(suffix):], suffix) +} + +// stringsHasPrefix is strings.HasPrefix. It reports whether s begins with prefix. +func stringsHasPrefix(s, prefix string) bool { + return len(s) >= len(prefix) && s[:len(prefix)] == prefix +} + +// stringsEqualFold is strings.EqualFold, ASCII only. It reports whether s and t +// are equal, ASCII-case-insensitively. +func stringsEqualFold(s, t string) bool { + if len(s) != len(t) { + return false + } + for i := 0; i < len(s); i++ { + if lowerASCII(s[i]) != lowerASCII(t[i]) { + return false + } + } + return true +} diff --git a/vendor/github.com/mjl-/adns/port.go b/vendor/github.com/mjl-/adns/port.go new file mode 100644 index 0000000..da77be8 --- /dev/null +++ b/vendor/github.com/mjl-/adns/port.go @@ -0,0 +1,62 @@ +// Copyright 2016 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. + +package adns + +// parsePort parses service as a decimal integer and returns the +// corresponding value as port. It is the caller's responsibility to +// parse service as a non-decimal integer when needsLookup is true. +// +// Some system resolvers will return a valid port number when given a number +// over 65536 (see https://golang.org/issues/11715). Alas, the parser +// can't bail early on numbers > 65536. Therefore reasonably large/small +// numbers are parsed in full and rejected if invalid. +func parsePort(service string) (port int, needsLookup bool) { + if service == "" { + // Lock in the legacy behavior that an empty string + // means port 0. See golang.org/issue/13610. + return 0, false + } + const ( + max = uint32(1<<32 - 1) + cutoff = uint32(1 << 30) + ) + neg := false + if service[0] == '+' { + service = service[1:] + } else if service[0] == '-' { + neg = true + service = service[1:] + } + var n uint32 + for _, d := range service { + if '0' <= d && d <= '9' { + d -= '0' + } else { + return 0, true + } + if n >= cutoff { + n = max + break + } + n *= 10 + nn := n + uint32(d) + if nn < n || nn > max { + n = max + break + } + n = nn + } + if !neg && n >= cutoff { + port = int(cutoff - 1) + } else if neg && n > cutoff { + port = int(cutoff) + } else { + port = int(n) + } + if neg { + port = -port + } + return port, false +} diff --git a/vendor/github.com/mjl-/adns/port_unix.go b/vendor/github.com/mjl-/adns/port_unix.go new file mode 100644 index 0000000..829b71a --- /dev/null +++ b/vendor/github.com/mjl-/adns/port_unix.go @@ -0,0 +1,58 @@ +// Copyright 2009 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 unix || (js && wasm) || wasip1 + +// Read system port mappings from /etc/services + +package adns + +import ( + "sync" + + "github.com/mjl-/adns/internal/bytealg" +) + +var onceReadServices sync.Once + +func readServices() { + file, err := open("/etc/services") + if err != nil { + return + } + defer file.close() + + for line, ok := file.readLine(); ok; line, ok = file.readLine() { + // "http 80/tcp www www-http # World Wide Web HTTP" + if i := bytealg.IndexByteString(line, '#'); i >= 0 { + line = line[:i] + } + f := getFields(line) + if len(f) < 2 { + continue + } + portnet := f[1] // "80/tcp" + port, j, ok := dtoi(portnet) + if !ok || port <= 0 || j >= len(portnet) || portnet[j] != '/' { + continue + } + netw := portnet[j+1:] // "tcp" + m, ok1 := services[netw] + if !ok1 { + m = make(map[string]int) + services[netw] = m + } + for i := 0; i < len(f); i++ { + if i != 1 { // f[1] was port/net + m[f[i]] = port + } + } + } +} + +// goLookupPort is the native Go implementation of LookupPort. +func goLookupPort(network, service string) (port int, err error) { + onceReadServices.Do(readServices) + return lookupPortMap(network, service) +} diff --git a/vendor/github.com/mjl-/adns/tlsa.go b/vendor/github.com/mjl-/adns/tlsa.go new file mode 100644 index 0000000..6b680ab --- /dev/null +++ b/vendor/github.com/mjl-/adns/tlsa.go @@ -0,0 +1,115 @@ +package adns + +import ( + "fmt" +) + +// TLSAUsage indicates which certificate/public key verification must be done. +type TLSAUsage uint8 + +const ( + // PKIX/WebPKI, certificate must be valid (name, expiry, signed by CA, etc) and + // signed by the trusted-anchor (TA) in this record. + TLSAUsagePKIXTA TLSAUsage = 0 + // PKIX/WebPKI, certificate must be valid (name, expiry, signed by CA, etc) and + // match the certificate in the record. + TLSAUsagePKIXEE TLSAUsage = 1 + // Certificate must be signed by trusted-anchor referenced in record, with matching + // name, non-expired, etc. + TLSAUsageDANETA TLSAUsage = 2 + // Certificate must match the record. No further requirements on name, expiration + // or who signed it. + TLSAUsageDANEEE TLSAUsage = 3 +) + +// String returns the lower-case acronym of a usage, or "(unknown)" for +// unrecognized values. +func (u TLSAUsage) String() string { + switch u { + case TLSAUsagePKIXTA: + return "pkix-ta" + case TLSAUsagePKIXEE: + return "pkix-ee" + case TLSAUsageDANETA: + return "dane-ta" + case TLSAUsageDANEEE: + return "dane-ee" + } + return "(unknown)" +} + +// TLSASelecter indicates the data the "certificate association" field is based on. +type TLSASelector uint8 + +const ( + // DER-encoded x509 certificate. + TLSASelectorCert TLSASelector = 0 + // DER-encoded subject public key info (SPKI), so only the public key and its type. + TLSASelectorSPKI TLSASelector = 1 +) + +// String returns the lower-case acronym of a selector, or "(unknown)" for +// unrecognized values. +func (s TLSASelector) String() string { + switch s { + case TLSASelectorCert: + return "cert" + case TLSASelectorSPKI: + return "spki" + } + return "(unknown)" +} + +// TLSAMatchType indicates in which form the data as indicated by the selector +// is stored in the record as certificate association. +type TLSAMatchType uint8 + +const ( + // Full data, e.g. a full DER-encoded SPKI or even certificate. + TLSAMatchTypeFull TLSAMatchType = 0 + // SHA2-256-hashed data, either SPKI or certificate. + TLSAMatchTypeSHA256 TLSAMatchType = 1 + // SHA2-512-hashed data. + TLSAMatchTypeSHA512 TLSAMatchType = 2 +) + +// String returns the lower-case acronym of a match type, or "(unknown)" for +// unrecognized values. +func (mt TLSAMatchType) String() string { + switch mt { + case TLSAMatchTypeFull: + return "full" + case TLSAMatchTypeSHA256: + return "sha2-256" + case TLSAMatchTypeSHA512: + return "sha2-512" + } + return "(unknown)" +} + +// TLSA represents a TLSA DNS record. +type TLSA struct { + Usage TLSAUsage // Which validations must be performed. + Selector TLSASelector // What needs to be validated (full certificate or only public key). + MatchType TLSAMatchType // In which form the certificate/public key is stored in CertAssoc. + CertAssoc []byte // Certificate association data. +} + +// Record returns a TLSA record value for inclusion in DNS. For example: +// +// 3 1 1 133b919c9d65d8b1488157315327334ead8d83372db57465ecabf53ee5748aee +// +// A full record in a zone file may look like this: +// +// _25._tcp.example.com. IN TLSA 3 1 1 133b919c9d65d8b1488157315327334ead8d83372db57465ecabf53ee5748aee +// +// This record is dane-ee (3), spki (1), sha2-256 (1), and the hexadecimal data +// is the sha2-256 hash. +func (r TLSA) Record() string { + return fmt.Sprintf("%d %d %d %x", r.Usage, r.Selector, r.MatchType, r.CertAssoc) +} + +// String is like Record but prints both the acronym and code for each field. +func (r TLSA) String() string { + return fmt.Sprintf("%s(%d) %s(%d) %s(%d) %x", r.Usage, r.Usage, r.Selector, r.Selector, r.MatchType, r.MatchType, r.CertAssoc) +} diff --git a/vendor/github.com/mjl-/autocert/LICENSE b/vendor/github.com/mjl-/autocert/LICENSE new file mode 100644 index 0000000..6a66aea --- /dev/null +++ b/vendor/github.com/mjl-/autocert/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/mjl-/autocert/README.txt b/vendor/github.com/mjl-/autocert/README.txt new file mode 100644 index 0000000..716239e --- /dev/null +++ b/vendor/github.com/mjl-/autocert/README.txt @@ -0,0 +1 @@ +From golang.org/x/crypto/acme/autocert, with Manager.GetPrivateKey added. diff --git a/vendor/golang.org/x/crypto/acme/autocert/autocert.go b/vendor/github.com/mjl-/autocert/autocert.go similarity index 97% rename from vendor/golang.org/x/crypto/acme/autocert/autocert.go rename to vendor/github.com/mjl-/autocert/autocert.go index 6b4cdf4..4e2f413 100644 --- a/vendor/golang.org/x/crypto/acme/autocert/autocert.go +++ b/vendor/github.com/mjl-/autocert/autocert.go @@ -92,6 +92,15 @@ func defaultHostPolicy(context.Context, string) error { return nil } +// KeyType represents a private key type that can be used to request a certificate +// with Manager.GetPrivateKey. More may be added in the future. +type KeyType uint8 + +const ( + KeyRSA2048 KeyType = 0 + KeyECDSAP256 KeyType = 1 +) + // Manager is a stateful certificate manager built on top of acme.Client. // It obtains and refreshes certificates automatically using "tls-alpn-01" // or "http-01" challenge types, as well as providing them to a TLS server @@ -148,6 +157,11 @@ type Manager struct { // Mutating the field after the first call of GetCertificate method will have no effect. Client *acme.Client + // GetPrivateKey is called to get a private key for a host (A-labels only, no + // trailing dot) when there is no valid certificate in the cache. If GetPrivateKey + // is nil, a new key is automatically generated. + GetPrivateKey func(host string, keyType KeyType) (crypto.Signer, error) + // Email optionally specifies a contact email address. // This is used by CAs, such as Let's Encrypt, to notify about problems // with issued certificates. @@ -632,7 +646,23 @@ func (m *Manager) certState(ck certKey) (*certState, error) { err error key crypto.Signer ) - if ck.isRSA { + if m.GetPrivateKey != nil { + if ck.isRSA { + key, err = m.GetPrivateKey(ck.domain, KeyRSA2048) + if err == nil { + if _, ok := key.(*rsa.PrivateKey); !ok { + err = fmt.Errorf("got %T, expected *rsa.PrivateKey", key) + } + } + } else { + key, err = m.GetPrivateKey(ck.domain, KeyECDSAP256) + if err == nil { + if _, ok := key.(*ecdsa.PrivateKey); !ok { + err = fmt.Errorf("got %T, expected *ecdsa.PrivateKey", key) + } + } + } + } else if ck.isRSA { key, err = rsa.GenerateKey(rand.Reader, 2048) } else { key, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader) diff --git a/vendor/golang.org/x/crypto/acme/autocert/cache.go b/vendor/github.com/mjl-/autocert/cache.go similarity index 100% rename from vendor/golang.org/x/crypto/acme/autocert/cache.go rename to vendor/github.com/mjl-/autocert/cache.go diff --git a/vendor/golang.org/x/crypto/acme/autocert/listener.go b/vendor/github.com/mjl-/autocert/listener.go similarity index 100% rename from vendor/golang.org/x/crypto/acme/autocert/listener.go rename to vendor/github.com/mjl-/autocert/listener.go diff --git a/vendor/golang.org/x/crypto/acme/autocert/renewal.go b/vendor/github.com/mjl-/autocert/renewal.go similarity index 100% rename from vendor/golang.org/x/crypto/acme/autocert/renewal.go rename to vendor/github.com/mjl-/autocert/renewal.go diff --git a/vendor/golang.org/x/net/dns/dnsmessage/message.go b/vendor/golang.org/x/net/dns/dnsmessage/message.go new file mode 100644 index 0000000..b6b4f9c --- /dev/null +++ b/vendor/golang.org/x/net/dns/dnsmessage/message.go @@ -0,0 +1,2711 @@ +// Copyright 2009 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. + +// Package dnsmessage provides a mostly RFC 1035 compliant implementation of +// DNS message packing and unpacking. +// +// The package also supports messages with Extension Mechanisms for DNS +// (EDNS(0)) as defined in RFC 6891. +// +// This implementation is designed to minimize heap allocations and avoid +// unnecessary packing and unpacking as much as possible. +package dnsmessage + +import ( + "errors" +) + +// Message formats + +// A Type is a type of DNS request and response. +type Type uint16 + +const ( + // ResourceHeader.Type and Question.Type + TypeA Type = 1 + TypeNS Type = 2 + TypeCNAME Type = 5 + TypeSOA Type = 6 + TypePTR Type = 12 + TypeMX Type = 15 + TypeTXT Type = 16 + TypeAAAA Type = 28 + TypeSRV Type = 33 + TypeOPT Type = 41 + + // Question.Type + TypeWKS Type = 11 + TypeHINFO Type = 13 + TypeMINFO Type = 14 + TypeAXFR Type = 252 + TypeALL Type = 255 +) + +var typeNames = map[Type]string{ + TypeA: "TypeA", + TypeNS: "TypeNS", + TypeCNAME: "TypeCNAME", + TypeSOA: "TypeSOA", + TypePTR: "TypePTR", + TypeMX: "TypeMX", + TypeTXT: "TypeTXT", + TypeAAAA: "TypeAAAA", + TypeSRV: "TypeSRV", + TypeOPT: "TypeOPT", + TypeWKS: "TypeWKS", + TypeHINFO: "TypeHINFO", + TypeMINFO: "TypeMINFO", + TypeAXFR: "TypeAXFR", + TypeALL: "TypeALL", +} + +// String implements fmt.Stringer.String. +func (t Type) String() string { + if n, ok := typeNames[t]; ok { + return n + } + return printUint16(uint16(t)) +} + +// GoString implements fmt.GoStringer.GoString. +func (t Type) GoString() string { + if n, ok := typeNames[t]; ok { + return "dnsmessage." + n + } + return printUint16(uint16(t)) +} + +// A Class is a type of network. +type Class uint16 + +const ( + // ResourceHeader.Class and Question.Class + ClassINET Class = 1 + ClassCSNET Class = 2 + ClassCHAOS Class = 3 + ClassHESIOD Class = 4 + + // Question.Class + ClassANY Class = 255 +) + +var classNames = map[Class]string{ + ClassINET: "ClassINET", + ClassCSNET: "ClassCSNET", + ClassCHAOS: "ClassCHAOS", + ClassHESIOD: "ClassHESIOD", + ClassANY: "ClassANY", +} + +// String implements fmt.Stringer.String. +func (c Class) String() string { + if n, ok := classNames[c]; ok { + return n + } + return printUint16(uint16(c)) +} + +// GoString implements fmt.GoStringer.GoString. +func (c Class) GoString() string { + if n, ok := classNames[c]; ok { + return "dnsmessage." + n + } + return printUint16(uint16(c)) +} + +// An OpCode is a DNS operation code. +type OpCode uint16 + +// GoString implements fmt.GoStringer.GoString. +func (o OpCode) GoString() string { + return printUint16(uint16(o)) +} + +// An RCode is a DNS response status code. +type RCode uint16 + +// Header.RCode values. +const ( + RCodeSuccess RCode = 0 // NoError + RCodeFormatError RCode = 1 // FormErr + RCodeServerFailure RCode = 2 // ServFail + RCodeNameError RCode = 3 // NXDomain + RCodeNotImplemented RCode = 4 // NotImp + RCodeRefused RCode = 5 // Refused +) + +var rCodeNames = map[RCode]string{ + RCodeSuccess: "RCodeSuccess", + RCodeFormatError: "RCodeFormatError", + RCodeServerFailure: "RCodeServerFailure", + RCodeNameError: "RCodeNameError", + RCodeNotImplemented: "RCodeNotImplemented", + RCodeRefused: "RCodeRefused", +} + +// String implements fmt.Stringer.String. +func (r RCode) String() string { + if n, ok := rCodeNames[r]; ok { + return n + } + return printUint16(uint16(r)) +} + +// GoString implements fmt.GoStringer.GoString. +func (r RCode) GoString() string { + if n, ok := rCodeNames[r]; ok { + return "dnsmessage." + n + } + return printUint16(uint16(r)) +} + +func printPaddedUint8(i uint8) string { + b := byte(i) + return string([]byte{ + b/100 + '0', + b/10%10 + '0', + b%10 + '0', + }) +} + +func printUint8Bytes(buf []byte, i uint8) []byte { + b := byte(i) + if i >= 100 { + buf = append(buf, b/100+'0') + } + if i >= 10 { + buf = append(buf, b/10%10+'0') + } + return append(buf, b%10+'0') +} + +func printByteSlice(b []byte) string { + if len(b) == 0 { + return "" + } + buf := make([]byte, 0, 5*len(b)) + buf = printUint8Bytes(buf, uint8(b[0])) + for _, n := range b[1:] { + buf = append(buf, ',', ' ') + buf = printUint8Bytes(buf, uint8(n)) + } + return string(buf) +} + +const hexDigits = "0123456789abcdef" + +func printString(str []byte) string { + buf := make([]byte, 0, len(str)) + for i := 0; i < len(str); i++ { + c := str[i] + if c == '.' || c == '-' || c == ' ' || + 'A' <= c && c <= 'Z' || + 'a' <= c && c <= 'z' || + '0' <= c && c <= '9' { + buf = append(buf, c) + continue + } + + upper := c >> 4 + lower := (c << 4) >> 4 + buf = append( + buf, + '\\', + 'x', + hexDigits[upper], + hexDigits[lower], + ) + } + return string(buf) +} + +func printUint16(i uint16) string { + return printUint32(uint32(i)) +} + +func printUint32(i uint32) string { + // Max value is 4294967295. + buf := make([]byte, 10) + for b, d := buf, uint32(1000000000); d > 0; d /= 10 { + b[0] = byte(i/d%10 + '0') + if b[0] == '0' && len(b) == len(buf) && len(buf) > 1 { + buf = buf[1:] + } + b = b[1:] + i %= d + } + return string(buf) +} + +func printBool(b bool) string { + if b { + return "true" + } + return "false" +} + +var ( + // ErrNotStarted indicates that the prerequisite information isn't + // available yet because the previous records haven't been appropriately + // parsed, skipped or finished. + ErrNotStarted = errors.New("parsing/packing of this type isn't available yet") + + // ErrSectionDone indicated that all records in the section have been + // parsed or finished. + ErrSectionDone = errors.New("parsing/packing of this section has completed") + + errBaseLen = errors.New("insufficient data for base length type") + errCalcLen = errors.New("insufficient data for calculated length type") + errReserved = errors.New("segment prefix is reserved") + errTooManyPtr = errors.New("too many pointers (>10)") + errInvalidPtr = errors.New("invalid pointer") + errInvalidName = errors.New("invalid dns name") + errNilResouceBody = errors.New("nil resource body") + errResourceLen = errors.New("insufficient data for resource body length") + errSegTooLong = errors.New("segment length too long") + errNameTooLong = errors.New("name too long") + errZeroSegLen = errors.New("zero length segment") + errResTooLong = errors.New("resource length too long") + errTooManyQuestions = errors.New("too many Questions to pack (>65535)") + errTooManyAnswers = errors.New("too many Answers to pack (>65535)") + errTooManyAuthorities = errors.New("too many Authorities to pack (>65535)") + errTooManyAdditionals = errors.New("too many Additionals to pack (>65535)") + errNonCanonicalName = errors.New("name is not in canonical format (it must end with a .)") + errStringTooLong = errors.New("character string exceeds maximum length (255)") + errCompressedSRV = errors.New("compressed name in SRV resource data") +) + +// Internal constants. +const ( + // packStartingCap is the default initial buffer size allocated during + // packing. + // + // The starting capacity doesn't matter too much, but most DNS responses + // Will be <= 512 bytes as it is the limit for DNS over UDP. + packStartingCap = 512 + + // uint16Len is the length (in bytes) of a uint16. + uint16Len = 2 + + // uint32Len is the length (in bytes) of a uint32. + uint32Len = 4 + + // headerLen is the length (in bytes) of a DNS header. + // + // A header is comprised of 6 uint16s and no padding. + headerLen = 6 * uint16Len +) + +type nestedError struct { + // s is the current level's error message. + s string + + // err is the nested error. + err error +} + +// nestedError implements error.Error. +func (e *nestedError) Error() string { + return e.s + ": " + e.err.Error() +} + +// Header is a representation of a DNS message header. +type Header struct { + ID uint16 + Response bool + OpCode OpCode + Authoritative bool + Truncated bool + RecursionDesired bool + RecursionAvailable bool + AuthenticData bool + CheckingDisabled bool + RCode RCode +} + +func (m *Header) pack() (id uint16, bits uint16) { + id = m.ID + bits = uint16(m.OpCode)<<11 | uint16(m.RCode) + if m.RecursionAvailable { + bits |= headerBitRA + } + if m.RecursionDesired { + bits |= headerBitRD + } + if m.Truncated { + bits |= headerBitTC + } + if m.Authoritative { + bits |= headerBitAA + } + if m.Response { + bits |= headerBitQR + } + if m.AuthenticData { + bits |= headerBitAD + } + if m.CheckingDisabled { + bits |= headerBitCD + } + return +} + +// GoString implements fmt.GoStringer.GoString. +func (m *Header) GoString() string { + return "dnsmessage.Header{" + + "ID: " + printUint16(m.ID) + ", " + + "Response: " + printBool(m.Response) + ", " + + "OpCode: " + m.OpCode.GoString() + ", " + + "Authoritative: " + printBool(m.Authoritative) + ", " + + "Truncated: " + printBool(m.Truncated) + ", " + + "RecursionDesired: " + printBool(m.RecursionDesired) + ", " + + "RecursionAvailable: " + printBool(m.RecursionAvailable) + ", " + + "AuthenticData: " + printBool(m.AuthenticData) + ", " + + "CheckingDisabled: " + printBool(m.CheckingDisabled) + ", " + + "RCode: " + m.RCode.GoString() + "}" +} + +// Message is a representation of a DNS message. +type Message struct { + Header + Questions []Question + Answers []Resource + Authorities []Resource + Additionals []Resource +} + +type section uint8 + +const ( + sectionNotStarted section = iota + sectionHeader + sectionQuestions + sectionAnswers + sectionAuthorities + sectionAdditionals + sectionDone + + headerBitQR = 1 << 15 // query/response (response=1) + headerBitAA = 1 << 10 // authoritative + headerBitTC = 1 << 9 // truncated + headerBitRD = 1 << 8 // recursion desired + headerBitRA = 1 << 7 // recursion available + headerBitAD = 1 << 5 // authentic data + headerBitCD = 1 << 4 // checking disabled +) + +var sectionNames = map[section]string{ + sectionHeader: "header", + sectionQuestions: "Question", + sectionAnswers: "Answer", + sectionAuthorities: "Authority", + sectionAdditionals: "Additional", +} + +// header is the wire format for a DNS message header. +type header struct { + id uint16 + bits uint16 + questions uint16 + answers uint16 + authorities uint16 + additionals uint16 +} + +func (h *header) count(sec section) uint16 { + switch sec { + case sectionQuestions: + return h.questions + case sectionAnswers: + return h.answers + case sectionAuthorities: + return h.authorities + case sectionAdditionals: + return h.additionals + } + return 0 +} + +// pack appends the wire format of the header to msg. +func (h *header) pack(msg []byte) []byte { + msg = packUint16(msg, h.id) + msg = packUint16(msg, h.bits) + msg = packUint16(msg, h.questions) + msg = packUint16(msg, h.answers) + msg = packUint16(msg, h.authorities) + return packUint16(msg, h.additionals) +} + +func (h *header) unpack(msg []byte, off int) (int, error) { + newOff := off + var err error + if h.id, newOff, err = unpackUint16(msg, newOff); err != nil { + return off, &nestedError{"id", err} + } + if h.bits, newOff, err = unpackUint16(msg, newOff); err != nil { + return off, &nestedError{"bits", err} + } + if h.questions, newOff, err = unpackUint16(msg, newOff); err != nil { + return off, &nestedError{"questions", err} + } + if h.answers, newOff, err = unpackUint16(msg, newOff); err != nil { + return off, &nestedError{"answers", err} + } + if h.authorities, newOff, err = unpackUint16(msg, newOff); err != nil { + return off, &nestedError{"authorities", err} + } + if h.additionals, newOff, err = unpackUint16(msg, newOff); err != nil { + return off, &nestedError{"additionals", err} + } + return newOff, nil +} + +func (h *header) header() Header { + return Header{ + ID: h.id, + Response: (h.bits & headerBitQR) != 0, + OpCode: OpCode(h.bits>>11) & 0xF, + Authoritative: (h.bits & headerBitAA) != 0, + Truncated: (h.bits & headerBitTC) != 0, + RecursionDesired: (h.bits & headerBitRD) != 0, + RecursionAvailable: (h.bits & headerBitRA) != 0, + AuthenticData: (h.bits & headerBitAD) != 0, + CheckingDisabled: (h.bits & headerBitCD) != 0, + RCode: RCode(h.bits & 0xF), + } +} + +// A Resource is a DNS resource record. +type Resource struct { + Header ResourceHeader + Body ResourceBody +} + +func (r *Resource) GoString() string { + return "dnsmessage.Resource{" + + "Header: " + r.Header.GoString() + + ", Body: &" + r.Body.GoString() + + "}" +} + +// A ResourceBody is a DNS resource record minus the header. +type ResourceBody interface { + // pack packs a Resource except for its header. + pack(msg []byte, compression map[string]uint16, compressionOff int) ([]byte, error) + + // realType returns the actual type of the Resource. This is used to + // fill in the header Type field. + realType() Type + + // GoString implements fmt.GoStringer.GoString. + GoString() string +} + +// pack appends the wire format of the Resource to msg. +func (r *Resource) pack(msg []byte, compression map[string]uint16, compressionOff int) ([]byte, error) { + if r.Body == nil { + return msg, errNilResouceBody + } + oldMsg := msg + r.Header.Type = r.Body.realType() + msg, lenOff, err := r.Header.pack(msg, compression, compressionOff) + if err != nil { + return msg, &nestedError{"ResourceHeader", err} + } + preLen := len(msg) + msg, err = r.Body.pack(msg, compression, compressionOff) + if err != nil { + return msg, &nestedError{"content", err} + } + if err := r.Header.fixLen(msg, lenOff, preLen); err != nil { + return oldMsg, err + } + return msg, nil +} + +// A Parser allows incrementally parsing a DNS message. +// +// When parsing is started, the Header is parsed. Next, each Question can be +// either parsed or skipped. Alternatively, all Questions can be skipped at +// once. When all Questions have been parsed, attempting to parse Questions +// will return the [ErrSectionDone] error. +// After all Questions have been either parsed or skipped, all +// Answers, Authorities and Additionals can be either parsed or skipped in the +// same way, and each type of Resource must be fully parsed or skipped before +// proceeding to the next type of Resource. +// +// Parser is safe to copy to preserve the parsing state. +// +// Note that there is no requirement to fully skip or parse the message. +type Parser struct { + msg []byte + header header + + section section + off int + index int + resHeaderValid bool + resHeaderOffset int + resHeaderType Type + resHeaderLength uint16 +} + +// Start parses the header and enables the parsing of Questions. +func (p *Parser) Start(msg []byte) (Header, error) { + if p.msg != nil { + *p = Parser{} + } + p.msg = msg + var err error + if p.off, err = p.header.unpack(msg, 0); err != nil { + return Header{}, &nestedError{"unpacking header", err} + } + p.section = sectionQuestions + return p.header.header(), nil +} + +func (p *Parser) checkAdvance(sec section) error { + if p.section < sec { + return ErrNotStarted + } + if p.section > sec { + return ErrSectionDone + } + p.resHeaderValid = false + if p.index == int(p.header.count(sec)) { + p.index = 0 + p.section++ + return ErrSectionDone + } + return nil +} + +func (p *Parser) resource(sec section) (Resource, error) { + var r Resource + var err error + r.Header, err = p.resourceHeader(sec) + if err != nil { + return r, err + } + p.resHeaderValid = false + r.Body, p.off, err = unpackResourceBody(p.msg, p.off, r.Header) + if err != nil { + return Resource{}, &nestedError{"unpacking " + sectionNames[sec], err} + } + p.index++ + return r, nil +} + +func (p *Parser) resourceHeader(sec section) (ResourceHeader, error) { + if p.resHeaderValid { + p.off = p.resHeaderOffset + } + + if err := p.checkAdvance(sec); err != nil { + return ResourceHeader{}, err + } + var hdr ResourceHeader + off, err := hdr.unpack(p.msg, p.off) + if err != nil { + return ResourceHeader{}, err + } + p.resHeaderValid = true + p.resHeaderOffset = p.off + p.resHeaderType = hdr.Type + p.resHeaderLength = hdr.Length + p.off = off + return hdr, nil +} + +func (p *Parser) skipResource(sec section) error { + if p.resHeaderValid && p.section == sec { + newOff := p.off + int(p.resHeaderLength) + if newOff > len(p.msg) { + return errResourceLen + } + p.off = newOff + p.resHeaderValid = false + p.index++ + return nil + } + if err := p.checkAdvance(sec); err != nil { + return err + } + var err error + p.off, err = skipResource(p.msg, p.off) + if err != nil { + return &nestedError{"skipping: " + sectionNames[sec], err} + } + p.index++ + return nil +} + +// Question parses a single Question. +func (p *Parser) Question() (Question, error) { + if err := p.checkAdvance(sectionQuestions); err != nil { + return Question{}, err + } + var name Name + off, err := name.unpack(p.msg, p.off) + if err != nil { + return Question{}, &nestedError{"unpacking Question.Name", err} + } + typ, off, err := unpackType(p.msg, off) + if err != nil { + return Question{}, &nestedError{"unpacking Question.Type", err} + } + class, off, err := unpackClass(p.msg, off) + if err != nil { + return Question{}, &nestedError{"unpacking Question.Class", err} + } + p.off = off + p.index++ + return Question{name, typ, class}, nil +} + +// AllQuestions parses all Questions. +func (p *Parser) AllQuestions() ([]Question, error) { + // Multiple questions are valid according to the spec, + // but servers don't actually support them. There will + // be at most one question here. + // + // Do not pre-allocate based on info in p.header, since + // the data is untrusted. + qs := []Question{} + for { + q, err := p.Question() + if err == ErrSectionDone { + return qs, nil + } + if err != nil { + return nil, err + } + qs = append(qs, q) + } +} + +// SkipQuestion skips a single Question. +func (p *Parser) SkipQuestion() error { + if err := p.checkAdvance(sectionQuestions); err != nil { + return err + } + off, err := skipName(p.msg, p.off) + if err != nil { + return &nestedError{"skipping Question Name", err} + } + if off, err = skipType(p.msg, off); err != nil { + return &nestedError{"skipping Question Type", err} + } + if off, err = skipClass(p.msg, off); err != nil { + return &nestedError{"skipping Question Class", err} + } + p.off = off + p.index++ + return nil +} + +// SkipAllQuestions skips all Questions. +func (p *Parser) SkipAllQuestions() error { + for { + if err := p.SkipQuestion(); err == ErrSectionDone { + return nil + } else if err != nil { + return err + } + } +} + +// AnswerHeader parses a single Answer ResourceHeader. +func (p *Parser) AnswerHeader() (ResourceHeader, error) { + return p.resourceHeader(sectionAnswers) +} + +// Answer parses a single Answer Resource. +func (p *Parser) Answer() (Resource, error) { + return p.resource(sectionAnswers) +} + +// AllAnswers parses all Answer Resources. +func (p *Parser) AllAnswers() ([]Resource, error) { + // The most common query is for A/AAAA, which usually returns + // a handful of IPs. + // + // Pre-allocate up to a certain limit, since p.header is + // untrusted data. + n := int(p.header.answers) + if n > 20 { + n = 20 + } + as := make([]Resource, 0, n) + for { + a, err := p.Answer() + if err == ErrSectionDone { + return as, nil + } + if err != nil { + return nil, err + } + as = append(as, a) + } +} + +// SkipAnswer skips a single Answer Resource. +func (p *Parser) SkipAnswer() error { + return p.skipResource(sectionAnswers) +} + +// SkipAllAnswers skips all Answer Resources. +func (p *Parser) SkipAllAnswers() error { + for { + if err := p.SkipAnswer(); err == ErrSectionDone { + return nil + } else if err != nil { + return err + } + } +} + +// AuthorityHeader parses a single Authority ResourceHeader. +func (p *Parser) AuthorityHeader() (ResourceHeader, error) { + return p.resourceHeader(sectionAuthorities) +} + +// Authority parses a single Authority Resource. +func (p *Parser) Authority() (Resource, error) { + return p.resource(sectionAuthorities) +} + +// AllAuthorities parses all Authority Resources. +func (p *Parser) AllAuthorities() ([]Resource, error) { + // Authorities contains SOA in case of NXDOMAIN and friends, + // otherwise it is empty. + // + // Pre-allocate up to a certain limit, since p.header is + // untrusted data. + n := int(p.header.authorities) + if n > 10 { + n = 10 + } + as := make([]Resource, 0, n) + for { + a, err := p.Authority() + if err == ErrSectionDone { + return as, nil + } + if err != nil { + return nil, err + } + as = append(as, a) + } +} + +// SkipAuthority skips a single Authority Resource. +func (p *Parser) SkipAuthority() error { + return p.skipResource(sectionAuthorities) +} + +// SkipAllAuthorities skips all Authority Resources. +func (p *Parser) SkipAllAuthorities() error { + for { + if err := p.SkipAuthority(); err == ErrSectionDone { + return nil + } else if err != nil { + return err + } + } +} + +// AdditionalHeader parses a single Additional ResourceHeader. +func (p *Parser) AdditionalHeader() (ResourceHeader, error) { + return p.resourceHeader(sectionAdditionals) +} + +// Additional parses a single Additional Resource. +func (p *Parser) Additional() (Resource, error) { + return p.resource(sectionAdditionals) +} + +// AllAdditionals parses all Additional Resources. +func (p *Parser) AllAdditionals() ([]Resource, error) { + // Additionals usually contain OPT, and sometimes A/AAAA + // glue records. + // + // Pre-allocate up to a certain limit, since p.header is + // untrusted data. + n := int(p.header.additionals) + if n > 10 { + n = 10 + } + as := make([]Resource, 0, n) + for { + a, err := p.Additional() + if err == ErrSectionDone { + return as, nil + } + if err != nil { + return nil, err + } + as = append(as, a) + } +} + +// SkipAdditional skips a single Additional Resource. +func (p *Parser) SkipAdditional() error { + return p.skipResource(sectionAdditionals) +} + +// SkipAllAdditionals skips all Additional Resources. +func (p *Parser) SkipAllAdditionals() error { + for { + if err := p.SkipAdditional(); err == ErrSectionDone { + return nil + } else if err != nil { + return err + } + } +} + +// CNAMEResource parses a single CNAMEResource. +// +// One of the XXXHeader methods must have been called before calling this +// method. +func (p *Parser) CNAMEResource() (CNAMEResource, error) { + if !p.resHeaderValid || p.resHeaderType != TypeCNAME { + return CNAMEResource{}, ErrNotStarted + } + r, err := unpackCNAMEResource(p.msg, p.off) + if err != nil { + return CNAMEResource{}, err + } + p.off += int(p.resHeaderLength) + p.resHeaderValid = false + p.index++ + return r, nil +} + +// MXResource parses a single MXResource. +// +// One of the XXXHeader methods must have been called before calling this +// method. +func (p *Parser) MXResource() (MXResource, error) { + if !p.resHeaderValid || p.resHeaderType != TypeMX { + return MXResource{}, ErrNotStarted + } + r, err := unpackMXResource(p.msg, p.off) + if err != nil { + return MXResource{}, err + } + p.off += int(p.resHeaderLength) + p.resHeaderValid = false + p.index++ + return r, nil +} + +// NSResource parses a single NSResource. +// +// One of the XXXHeader methods must have been called before calling this +// method. +func (p *Parser) NSResource() (NSResource, error) { + if !p.resHeaderValid || p.resHeaderType != TypeNS { + return NSResource{}, ErrNotStarted + } + r, err := unpackNSResource(p.msg, p.off) + if err != nil { + return NSResource{}, err + } + p.off += int(p.resHeaderLength) + p.resHeaderValid = false + p.index++ + return r, nil +} + +// PTRResource parses a single PTRResource. +// +// One of the XXXHeader methods must have been called before calling this +// method. +func (p *Parser) PTRResource() (PTRResource, error) { + if !p.resHeaderValid || p.resHeaderType != TypePTR { + return PTRResource{}, ErrNotStarted + } + r, err := unpackPTRResource(p.msg, p.off) + if err != nil { + return PTRResource{}, err + } + p.off += int(p.resHeaderLength) + p.resHeaderValid = false + p.index++ + return r, nil +} + +// SOAResource parses a single SOAResource. +// +// One of the XXXHeader methods must have been called before calling this +// method. +func (p *Parser) SOAResource() (SOAResource, error) { + if !p.resHeaderValid || p.resHeaderType != TypeSOA { + return SOAResource{}, ErrNotStarted + } + r, err := unpackSOAResource(p.msg, p.off) + if err != nil { + return SOAResource{}, err + } + p.off += int(p.resHeaderLength) + p.resHeaderValid = false + p.index++ + return r, nil +} + +// TXTResource parses a single TXTResource. +// +// One of the XXXHeader methods must have been called before calling this +// method. +func (p *Parser) TXTResource() (TXTResource, error) { + if !p.resHeaderValid || p.resHeaderType != TypeTXT { + return TXTResource{}, ErrNotStarted + } + r, err := unpackTXTResource(p.msg, p.off, p.resHeaderLength) + if err != nil { + return TXTResource{}, err + } + p.off += int(p.resHeaderLength) + p.resHeaderValid = false + p.index++ + return r, nil +} + +// SRVResource parses a single SRVResource. +// +// One of the XXXHeader methods must have been called before calling this +// method. +func (p *Parser) SRVResource() (SRVResource, error) { + if !p.resHeaderValid || p.resHeaderType != TypeSRV { + return SRVResource{}, ErrNotStarted + } + r, err := unpackSRVResource(p.msg, p.off) + if err != nil { + return SRVResource{}, err + } + p.off += int(p.resHeaderLength) + p.resHeaderValid = false + p.index++ + return r, nil +} + +// AResource parses a single AResource. +// +// One of the XXXHeader methods must have been called before calling this +// method. +func (p *Parser) AResource() (AResource, error) { + if !p.resHeaderValid || p.resHeaderType != TypeA { + return AResource{}, ErrNotStarted + } + r, err := unpackAResource(p.msg, p.off) + if err != nil { + return AResource{}, err + } + p.off += int(p.resHeaderLength) + p.resHeaderValid = false + p.index++ + return r, nil +} + +// AAAAResource parses a single AAAAResource. +// +// One of the XXXHeader methods must have been called before calling this +// method. +func (p *Parser) AAAAResource() (AAAAResource, error) { + if !p.resHeaderValid || p.resHeaderType != TypeAAAA { + return AAAAResource{}, ErrNotStarted + } + r, err := unpackAAAAResource(p.msg, p.off) + if err != nil { + return AAAAResource{}, err + } + p.off += int(p.resHeaderLength) + p.resHeaderValid = false + p.index++ + return r, nil +} + +// OPTResource parses a single OPTResource. +// +// One of the XXXHeader methods must have been called before calling this +// method. +func (p *Parser) OPTResource() (OPTResource, error) { + if !p.resHeaderValid || p.resHeaderType != TypeOPT { + return OPTResource{}, ErrNotStarted + } + r, err := unpackOPTResource(p.msg, p.off, p.resHeaderLength) + if err != nil { + return OPTResource{}, err + } + p.off += int(p.resHeaderLength) + p.resHeaderValid = false + p.index++ + return r, nil +} + +// UnknownResource parses a single UnknownResource. +// +// One of the XXXHeader methods must have been called before calling this +// method. +func (p *Parser) UnknownResource() (UnknownResource, error) { + if !p.resHeaderValid { + return UnknownResource{}, ErrNotStarted + } + r, err := unpackUnknownResource(p.resHeaderType, p.msg, p.off, p.resHeaderLength) + if err != nil { + return UnknownResource{}, err + } + p.off += int(p.resHeaderLength) + p.resHeaderValid = false + p.index++ + return r, nil +} + +// Unpack parses a full Message. +func (m *Message) Unpack(msg []byte) error { + var p Parser + var err error + if m.Header, err = p.Start(msg); err != nil { + return err + } + if m.Questions, err = p.AllQuestions(); err != nil { + return err + } + if m.Answers, err = p.AllAnswers(); err != nil { + return err + } + if m.Authorities, err = p.AllAuthorities(); err != nil { + return err + } + if m.Additionals, err = p.AllAdditionals(); err != nil { + return err + } + return nil +} + +// Pack packs a full Message. +func (m *Message) Pack() ([]byte, error) { + return m.AppendPack(make([]byte, 0, packStartingCap)) +} + +// AppendPack is like Pack but appends the full Message to b and returns the +// extended buffer. +func (m *Message) AppendPack(b []byte) ([]byte, error) { + // Validate the lengths. It is very unlikely that anyone will try to + // pack more than 65535 of any particular type, but it is possible and + // we should fail gracefully. + if len(m.Questions) > int(^uint16(0)) { + return nil, errTooManyQuestions + } + if len(m.Answers) > int(^uint16(0)) { + return nil, errTooManyAnswers + } + if len(m.Authorities) > int(^uint16(0)) { + return nil, errTooManyAuthorities + } + if len(m.Additionals) > int(^uint16(0)) { + return nil, errTooManyAdditionals + } + + var h header + h.id, h.bits = m.Header.pack() + + h.questions = uint16(len(m.Questions)) + h.answers = uint16(len(m.Answers)) + h.authorities = uint16(len(m.Authorities)) + h.additionals = uint16(len(m.Additionals)) + + compressionOff := len(b) + msg := h.pack(b) + + // RFC 1035 allows (but does not require) compression for packing. RFC + // 1035 requires unpacking implementations to support compression, so + // unconditionally enabling it is fine. + // + // DNS lookups are typically done over UDP, and RFC 1035 states that UDP + // DNS messages can be a maximum of 512 bytes long. Without compression, + // many DNS response messages are over this limit, so enabling + // compression will help ensure compliance. + compression := map[string]uint16{} + + for i := range m.Questions { + var err error + if msg, err = m.Questions[i].pack(msg, compression, compressionOff); err != nil { + return nil, &nestedError{"packing Question", err} + } + } + for i := range m.Answers { + var err error + if msg, err = m.Answers[i].pack(msg, compression, compressionOff); err != nil { + return nil, &nestedError{"packing Answer", err} + } + } + for i := range m.Authorities { + var err error + if msg, err = m.Authorities[i].pack(msg, compression, compressionOff); err != nil { + return nil, &nestedError{"packing Authority", err} + } + } + for i := range m.Additionals { + var err error + if msg, err = m.Additionals[i].pack(msg, compression, compressionOff); err != nil { + return nil, &nestedError{"packing Additional", err} + } + } + + return msg, nil +} + +// GoString implements fmt.GoStringer.GoString. +func (m *Message) GoString() string { + s := "dnsmessage.Message{Header: " + m.Header.GoString() + ", " + + "Questions: []dnsmessage.Question{" + if len(m.Questions) > 0 { + s += m.Questions[0].GoString() + for _, q := range m.Questions[1:] { + s += ", " + q.GoString() + } + } + s += "}, Answers: []dnsmessage.Resource{" + if len(m.Answers) > 0 { + s += m.Answers[0].GoString() + for _, a := range m.Answers[1:] { + s += ", " + a.GoString() + } + } + s += "}, Authorities: []dnsmessage.Resource{" + if len(m.Authorities) > 0 { + s += m.Authorities[0].GoString() + for _, a := range m.Authorities[1:] { + s += ", " + a.GoString() + } + } + s += "}, Additionals: []dnsmessage.Resource{" + if len(m.Additionals) > 0 { + s += m.Additionals[0].GoString() + for _, a := range m.Additionals[1:] { + s += ", " + a.GoString() + } + } + return s + "}}" +} + +// A Builder allows incrementally packing a DNS message. +// +// Example usage: +// +// buf := make([]byte, 2, 514) +// b := NewBuilder(buf, Header{...}) +// b.EnableCompression() +// // Optionally start a section and add things to that section. +// // Repeat adding sections as necessary. +// buf, err := b.Finish() +// // If err is nil, buf[2:] will contain the built bytes. +type Builder struct { + // msg is the storage for the message being built. + msg []byte + + // section keeps track of the current section being built. + section section + + // header keeps track of what should go in the header when Finish is + // called. + header header + + // start is the starting index of the bytes allocated in msg for header. + start int + + // compression is a mapping from name suffixes to their starting index + // in msg. + compression map[string]uint16 +} + +// NewBuilder creates a new builder with compression disabled. +// +// Note: Most users will want to immediately enable compression with the +// EnableCompression method. See that method's comment for why you may or may +// not want to enable compression. +// +// The DNS message is appended to the provided initial buffer buf (which may be +// nil) as it is built. The final message is returned by the (*Builder).Finish +// method, which includes buf[:len(buf)] and may return the same underlying +// array if there was sufficient capacity in the slice. +func NewBuilder(buf []byte, h Header) Builder { + if buf == nil { + buf = make([]byte, 0, packStartingCap) + } + b := Builder{msg: buf, start: len(buf)} + b.header.id, b.header.bits = h.pack() + var hb [headerLen]byte + b.msg = append(b.msg, hb[:]...) + b.section = sectionHeader + return b +} + +// EnableCompression enables compression in the Builder. +// +// Leaving compression disabled avoids compression related allocations, but can +// result in larger message sizes. Be careful with this mode as it can cause +// messages to exceed the UDP size limit. +// +// According to RFC 1035, section 4.1.4, the use of compression is optional, but +// all implementations must accept both compressed and uncompressed DNS +// messages. +// +// Compression should be enabled before any sections are added for best results. +func (b *Builder) EnableCompression() { + b.compression = map[string]uint16{} +} + +func (b *Builder) startCheck(s section) error { + if b.section <= sectionNotStarted { + return ErrNotStarted + } + if b.section > s { + return ErrSectionDone + } + return nil +} + +// StartQuestions prepares the builder for packing Questions. +func (b *Builder) StartQuestions() error { + if err := b.startCheck(sectionQuestions); err != nil { + return err + } + b.section = sectionQuestions + return nil +} + +// StartAnswers prepares the builder for packing Answers. +func (b *Builder) StartAnswers() error { + if err := b.startCheck(sectionAnswers); err != nil { + return err + } + b.section = sectionAnswers + return nil +} + +// StartAuthorities prepares the builder for packing Authorities. +func (b *Builder) StartAuthorities() error { + if err := b.startCheck(sectionAuthorities); err != nil { + return err + } + b.section = sectionAuthorities + return nil +} + +// StartAdditionals prepares the builder for packing Additionals. +func (b *Builder) StartAdditionals() error { + if err := b.startCheck(sectionAdditionals); err != nil { + return err + } + b.section = sectionAdditionals + return nil +} + +func (b *Builder) incrementSectionCount() error { + var count *uint16 + var err error + switch b.section { + case sectionQuestions: + count = &b.header.questions + err = errTooManyQuestions + case sectionAnswers: + count = &b.header.answers + err = errTooManyAnswers + case sectionAuthorities: + count = &b.header.authorities + err = errTooManyAuthorities + case sectionAdditionals: + count = &b.header.additionals + err = errTooManyAdditionals + } + if *count == ^uint16(0) { + return err + } + *count++ + return nil +} + +// Question adds a single Question. +func (b *Builder) Question(q Question) error { + if b.section < sectionQuestions { + return ErrNotStarted + } + if b.section > sectionQuestions { + return ErrSectionDone + } + msg, err := q.pack(b.msg, b.compression, b.start) + if err != nil { + return err + } + if err := b.incrementSectionCount(); err != nil { + return err + } + b.msg = msg + return nil +} + +func (b *Builder) checkResourceSection() error { + if b.section < sectionAnswers { + return ErrNotStarted + } + if b.section > sectionAdditionals { + return ErrSectionDone + } + return nil +} + +// CNAMEResource adds a single CNAMEResource. +func (b *Builder) CNAMEResource(h ResourceHeader, r CNAMEResource) error { + if err := b.checkResourceSection(); err != nil { + return err + } + h.Type = r.realType() + msg, lenOff, err := h.pack(b.msg, b.compression, b.start) + if err != nil { + return &nestedError{"ResourceHeader", err} + } + preLen := len(msg) + if msg, err = r.pack(msg, b.compression, b.start); err != nil { + return &nestedError{"CNAMEResource body", err} + } + if err := h.fixLen(msg, lenOff, preLen); err != nil { + return err + } + if err := b.incrementSectionCount(); err != nil { + return err + } + b.msg = msg + return nil +} + +// MXResource adds a single MXResource. +func (b *Builder) MXResource(h ResourceHeader, r MXResource) error { + if err := b.checkResourceSection(); err != nil { + return err + } + h.Type = r.realType() + msg, lenOff, err := h.pack(b.msg, b.compression, b.start) + if err != nil { + return &nestedError{"ResourceHeader", err} + } + preLen := len(msg) + if msg, err = r.pack(msg, b.compression, b.start); err != nil { + return &nestedError{"MXResource body", err} + } + if err := h.fixLen(msg, lenOff, preLen); err != nil { + return err + } + if err := b.incrementSectionCount(); err != nil { + return err + } + b.msg = msg + return nil +} + +// NSResource adds a single NSResource. +func (b *Builder) NSResource(h ResourceHeader, r NSResource) error { + if err := b.checkResourceSection(); err != nil { + return err + } + h.Type = r.realType() + msg, lenOff, err := h.pack(b.msg, b.compression, b.start) + if err != nil { + return &nestedError{"ResourceHeader", err} + } + preLen := len(msg) + if msg, err = r.pack(msg, b.compression, b.start); err != nil { + return &nestedError{"NSResource body", err} + } + if err := h.fixLen(msg, lenOff, preLen); err != nil { + return err + } + if err := b.incrementSectionCount(); err != nil { + return err + } + b.msg = msg + return nil +} + +// PTRResource adds a single PTRResource. +func (b *Builder) PTRResource(h ResourceHeader, r PTRResource) error { + if err := b.checkResourceSection(); err != nil { + return err + } + h.Type = r.realType() + msg, lenOff, err := h.pack(b.msg, b.compression, b.start) + if err != nil { + return &nestedError{"ResourceHeader", err} + } + preLen := len(msg) + if msg, err = r.pack(msg, b.compression, b.start); err != nil { + return &nestedError{"PTRResource body", err} + } + if err := h.fixLen(msg, lenOff, preLen); err != nil { + return err + } + if err := b.incrementSectionCount(); err != nil { + return err + } + b.msg = msg + return nil +} + +// SOAResource adds a single SOAResource. +func (b *Builder) SOAResource(h ResourceHeader, r SOAResource) error { + if err := b.checkResourceSection(); err != nil { + return err + } + h.Type = r.realType() + msg, lenOff, err := h.pack(b.msg, b.compression, b.start) + if err != nil { + return &nestedError{"ResourceHeader", err} + } + preLen := len(msg) + if msg, err = r.pack(msg, b.compression, b.start); err != nil { + return &nestedError{"SOAResource body", err} + } + if err := h.fixLen(msg, lenOff, preLen); err != nil { + return err + } + if err := b.incrementSectionCount(); err != nil { + return err + } + b.msg = msg + return nil +} + +// TXTResource adds a single TXTResource. +func (b *Builder) TXTResource(h ResourceHeader, r TXTResource) error { + if err := b.checkResourceSection(); err != nil { + return err + } + h.Type = r.realType() + msg, lenOff, err := h.pack(b.msg, b.compression, b.start) + if err != nil { + return &nestedError{"ResourceHeader", err} + } + preLen := len(msg) + if msg, err = r.pack(msg, b.compression, b.start); err != nil { + return &nestedError{"TXTResource body", err} + } + if err := h.fixLen(msg, lenOff, preLen); err != nil { + return err + } + if err := b.incrementSectionCount(); err != nil { + return err + } + b.msg = msg + return nil +} + +// SRVResource adds a single SRVResource. +func (b *Builder) SRVResource(h ResourceHeader, r SRVResource) error { + if err := b.checkResourceSection(); err != nil { + return err + } + h.Type = r.realType() + msg, lenOff, err := h.pack(b.msg, b.compression, b.start) + if err != nil { + return &nestedError{"ResourceHeader", err} + } + preLen := len(msg) + if msg, err = r.pack(msg, b.compression, b.start); err != nil { + return &nestedError{"SRVResource body", err} + } + if err := h.fixLen(msg, lenOff, preLen); err != nil { + return err + } + if err := b.incrementSectionCount(); err != nil { + return err + } + b.msg = msg + return nil +} + +// AResource adds a single AResource. +func (b *Builder) AResource(h ResourceHeader, r AResource) error { + if err := b.checkResourceSection(); err != nil { + return err + } + h.Type = r.realType() + msg, lenOff, err := h.pack(b.msg, b.compression, b.start) + if err != nil { + return &nestedError{"ResourceHeader", err} + } + preLen := len(msg) + if msg, err = r.pack(msg, b.compression, b.start); err != nil { + return &nestedError{"AResource body", err} + } + if err := h.fixLen(msg, lenOff, preLen); err != nil { + return err + } + if err := b.incrementSectionCount(); err != nil { + return err + } + b.msg = msg + return nil +} + +// AAAAResource adds a single AAAAResource. +func (b *Builder) AAAAResource(h ResourceHeader, r AAAAResource) error { + if err := b.checkResourceSection(); err != nil { + return err + } + h.Type = r.realType() + msg, lenOff, err := h.pack(b.msg, b.compression, b.start) + if err != nil { + return &nestedError{"ResourceHeader", err} + } + preLen := len(msg) + if msg, err = r.pack(msg, b.compression, b.start); err != nil { + return &nestedError{"AAAAResource body", err} + } + if err := h.fixLen(msg, lenOff, preLen); err != nil { + return err + } + if err := b.incrementSectionCount(); err != nil { + return err + } + b.msg = msg + return nil +} + +// OPTResource adds a single OPTResource. +func (b *Builder) OPTResource(h ResourceHeader, r OPTResource) error { + if err := b.checkResourceSection(); err != nil { + return err + } + h.Type = r.realType() + msg, lenOff, err := h.pack(b.msg, b.compression, b.start) + if err != nil { + return &nestedError{"ResourceHeader", err} + } + preLen := len(msg) + if msg, err = r.pack(msg, b.compression, b.start); err != nil { + return &nestedError{"OPTResource body", err} + } + if err := h.fixLen(msg, lenOff, preLen); err != nil { + return err + } + if err := b.incrementSectionCount(); err != nil { + return err + } + b.msg = msg + return nil +} + +// UnknownResource adds a single UnknownResource. +func (b *Builder) UnknownResource(h ResourceHeader, r UnknownResource) error { + if err := b.checkResourceSection(); err != nil { + return err + } + h.Type = r.realType() + msg, lenOff, err := h.pack(b.msg, b.compression, b.start) + if err != nil { + return &nestedError{"ResourceHeader", err} + } + preLen := len(msg) + if msg, err = r.pack(msg, b.compression, b.start); err != nil { + return &nestedError{"UnknownResource body", err} + } + if err := h.fixLen(msg, lenOff, preLen); err != nil { + return err + } + if err := b.incrementSectionCount(); err != nil { + return err + } + b.msg = msg + return nil +} + +// Finish ends message building and generates a binary message. +func (b *Builder) Finish() ([]byte, error) { + if b.section < sectionHeader { + return nil, ErrNotStarted + } + b.section = sectionDone + // Space for the header was allocated in NewBuilder. + b.header.pack(b.msg[b.start:b.start]) + return b.msg, nil +} + +// A ResourceHeader is the header of a DNS resource record. There are +// many types of DNS resource records, but they all share the same header. +type ResourceHeader struct { + // Name is the domain name for which this resource record pertains. + Name Name + + // Type is the type of DNS resource record. + // + // This field will be set automatically during packing. + Type Type + + // Class is the class of network to which this DNS resource record + // pertains. + Class Class + + // TTL is the length of time (measured in seconds) which this resource + // record is valid for (time to live). All Resources in a set should + // have the same TTL (RFC 2181 Section 5.2). + TTL uint32 + + // Length is the length of data in the resource record after the header. + // + // This field will be set automatically during packing. + Length uint16 +} + +// GoString implements fmt.GoStringer.GoString. +func (h *ResourceHeader) GoString() string { + return "dnsmessage.ResourceHeader{" + + "Name: " + h.Name.GoString() + ", " + + "Type: " + h.Type.GoString() + ", " + + "Class: " + h.Class.GoString() + ", " + + "TTL: " + printUint32(h.TTL) + ", " + + "Length: " + printUint16(h.Length) + "}" +} + +// pack appends the wire format of the ResourceHeader to oldMsg. +// +// lenOff is the offset in msg where the Length field was packed. +func (h *ResourceHeader) pack(oldMsg []byte, compression map[string]uint16, compressionOff int) (msg []byte, lenOff int, err error) { + msg = oldMsg + if msg, err = h.Name.pack(msg, compression, compressionOff); err != nil { + return oldMsg, 0, &nestedError{"Name", err} + } + msg = packType(msg, h.Type) + msg = packClass(msg, h.Class) + msg = packUint32(msg, h.TTL) + lenOff = len(msg) + msg = packUint16(msg, h.Length) + return msg, lenOff, nil +} + +func (h *ResourceHeader) unpack(msg []byte, off int) (int, error) { + newOff := off + var err error + if newOff, err = h.Name.unpack(msg, newOff); err != nil { + return off, &nestedError{"Name", err} + } + if h.Type, newOff, err = unpackType(msg, newOff); err != nil { + return off, &nestedError{"Type", err} + } + if h.Class, newOff, err = unpackClass(msg, newOff); err != nil { + return off, &nestedError{"Class", err} + } + if h.TTL, newOff, err = unpackUint32(msg, newOff); err != nil { + return off, &nestedError{"TTL", err} + } + if h.Length, newOff, err = unpackUint16(msg, newOff); err != nil { + return off, &nestedError{"Length", err} + } + return newOff, nil +} + +// fixLen updates a packed ResourceHeader to include the length of the +// ResourceBody. +// +// lenOff is the offset of the ResourceHeader.Length field in msg. +// +// preLen is the length that msg was before the ResourceBody was packed. +func (h *ResourceHeader) fixLen(msg []byte, lenOff int, preLen int) error { + conLen := len(msg) - preLen + if conLen > int(^uint16(0)) { + return errResTooLong + } + + // Fill in the length now that we know how long the content is. + packUint16(msg[lenOff:lenOff], uint16(conLen)) + h.Length = uint16(conLen) + + return nil +} + +// EDNS(0) wire constants. +const ( + edns0Version = 0 + + edns0DNSSECOK = 0x00008000 + ednsVersionMask = 0x00ff0000 + edns0DNSSECOKMask = 0x00ff8000 +) + +// SetEDNS0 configures h for EDNS(0). +// +// The provided extRCode must be an extended RCode. +func (h *ResourceHeader) SetEDNS0(udpPayloadLen int, extRCode RCode, dnssecOK bool) error { + h.Name = Name{Data: [255]byte{'.'}, Length: 1} // RFC 6891 section 6.1.2 + h.Type = TypeOPT + h.Class = Class(udpPayloadLen) + h.TTL = uint32(extRCode) >> 4 << 24 + if dnssecOK { + h.TTL |= edns0DNSSECOK + } + return nil +} + +// DNSSECAllowed reports whether the DNSSEC OK bit is set. +func (h *ResourceHeader) DNSSECAllowed() bool { + return h.TTL&edns0DNSSECOKMask == edns0DNSSECOK // RFC 6891 section 6.1.3 +} + +// ExtendedRCode returns an extended RCode. +// +// The provided rcode must be the RCode in DNS message header. +func (h *ResourceHeader) ExtendedRCode(rcode RCode) RCode { + if h.TTL&ednsVersionMask == edns0Version { // RFC 6891 section 6.1.3 + return RCode(h.TTL>>24<<4) | rcode + } + return rcode +} + +func skipResource(msg []byte, off int) (int, error) { + newOff, err := skipName(msg, off) + if err != nil { + return off, &nestedError{"Name", err} + } + if newOff, err = skipType(msg, newOff); err != nil { + return off, &nestedError{"Type", err} + } + if newOff, err = skipClass(msg, newOff); err != nil { + return off, &nestedError{"Class", err} + } + if newOff, err = skipUint32(msg, newOff); err != nil { + return off, &nestedError{"TTL", err} + } + length, newOff, err := unpackUint16(msg, newOff) + if err != nil { + return off, &nestedError{"Length", err} + } + if newOff += int(length); newOff > len(msg) { + return off, errResourceLen + } + return newOff, nil +} + +// packUint16 appends the wire format of field to msg. +func packUint16(msg []byte, field uint16) []byte { + return append(msg, byte(field>>8), byte(field)) +} + +func unpackUint16(msg []byte, off int) (uint16, int, error) { + if off+uint16Len > len(msg) { + return 0, off, errBaseLen + } + return uint16(msg[off])<<8 | uint16(msg[off+1]), off + uint16Len, nil +} + +func skipUint16(msg []byte, off int) (int, error) { + if off+uint16Len > len(msg) { + return off, errBaseLen + } + return off + uint16Len, nil +} + +// packType appends the wire format of field to msg. +func packType(msg []byte, field Type) []byte { + return packUint16(msg, uint16(field)) +} + +func unpackType(msg []byte, off int) (Type, int, error) { + t, o, err := unpackUint16(msg, off) + return Type(t), o, err +} + +func skipType(msg []byte, off int) (int, error) { + return skipUint16(msg, off) +} + +// packClass appends the wire format of field to msg. +func packClass(msg []byte, field Class) []byte { + return packUint16(msg, uint16(field)) +} + +func unpackClass(msg []byte, off int) (Class, int, error) { + c, o, err := unpackUint16(msg, off) + return Class(c), o, err +} + +func skipClass(msg []byte, off int) (int, error) { + return skipUint16(msg, off) +} + +// packUint32 appends the wire format of field to msg. +func packUint32(msg []byte, field uint32) []byte { + return append( + msg, + byte(field>>24), + byte(field>>16), + byte(field>>8), + byte(field), + ) +} + +func unpackUint32(msg []byte, off int) (uint32, int, error) { + if off+uint32Len > len(msg) { + return 0, off, errBaseLen + } + v := uint32(msg[off])<<24 | uint32(msg[off+1])<<16 | uint32(msg[off+2])<<8 | uint32(msg[off+3]) + return v, off + uint32Len, nil +} + +func skipUint32(msg []byte, off int) (int, error) { + if off+uint32Len > len(msg) { + return off, errBaseLen + } + return off + uint32Len, nil +} + +// packText appends the wire format of field to msg. +func packText(msg []byte, field string) ([]byte, error) { + l := len(field) + if l > 255 { + return nil, errStringTooLong + } + msg = append(msg, byte(l)) + msg = append(msg, field...) + + return msg, nil +} + +func unpackText(msg []byte, off int) (string, int, error) { + if off >= len(msg) { + return "", off, errBaseLen + } + beginOff := off + 1 + endOff := beginOff + int(msg[off]) + if endOff > len(msg) { + return "", off, errCalcLen + } + return string(msg[beginOff:endOff]), endOff, nil +} + +// packBytes appends the wire format of field to msg. +func packBytes(msg []byte, field []byte) []byte { + return append(msg, field...) +} + +func unpackBytes(msg []byte, off int, field []byte) (int, error) { + newOff := off + len(field) + if newOff > len(msg) { + return off, errBaseLen + } + copy(field, msg[off:newOff]) + return newOff, nil +} + +const nonEncodedNameMax = 254 + +// A Name is a non-encoded and non-escaped domain name. It is used instead of strings to avoid +// allocations. +type Name struct { + Data [255]byte + Length uint8 +} + +// NewName creates a new Name from a string. +func NewName(name string) (Name, error) { + n := Name{Length: uint8(len(name))} + if len(name) > len(n.Data) { + return Name{}, errCalcLen + } + copy(n.Data[:], name) + return n, nil +} + +// MustNewName creates a new Name from a string and panics on error. +func MustNewName(name string) Name { + n, err := NewName(name) + if err != nil { + panic("creating name: " + err.Error()) + } + return n +} + +// String implements fmt.Stringer.String. +// +// Note: characters inside the labels are not escaped in any way. +func (n Name) String() string { + return string(n.Data[:n.Length]) +} + +// GoString implements fmt.GoStringer.GoString. +func (n *Name) GoString() string { + return `dnsmessage.MustNewName("` + printString(n.Data[:n.Length]) + `")` +} + +// pack appends the wire format of the Name to msg. +// +// Domain names are a sequence of counted strings split at the dots. They end +// with a zero-length string. Compression can be used to reuse domain suffixes. +// +// The compression map will be updated with new domain suffixes. If compression +// is nil, compression will not be used. +func (n *Name) pack(msg []byte, compression map[string]uint16, compressionOff int) ([]byte, error) { + oldMsg := msg + + if n.Length > nonEncodedNameMax { + return nil, errNameTooLong + } + + // Add a trailing dot to canonicalize name. + if n.Length == 0 || n.Data[n.Length-1] != '.' { + return oldMsg, errNonCanonicalName + } + + // Allow root domain. + if n.Data[0] == '.' && n.Length == 1 { + return append(msg, 0), nil + } + + var nameAsStr string + + // Emit sequence of counted strings, chopping at dots. + for i, begin := 0, 0; i < int(n.Length); i++ { + // Check for the end of the segment. + if n.Data[i] == '.' { + // The two most significant bits have special meaning. + // It isn't allowed for segments to be long enough to + // need them. + if i-begin >= 1<<6 { + return oldMsg, errSegTooLong + } + + // Segments must have a non-zero length. + if i-begin == 0 { + return oldMsg, errZeroSegLen + } + + msg = append(msg, byte(i-begin)) + + for j := begin; j < i; j++ { + msg = append(msg, n.Data[j]) + } + + begin = i + 1 + continue + } + + // We can only compress domain suffixes starting with a new + // segment. A pointer is two bytes with the two most significant + // bits set to 1 to indicate that it is a pointer. + if (i == 0 || n.Data[i-1] == '.') && compression != nil { + if ptr, ok := compression[string(n.Data[i:n.Length])]; ok { + // Hit. Emit a pointer instead of the rest of + // the domain. + return append(msg, byte(ptr>>8|0xC0), byte(ptr)), nil + } + + // Miss. Add the suffix to the compression table if the + // offset can be stored in the available 14 bits. + newPtr := len(msg) - compressionOff + if newPtr <= int(^uint16(0)>>2) { + if nameAsStr == "" { + // allocate n.Data on the heap once, to avoid allocating it + // multiple times (for next labels). + nameAsStr = string(n.Data[:n.Length]) + } + compression[nameAsStr[i:]] = uint16(newPtr) + } + } + } + return append(msg, 0), nil +} + +// unpack unpacks a domain name. +func (n *Name) unpack(msg []byte, off int) (int, error) { + return n.unpackCompressed(msg, off, true /* allowCompression */) +} + +func (n *Name) unpackCompressed(msg []byte, off int, allowCompression bool) (int, error) { + // currOff is the current working offset. + currOff := off + + // newOff is the offset where the next record will start. Pointers lead + // to data that belongs to other names and thus doesn't count towards to + // the usage of this name. + newOff := off + + // ptr is the number of pointers followed. + var ptr int + + // Name is a slice representation of the name data. + name := n.Data[:0] + +Loop: + for { + if currOff >= len(msg) { + return off, errBaseLen + } + c := int(msg[currOff]) + currOff++ + switch c & 0xC0 { + case 0x00: // String segment + if c == 0x00 { + // A zero length signals the end of the name. + break Loop + } + endOff := currOff + c + if endOff > len(msg) { + return off, errCalcLen + } + + // Reject names containing dots. + // See issue golang/go#56246 + for _, v := range msg[currOff:endOff] { + if v == '.' { + return off, errInvalidName + } + } + + name = append(name, msg[currOff:endOff]...) + name = append(name, '.') + currOff = endOff + case 0xC0: // Pointer + if !allowCompression { + return off, errCompressedSRV + } + if currOff >= len(msg) { + return off, errInvalidPtr + } + c1 := msg[currOff] + currOff++ + if ptr == 0 { + newOff = currOff + } + // Don't follow too many pointers, maybe there's a loop. + if ptr++; ptr > 10 { + return off, errTooManyPtr + } + currOff = (c^0xC0)<<8 | int(c1) + default: + // Prefixes 0x80 and 0x40 are reserved. + return off, errReserved + } + } + if len(name) == 0 { + name = append(name, '.') + } + if len(name) > nonEncodedNameMax { + return off, errNameTooLong + } + n.Length = uint8(len(name)) + if ptr == 0 { + newOff = currOff + } + return newOff, nil +} + +func skipName(msg []byte, off int) (int, error) { + // newOff is the offset where the next record will start. Pointers lead + // to data that belongs to other names and thus doesn't count towards to + // the usage of this name. + newOff := off + +Loop: + for { + if newOff >= len(msg) { + return off, errBaseLen + } + c := int(msg[newOff]) + newOff++ + switch c & 0xC0 { + case 0x00: + if c == 0x00 { + // A zero length signals the end of the name. + break Loop + } + // literal string + newOff += c + if newOff > len(msg) { + return off, errCalcLen + } + case 0xC0: + // Pointer to somewhere else in msg. + + // Pointers are two bytes. + newOff++ + + // Don't follow the pointer as the data here has ended. + break Loop + default: + // Prefixes 0x80 and 0x40 are reserved. + return off, errReserved + } + } + + return newOff, nil +} + +// A Question is a DNS query. +type Question struct { + Name Name + Type Type + Class Class +} + +// pack appends the wire format of the Question to msg. +func (q *Question) pack(msg []byte, compression map[string]uint16, compressionOff int) ([]byte, error) { + msg, err := q.Name.pack(msg, compression, compressionOff) + if err != nil { + return msg, &nestedError{"Name", err} + } + msg = packType(msg, q.Type) + return packClass(msg, q.Class), nil +} + +// GoString implements fmt.GoStringer.GoString. +func (q *Question) GoString() string { + return "dnsmessage.Question{" + + "Name: " + q.Name.GoString() + ", " + + "Type: " + q.Type.GoString() + ", " + + "Class: " + q.Class.GoString() + "}" +} + +func unpackResourceBody(msg []byte, off int, hdr ResourceHeader) (ResourceBody, int, error) { + var ( + r ResourceBody + err error + name string + ) + switch hdr.Type { + case TypeA: + var rb AResource + rb, err = unpackAResource(msg, off) + r = &rb + name = "A" + case TypeNS: + var rb NSResource + rb, err = unpackNSResource(msg, off) + r = &rb + name = "NS" + case TypeCNAME: + var rb CNAMEResource + rb, err = unpackCNAMEResource(msg, off) + r = &rb + name = "CNAME" + case TypeSOA: + var rb SOAResource + rb, err = unpackSOAResource(msg, off) + r = &rb + name = "SOA" + case TypePTR: + var rb PTRResource + rb, err = unpackPTRResource(msg, off) + r = &rb + name = "PTR" + case TypeMX: + var rb MXResource + rb, err = unpackMXResource(msg, off) + r = &rb + name = "MX" + case TypeTXT: + var rb TXTResource + rb, err = unpackTXTResource(msg, off, hdr.Length) + r = &rb + name = "TXT" + case TypeAAAA: + var rb AAAAResource + rb, err = unpackAAAAResource(msg, off) + r = &rb + name = "AAAA" + case TypeSRV: + var rb SRVResource + rb, err = unpackSRVResource(msg, off) + r = &rb + name = "SRV" + case TypeOPT: + var rb OPTResource + rb, err = unpackOPTResource(msg, off, hdr.Length) + r = &rb + name = "OPT" + default: + var rb UnknownResource + rb, err = unpackUnknownResource(hdr.Type, msg, off, hdr.Length) + r = &rb + name = "Unknown" + } + if err != nil { + return nil, off, &nestedError{name + " record", err} + } + return r, off + int(hdr.Length), nil +} + +// A CNAMEResource is a CNAME Resource record. +type CNAMEResource struct { + CNAME Name +} + +func (r *CNAMEResource) realType() Type { + return TypeCNAME +} + +// pack appends the wire format of the CNAMEResource to msg. +func (r *CNAMEResource) pack(msg []byte, compression map[string]uint16, compressionOff int) ([]byte, error) { + return r.CNAME.pack(msg, compression, compressionOff) +} + +// GoString implements fmt.GoStringer.GoString. +func (r *CNAMEResource) GoString() string { + return "dnsmessage.CNAMEResource{CNAME: " + r.CNAME.GoString() + "}" +} + +func unpackCNAMEResource(msg []byte, off int) (CNAMEResource, error) { + var cname Name + if _, err := cname.unpack(msg, off); err != nil { + return CNAMEResource{}, err + } + return CNAMEResource{cname}, nil +} + +// An MXResource is an MX Resource record. +type MXResource struct { + Pref uint16 + MX Name +} + +func (r *MXResource) realType() Type { + return TypeMX +} + +// pack appends the wire format of the MXResource to msg. +func (r *MXResource) pack(msg []byte, compression map[string]uint16, compressionOff int) ([]byte, error) { + oldMsg := msg + msg = packUint16(msg, r.Pref) + msg, err := r.MX.pack(msg, compression, compressionOff) + if err != nil { + return oldMsg, &nestedError{"MXResource.MX", err} + } + return msg, nil +} + +// GoString implements fmt.GoStringer.GoString. +func (r *MXResource) GoString() string { + return "dnsmessage.MXResource{" + + "Pref: " + printUint16(r.Pref) + ", " + + "MX: " + r.MX.GoString() + "}" +} + +func unpackMXResource(msg []byte, off int) (MXResource, error) { + pref, off, err := unpackUint16(msg, off) + if err != nil { + return MXResource{}, &nestedError{"Pref", err} + } + var mx Name + if _, err := mx.unpack(msg, off); err != nil { + return MXResource{}, &nestedError{"MX", err} + } + return MXResource{pref, mx}, nil +} + +// An NSResource is an NS Resource record. +type NSResource struct { + NS Name +} + +func (r *NSResource) realType() Type { + return TypeNS +} + +// pack appends the wire format of the NSResource to msg. +func (r *NSResource) pack(msg []byte, compression map[string]uint16, compressionOff int) ([]byte, error) { + return r.NS.pack(msg, compression, compressionOff) +} + +// GoString implements fmt.GoStringer.GoString. +func (r *NSResource) GoString() string { + return "dnsmessage.NSResource{NS: " + r.NS.GoString() + "}" +} + +func unpackNSResource(msg []byte, off int) (NSResource, error) { + var ns Name + if _, err := ns.unpack(msg, off); err != nil { + return NSResource{}, err + } + return NSResource{ns}, nil +} + +// A PTRResource is a PTR Resource record. +type PTRResource struct { + PTR Name +} + +func (r *PTRResource) realType() Type { + return TypePTR +} + +// pack appends the wire format of the PTRResource to msg. +func (r *PTRResource) pack(msg []byte, compression map[string]uint16, compressionOff int) ([]byte, error) { + return r.PTR.pack(msg, compression, compressionOff) +} + +// GoString implements fmt.GoStringer.GoString. +func (r *PTRResource) GoString() string { + return "dnsmessage.PTRResource{PTR: " + r.PTR.GoString() + "}" +} + +func unpackPTRResource(msg []byte, off int) (PTRResource, error) { + var ptr Name + if _, err := ptr.unpack(msg, off); err != nil { + return PTRResource{}, err + } + return PTRResource{ptr}, nil +} + +// An SOAResource is an SOA Resource record. +type SOAResource struct { + NS Name + MBox Name + Serial uint32 + Refresh uint32 + Retry uint32 + Expire uint32 + + // MinTTL the is the default TTL of Resources records which did not + // contain a TTL value and the TTL of negative responses. (RFC 2308 + // Section 4) + MinTTL uint32 +} + +func (r *SOAResource) realType() Type { + return TypeSOA +} + +// pack appends the wire format of the SOAResource to msg. +func (r *SOAResource) pack(msg []byte, compression map[string]uint16, compressionOff int) ([]byte, error) { + oldMsg := msg + msg, err := r.NS.pack(msg, compression, compressionOff) + if err != nil { + return oldMsg, &nestedError{"SOAResource.NS", err} + } + msg, err = r.MBox.pack(msg, compression, compressionOff) + if err != nil { + return oldMsg, &nestedError{"SOAResource.MBox", err} + } + msg = packUint32(msg, r.Serial) + msg = packUint32(msg, r.Refresh) + msg = packUint32(msg, r.Retry) + msg = packUint32(msg, r.Expire) + return packUint32(msg, r.MinTTL), nil +} + +// GoString implements fmt.GoStringer.GoString. +func (r *SOAResource) GoString() string { + return "dnsmessage.SOAResource{" + + "NS: " + r.NS.GoString() + ", " + + "MBox: " + r.MBox.GoString() + ", " + + "Serial: " + printUint32(r.Serial) + ", " + + "Refresh: " + printUint32(r.Refresh) + ", " + + "Retry: " + printUint32(r.Retry) + ", " + + "Expire: " + printUint32(r.Expire) + ", " + + "MinTTL: " + printUint32(r.MinTTL) + "}" +} + +func unpackSOAResource(msg []byte, off int) (SOAResource, error) { + var ns Name + off, err := ns.unpack(msg, off) + if err != nil { + return SOAResource{}, &nestedError{"NS", err} + } + var mbox Name + if off, err = mbox.unpack(msg, off); err != nil { + return SOAResource{}, &nestedError{"MBox", err} + } + serial, off, err := unpackUint32(msg, off) + if err != nil { + return SOAResource{}, &nestedError{"Serial", err} + } + refresh, off, err := unpackUint32(msg, off) + if err != nil { + return SOAResource{}, &nestedError{"Refresh", err} + } + retry, off, err := unpackUint32(msg, off) + if err != nil { + return SOAResource{}, &nestedError{"Retry", err} + } + expire, off, err := unpackUint32(msg, off) + if err != nil { + return SOAResource{}, &nestedError{"Expire", err} + } + minTTL, _, err := unpackUint32(msg, off) + if err != nil { + return SOAResource{}, &nestedError{"MinTTL", err} + } + return SOAResource{ns, mbox, serial, refresh, retry, expire, minTTL}, nil +} + +// A TXTResource is a TXT Resource record. +type TXTResource struct { + TXT []string +} + +func (r *TXTResource) realType() Type { + return TypeTXT +} + +// pack appends the wire format of the TXTResource to msg. +func (r *TXTResource) pack(msg []byte, compression map[string]uint16, compressionOff int) ([]byte, error) { + oldMsg := msg + for _, s := range r.TXT { + var err error + msg, err = packText(msg, s) + if err != nil { + return oldMsg, err + } + } + return msg, nil +} + +// GoString implements fmt.GoStringer.GoString. +func (r *TXTResource) GoString() string { + s := "dnsmessage.TXTResource{TXT: []string{" + if len(r.TXT) == 0 { + return s + "}}" + } + s += `"` + printString([]byte(r.TXT[0])) + for _, t := range r.TXT[1:] { + s += `", "` + printString([]byte(t)) + } + return s + `"}}` +} + +func unpackTXTResource(msg []byte, off int, length uint16) (TXTResource, error) { + txts := make([]string, 0, 1) + for n := uint16(0); n < length; { + var t string + var err error + if t, off, err = unpackText(msg, off); err != nil { + return TXTResource{}, &nestedError{"text", err} + } + // Check if we got too many bytes. + if length-n < uint16(len(t))+1 { + return TXTResource{}, errCalcLen + } + n += uint16(len(t)) + 1 + txts = append(txts, t) + } + return TXTResource{txts}, nil +} + +// An SRVResource is an SRV Resource record. +type SRVResource struct { + Priority uint16 + Weight uint16 + Port uint16 + Target Name // Not compressed as per RFC 2782. +} + +func (r *SRVResource) realType() Type { + return TypeSRV +} + +// pack appends the wire format of the SRVResource to msg. +func (r *SRVResource) pack(msg []byte, compression map[string]uint16, compressionOff int) ([]byte, error) { + oldMsg := msg + msg = packUint16(msg, r.Priority) + msg = packUint16(msg, r.Weight) + msg = packUint16(msg, r.Port) + msg, err := r.Target.pack(msg, nil, compressionOff) + if err != nil { + return oldMsg, &nestedError{"SRVResource.Target", err} + } + return msg, nil +} + +// GoString implements fmt.GoStringer.GoString. +func (r *SRVResource) GoString() string { + return "dnsmessage.SRVResource{" + + "Priority: " + printUint16(r.Priority) + ", " + + "Weight: " + printUint16(r.Weight) + ", " + + "Port: " + printUint16(r.Port) + ", " + + "Target: " + r.Target.GoString() + "}" +} + +func unpackSRVResource(msg []byte, off int) (SRVResource, error) { + priority, off, err := unpackUint16(msg, off) + if err != nil { + return SRVResource{}, &nestedError{"Priority", err} + } + weight, off, err := unpackUint16(msg, off) + if err != nil { + return SRVResource{}, &nestedError{"Weight", err} + } + port, off, err := unpackUint16(msg, off) + if err != nil { + return SRVResource{}, &nestedError{"Port", err} + } + var target Name + if _, err := target.unpackCompressed(msg, off, false /* allowCompression */); err != nil { + return SRVResource{}, &nestedError{"Target", err} + } + return SRVResource{priority, weight, port, target}, nil +} + +// An AResource is an A Resource record. +type AResource struct { + A [4]byte +} + +func (r *AResource) realType() Type { + return TypeA +} + +// pack appends the wire format of the AResource to msg. +func (r *AResource) pack(msg []byte, compression map[string]uint16, compressionOff int) ([]byte, error) { + return packBytes(msg, r.A[:]), nil +} + +// GoString implements fmt.GoStringer.GoString. +func (r *AResource) GoString() string { + return "dnsmessage.AResource{" + + "A: [4]byte{" + printByteSlice(r.A[:]) + "}}" +} + +func unpackAResource(msg []byte, off int) (AResource, error) { + var a [4]byte + if _, err := unpackBytes(msg, off, a[:]); err != nil { + return AResource{}, err + } + return AResource{a}, nil +} + +// An AAAAResource is an AAAA Resource record. +type AAAAResource struct { + AAAA [16]byte +} + +func (r *AAAAResource) realType() Type { + return TypeAAAA +} + +// GoString implements fmt.GoStringer.GoString. +func (r *AAAAResource) GoString() string { + return "dnsmessage.AAAAResource{" + + "AAAA: [16]byte{" + printByteSlice(r.AAAA[:]) + "}}" +} + +// pack appends the wire format of the AAAAResource to msg. +func (r *AAAAResource) pack(msg []byte, compression map[string]uint16, compressionOff int) ([]byte, error) { + return packBytes(msg, r.AAAA[:]), nil +} + +func unpackAAAAResource(msg []byte, off int) (AAAAResource, error) { + var aaaa [16]byte + if _, err := unpackBytes(msg, off, aaaa[:]); err != nil { + return AAAAResource{}, err + } + return AAAAResource{aaaa}, nil +} + +// An OPTResource is an OPT pseudo Resource record. +// +// The pseudo resource record is part of the extension mechanisms for DNS +// as defined in RFC 6891. +type OPTResource struct { + Options []Option +} + +// An Option represents a DNS message option within OPTResource. +// +// The message option is part of the extension mechanisms for DNS as +// defined in RFC 6891. +type Option struct { + Code uint16 // option code + Data []byte +} + +// GoString implements fmt.GoStringer.GoString. +func (o *Option) GoString() string { + return "dnsmessage.Option{" + + "Code: " + printUint16(o.Code) + ", " + + "Data: []byte{" + printByteSlice(o.Data) + "}}" +} + +func (r *OPTResource) realType() Type { + return TypeOPT +} + +func (r *OPTResource) pack(msg []byte, compression map[string]uint16, compressionOff int) ([]byte, error) { + for _, opt := range r.Options { + msg = packUint16(msg, opt.Code) + l := uint16(len(opt.Data)) + msg = packUint16(msg, l) + msg = packBytes(msg, opt.Data) + } + return msg, nil +} + +// GoString implements fmt.GoStringer.GoString. +func (r *OPTResource) GoString() string { + s := "dnsmessage.OPTResource{Options: []dnsmessage.Option{" + if len(r.Options) == 0 { + return s + "}}" + } + s += r.Options[0].GoString() + for _, o := range r.Options[1:] { + s += ", " + o.GoString() + } + return s + "}}" +} + +func unpackOPTResource(msg []byte, off int, length uint16) (OPTResource, error) { + var opts []Option + for oldOff := off; off < oldOff+int(length); { + var err error + var o Option + o.Code, off, err = unpackUint16(msg, off) + if err != nil { + return OPTResource{}, &nestedError{"Code", err} + } + var l uint16 + l, off, err = unpackUint16(msg, off) + if err != nil { + return OPTResource{}, &nestedError{"Data", err} + } + o.Data = make([]byte, l) + if copy(o.Data, msg[off:]) != int(l) { + return OPTResource{}, &nestedError{"Data", errCalcLen} + } + off += int(l) + opts = append(opts, o) + } + return OPTResource{opts}, nil +} + +// An UnknownResource is a catch-all container for unknown record types. +type UnknownResource struct { + Type Type + Data []byte +} + +func (r *UnknownResource) realType() Type { + return r.Type +} + +// pack appends the wire format of the UnknownResource to msg. +func (r *UnknownResource) pack(msg []byte, compression map[string]uint16, compressionOff int) ([]byte, error) { + return packBytes(msg, r.Data[:]), nil +} + +// GoString implements fmt.GoStringer.GoString. +func (r *UnknownResource) GoString() string { + return "dnsmessage.UnknownResource{" + + "Type: " + r.Type.GoString() + ", " + + "Data: []byte{" + printByteSlice(r.Data) + "}}" +} + +func unpackUnknownResource(recordType Type, msg []byte, off int, length uint16) (UnknownResource, error) { + parsed := UnknownResource{ + Type: recordType, + Data: make([]byte, length), + } + if _, err := unpackBytes(msg, off, parsed.Data); err != nil { + return UnknownResource{}, err + } + return parsed, nil +} diff --git a/vendor/golang.org/x/sys/cpu/cpu_riscv64.go b/vendor/golang.org/x/sys/cpu/cpu_riscv64.go index bd6c128..ff7da60 100644 --- a/vendor/golang.org/x/sys/cpu/cpu_riscv64.go +++ b/vendor/golang.org/x/sys/cpu/cpu_riscv64.go @@ -7,6 +7,6 @@ package cpu -const cacheLineSize = 32 +const cacheLineSize = 64 func initOptions() {} diff --git a/vendor/golang.org/x/sys/cpu/hwcap_linux.go b/vendor/golang.org/x/sys/cpu/hwcap_linux.go index 1d9d91f..34e49f9 100644 --- a/vendor/golang.org/x/sys/cpu/hwcap_linux.go +++ b/vendor/golang.org/x/sys/cpu/hwcap_linux.go @@ -5,7 +5,7 @@ package cpu import ( - "io/ioutil" + "os" ) const ( @@ -39,7 +39,7 @@ func readHWCAP() error { return nil } - buf, err := ioutil.ReadFile(procAuxv) + buf, err := os.ReadFile(procAuxv) if err != nil { // e.g. on android /proc/self/auxv is not accessible, so silently // ignore the error and leave Initialized = false. On some diff --git a/vendor/golang.org/x/sys/internal/unsafeheader/unsafeheader.go b/vendor/golang.org/x/sys/internal/unsafeheader/unsafeheader.go deleted file mode 100644 index e07899b..0000000 --- a/vendor/golang.org/x/sys/internal/unsafeheader/unsafeheader.go +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright 2020 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. - -// Package unsafeheader contains header declarations for the Go runtime's -// slice and string implementations. -// -// This package allows x/sys to use types equivalent to -// reflect.SliceHeader and reflect.StringHeader without introducing -// a dependency on the (relatively heavy) "reflect" package. -package unsafeheader - -import ( - "unsafe" -) - -// Slice is the runtime representation of a slice. -// It cannot be used safely or portably and its representation may change in a later release. -type Slice struct { - Data unsafe.Pointer - Len int - Cap int -} - -// String is the runtime representation of a string. -// It cannot be used safely or portably and its representation may change in a later release. -type String struct { - Data unsafe.Pointer - Len int -} diff --git a/vendor/golang.org/x/sys/unix/ptrace_darwin.go b/vendor/golang.org/x/sys/unix/ptrace_darwin.go index 39dba6c..463c3ef 100644 --- a/vendor/golang.org/x/sys/unix/ptrace_darwin.go +++ b/vendor/golang.org/x/sys/unix/ptrace_darwin.go @@ -7,12 +7,6 @@ package unix -import "unsafe" - func ptrace(request int, pid int, addr uintptr, data uintptr) error { return ptrace1(request, pid, addr, data) } - -func ptracePtr(request int, pid int, addr uintptr, data unsafe.Pointer) error { - return ptrace1Ptr(request, pid, addr, data) -} diff --git a/vendor/golang.org/x/sys/unix/ptrace_ios.go b/vendor/golang.org/x/sys/unix/ptrace_ios.go index 9ea6633..ed0509a 100644 --- a/vendor/golang.org/x/sys/unix/ptrace_ios.go +++ b/vendor/golang.org/x/sys/unix/ptrace_ios.go @@ -7,12 +7,6 @@ package unix -import "unsafe" - func ptrace(request int, pid int, addr uintptr, data uintptr) (err error) { return ENOTSUP } - -func ptracePtr(request int, pid int, addr uintptr, data unsafe.Pointer) (err error) { - return ENOTSUP -} diff --git a/vendor/golang.org/x/sys/unix/syscall_aix.go b/vendor/golang.org/x/sys/unix/syscall_aix.go index 9a6e5ac..e94e6cd 100644 --- a/vendor/golang.org/x/sys/unix/syscall_aix.go +++ b/vendor/golang.org/x/sys/unix/syscall_aix.go @@ -487,8 +487,6 @@ func Fsync(fd int) error { //sys Unlinkat(dirfd int, path string, flags int) (err error) //sys Ustat(dev int, ubuf *Ustat_t) (err error) //sys write(fd int, p []byte) (n int, err error) -//sys readlen(fd int, p *byte, np int) (n int, err error) = read -//sys writelen(fd int, p *byte, np int) (n int, err error) = write //sys Dup2(oldfd int, newfd int) (err error) //sys Fadvise(fd int, offset int64, length int64, advice int) (err error) = posix_fadvise64 diff --git a/vendor/golang.org/x/sys/unix/syscall_darwin.go b/vendor/golang.org/x/sys/unix/syscall_darwin.go index 135cc3c..59542a8 100644 --- a/vendor/golang.org/x/sys/unix/syscall_darwin.go +++ b/vendor/golang.org/x/sys/unix/syscall_darwin.go @@ -644,189 +644,3 @@ func SysctlKinfoProcSlice(name string, args ...int) ([]KinfoProc, error) { //sys write(fd int, p []byte) (n int, err error) //sys mmap(addr uintptr, length uintptr, prot int, flag int, fd int, pos int64) (ret uintptr, err error) //sys munmap(addr uintptr, length uintptr) (err error) -//sys readlen(fd int, buf *byte, nbuf int) (n int, err error) = SYS_READ -//sys writelen(fd int, buf *byte, nbuf int) (n int, err error) = SYS_WRITE - -/* - * Unimplemented - */ -// Profil -// Sigaction -// Sigprocmask -// Getlogin -// Sigpending -// Sigaltstack -// Ioctl -// Reboot -// Execve -// Vfork -// Sbrk -// Sstk -// Ovadvise -// Mincore -// Setitimer -// Swapon -// Select -// Sigsuspend -// Readv -// Writev -// Nfssvc -// Getfh -// Quotactl -// Csops -// Waitid -// Add_profil -// Kdebug_trace -// Sigreturn -// Atsocket -// Kqueue_from_portset_np -// Kqueue_portset -// Getattrlist -// Getdirentriesattr -// Searchfs -// Delete -// Copyfile -// Watchevent -// Waitevent -// Modwatch -// Fsctl -// Initgroups -// Posix_spawn -// Nfsclnt -// Fhopen -// Minherit -// Semsys -// Msgsys -// Shmsys -// Semctl -// Semget -// Semop -// Msgctl -// Msgget -// Msgsnd -// Msgrcv -// Shm_open -// Shm_unlink -// Sem_open -// Sem_close -// Sem_unlink -// Sem_wait -// Sem_trywait -// Sem_post -// Sem_getvalue -// Sem_init -// Sem_destroy -// Open_extended -// Umask_extended -// Stat_extended -// Lstat_extended -// Fstat_extended -// Chmod_extended -// Fchmod_extended -// Access_extended -// Settid -// Gettid -// Setsgroups -// Getsgroups -// Setwgroups -// Getwgroups -// Mkfifo_extended -// Mkdir_extended -// Identitysvc -// Shared_region_check_np -// Shared_region_map_np -// __pthread_mutex_destroy -// __pthread_mutex_init -// __pthread_mutex_lock -// __pthread_mutex_trylock -// __pthread_mutex_unlock -// __pthread_cond_init -// __pthread_cond_destroy -// __pthread_cond_broadcast -// __pthread_cond_signal -// Setsid_with_pid -// __pthread_cond_timedwait -// Aio_fsync -// Aio_return -// Aio_suspend -// Aio_cancel -// Aio_error -// Aio_read -// Aio_write -// Lio_listio -// __pthread_cond_wait -// Iopolicysys -// __pthread_kill -// __pthread_sigmask -// __sigwait -// __disable_threadsignal -// __pthread_markcancel -// __pthread_canceled -// __semwait_signal -// Proc_info -// sendfile -// Stat64_extended -// Lstat64_extended -// Fstat64_extended -// __pthread_chdir -// __pthread_fchdir -// Audit -// Auditon -// Getauid -// Setauid -// Getaudit -// Setaudit -// Getaudit_addr -// Setaudit_addr -// Auditctl -// Bsdthread_create -// Bsdthread_terminate -// Stack_snapshot -// Bsdthread_register -// Workq_open -// Workq_ops -// __mac_execve -// __mac_syscall -// __mac_get_file -// __mac_set_file -// __mac_get_link -// __mac_set_link -// __mac_get_proc -// __mac_set_proc -// __mac_get_fd -// __mac_set_fd -// __mac_get_pid -// __mac_get_lcid -// __mac_get_lctx -// __mac_set_lctx -// Setlcid -// Read_nocancel -// Write_nocancel -// Open_nocancel -// Close_nocancel -// Wait4_nocancel -// Recvmsg_nocancel -// Sendmsg_nocancel -// Recvfrom_nocancel -// Accept_nocancel -// Fcntl_nocancel -// Select_nocancel -// Fsync_nocancel -// Connect_nocancel -// Sigsuspend_nocancel -// Readv_nocancel -// Writev_nocancel -// Sendto_nocancel -// Pread_nocancel -// Pwrite_nocancel -// Waitid_nocancel -// Poll_nocancel -// Msgsnd_nocancel -// Msgrcv_nocancel -// Sem_wait_nocancel -// Aio_suspend_nocancel -// __sigwait_nocancel -// __semwait_signal_nocancel -// __mac_mount -// __mac_get_mount -// __mac_getfsstat diff --git a/vendor/golang.org/x/sys/unix/syscall_darwin_amd64.go b/vendor/golang.org/x/sys/unix/syscall_darwin_amd64.go index 9fa8798..b37310c 100644 --- a/vendor/golang.org/x/sys/unix/syscall_darwin_amd64.go +++ b/vendor/golang.org/x/sys/unix/syscall_darwin_amd64.go @@ -47,6 +47,5 @@ func Syscall9(num, a1, a2, a3, a4, a5, a6, a7, a8, a9 uintptr) (r1, r2 uintptr, //sys getfsstat(buf unsafe.Pointer, size uintptr, flags int) (n int, err error) = SYS_GETFSSTAT64 //sys Lstat(path string, stat *Stat_t) (err error) = SYS_LSTAT64 //sys ptrace1(request int, pid int, addr uintptr, data uintptr) (err error) = SYS_ptrace -//sys ptrace1Ptr(request int, pid int, addr unsafe.Pointer, data uintptr) (err error) = SYS_ptrace //sys Stat(path string, stat *Stat_t) (err error) = SYS_STAT64 //sys Statfs(path string, stat *Statfs_t) (err error) = SYS_STATFS64 diff --git a/vendor/golang.org/x/sys/unix/syscall_darwin_arm64.go b/vendor/golang.org/x/sys/unix/syscall_darwin_arm64.go index f17b8c5..d51ec99 100644 --- a/vendor/golang.org/x/sys/unix/syscall_darwin_arm64.go +++ b/vendor/golang.org/x/sys/unix/syscall_darwin_arm64.go @@ -47,6 +47,5 @@ func Syscall9(num, a1, a2, a3, a4, a5, a6, a7, a8, a9 uintptr) (r1, r2 uintptr, //sys getfsstat(buf unsafe.Pointer, size uintptr, flags int) (n int, err error) = SYS_GETFSSTAT //sys Lstat(path string, stat *Stat_t) (err error) //sys ptrace1(request int, pid int, addr uintptr, data uintptr) (err error) = SYS_ptrace -//sys ptrace1Ptr(request int, pid int, addr unsafe.Pointer, data uintptr) (err error) = SYS_ptrace //sys Stat(path string, stat *Stat_t) (err error) //sys Statfs(path string, stat *Statfs_t) (err error) diff --git a/vendor/golang.org/x/sys/unix/syscall_dragonfly.go b/vendor/golang.org/x/sys/unix/syscall_dragonfly.go index d4ce988..97cb916 100644 --- a/vendor/golang.org/x/sys/unix/syscall_dragonfly.go +++ b/vendor/golang.org/x/sys/unix/syscall_dragonfly.go @@ -343,203 +343,5 @@ func Sendfile(outfd int, infd int, offset *int64, count int) (written int, err e //sys write(fd int, p []byte) (n int, err error) //sys mmap(addr uintptr, length uintptr, prot int, flag int, fd int, pos int64) (ret uintptr, err error) //sys munmap(addr uintptr, length uintptr) (err error) -//sys readlen(fd int, buf *byte, nbuf int) (n int, err error) = SYS_READ -//sys writelen(fd int, buf *byte, nbuf int) (n int, err error) = SYS_WRITE //sys accept4(fd int, rsa *RawSockaddrAny, addrlen *_Socklen, flags int) (nfd int, err error) //sys utimensat(dirfd int, path string, times *[2]Timespec, flags int) (err error) - -/* - * Unimplemented - * TODO(jsing): Update this list for DragonFly. - */ -// Profil -// Sigaction -// Sigprocmask -// Getlogin -// Sigpending -// Sigaltstack -// Reboot -// Execve -// Vfork -// Sbrk -// Sstk -// Ovadvise -// Mincore -// Setitimer -// Swapon -// Select -// Sigsuspend -// Readv -// Writev -// Nfssvc -// Getfh -// Quotactl -// Mount -// Csops -// Waitid -// Add_profil -// Kdebug_trace -// Sigreturn -// Atsocket -// Kqueue_from_portset_np -// Kqueue_portset -// Getattrlist -// Setattrlist -// Getdirentriesattr -// Searchfs -// Delete -// Copyfile -// Watchevent -// Waitevent -// Modwatch -// Getxattr -// Fgetxattr -// Setxattr -// Fsetxattr -// Removexattr -// Fremovexattr -// Listxattr -// Flistxattr -// Fsctl -// Initgroups -// Posix_spawn -// Nfsclnt -// Fhopen -// Minherit -// Semsys -// Msgsys -// Shmsys -// Semctl -// Semget -// Semop -// Msgctl -// Msgget -// Msgsnd -// Msgrcv -// Shmat -// Shmctl -// Shmdt -// Shmget -// Shm_open -// Shm_unlink -// Sem_open -// Sem_close -// Sem_unlink -// Sem_wait -// Sem_trywait -// Sem_post -// Sem_getvalue -// Sem_init -// Sem_destroy -// Open_extended -// Umask_extended -// Stat_extended -// Lstat_extended -// Fstat_extended -// Chmod_extended -// Fchmod_extended -// Access_extended -// Settid -// Gettid -// Setsgroups -// Getsgroups -// Setwgroups -// Getwgroups -// Mkfifo_extended -// Mkdir_extended -// Identitysvc -// Shared_region_check_np -// Shared_region_map_np -// __pthread_mutex_destroy -// __pthread_mutex_init -// __pthread_mutex_lock -// __pthread_mutex_trylock -// __pthread_mutex_unlock -// __pthread_cond_init -// __pthread_cond_destroy -// __pthread_cond_broadcast -// __pthread_cond_signal -// Setsid_with_pid -// __pthread_cond_timedwait -// Aio_fsync -// Aio_return -// Aio_suspend -// Aio_cancel -// Aio_error -// Aio_read -// Aio_write -// Lio_listio -// __pthread_cond_wait -// Iopolicysys -// __pthread_kill -// __pthread_sigmask -// __sigwait -// __disable_threadsignal -// __pthread_markcancel -// __pthread_canceled -// __semwait_signal -// Proc_info -// Stat64_extended -// Lstat64_extended -// Fstat64_extended -// __pthread_chdir -// __pthread_fchdir -// Audit -// Auditon -// Getauid -// Setauid -// Getaudit -// Setaudit -// Getaudit_addr -// Setaudit_addr -// Auditctl -// Bsdthread_create -// Bsdthread_terminate -// Stack_snapshot -// Bsdthread_register -// Workq_open -// Workq_ops -// __mac_execve -// __mac_syscall -// __mac_get_file -// __mac_set_file -// __mac_get_link -// __mac_set_link -// __mac_get_proc -// __mac_set_proc -// __mac_get_fd -// __mac_set_fd -// __mac_get_pid -// __mac_get_lcid -// __mac_get_lctx -// __mac_set_lctx -// Setlcid -// Read_nocancel -// Write_nocancel -// Open_nocancel -// Close_nocancel -// Wait4_nocancel -// Recvmsg_nocancel -// Sendmsg_nocancel -// Recvfrom_nocancel -// Accept_nocancel -// Fcntl_nocancel -// Select_nocancel -// Fsync_nocancel -// Connect_nocancel -// Sigsuspend_nocancel -// Readv_nocancel -// Writev_nocancel -// Sendto_nocancel -// Pread_nocancel -// Pwrite_nocancel -// Waitid_nocancel -// Msgsnd_nocancel -// Msgrcv_nocancel -// Sem_wait_nocancel -// Aio_suspend_nocancel -// __sigwait_nocancel -// __semwait_signal_nocancel -// __mac_mount -// __mac_get_mount -// __mac_getfsstat diff --git a/vendor/golang.org/x/sys/unix/syscall_freebsd.go b/vendor/golang.org/x/sys/unix/syscall_freebsd.go index afb1010..64d1bb4 100644 --- a/vendor/golang.org/x/sys/unix/syscall_freebsd.go +++ b/vendor/golang.org/x/sys/unix/syscall_freebsd.go @@ -449,197 +449,5 @@ func Dup3(oldfd, newfd, flags int) error { //sys write(fd int, p []byte) (n int, err error) //sys mmap(addr uintptr, length uintptr, prot int, flag int, fd int, pos int64) (ret uintptr, err error) //sys munmap(addr uintptr, length uintptr) (err error) -//sys readlen(fd int, buf *byte, nbuf int) (n int, err error) = SYS_READ -//sys writelen(fd int, buf *byte, nbuf int) (n int, err error) = SYS_WRITE //sys accept4(fd int, rsa *RawSockaddrAny, addrlen *_Socklen, flags int) (nfd int, err error) //sys utimensat(dirfd int, path string, times *[2]Timespec, flags int) (err error) - -/* - * Unimplemented - */ -// Profil -// Sigaction -// Sigprocmask -// Getlogin -// Sigpending -// Sigaltstack -// Ioctl -// Reboot -// Execve -// Vfork -// Sbrk -// Sstk -// Ovadvise -// Mincore -// Setitimer -// Swapon -// Select -// Sigsuspend -// Readv -// Writev -// Nfssvc -// Getfh -// Quotactl -// Mount -// Csops -// Waitid -// Add_profil -// Kdebug_trace -// Sigreturn -// Atsocket -// Kqueue_from_portset_np -// Kqueue_portset -// Getattrlist -// Setattrlist -// Getdents -// Getdirentriesattr -// Searchfs -// Delete -// Copyfile -// Watchevent -// Waitevent -// Modwatch -// Fsctl -// Initgroups -// Posix_spawn -// Nfsclnt -// Fhopen -// Minherit -// Semsys -// Msgsys -// Shmsys -// Semctl -// Semget -// Semop -// Msgctl -// Msgget -// Msgsnd -// Msgrcv -// Shmat -// Shmctl -// Shmdt -// Shmget -// Shm_open -// Shm_unlink -// Sem_open -// Sem_close -// Sem_unlink -// Sem_wait -// Sem_trywait -// Sem_post -// Sem_getvalue -// Sem_init -// Sem_destroy -// Open_extended -// Umask_extended -// Stat_extended -// Lstat_extended -// Fstat_extended -// Chmod_extended -// Fchmod_extended -// Access_extended -// Settid -// Gettid -// Setsgroups -// Getsgroups -// Setwgroups -// Getwgroups -// Mkfifo_extended -// Mkdir_extended -// Identitysvc -// Shared_region_check_np -// Shared_region_map_np -// __pthread_mutex_destroy -// __pthread_mutex_init -// __pthread_mutex_lock -// __pthread_mutex_trylock -// __pthread_mutex_unlock -// __pthread_cond_init -// __pthread_cond_destroy -// __pthread_cond_broadcast -// __pthread_cond_signal -// Setsid_with_pid -// __pthread_cond_timedwait -// Aio_fsync -// Aio_return -// Aio_suspend -// Aio_cancel -// Aio_error -// Aio_read -// Aio_write -// Lio_listio -// __pthread_cond_wait -// Iopolicysys -// __pthread_kill -// __pthread_sigmask -// __sigwait -// __disable_threadsignal -// __pthread_markcancel -// __pthread_canceled -// __semwait_signal -// Proc_info -// Stat64_extended -// Lstat64_extended -// Fstat64_extended -// __pthread_chdir -// __pthread_fchdir -// Audit -// Auditon -// Getauid -// Setauid -// Getaudit -// Setaudit -// Getaudit_addr -// Setaudit_addr -// Auditctl -// Bsdthread_create -// Bsdthread_terminate -// Stack_snapshot -// Bsdthread_register -// Workq_open -// Workq_ops -// __mac_execve -// __mac_syscall -// __mac_get_file -// __mac_set_file -// __mac_get_link -// __mac_set_link -// __mac_get_proc -// __mac_set_proc -// __mac_get_fd -// __mac_set_fd -// __mac_get_pid -// __mac_get_lcid -// __mac_get_lctx -// __mac_set_lctx -// Setlcid -// Read_nocancel -// Write_nocancel -// Open_nocancel -// Close_nocancel -// Wait4_nocancel -// Recvmsg_nocancel -// Sendmsg_nocancel -// Recvfrom_nocancel -// Accept_nocancel -// Fcntl_nocancel -// Select_nocancel -// Fsync_nocancel -// Connect_nocancel -// Sigsuspend_nocancel -// Readv_nocancel -// Writev_nocancel -// Sendto_nocancel -// Pread_nocancel -// Pwrite_nocancel -// Waitid_nocancel -// Poll_nocancel -// Msgsnd_nocancel -// Msgrcv_nocancel -// Sem_wait_nocancel -// Aio_suspend_nocancel -// __sigwait_nocancel -// __semwait_signal_nocancel -// __mac_mount -// __mac_get_mount -// __mac_getfsstat diff --git a/vendor/golang.org/x/sys/unix/syscall_linux.go b/vendor/golang.org/x/sys/unix/syscall_linux.go index 0ba0301..fb4e502 100644 --- a/vendor/golang.org/x/sys/unix/syscall_linux.go +++ b/vendor/golang.org/x/sys/unix/syscall_linux.go @@ -693,10 +693,10 @@ type SockaddrALG struct { func (sa *SockaddrALG) sockaddr() (unsafe.Pointer, _Socklen, error) { // Leave room for NUL byte terminator. - if len(sa.Type) > 13 { + if len(sa.Type) > len(sa.raw.Type)-1 { return nil, 0, EINVAL } - if len(sa.Name) > 63 { + if len(sa.Name) > len(sa.raw.Name)-1 { return nil, 0, EINVAL } @@ -704,17 +704,8 @@ func (sa *SockaddrALG) sockaddr() (unsafe.Pointer, _Socklen, error) { sa.raw.Feat = sa.Feature sa.raw.Mask = sa.Mask - typ, err := ByteSliceFromString(sa.Type) - if err != nil { - return nil, 0, err - } - name, err := ByteSliceFromString(sa.Name) - if err != nil { - return nil, 0, err - } - - copy(sa.raw.Type[:], typ) - copy(sa.raw.Name[:], name) + copy(sa.raw.Type[:], sa.Type) + copy(sa.raw.Name[:], sa.Name) return unsafe.Pointer(&sa.raw), SizeofSockaddrALG, nil } @@ -1988,8 +1979,6 @@ func Signalfd(fd int, sigmask *Sigset_t, flags int) (newfd int, err error) { //sys Unshare(flags int) (err error) //sys write(fd int, p []byte) (n int, err error) //sys exitThread(code int) (err error) = SYS_EXIT -//sys readlen(fd int, p *byte, np int) (n int, err error) = SYS_READ -//sys writelen(fd int, p *byte, np int) (n int, err error) = SYS_WRITE //sys readv(fd int, iovs []Iovec) (n int, err error) = SYS_READV //sys writev(fd int, iovs []Iovec) (n int, err error) = SYS_WRITEV //sys preadv(fd int, iovs []Iovec, offs_l uintptr, offs_h uintptr) (n int, err error) = SYS_PREADV @@ -2493,99 +2482,3 @@ func SchedGetAttr(pid int, flags uint) (*SchedAttr, error) { } return attr, nil } - -/* - * Unimplemented - */ -// AfsSyscall -// ArchPrctl -// Brk -// ClockNanosleep -// ClockSettime -// Clone -// EpollCtlOld -// EpollPwait -// EpollWaitOld -// Execve -// Fork -// Futex -// GetKernelSyms -// GetMempolicy -// GetRobustList -// GetThreadArea -// Getpmsg -// IoCancel -// IoDestroy -// IoGetevents -// IoSetup -// IoSubmit -// IoprioGet -// IoprioSet -// KexecLoad -// LookupDcookie -// Mbind -// MigratePages -// Mincore -// ModifyLdt -// Mount -// MovePages -// MqGetsetattr -// MqNotify -// MqOpen -// MqTimedreceive -// MqTimedsend -// MqUnlink -// Msgctl -// Msgget -// Msgrcv -// Msgsnd -// Nfsservctl -// Personality -// Pselect6 -// Ptrace -// Putpmsg -// Quotactl -// Readahead -// Readv -// RemapFilePages -// RestartSyscall -// RtSigaction -// RtSigpending -// RtSigqueueinfo -// RtSigreturn -// RtSigsuspend -// RtSigtimedwait -// SchedGetPriorityMax -// SchedGetPriorityMin -// SchedGetparam -// SchedGetscheduler -// SchedRrGetInterval -// SchedSetparam -// SchedYield -// Security -// Semctl -// Semget -// Semop -// Semtimedop -// SetMempolicy -// SetRobustList -// SetThreadArea -// SetTidAddress -// Sigaltstack -// Swapoff -// Swapon -// Sysfs -// TimerCreate -// TimerDelete -// TimerGetoverrun -// TimerGettime -// TimerSettime -// Tkill (obsolete) -// Tuxcall -// Umount2 -// Uselib -// Utimensat -// Vfork -// Vhangup -// Vserver -// _Sysctl diff --git a/vendor/golang.org/x/sys/unix/syscall_netbsd.go b/vendor/golang.org/x/sys/unix/syscall_netbsd.go index ddd1ac8..8816209 100644 --- a/vendor/golang.org/x/sys/unix/syscall_netbsd.go +++ b/vendor/golang.org/x/sys/unix/syscall_netbsd.go @@ -356,8 +356,6 @@ func Statvfs(path string, buf *Statvfs_t) (err error) { //sys write(fd int, p []byte) (n int, err error) //sys mmap(addr uintptr, length uintptr, prot int, flag int, fd int, pos int64) (ret uintptr, err error) //sys munmap(addr uintptr, length uintptr) (err error) -//sys readlen(fd int, buf *byte, nbuf int) (n int, err error) = SYS_READ -//sys writelen(fd int, buf *byte, nbuf int) (n int, err error) = SYS_WRITE //sys utimensat(dirfd int, path string, times *[2]Timespec, flags int) (err error) const ( @@ -371,262 +369,3 @@ const ( func mremap(oldaddr uintptr, oldlength uintptr, newlength uintptr, flags int, newaddr uintptr) (uintptr, error) { return mremapNetBSD(oldaddr, oldlength, newaddr, newlength, flags) } - -/* - * Unimplemented - */ -// ____semctl13 -// __clone -// __fhopen40 -// __fhstat40 -// __fhstatvfs140 -// __fstat30 -// __getcwd -// __getfh30 -// __getlogin -// __lstat30 -// __mount50 -// __msgctl13 -// __msync13 -// __ntp_gettime30 -// __posix_chown -// __posix_fchown -// __posix_lchown -// __posix_rename -// __setlogin -// __shmctl13 -// __sigaction_sigtramp -// __sigaltstack14 -// __sigpending14 -// __sigprocmask14 -// __sigsuspend14 -// __sigtimedwait -// __stat30 -// __syscall -// __vfork14 -// _ksem_close -// _ksem_destroy -// _ksem_getvalue -// _ksem_init -// _ksem_open -// _ksem_post -// _ksem_trywait -// _ksem_unlink -// _ksem_wait -// _lwp_continue -// _lwp_create -// _lwp_ctl -// _lwp_detach -// _lwp_exit -// _lwp_getname -// _lwp_getprivate -// _lwp_kill -// _lwp_park -// _lwp_self -// _lwp_setname -// _lwp_setprivate -// _lwp_suspend -// _lwp_unpark -// _lwp_unpark_all -// _lwp_wait -// _lwp_wakeup -// _pset_bind -// _sched_getaffinity -// _sched_getparam -// _sched_setaffinity -// _sched_setparam -// acct -// aio_cancel -// aio_error -// aio_fsync -// aio_read -// aio_return -// aio_suspend -// aio_write -// break -// clock_getres -// clock_gettime -// clock_settime -// compat_09_ogetdomainname -// compat_09_osetdomainname -// compat_09_ouname -// compat_10_omsgsys -// compat_10_osemsys -// compat_10_oshmsys -// compat_12_fstat12 -// compat_12_getdirentries -// compat_12_lstat12 -// compat_12_msync -// compat_12_oreboot -// compat_12_oswapon -// compat_12_stat12 -// compat_13_sigaction13 -// compat_13_sigaltstack13 -// compat_13_sigpending13 -// compat_13_sigprocmask13 -// compat_13_sigreturn13 -// compat_13_sigsuspend13 -// compat_14___semctl -// compat_14_msgctl -// compat_14_shmctl -// compat_16___sigaction14 -// compat_16___sigreturn14 -// compat_20_fhstatfs -// compat_20_fstatfs -// compat_20_getfsstat -// compat_20_statfs -// compat_30___fhstat30 -// compat_30___fstat13 -// compat_30___lstat13 -// compat_30___stat13 -// compat_30_fhopen -// compat_30_fhstat -// compat_30_fhstatvfs1 -// compat_30_getdents -// compat_30_getfh -// compat_30_ntp_gettime -// compat_30_socket -// compat_40_mount -// compat_43_fstat43 -// compat_43_lstat43 -// compat_43_oaccept -// compat_43_ocreat -// compat_43_oftruncate -// compat_43_ogetdirentries -// compat_43_ogetdtablesize -// compat_43_ogethostid -// compat_43_ogethostname -// compat_43_ogetkerninfo -// compat_43_ogetpagesize -// compat_43_ogetpeername -// compat_43_ogetrlimit -// compat_43_ogetsockname -// compat_43_okillpg -// compat_43_olseek -// compat_43_ommap -// compat_43_oquota -// compat_43_orecv -// compat_43_orecvfrom -// compat_43_orecvmsg -// compat_43_osend -// compat_43_osendmsg -// compat_43_osethostid -// compat_43_osethostname -// compat_43_osigblock -// compat_43_osigsetmask -// compat_43_osigstack -// compat_43_osigvec -// compat_43_otruncate -// compat_43_owait -// compat_43_stat43 -// execve -// extattr_delete_fd -// extattr_delete_file -// extattr_delete_link -// extattr_get_fd -// extattr_get_file -// extattr_get_link -// extattr_list_fd -// extattr_list_file -// extattr_list_link -// extattr_set_fd -// extattr_set_file -// extattr_set_link -// extattrctl -// fchroot -// fdatasync -// fgetxattr -// fktrace -// flistxattr -// fork -// fremovexattr -// fsetxattr -// fstatvfs1 -// fsync_range -// getcontext -// getitimer -// getvfsstat -// getxattr -// ktrace -// lchflags -// lchmod -// lfs_bmapv -// lfs_markv -// lfs_segclean -// lfs_segwait -// lgetxattr -// lio_listio -// listxattr -// llistxattr -// lremovexattr -// lseek -// lsetxattr -// lutimes -// madvise -// mincore -// minherit -// modctl -// mq_close -// mq_getattr -// mq_notify -// mq_open -// mq_receive -// mq_send -// mq_setattr -// mq_timedreceive -// mq_timedsend -// mq_unlink -// msgget -// msgrcv -// msgsnd -// nfssvc -// ntp_adjtime -// pmc_control -// pmc_get_info -// pollts -// preadv -// profil -// pselect -// pset_assign -// pset_create -// pset_destroy -// ptrace -// pwritev -// quotactl -// rasctl -// readv -// reboot -// removexattr -// sa_enable -// sa_preempt -// sa_register -// sa_setconcurrency -// sa_stacks -// sa_yield -// sbrk -// sched_yield -// semconfig -// semget -// semop -// setcontext -// setitimer -// setxattr -// shmat -// shmdt -// shmget -// sstk -// statvfs1 -// swapctl -// sysarch -// syscall -// timer_create -// timer_delete -// timer_getoverrun -// timer_gettime -// timer_settime -// undelete -// utrace -// uuidgen -// vadvise -// vfork -// writev diff --git a/vendor/golang.org/x/sys/unix/syscall_openbsd.go b/vendor/golang.org/x/sys/unix/syscall_openbsd.go index c5f166a..6f34479 100644 --- a/vendor/golang.org/x/sys/unix/syscall_openbsd.go +++ b/vendor/golang.org/x/sys/unix/syscall_openbsd.go @@ -326,78 +326,4 @@ func Uname(uname *Utsname) error { //sys write(fd int, p []byte) (n int, err error) //sys mmap(addr uintptr, length uintptr, prot int, flag int, fd int, pos int64) (ret uintptr, err error) //sys munmap(addr uintptr, length uintptr) (err error) -//sys readlen(fd int, buf *byte, nbuf int) (n int, err error) = SYS_READ -//sys writelen(fd int, buf *byte, nbuf int) (n int, err error) = SYS_WRITE //sys utimensat(dirfd int, path string, times *[2]Timespec, flags int) (err error) - -/* - * Unimplemented - */ -// __getcwd -// __semctl -// __syscall -// __sysctl -// adjfreq -// break -// clock_getres -// clock_gettime -// clock_settime -// closefrom -// execve -// fhopen -// fhstat -// fhstatfs -// fork -// futimens -// getfh -// getgid -// getitimer -// getlogin -// getthrid -// ktrace -// lfs_bmapv -// lfs_markv -// lfs_segclean -// lfs_segwait -// mincore -// minherit -// mount -// mquery -// msgctl -// msgget -// msgrcv -// msgsnd -// nfssvc -// nnpfspioctl -// preadv -// profil -// pwritev -// quotactl -// readv -// reboot -// renameat -// rfork -// sched_yield -// semget -// semop -// setgroups -// setitimer -// setsockopt -// shmat -// shmctl -// shmdt -// shmget -// sigaction -// sigaltstack -// sigpending -// sigprocmask -// sigreturn -// sigsuspend -// sysarch -// syscall -// threxit -// thrsigdivert -// thrsleep -// thrwakeup -// vfork -// writev diff --git a/vendor/golang.org/x/sys/unix/syscall_solaris.go b/vendor/golang.org/x/sys/unix/syscall_solaris.go index 72d2357..b99cfa1 100644 --- a/vendor/golang.org/x/sys/unix/syscall_solaris.go +++ b/vendor/golang.org/x/sys/unix/syscall_solaris.go @@ -698,24 +698,6 @@ func Sendfile(outfd int, infd int, offset *int64, count int) (written int, err e //sys setsockopt(s int, level int, name int, val unsafe.Pointer, vallen uintptr) (err error) = libsocket.setsockopt //sys recvfrom(fd int, p []byte, flags int, from *RawSockaddrAny, fromlen *_Socklen) (n int, err error) = libsocket.recvfrom -func readlen(fd int, buf *byte, nbuf int) (n int, err error) { - r0, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procread)), 3, uintptr(fd), uintptr(unsafe.Pointer(buf)), uintptr(nbuf), 0, 0, 0) - n = int(r0) - if e1 != 0 { - err = e1 - } - return -} - -func writelen(fd int, buf *byte, nbuf int) (n int, err error) { - r0, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procwrite)), 3, uintptr(fd), uintptr(unsafe.Pointer(buf)), uintptr(nbuf), 0, 0, 0) - n = int(r0) - if e1 != 0 { - err = e1 - } - return -} - // Event Ports type fileObjCookie struct { diff --git a/vendor/golang.org/x/sys/unix/syscall_zos_s390x.go b/vendor/golang.org/x/sys/unix/syscall_zos_s390x.go index 44e72ed..4596d04 100644 --- a/vendor/golang.org/x/sys/unix/syscall_zos_s390x.go +++ b/vendor/golang.org/x/sys/unix/syscall_zos_s390x.go @@ -192,7 +192,6 @@ func (cmsg *Cmsghdr) SetLen(length int) { //sys fcntl(fd int, cmd int, arg int) (val int, err error) //sys read(fd int, p []byte) (n int, err error) -//sys readlen(fd int, buf *byte, nbuf int) (n int, err error) = SYS_READ //sys write(fd int, p []byte) (n int, err error) //sys accept(s int, rsa *RawSockaddrAny, addrlen *_Socklen) (fd int, err error) = SYS___ACCEPT_A diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux.go b/vendor/golang.org/x/sys/unix/zerrors_linux.go index 0787a04..f9c7f47 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux.go @@ -2421,6 +2421,15 @@ const ( PR_PAC_GET_ENABLED_KEYS = 0x3d PR_PAC_RESET_KEYS = 0x36 PR_PAC_SET_ENABLED_KEYS = 0x3c + PR_RISCV_V_GET_CONTROL = 0x46 + PR_RISCV_V_SET_CONTROL = 0x45 + PR_RISCV_V_VSTATE_CTRL_CUR_MASK = 0x3 + PR_RISCV_V_VSTATE_CTRL_DEFAULT = 0x0 + PR_RISCV_V_VSTATE_CTRL_INHERIT = 0x10 + PR_RISCV_V_VSTATE_CTRL_MASK = 0x1f + PR_RISCV_V_VSTATE_CTRL_NEXT_MASK = 0xc + PR_RISCV_V_VSTATE_CTRL_OFF = 0x1 + PR_RISCV_V_VSTATE_CTRL_ON = 0x2 PR_SCHED_CORE = 0x3e PR_SCHED_CORE_CREATE = 0x1 PR_SCHED_CORE_GET = 0x0 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_386.go b/vendor/golang.org/x/sys/unix/zerrors_linux_386.go index cfb1430..30aee00 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_386.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_386.go @@ -326,10 +326,12 @@ const ( SO_NOFCS = 0x2b SO_OOBINLINE = 0xa SO_PASSCRED = 0x10 + SO_PASSPIDFD = 0x4c SO_PASSSEC = 0x22 SO_PEEK_OFF = 0x2a SO_PEERCRED = 0x11 SO_PEERGROUPS = 0x3b + SO_PEERPIDFD = 0x4d SO_PEERSEC = 0x1f SO_PREFER_BUSY_POLL = 0x45 SO_PROTOCOL = 0x26 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_amd64.go b/vendor/golang.org/x/sys/unix/zerrors_linux_amd64.go index df64f2d..8ebfa51 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_amd64.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_amd64.go @@ -327,10 +327,12 @@ const ( SO_NOFCS = 0x2b SO_OOBINLINE = 0xa SO_PASSCRED = 0x10 + SO_PASSPIDFD = 0x4c SO_PASSSEC = 0x22 SO_PEEK_OFF = 0x2a SO_PEERCRED = 0x11 SO_PEERGROUPS = 0x3b + SO_PEERPIDFD = 0x4d SO_PEERSEC = 0x1f SO_PREFER_BUSY_POLL = 0x45 SO_PROTOCOL = 0x26 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_arm.go b/vendor/golang.org/x/sys/unix/zerrors_linux_arm.go index 3025cd5..271a21c 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_arm.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_arm.go @@ -333,10 +333,12 @@ const ( SO_NOFCS = 0x2b SO_OOBINLINE = 0xa SO_PASSCRED = 0x10 + SO_PASSPIDFD = 0x4c SO_PASSSEC = 0x22 SO_PEEK_OFF = 0x2a SO_PEERCRED = 0x11 SO_PEERGROUPS = 0x3b + SO_PEERPIDFD = 0x4d SO_PEERSEC = 0x1f SO_PREFER_BUSY_POLL = 0x45 SO_PROTOCOL = 0x26 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_arm64.go b/vendor/golang.org/x/sys/unix/zerrors_linux_arm64.go index 09e1ffb..910c330 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_arm64.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_arm64.go @@ -323,10 +323,12 @@ const ( SO_NOFCS = 0x2b SO_OOBINLINE = 0xa SO_PASSCRED = 0x10 + SO_PASSPIDFD = 0x4c SO_PASSSEC = 0x22 SO_PEEK_OFF = 0x2a SO_PEERCRED = 0x11 SO_PEERGROUPS = 0x3b + SO_PEERPIDFD = 0x4d SO_PEERSEC = 0x1f SO_PREFER_BUSY_POLL = 0x45 SO_PROTOCOL = 0x26 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_loong64.go b/vendor/golang.org/x/sys/unix/zerrors_linux_loong64.go index a457235..a640798 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_loong64.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_loong64.go @@ -118,6 +118,8 @@ const ( IUCLC = 0x200 IXOFF = 0x1000 IXON = 0x400 + LASX_CTX_MAGIC = 0x41535801 + LSX_CTX_MAGIC = 0x53580001 MAP_ANON = 0x20 MAP_ANONYMOUS = 0x20 MAP_DENYWRITE = 0x800 @@ -317,10 +319,12 @@ const ( SO_NOFCS = 0x2b SO_OOBINLINE = 0xa SO_PASSCRED = 0x10 + SO_PASSPIDFD = 0x4c SO_PASSSEC = 0x22 SO_PEEK_OFF = 0x2a SO_PEERCRED = 0x11 SO_PEERGROUPS = 0x3b + SO_PEERPIDFD = 0x4d SO_PEERSEC = 0x1f SO_PREFER_BUSY_POLL = 0x45 SO_PROTOCOL = 0x26 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_mips.go b/vendor/golang.org/x/sys/unix/zerrors_linux_mips.go index fee7dfb..0d5925d 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_mips.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_mips.go @@ -326,10 +326,12 @@ const ( SO_NOFCS = 0x2b SO_OOBINLINE = 0x100 SO_PASSCRED = 0x11 + SO_PASSPIDFD = 0x4c SO_PASSSEC = 0x22 SO_PEEK_OFF = 0x2a SO_PEERCRED = 0x12 SO_PEERGROUPS = 0x3b + SO_PEERPIDFD = 0x4d SO_PEERSEC = 0x1e SO_PREFER_BUSY_POLL = 0x45 SO_PROTOCOL = 0x1028 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_mips64.go b/vendor/golang.org/x/sys/unix/zerrors_linux_mips64.go index a5b2373..d72a00e 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_mips64.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_mips64.go @@ -326,10 +326,12 @@ const ( SO_NOFCS = 0x2b SO_OOBINLINE = 0x100 SO_PASSCRED = 0x11 + SO_PASSPIDFD = 0x4c SO_PASSSEC = 0x22 SO_PEEK_OFF = 0x2a SO_PEERCRED = 0x12 SO_PEERGROUPS = 0x3b + SO_PEERPIDFD = 0x4d SO_PEERSEC = 0x1e SO_PREFER_BUSY_POLL = 0x45 SO_PROTOCOL = 0x1028 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_mips64le.go b/vendor/golang.org/x/sys/unix/zerrors_linux_mips64le.go index 5dde82c..02ba129 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_mips64le.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_mips64le.go @@ -326,10 +326,12 @@ const ( SO_NOFCS = 0x2b SO_OOBINLINE = 0x100 SO_PASSCRED = 0x11 + SO_PASSPIDFD = 0x4c SO_PASSSEC = 0x22 SO_PEEK_OFF = 0x2a SO_PEERCRED = 0x12 SO_PEERGROUPS = 0x3b + SO_PEERPIDFD = 0x4d SO_PEERSEC = 0x1e SO_PREFER_BUSY_POLL = 0x45 SO_PROTOCOL = 0x1028 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_mipsle.go b/vendor/golang.org/x/sys/unix/zerrors_linux_mipsle.go index 2e80ea6..8daa6dd 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_mipsle.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_mipsle.go @@ -326,10 +326,12 @@ const ( SO_NOFCS = 0x2b SO_OOBINLINE = 0x100 SO_PASSCRED = 0x11 + SO_PASSPIDFD = 0x4c SO_PASSSEC = 0x22 SO_PEEK_OFF = 0x2a SO_PEERCRED = 0x12 SO_PEERGROUPS = 0x3b + SO_PEERPIDFD = 0x4d SO_PEERSEC = 0x1e SO_PREFER_BUSY_POLL = 0x45 SO_PROTOCOL = 0x1028 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_ppc.go b/vendor/golang.org/x/sys/unix/zerrors_linux_ppc.go index a65dcd7..63c8fa2 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_ppc.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_ppc.go @@ -381,10 +381,12 @@ const ( SO_NOFCS = 0x2b SO_OOBINLINE = 0xa SO_PASSCRED = 0x14 + SO_PASSPIDFD = 0x4c SO_PASSSEC = 0x22 SO_PEEK_OFF = 0x2a SO_PEERCRED = 0x15 SO_PEERGROUPS = 0x3b + SO_PEERPIDFD = 0x4d SO_PEERSEC = 0x1f SO_PREFER_BUSY_POLL = 0x45 SO_PROTOCOL = 0x26 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64.go b/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64.go index cbd34e3..930799e 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64.go @@ -385,10 +385,12 @@ const ( SO_NOFCS = 0x2b SO_OOBINLINE = 0xa SO_PASSCRED = 0x14 + SO_PASSPIDFD = 0x4c SO_PASSSEC = 0x22 SO_PEEK_OFF = 0x2a SO_PEERCRED = 0x15 SO_PEERGROUPS = 0x3b + SO_PEERPIDFD = 0x4d SO_PEERSEC = 0x1f SO_PREFER_BUSY_POLL = 0x45 SO_PROTOCOL = 0x26 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64le.go b/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64le.go index e4afa7a..8605a7d 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64le.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64le.go @@ -385,10 +385,12 @@ const ( SO_NOFCS = 0x2b SO_OOBINLINE = 0xa SO_PASSCRED = 0x14 + SO_PASSPIDFD = 0x4c SO_PASSSEC = 0x22 SO_PEEK_OFF = 0x2a SO_PEERCRED = 0x15 SO_PEERGROUPS = 0x3b + SO_PEERPIDFD = 0x4d SO_PEERSEC = 0x1f SO_PREFER_BUSY_POLL = 0x45 SO_PROTOCOL = 0x26 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_riscv64.go b/vendor/golang.org/x/sys/unix/zerrors_linux_riscv64.go index 44f45a0..95a016f 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_riscv64.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_riscv64.go @@ -314,10 +314,12 @@ const ( SO_NOFCS = 0x2b SO_OOBINLINE = 0xa SO_PASSCRED = 0x10 + SO_PASSPIDFD = 0x4c SO_PASSSEC = 0x22 SO_PEEK_OFF = 0x2a SO_PEERCRED = 0x11 SO_PEERGROUPS = 0x3b + SO_PEERPIDFD = 0x4d SO_PEERSEC = 0x1f SO_PREFER_BUSY_POLL = 0x45 SO_PROTOCOL = 0x26 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_s390x.go b/vendor/golang.org/x/sys/unix/zerrors_linux_s390x.go index 74733e2..1ae0108 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_s390x.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_s390x.go @@ -389,10 +389,12 @@ const ( SO_NOFCS = 0x2b SO_OOBINLINE = 0xa SO_PASSCRED = 0x10 + SO_PASSPIDFD = 0x4c SO_PASSSEC = 0x22 SO_PEEK_OFF = 0x2a SO_PEERCRED = 0x11 SO_PEERGROUPS = 0x3b + SO_PEERPIDFD = 0x4d SO_PEERSEC = 0x1f SO_PREFER_BUSY_POLL = 0x45 SO_PROTOCOL = 0x26 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_sparc64.go b/vendor/golang.org/x/sys/unix/zerrors_linux_sparc64.go index f5f3934..1bb7c63 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_sparc64.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_sparc64.go @@ -428,10 +428,12 @@ const ( SO_NOFCS = 0x27 SO_OOBINLINE = 0x100 SO_PASSCRED = 0x2 + SO_PASSPIDFD = 0x55 SO_PASSSEC = 0x1f SO_PEEK_OFF = 0x26 SO_PEERCRED = 0x40 SO_PEERGROUPS = 0x3d + SO_PEERPIDFD = 0x56 SO_PEERSEC = 0x1e SO_PREFER_BUSY_POLL = 0x48 SO_PROTOCOL = 0x1028 diff --git a/vendor/golang.org/x/sys/unix/zsyscall_aix_ppc.go b/vendor/golang.org/x/sys/unix/zsyscall_aix_ppc.go index 9a25721..d1d1d23 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_aix_ppc.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_aix_ppc.go @@ -817,28 +817,6 @@ func write(fd int, p []byte) (n int, err error) { // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT -func readlen(fd int, p *byte, np int) (n int, err error) { - r0, er := C.read(C.int(fd), C.uintptr_t(uintptr(unsafe.Pointer(p))), C.size_t(np)) - n = int(r0) - if r0 == -1 && er != nil { - err = er - } - return -} - -// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT - -func writelen(fd int, p *byte, np int) (n int, err error) { - r0, er := C.write(C.int(fd), C.uintptr_t(uintptr(unsafe.Pointer(p))), C.size_t(np)) - n = int(r0) - if r0 == -1 && er != nil { - err = er - } - return -} - -// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT - func Dup2(oldfd int, newfd int) (err error) { r0, er := C.dup2(C.int(oldfd), C.int(newfd)) if r0 == -1 && er != nil { diff --git a/vendor/golang.org/x/sys/unix/zsyscall_aix_ppc64.go b/vendor/golang.org/x/sys/unix/zsyscall_aix_ppc64.go index 6de80c2..f99a18a 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_aix_ppc64.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_aix_ppc64.go @@ -762,28 +762,6 @@ func write(fd int, p []byte) (n int, err error) { // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT -func readlen(fd int, p *byte, np int) (n int, err error) { - r0, e1 := callread(fd, uintptr(unsafe.Pointer(p)), np) - n = int(r0) - if e1 != 0 { - err = errnoErr(e1) - } - return -} - -// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT - -func writelen(fd int, p *byte, np int) (n int, err error) { - r0, e1 := callwrite(fd, uintptr(unsafe.Pointer(p)), np) - n = int(r0) - if e1 != 0 { - err = errnoErr(e1) - } - return -} - -// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT - func Dup2(oldfd int, newfd int) (err error) { _, e1 := calldup2(oldfd, newfd) if e1 != 0 { diff --git a/vendor/golang.org/x/sys/unix/zsyscall_darwin_amd64.go b/vendor/golang.org/x/sys/unix/zsyscall_darwin_amd64.go index 4037ccf..1cad561 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_darwin_amd64.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_darwin_amd64.go @@ -725,6 +725,12 @@ func ioctl(fd int, req uint, arg uintptr) (err error) { return } +var libc_ioctl_trampoline_addr uintptr + +//go:cgo_import_dynamic libc_ioctl ioctl "/usr/lib/libSystem.B.dylib" + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + func ioctlPtr(fd int, req uint, arg unsafe.Pointer) (err error) { _, _, e1 := syscall_syscall(libc_ioctl_trampoline_addr, uintptr(fd), uintptr(req), uintptr(arg)) if e1 != 0 { @@ -733,10 +739,6 @@ func ioctlPtr(fd int, req uint, arg unsafe.Pointer) (err error) { return } -var libc_ioctl_trampoline_addr uintptr - -//go:cgo_import_dynamic libc_ioctl ioctl "/usr/lib/libSystem.B.dylib" - // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT func sysctl(mib []_C_int, old *byte, oldlen *uintptr, new *byte, newlen uintptr) (err error) { @@ -2410,28 +2412,6 @@ var libc_munmap_trampoline_addr uintptr // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT -func readlen(fd int, buf *byte, nbuf int) (n int, err error) { - r0, _, e1 := syscall_syscall(libc_read_trampoline_addr, uintptr(fd), uintptr(unsafe.Pointer(buf)), uintptr(nbuf)) - n = int(r0) - if e1 != 0 { - err = errnoErr(e1) - } - return -} - -// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT - -func writelen(fd int, buf *byte, nbuf int) (n int, err error) { - r0, _, e1 := syscall_syscall(libc_write_trampoline_addr, uintptr(fd), uintptr(unsafe.Pointer(buf)), uintptr(nbuf)) - n = int(r0) - if e1 != 0 { - err = errnoErr(e1) - } - return -} - -// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT - func Fstat(fd int, stat *Stat_t) (err error) { _, _, e1 := syscall_syscall(libc_fstat64_trampoline_addr, uintptr(fd), uintptr(unsafe.Pointer(stat)), 0) if e1 != 0 { @@ -2521,14 +2501,6 @@ func ptrace1(request int, pid int, addr uintptr, data uintptr) (err error) { return } -func ptrace1Ptr(request int, pid int, addr uintptr, data unsafe.Pointer) (err error) { - _, _, e1 := syscall_syscall6(libc_ptrace_trampoline_addr, uintptr(request), uintptr(pid), addr, uintptr(data), 0, 0) - if e1 != 0 { - err = errnoErr(e1) - } - return -} - var libc_ptrace_trampoline_addr uintptr //go:cgo_import_dynamic libc_ptrace ptrace "/usr/lib/libSystem.B.dylib" diff --git a/vendor/golang.org/x/sys/unix/zsyscall_darwin_amd64.s b/vendor/golang.org/x/sys/unix/zsyscall_darwin_amd64.s index 4baaed0..8b8bb28 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_darwin_amd64.s +++ b/vendor/golang.org/x/sys/unix/zsyscall_darwin_amd64.s @@ -5,703 +5,586 @@ TEXT libc_fdopendir_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_fdopendir(SB) - GLOBL ·libc_fdopendir_trampoline_addr(SB), RODATA, $8 DATA ·libc_fdopendir_trampoline_addr(SB)/8, $libc_fdopendir_trampoline<>(SB) TEXT libc_getgroups_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_getgroups(SB) - GLOBL ·libc_getgroups_trampoline_addr(SB), RODATA, $8 DATA ·libc_getgroups_trampoline_addr(SB)/8, $libc_getgroups_trampoline<>(SB) TEXT libc_setgroups_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_setgroups(SB) - GLOBL ·libc_setgroups_trampoline_addr(SB), RODATA, $8 DATA ·libc_setgroups_trampoline_addr(SB)/8, $libc_setgroups_trampoline<>(SB) TEXT libc_wait4_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_wait4(SB) - GLOBL ·libc_wait4_trampoline_addr(SB), RODATA, $8 DATA ·libc_wait4_trampoline_addr(SB)/8, $libc_wait4_trampoline<>(SB) TEXT libc_accept_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_accept(SB) - GLOBL ·libc_accept_trampoline_addr(SB), RODATA, $8 DATA ·libc_accept_trampoline_addr(SB)/8, $libc_accept_trampoline<>(SB) TEXT libc_bind_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_bind(SB) - GLOBL ·libc_bind_trampoline_addr(SB), RODATA, $8 DATA ·libc_bind_trampoline_addr(SB)/8, $libc_bind_trampoline<>(SB) TEXT libc_connect_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_connect(SB) - GLOBL ·libc_connect_trampoline_addr(SB), RODATA, $8 DATA ·libc_connect_trampoline_addr(SB)/8, $libc_connect_trampoline<>(SB) TEXT libc_socket_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_socket(SB) - GLOBL ·libc_socket_trampoline_addr(SB), RODATA, $8 DATA ·libc_socket_trampoline_addr(SB)/8, $libc_socket_trampoline<>(SB) TEXT libc_getsockopt_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_getsockopt(SB) - GLOBL ·libc_getsockopt_trampoline_addr(SB), RODATA, $8 DATA ·libc_getsockopt_trampoline_addr(SB)/8, $libc_getsockopt_trampoline<>(SB) TEXT libc_setsockopt_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_setsockopt(SB) - GLOBL ·libc_setsockopt_trampoline_addr(SB), RODATA, $8 DATA ·libc_setsockopt_trampoline_addr(SB)/8, $libc_setsockopt_trampoline<>(SB) TEXT libc_getpeername_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_getpeername(SB) - GLOBL ·libc_getpeername_trampoline_addr(SB), RODATA, $8 DATA ·libc_getpeername_trampoline_addr(SB)/8, $libc_getpeername_trampoline<>(SB) TEXT libc_getsockname_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_getsockname(SB) - GLOBL ·libc_getsockname_trampoline_addr(SB), RODATA, $8 DATA ·libc_getsockname_trampoline_addr(SB)/8, $libc_getsockname_trampoline<>(SB) TEXT libc_shutdown_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_shutdown(SB) - GLOBL ·libc_shutdown_trampoline_addr(SB), RODATA, $8 DATA ·libc_shutdown_trampoline_addr(SB)/8, $libc_shutdown_trampoline<>(SB) TEXT libc_socketpair_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_socketpair(SB) - GLOBL ·libc_socketpair_trampoline_addr(SB), RODATA, $8 DATA ·libc_socketpair_trampoline_addr(SB)/8, $libc_socketpair_trampoline<>(SB) TEXT libc_recvfrom_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_recvfrom(SB) - GLOBL ·libc_recvfrom_trampoline_addr(SB), RODATA, $8 DATA ·libc_recvfrom_trampoline_addr(SB)/8, $libc_recvfrom_trampoline<>(SB) TEXT libc_sendto_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_sendto(SB) - GLOBL ·libc_sendto_trampoline_addr(SB), RODATA, $8 DATA ·libc_sendto_trampoline_addr(SB)/8, $libc_sendto_trampoline<>(SB) TEXT libc_recvmsg_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_recvmsg(SB) - GLOBL ·libc_recvmsg_trampoline_addr(SB), RODATA, $8 DATA ·libc_recvmsg_trampoline_addr(SB)/8, $libc_recvmsg_trampoline<>(SB) TEXT libc_sendmsg_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_sendmsg(SB) - GLOBL ·libc_sendmsg_trampoline_addr(SB), RODATA, $8 DATA ·libc_sendmsg_trampoline_addr(SB)/8, $libc_sendmsg_trampoline<>(SB) TEXT libc_kevent_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_kevent(SB) - GLOBL ·libc_kevent_trampoline_addr(SB), RODATA, $8 DATA ·libc_kevent_trampoline_addr(SB)/8, $libc_kevent_trampoline<>(SB) TEXT libc_utimes_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_utimes(SB) - GLOBL ·libc_utimes_trampoline_addr(SB), RODATA, $8 DATA ·libc_utimes_trampoline_addr(SB)/8, $libc_utimes_trampoline<>(SB) TEXT libc_futimes_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_futimes(SB) - GLOBL ·libc_futimes_trampoline_addr(SB), RODATA, $8 DATA ·libc_futimes_trampoline_addr(SB)/8, $libc_futimes_trampoline<>(SB) TEXT libc_poll_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_poll(SB) - GLOBL ·libc_poll_trampoline_addr(SB), RODATA, $8 DATA ·libc_poll_trampoline_addr(SB)/8, $libc_poll_trampoline<>(SB) TEXT libc_madvise_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_madvise(SB) - GLOBL ·libc_madvise_trampoline_addr(SB), RODATA, $8 DATA ·libc_madvise_trampoline_addr(SB)/8, $libc_madvise_trampoline<>(SB) TEXT libc_mlock_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_mlock(SB) - GLOBL ·libc_mlock_trampoline_addr(SB), RODATA, $8 DATA ·libc_mlock_trampoline_addr(SB)/8, $libc_mlock_trampoline<>(SB) TEXT libc_mlockall_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_mlockall(SB) - GLOBL ·libc_mlockall_trampoline_addr(SB), RODATA, $8 DATA ·libc_mlockall_trampoline_addr(SB)/8, $libc_mlockall_trampoline<>(SB) TEXT libc_mprotect_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_mprotect(SB) - GLOBL ·libc_mprotect_trampoline_addr(SB), RODATA, $8 DATA ·libc_mprotect_trampoline_addr(SB)/8, $libc_mprotect_trampoline<>(SB) TEXT libc_msync_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_msync(SB) - GLOBL ·libc_msync_trampoline_addr(SB), RODATA, $8 DATA ·libc_msync_trampoline_addr(SB)/8, $libc_msync_trampoline<>(SB) TEXT libc_munlock_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_munlock(SB) - GLOBL ·libc_munlock_trampoline_addr(SB), RODATA, $8 DATA ·libc_munlock_trampoline_addr(SB)/8, $libc_munlock_trampoline<>(SB) TEXT libc_munlockall_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_munlockall(SB) - GLOBL ·libc_munlockall_trampoline_addr(SB), RODATA, $8 DATA ·libc_munlockall_trampoline_addr(SB)/8, $libc_munlockall_trampoline<>(SB) TEXT libc_closedir_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_closedir(SB) - GLOBL ·libc_closedir_trampoline_addr(SB), RODATA, $8 DATA ·libc_closedir_trampoline_addr(SB)/8, $libc_closedir_trampoline<>(SB) TEXT libc_readdir_r_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_readdir_r(SB) - GLOBL ·libc_readdir_r_trampoline_addr(SB), RODATA, $8 DATA ·libc_readdir_r_trampoline_addr(SB)/8, $libc_readdir_r_trampoline<>(SB) TEXT libc_pipe_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_pipe(SB) - GLOBL ·libc_pipe_trampoline_addr(SB), RODATA, $8 DATA ·libc_pipe_trampoline_addr(SB)/8, $libc_pipe_trampoline<>(SB) TEXT libc_getxattr_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_getxattr(SB) - GLOBL ·libc_getxattr_trampoline_addr(SB), RODATA, $8 DATA ·libc_getxattr_trampoline_addr(SB)/8, $libc_getxattr_trampoline<>(SB) TEXT libc_fgetxattr_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_fgetxattr(SB) - GLOBL ·libc_fgetxattr_trampoline_addr(SB), RODATA, $8 DATA ·libc_fgetxattr_trampoline_addr(SB)/8, $libc_fgetxattr_trampoline<>(SB) TEXT libc_setxattr_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_setxattr(SB) - GLOBL ·libc_setxattr_trampoline_addr(SB), RODATA, $8 DATA ·libc_setxattr_trampoline_addr(SB)/8, $libc_setxattr_trampoline<>(SB) TEXT libc_fsetxattr_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_fsetxattr(SB) - GLOBL ·libc_fsetxattr_trampoline_addr(SB), RODATA, $8 DATA ·libc_fsetxattr_trampoline_addr(SB)/8, $libc_fsetxattr_trampoline<>(SB) TEXT libc_removexattr_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_removexattr(SB) - GLOBL ·libc_removexattr_trampoline_addr(SB), RODATA, $8 DATA ·libc_removexattr_trampoline_addr(SB)/8, $libc_removexattr_trampoline<>(SB) TEXT libc_fremovexattr_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_fremovexattr(SB) - GLOBL ·libc_fremovexattr_trampoline_addr(SB), RODATA, $8 DATA ·libc_fremovexattr_trampoline_addr(SB)/8, $libc_fremovexattr_trampoline<>(SB) TEXT libc_listxattr_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_listxattr(SB) - GLOBL ·libc_listxattr_trampoline_addr(SB), RODATA, $8 DATA ·libc_listxattr_trampoline_addr(SB)/8, $libc_listxattr_trampoline<>(SB) TEXT libc_flistxattr_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_flistxattr(SB) - GLOBL ·libc_flistxattr_trampoline_addr(SB), RODATA, $8 DATA ·libc_flistxattr_trampoline_addr(SB)/8, $libc_flistxattr_trampoline<>(SB) TEXT libc_utimensat_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_utimensat(SB) - GLOBL ·libc_utimensat_trampoline_addr(SB), RODATA, $8 DATA ·libc_utimensat_trampoline_addr(SB)/8, $libc_utimensat_trampoline<>(SB) TEXT libc_fcntl_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_fcntl(SB) - GLOBL ·libc_fcntl_trampoline_addr(SB), RODATA, $8 DATA ·libc_fcntl_trampoline_addr(SB)/8, $libc_fcntl_trampoline<>(SB) TEXT libc_kill_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_kill(SB) - GLOBL ·libc_kill_trampoline_addr(SB), RODATA, $8 DATA ·libc_kill_trampoline_addr(SB)/8, $libc_kill_trampoline<>(SB) TEXT libc_ioctl_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_ioctl(SB) - GLOBL ·libc_ioctl_trampoline_addr(SB), RODATA, $8 DATA ·libc_ioctl_trampoline_addr(SB)/8, $libc_ioctl_trampoline<>(SB) TEXT libc_sysctl_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_sysctl(SB) - GLOBL ·libc_sysctl_trampoline_addr(SB), RODATA, $8 DATA ·libc_sysctl_trampoline_addr(SB)/8, $libc_sysctl_trampoline<>(SB) TEXT libc_sendfile_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_sendfile(SB) - GLOBL ·libc_sendfile_trampoline_addr(SB), RODATA, $8 DATA ·libc_sendfile_trampoline_addr(SB)/8, $libc_sendfile_trampoline<>(SB) TEXT libc_shmat_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_shmat(SB) - GLOBL ·libc_shmat_trampoline_addr(SB), RODATA, $8 DATA ·libc_shmat_trampoline_addr(SB)/8, $libc_shmat_trampoline<>(SB) TEXT libc_shmctl_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_shmctl(SB) - GLOBL ·libc_shmctl_trampoline_addr(SB), RODATA, $8 DATA ·libc_shmctl_trampoline_addr(SB)/8, $libc_shmctl_trampoline<>(SB) TEXT libc_shmdt_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_shmdt(SB) - GLOBL ·libc_shmdt_trampoline_addr(SB), RODATA, $8 DATA ·libc_shmdt_trampoline_addr(SB)/8, $libc_shmdt_trampoline<>(SB) TEXT libc_shmget_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_shmget(SB) - GLOBL ·libc_shmget_trampoline_addr(SB), RODATA, $8 DATA ·libc_shmget_trampoline_addr(SB)/8, $libc_shmget_trampoline<>(SB) TEXT libc_access_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_access(SB) - GLOBL ·libc_access_trampoline_addr(SB), RODATA, $8 DATA ·libc_access_trampoline_addr(SB)/8, $libc_access_trampoline<>(SB) TEXT libc_adjtime_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_adjtime(SB) - GLOBL ·libc_adjtime_trampoline_addr(SB), RODATA, $8 DATA ·libc_adjtime_trampoline_addr(SB)/8, $libc_adjtime_trampoline<>(SB) TEXT libc_chdir_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_chdir(SB) - GLOBL ·libc_chdir_trampoline_addr(SB), RODATA, $8 DATA ·libc_chdir_trampoline_addr(SB)/8, $libc_chdir_trampoline<>(SB) TEXT libc_chflags_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_chflags(SB) - GLOBL ·libc_chflags_trampoline_addr(SB), RODATA, $8 DATA ·libc_chflags_trampoline_addr(SB)/8, $libc_chflags_trampoline<>(SB) TEXT libc_chmod_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_chmod(SB) - GLOBL ·libc_chmod_trampoline_addr(SB), RODATA, $8 DATA ·libc_chmod_trampoline_addr(SB)/8, $libc_chmod_trampoline<>(SB) TEXT libc_chown_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_chown(SB) - GLOBL ·libc_chown_trampoline_addr(SB), RODATA, $8 DATA ·libc_chown_trampoline_addr(SB)/8, $libc_chown_trampoline<>(SB) TEXT libc_chroot_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_chroot(SB) - GLOBL ·libc_chroot_trampoline_addr(SB), RODATA, $8 DATA ·libc_chroot_trampoline_addr(SB)/8, $libc_chroot_trampoline<>(SB) TEXT libc_clock_gettime_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_clock_gettime(SB) - GLOBL ·libc_clock_gettime_trampoline_addr(SB), RODATA, $8 DATA ·libc_clock_gettime_trampoline_addr(SB)/8, $libc_clock_gettime_trampoline<>(SB) TEXT libc_close_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_close(SB) - GLOBL ·libc_close_trampoline_addr(SB), RODATA, $8 DATA ·libc_close_trampoline_addr(SB)/8, $libc_close_trampoline<>(SB) TEXT libc_clonefile_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_clonefile(SB) - GLOBL ·libc_clonefile_trampoline_addr(SB), RODATA, $8 DATA ·libc_clonefile_trampoline_addr(SB)/8, $libc_clonefile_trampoline<>(SB) TEXT libc_clonefileat_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_clonefileat(SB) - GLOBL ·libc_clonefileat_trampoline_addr(SB), RODATA, $8 DATA ·libc_clonefileat_trampoline_addr(SB)/8, $libc_clonefileat_trampoline<>(SB) TEXT libc_dup_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_dup(SB) - GLOBL ·libc_dup_trampoline_addr(SB), RODATA, $8 DATA ·libc_dup_trampoline_addr(SB)/8, $libc_dup_trampoline<>(SB) TEXT libc_dup2_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_dup2(SB) - GLOBL ·libc_dup2_trampoline_addr(SB), RODATA, $8 DATA ·libc_dup2_trampoline_addr(SB)/8, $libc_dup2_trampoline<>(SB) TEXT libc_exchangedata_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_exchangedata(SB) - GLOBL ·libc_exchangedata_trampoline_addr(SB), RODATA, $8 DATA ·libc_exchangedata_trampoline_addr(SB)/8, $libc_exchangedata_trampoline<>(SB) TEXT libc_exit_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_exit(SB) - GLOBL ·libc_exit_trampoline_addr(SB), RODATA, $8 DATA ·libc_exit_trampoline_addr(SB)/8, $libc_exit_trampoline<>(SB) TEXT libc_faccessat_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_faccessat(SB) - GLOBL ·libc_faccessat_trampoline_addr(SB), RODATA, $8 DATA ·libc_faccessat_trampoline_addr(SB)/8, $libc_faccessat_trampoline<>(SB) TEXT libc_fchdir_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_fchdir(SB) - GLOBL ·libc_fchdir_trampoline_addr(SB), RODATA, $8 DATA ·libc_fchdir_trampoline_addr(SB)/8, $libc_fchdir_trampoline<>(SB) TEXT libc_fchflags_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_fchflags(SB) - GLOBL ·libc_fchflags_trampoline_addr(SB), RODATA, $8 DATA ·libc_fchflags_trampoline_addr(SB)/8, $libc_fchflags_trampoline<>(SB) TEXT libc_fchmod_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_fchmod(SB) - GLOBL ·libc_fchmod_trampoline_addr(SB), RODATA, $8 DATA ·libc_fchmod_trampoline_addr(SB)/8, $libc_fchmod_trampoline<>(SB) TEXT libc_fchmodat_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_fchmodat(SB) - GLOBL ·libc_fchmodat_trampoline_addr(SB), RODATA, $8 DATA ·libc_fchmodat_trampoline_addr(SB)/8, $libc_fchmodat_trampoline<>(SB) TEXT libc_fchown_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_fchown(SB) - GLOBL ·libc_fchown_trampoline_addr(SB), RODATA, $8 DATA ·libc_fchown_trampoline_addr(SB)/8, $libc_fchown_trampoline<>(SB) TEXT libc_fchownat_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_fchownat(SB) - GLOBL ·libc_fchownat_trampoline_addr(SB), RODATA, $8 DATA ·libc_fchownat_trampoline_addr(SB)/8, $libc_fchownat_trampoline<>(SB) TEXT libc_fclonefileat_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_fclonefileat(SB) - GLOBL ·libc_fclonefileat_trampoline_addr(SB), RODATA, $8 DATA ·libc_fclonefileat_trampoline_addr(SB)/8, $libc_fclonefileat_trampoline<>(SB) TEXT libc_flock_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_flock(SB) - GLOBL ·libc_flock_trampoline_addr(SB), RODATA, $8 DATA ·libc_flock_trampoline_addr(SB)/8, $libc_flock_trampoline<>(SB) TEXT libc_fpathconf_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_fpathconf(SB) - GLOBL ·libc_fpathconf_trampoline_addr(SB), RODATA, $8 DATA ·libc_fpathconf_trampoline_addr(SB)/8, $libc_fpathconf_trampoline<>(SB) TEXT libc_fsync_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_fsync(SB) - GLOBL ·libc_fsync_trampoline_addr(SB), RODATA, $8 DATA ·libc_fsync_trampoline_addr(SB)/8, $libc_fsync_trampoline<>(SB) TEXT libc_ftruncate_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_ftruncate(SB) - GLOBL ·libc_ftruncate_trampoline_addr(SB), RODATA, $8 DATA ·libc_ftruncate_trampoline_addr(SB)/8, $libc_ftruncate_trampoline<>(SB) TEXT libc_getcwd_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_getcwd(SB) - GLOBL ·libc_getcwd_trampoline_addr(SB), RODATA, $8 DATA ·libc_getcwd_trampoline_addr(SB)/8, $libc_getcwd_trampoline<>(SB) TEXT libc_getdtablesize_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_getdtablesize(SB) - GLOBL ·libc_getdtablesize_trampoline_addr(SB), RODATA, $8 DATA ·libc_getdtablesize_trampoline_addr(SB)/8, $libc_getdtablesize_trampoline<>(SB) TEXT libc_getegid_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_getegid(SB) - GLOBL ·libc_getegid_trampoline_addr(SB), RODATA, $8 DATA ·libc_getegid_trampoline_addr(SB)/8, $libc_getegid_trampoline<>(SB) TEXT libc_geteuid_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_geteuid(SB) - GLOBL ·libc_geteuid_trampoline_addr(SB), RODATA, $8 DATA ·libc_geteuid_trampoline_addr(SB)/8, $libc_geteuid_trampoline<>(SB) TEXT libc_getgid_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_getgid(SB) - GLOBL ·libc_getgid_trampoline_addr(SB), RODATA, $8 DATA ·libc_getgid_trampoline_addr(SB)/8, $libc_getgid_trampoline<>(SB) TEXT libc_getpgid_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_getpgid(SB) - GLOBL ·libc_getpgid_trampoline_addr(SB), RODATA, $8 DATA ·libc_getpgid_trampoline_addr(SB)/8, $libc_getpgid_trampoline<>(SB) TEXT libc_getpgrp_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_getpgrp(SB) - GLOBL ·libc_getpgrp_trampoline_addr(SB), RODATA, $8 DATA ·libc_getpgrp_trampoline_addr(SB)/8, $libc_getpgrp_trampoline<>(SB) TEXT libc_getpid_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_getpid(SB) - GLOBL ·libc_getpid_trampoline_addr(SB), RODATA, $8 DATA ·libc_getpid_trampoline_addr(SB)/8, $libc_getpid_trampoline<>(SB) TEXT libc_getppid_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_getppid(SB) - GLOBL ·libc_getppid_trampoline_addr(SB), RODATA, $8 DATA ·libc_getppid_trampoline_addr(SB)/8, $libc_getppid_trampoline<>(SB) TEXT libc_getpriority_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_getpriority(SB) - GLOBL ·libc_getpriority_trampoline_addr(SB), RODATA, $8 DATA ·libc_getpriority_trampoline_addr(SB)/8, $libc_getpriority_trampoline<>(SB) TEXT libc_getrlimit_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_getrlimit(SB) - GLOBL ·libc_getrlimit_trampoline_addr(SB), RODATA, $8 DATA ·libc_getrlimit_trampoline_addr(SB)/8, $libc_getrlimit_trampoline<>(SB) TEXT libc_getrusage_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_getrusage(SB) - GLOBL ·libc_getrusage_trampoline_addr(SB), RODATA, $8 DATA ·libc_getrusage_trampoline_addr(SB)/8, $libc_getrusage_trampoline<>(SB) TEXT libc_getsid_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_getsid(SB) - GLOBL ·libc_getsid_trampoline_addr(SB), RODATA, $8 DATA ·libc_getsid_trampoline_addr(SB)/8, $libc_getsid_trampoline<>(SB) TEXT libc_gettimeofday_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_gettimeofday(SB) - GLOBL ·libc_gettimeofday_trampoline_addr(SB), RODATA, $8 DATA ·libc_gettimeofday_trampoline_addr(SB)/8, $libc_gettimeofday_trampoline<>(SB) TEXT libc_getuid_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_getuid(SB) - GLOBL ·libc_getuid_trampoline_addr(SB), RODATA, $8 DATA ·libc_getuid_trampoline_addr(SB)/8, $libc_getuid_trampoline<>(SB) TEXT libc_issetugid_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_issetugid(SB) - GLOBL ·libc_issetugid_trampoline_addr(SB), RODATA, $8 DATA ·libc_issetugid_trampoline_addr(SB)/8, $libc_issetugid_trampoline<>(SB) TEXT libc_kqueue_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_kqueue(SB) - GLOBL ·libc_kqueue_trampoline_addr(SB), RODATA, $8 DATA ·libc_kqueue_trampoline_addr(SB)/8, $libc_kqueue_trampoline<>(SB) TEXT libc_lchown_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_lchown(SB) - GLOBL ·libc_lchown_trampoline_addr(SB), RODATA, $8 DATA ·libc_lchown_trampoline_addr(SB)/8, $libc_lchown_trampoline<>(SB) TEXT libc_link_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_link(SB) - GLOBL ·libc_link_trampoline_addr(SB), RODATA, $8 DATA ·libc_link_trampoline_addr(SB)/8, $libc_link_trampoline<>(SB) TEXT libc_linkat_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_linkat(SB) - GLOBL ·libc_linkat_trampoline_addr(SB), RODATA, $8 DATA ·libc_linkat_trampoline_addr(SB)/8, $libc_linkat_trampoline<>(SB) TEXT libc_listen_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_listen(SB) - GLOBL ·libc_listen_trampoline_addr(SB), RODATA, $8 DATA ·libc_listen_trampoline_addr(SB)/8, $libc_listen_trampoline<>(SB) TEXT libc_mkdir_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_mkdir(SB) - GLOBL ·libc_mkdir_trampoline_addr(SB), RODATA, $8 DATA ·libc_mkdir_trampoline_addr(SB)/8, $libc_mkdir_trampoline<>(SB) TEXT libc_mkdirat_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_mkdirat(SB) - GLOBL ·libc_mkdirat_trampoline_addr(SB), RODATA, $8 DATA ·libc_mkdirat_trampoline_addr(SB)/8, $libc_mkdirat_trampoline<>(SB) TEXT libc_mkfifo_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_mkfifo(SB) - GLOBL ·libc_mkfifo_trampoline_addr(SB), RODATA, $8 DATA ·libc_mkfifo_trampoline_addr(SB)/8, $libc_mkfifo_trampoline<>(SB) TEXT libc_mknod_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_mknod(SB) - GLOBL ·libc_mknod_trampoline_addr(SB), RODATA, $8 DATA ·libc_mknod_trampoline_addr(SB)/8, $libc_mknod_trampoline<>(SB) TEXT libc_mount_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_mount(SB) - GLOBL ·libc_mount_trampoline_addr(SB), RODATA, $8 DATA ·libc_mount_trampoline_addr(SB)/8, $libc_mount_trampoline<>(SB) TEXT libc_open_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_open(SB) - GLOBL ·libc_open_trampoline_addr(SB), RODATA, $8 DATA ·libc_open_trampoline_addr(SB)/8, $libc_open_trampoline<>(SB) TEXT libc_openat_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_openat(SB) - GLOBL ·libc_openat_trampoline_addr(SB), RODATA, $8 DATA ·libc_openat_trampoline_addr(SB)/8, $libc_openat_trampoline<>(SB) TEXT libc_pathconf_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_pathconf(SB) - GLOBL ·libc_pathconf_trampoline_addr(SB), RODATA, $8 DATA ·libc_pathconf_trampoline_addr(SB)/8, $libc_pathconf_trampoline<>(SB) TEXT libc_pread_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_pread(SB) - GLOBL ·libc_pread_trampoline_addr(SB), RODATA, $8 DATA ·libc_pread_trampoline_addr(SB)/8, $libc_pread_trampoline<>(SB) TEXT libc_pwrite_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_pwrite(SB) - GLOBL ·libc_pwrite_trampoline_addr(SB), RODATA, $8 DATA ·libc_pwrite_trampoline_addr(SB)/8, $libc_pwrite_trampoline<>(SB) TEXT libc_read_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_read(SB) - GLOBL ·libc_read_trampoline_addr(SB), RODATA, $8 DATA ·libc_read_trampoline_addr(SB)/8, $libc_read_trampoline<>(SB) TEXT libc_readlink_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_readlink(SB) - GLOBL ·libc_readlink_trampoline_addr(SB), RODATA, $8 DATA ·libc_readlink_trampoline_addr(SB)/8, $libc_readlink_trampoline<>(SB) TEXT libc_readlinkat_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_readlinkat(SB) - GLOBL ·libc_readlinkat_trampoline_addr(SB), RODATA, $8 DATA ·libc_readlinkat_trampoline_addr(SB)/8, $libc_readlinkat_trampoline<>(SB) TEXT libc_rename_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_rename(SB) - GLOBL ·libc_rename_trampoline_addr(SB), RODATA, $8 DATA ·libc_rename_trampoline_addr(SB)/8, $libc_rename_trampoline<>(SB) TEXT libc_renameat_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_renameat(SB) - GLOBL ·libc_renameat_trampoline_addr(SB), RODATA, $8 DATA ·libc_renameat_trampoline_addr(SB)/8, $libc_renameat_trampoline<>(SB) TEXT libc_revoke_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_revoke(SB) - GLOBL ·libc_revoke_trampoline_addr(SB), RODATA, $8 DATA ·libc_revoke_trampoline_addr(SB)/8, $libc_revoke_trampoline<>(SB) TEXT libc_rmdir_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_rmdir(SB) - GLOBL ·libc_rmdir_trampoline_addr(SB), RODATA, $8 DATA ·libc_rmdir_trampoline_addr(SB)/8, $libc_rmdir_trampoline<>(SB) TEXT libc_lseek_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_lseek(SB) - GLOBL ·libc_lseek_trampoline_addr(SB), RODATA, $8 DATA ·libc_lseek_trampoline_addr(SB)/8, $libc_lseek_trampoline<>(SB) TEXT libc_select_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_select(SB) - GLOBL ·libc_select_trampoline_addr(SB), RODATA, $8 DATA ·libc_select_trampoline_addr(SB)/8, $libc_select_trampoline<>(SB) @@ -712,192 +595,160 @@ DATA ·libc_setattrlist_trampoline_addr(SB)/8, $libc_setattrlist_trampoline<>(SB TEXT libc_setegid_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_setegid(SB) - GLOBL ·libc_setegid_trampoline_addr(SB), RODATA, $8 DATA ·libc_setegid_trampoline_addr(SB)/8, $libc_setegid_trampoline<>(SB) TEXT libc_seteuid_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_seteuid(SB) - GLOBL ·libc_seteuid_trampoline_addr(SB), RODATA, $8 DATA ·libc_seteuid_trampoline_addr(SB)/8, $libc_seteuid_trampoline<>(SB) TEXT libc_setgid_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_setgid(SB) - GLOBL ·libc_setgid_trampoline_addr(SB), RODATA, $8 DATA ·libc_setgid_trampoline_addr(SB)/8, $libc_setgid_trampoline<>(SB) TEXT libc_setlogin_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_setlogin(SB) - GLOBL ·libc_setlogin_trampoline_addr(SB), RODATA, $8 DATA ·libc_setlogin_trampoline_addr(SB)/8, $libc_setlogin_trampoline<>(SB) TEXT libc_setpgid_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_setpgid(SB) - GLOBL ·libc_setpgid_trampoline_addr(SB), RODATA, $8 DATA ·libc_setpgid_trampoline_addr(SB)/8, $libc_setpgid_trampoline<>(SB) TEXT libc_setpriority_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_setpriority(SB) - GLOBL ·libc_setpriority_trampoline_addr(SB), RODATA, $8 DATA ·libc_setpriority_trampoline_addr(SB)/8, $libc_setpriority_trampoline<>(SB) TEXT libc_setprivexec_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_setprivexec(SB) - GLOBL ·libc_setprivexec_trampoline_addr(SB), RODATA, $8 DATA ·libc_setprivexec_trampoline_addr(SB)/8, $libc_setprivexec_trampoline<>(SB) TEXT libc_setregid_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_setregid(SB) - GLOBL ·libc_setregid_trampoline_addr(SB), RODATA, $8 DATA ·libc_setregid_trampoline_addr(SB)/8, $libc_setregid_trampoline<>(SB) TEXT libc_setreuid_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_setreuid(SB) - GLOBL ·libc_setreuid_trampoline_addr(SB), RODATA, $8 DATA ·libc_setreuid_trampoline_addr(SB)/8, $libc_setreuid_trampoline<>(SB) TEXT libc_setsid_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_setsid(SB) - GLOBL ·libc_setsid_trampoline_addr(SB), RODATA, $8 DATA ·libc_setsid_trampoline_addr(SB)/8, $libc_setsid_trampoline<>(SB) TEXT libc_settimeofday_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_settimeofday(SB) - GLOBL ·libc_settimeofday_trampoline_addr(SB), RODATA, $8 DATA ·libc_settimeofday_trampoline_addr(SB)/8, $libc_settimeofday_trampoline<>(SB) TEXT libc_setuid_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_setuid(SB) - GLOBL ·libc_setuid_trampoline_addr(SB), RODATA, $8 DATA ·libc_setuid_trampoline_addr(SB)/8, $libc_setuid_trampoline<>(SB) TEXT libc_symlink_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_symlink(SB) - GLOBL ·libc_symlink_trampoline_addr(SB), RODATA, $8 DATA ·libc_symlink_trampoline_addr(SB)/8, $libc_symlink_trampoline<>(SB) TEXT libc_symlinkat_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_symlinkat(SB) - GLOBL ·libc_symlinkat_trampoline_addr(SB), RODATA, $8 DATA ·libc_symlinkat_trampoline_addr(SB)/8, $libc_symlinkat_trampoline<>(SB) TEXT libc_sync_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_sync(SB) - GLOBL ·libc_sync_trampoline_addr(SB), RODATA, $8 DATA ·libc_sync_trampoline_addr(SB)/8, $libc_sync_trampoline<>(SB) TEXT libc_truncate_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_truncate(SB) - GLOBL ·libc_truncate_trampoline_addr(SB), RODATA, $8 DATA ·libc_truncate_trampoline_addr(SB)/8, $libc_truncate_trampoline<>(SB) TEXT libc_umask_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_umask(SB) - GLOBL ·libc_umask_trampoline_addr(SB), RODATA, $8 DATA ·libc_umask_trampoline_addr(SB)/8, $libc_umask_trampoline<>(SB) TEXT libc_undelete_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_undelete(SB) - GLOBL ·libc_undelete_trampoline_addr(SB), RODATA, $8 DATA ·libc_undelete_trampoline_addr(SB)/8, $libc_undelete_trampoline<>(SB) TEXT libc_unlink_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_unlink(SB) - GLOBL ·libc_unlink_trampoline_addr(SB), RODATA, $8 DATA ·libc_unlink_trampoline_addr(SB)/8, $libc_unlink_trampoline<>(SB) TEXT libc_unlinkat_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_unlinkat(SB) - GLOBL ·libc_unlinkat_trampoline_addr(SB), RODATA, $8 DATA ·libc_unlinkat_trampoline_addr(SB)/8, $libc_unlinkat_trampoline<>(SB) TEXT libc_unmount_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_unmount(SB) - GLOBL ·libc_unmount_trampoline_addr(SB), RODATA, $8 DATA ·libc_unmount_trampoline_addr(SB)/8, $libc_unmount_trampoline<>(SB) TEXT libc_write_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_write(SB) - GLOBL ·libc_write_trampoline_addr(SB), RODATA, $8 DATA ·libc_write_trampoline_addr(SB)/8, $libc_write_trampoline<>(SB) TEXT libc_mmap_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_mmap(SB) - GLOBL ·libc_mmap_trampoline_addr(SB), RODATA, $8 DATA ·libc_mmap_trampoline_addr(SB)/8, $libc_mmap_trampoline<>(SB) TEXT libc_munmap_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_munmap(SB) - GLOBL ·libc_munmap_trampoline_addr(SB), RODATA, $8 DATA ·libc_munmap_trampoline_addr(SB)/8, $libc_munmap_trampoline<>(SB) TEXT libc_fstat64_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_fstat64(SB) - GLOBL ·libc_fstat64_trampoline_addr(SB), RODATA, $8 DATA ·libc_fstat64_trampoline_addr(SB)/8, $libc_fstat64_trampoline<>(SB) TEXT libc_fstatat64_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_fstatat64(SB) - GLOBL ·libc_fstatat64_trampoline_addr(SB), RODATA, $8 DATA ·libc_fstatat64_trampoline_addr(SB)/8, $libc_fstatat64_trampoline<>(SB) TEXT libc_fstatfs64_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_fstatfs64(SB) - GLOBL ·libc_fstatfs64_trampoline_addr(SB), RODATA, $8 DATA ·libc_fstatfs64_trampoline_addr(SB)/8, $libc_fstatfs64_trampoline<>(SB) TEXT libc_getfsstat64_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_getfsstat64(SB) - GLOBL ·libc_getfsstat64_trampoline_addr(SB), RODATA, $8 DATA ·libc_getfsstat64_trampoline_addr(SB)/8, $libc_getfsstat64_trampoline<>(SB) TEXT libc_lstat64_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_lstat64(SB) - GLOBL ·libc_lstat64_trampoline_addr(SB), RODATA, $8 DATA ·libc_lstat64_trampoline_addr(SB)/8, $libc_lstat64_trampoline<>(SB) TEXT libc_ptrace_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_ptrace(SB) - GLOBL ·libc_ptrace_trampoline_addr(SB), RODATA, $8 DATA ·libc_ptrace_trampoline_addr(SB)/8, $libc_ptrace_trampoline<>(SB) TEXT libc_stat64_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_stat64(SB) - GLOBL ·libc_stat64_trampoline_addr(SB), RODATA, $8 DATA ·libc_stat64_trampoline_addr(SB)/8, $libc_stat64_trampoline<>(SB) TEXT libc_statfs64_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_statfs64(SB) - GLOBL ·libc_statfs64_trampoline_addr(SB), RODATA, $8 DATA ·libc_statfs64_trampoline_addr(SB)/8, $libc_statfs64_trampoline<>(SB) diff --git a/vendor/golang.org/x/sys/unix/zsyscall_darwin_arm64.go b/vendor/golang.org/x/sys/unix/zsyscall_darwin_arm64.go index 51d6f3f..b18edbd 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_darwin_arm64.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_darwin_arm64.go @@ -725,6 +725,12 @@ func ioctl(fd int, req uint, arg uintptr) (err error) { return } +var libc_ioctl_trampoline_addr uintptr + +//go:cgo_import_dynamic libc_ioctl ioctl "/usr/lib/libSystem.B.dylib" + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + func ioctlPtr(fd int, req uint, arg unsafe.Pointer) (err error) { _, _, e1 := syscall_syscall(libc_ioctl_trampoline_addr, uintptr(fd), uintptr(req), uintptr(arg)) if e1 != 0 { @@ -733,10 +739,6 @@ func ioctlPtr(fd int, req uint, arg unsafe.Pointer) (err error) { return } -var libc_ioctl_trampoline_addr uintptr - -//go:cgo_import_dynamic libc_ioctl ioctl "/usr/lib/libSystem.B.dylib" - // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT func sysctl(mib []_C_int, old *byte, oldlen *uintptr, new *byte, newlen uintptr) (err error) { @@ -2410,28 +2412,6 @@ var libc_munmap_trampoline_addr uintptr // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT -func readlen(fd int, buf *byte, nbuf int) (n int, err error) { - r0, _, e1 := syscall_syscall(libc_read_trampoline_addr, uintptr(fd), uintptr(unsafe.Pointer(buf)), uintptr(nbuf)) - n = int(r0) - if e1 != 0 { - err = errnoErr(e1) - } - return -} - -// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT - -func writelen(fd int, buf *byte, nbuf int) (n int, err error) { - r0, _, e1 := syscall_syscall(libc_write_trampoline_addr, uintptr(fd), uintptr(unsafe.Pointer(buf)), uintptr(nbuf)) - n = int(r0) - if e1 != 0 { - err = errnoErr(e1) - } - return -} - -// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT - func Fstat(fd int, stat *Stat_t) (err error) { _, _, e1 := syscall_syscall(libc_fstat_trampoline_addr, uintptr(fd), uintptr(unsafe.Pointer(stat)), 0) if e1 != 0 { @@ -2521,14 +2501,6 @@ func ptrace1(request int, pid int, addr uintptr, data uintptr) (err error) { return } -func ptrace1Ptr(request int, pid int, addr uintptr, data unsafe.Pointer) (err error) { - _, _, e1 := syscall_syscall6(libc_ptrace_trampoline_addr, uintptr(request), uintptr(pid), addr, uintptr(data), 0, 0) - if e1 != 0 { - err = errnoErr(e1) - } - return -} - var libc_ptrace_trampoline_addr uintptr //go:cgo_import_dynamic libc_ptrace ptrace "/usr/lib/libSystem.B.dylib" diff --git a/vendor/golang.org/x/sys/unix/zsyscall_darwin_arm64.s b/vendor/golang.org/x/sys/unix/zsyscall_darwin_arm64.s index c3b82c0..08362c1 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_darwin_arm64.s +++ b/vendor/golang.org/x/sys/unix/zsyscall_darwin_arm64.s @@ -5,703 +5,586 @@ TEXT libc_fdopendir_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_fdopendir(SB) - GLOBL ·libc_fdopendir_trampoline_addr(SB), RODATA, $8 DATA ·libc_fdopendir_trampoline_addr(SB)/8, $libc_fdopendir_trampoline<>(SB) TEXT libc_getgroups_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_getgroups(SB) - GLOBL ·libc_getgroups_trampoline_addr(SB), RODATA, $8 DATA ·libc_getgroups_trampoline_addr(SB)/8, $libc_getgroups_trampoline<>(SB) TEXT libc_setgroups_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_setgroups(SB) - GLOBL ·libc_setgroups_trampoline_addr(SB), RODATA, $8 DATA ·libc_setgroups_trampoline_addr(SB)/8, $libc_setgroups_trampoline<>(SB) TEXT libc_wait4_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_wait4(SB) - GLOBL ·libc_wait4_trampoline_addr(SB), RODATA, $8 DATA ·libc_wait4_trampoline_addr(SB)/8, $libc_wait4_trampoline<>(SB) TEXT libc_accept_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_accept(SB) - GLOBL ·libc_accept_trampoline_addr(SB), RODATA, $8 DATA ·libc_accept_trampoline_addr(SB)/8, $libc_accept_trampoline<>(SB) TEXT libc_bind_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_bind(SB) - GLOBL ·libc_bind_trampoline_addr(SB), RODATA, $8 DATA ·libc_bind_trampoline_addr(SB)/8, $libc_bind_trampoline<>(SB) TEXT libc_connect_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_connect(SB) - GLOBL ·libc_connect_trampoline_addr(SB), RODATA, $8 DATA ·libc_connect_trampoline_addr(SB)/8, $libc_connect_trampoline<>(SB) TEXT libc_socket_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_socket(SB) - GLOBL ·libc_socket_trampoline_addr(SB), RODATA, $8 DATA ·libc_socket_trampoline_addr(SB)/8, $libc_socket_trampoline<>(SB) TEXT libc_getsockopt_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_getsockopt(SB) - GLOBL ·libc_getsockopt_trampoline_addr(SB), RODATA, $8 DATA ·libc_getsockopt_trampoline_addr(SB)/8, $libc_getsockopt_trampoline<>(SB) TEXT libc_setsockopt_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_setsockopt(SB) - GLOBL ·libc_setsockopt_trampoline_addr(SB), RODATA, $8 DATA ·libc_setsockopt_trampoline_addr(SB)/8, $libc_setsockopt_trampoline<>(SB) TEXT libc_getpeername_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_getpeername(SB) - GLOBL ·libc_getpeername_trampoline_addr(SB), RODATA, $8 DATA ·libc_getpeername_trampoline_addr(SB)/8, $libc_getpeername_trampoline<>(SB) TEXT libc_getsockname_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_getsockname(SB) - GLOBL ·libc_getsockname_trampoline_addr(SB), RODATA, $8 DATA ·libc_getsockname_trampoline_addr(SB)/8, $libc_getsockname_trampoline<>(SB) TEXT libc_shutdown_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_shutdown(SB) - GLOBL ·libc_shutdown_trampoline_addr(SB), RODATA, $8 DATA ·libc_shutdown_trampoline_addr(SB)/8, $libc_shutdown_trampoline<>(SB) TEXT libc_socketpair_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_socketpair(SB) - GLOBL ·libc_socketpair_trampoline_addr(SB), RODATA, $8 DATA ·libc_socketpair_trampoline_addr(SB)/8, $libc_socketpair_trampoline<>(SB) TEXT libc_recvfrom_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_recvfrom(SB) - GLOBL ·libc_recvfrom_trampoline_addr(SB), RODATA, $8 DATA ·libc_recvfrom_trampoline_addr(SB)/8, $libc_recvfrom_trampoline<>(SB) TEXT libc_sendto_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_sendto(SB) - GLOBL ·libc_sendto_trampoline_addr(SB), RODATA, $8 DATA ·libc_sendto_trampoline_addr(SB)/8, $libc_sendto_trampoline<>(SB) TEXT libc_recvmsg_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_recvmsg(SB) - GLOBL ·libc_recvmsg_trampoline_addr(SB), RODATA, $8 DATA ·libc_recvmsg_trampoline_addr(SB)/8, $libc_recvmsg_trampoline<>(SB) TEXT libc_sendmsg_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_sendmsg(SB) - GLOBL ·libc_sendmsg_trampoline_addr(SB), RODATA, $8 DATA ·libc_sendmsg_trampoline_addr(SB)/8, $libc_sendmsg_trampoline<>(SB) TEXT libc_kevent_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_kevent(SB) - GLOBL ·libc_kevent_trampoline_addr(SB), RODATA, $8 DATA ·libc_kevent_trampoline_addr(SB)/8, $libc_kevent_trampoline<>(SB) TEXT libc_utimes_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_utimes(SB) - GLOBL ·libc_utimes_trampoline_addr(SB), RODATA, $8 DATA ·libc_utimes_trampoline_addr(SB)/8, $libc_utimes_trampoline<>(SB) TEXT libc_futimes_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_futimes(SB) - GLOBL ·libc_futimes_trampoline_addr(SB), RODATA, $8 DATA ·libc_futimes_trampoline_addr(SB)/8, $libc_futimes_trampoline<>(SB) TEXT libc_poll_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_poll(SB) - GLOBL ·libc_poll_trampoline_addr(SB), RODATA, $8 DATA ·libc_poll_trampoline_addr(SB)/8, $libc_poll_trampoline<>(SB) TEXT libc_madvise_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_madvise(SB) - GLOBL ·libc_madvise_trampoline_addr(SB), RODATA, $8 DATA ·libc_madvise_trampoline_addr(SB)/8, $libc_madvise_trampoline<>(SB) TEXT libc_mlock_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_mlock(SB) - GLOBL ·libc_mlock_trampoline_addr(SB), RODATA, $8 DATA ·libc_mlock_trampoline_addr(SB)/8, $libc_mlock_trampoline<>(SB) TEXT libc_mlockall_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_mlockall(SB) - GLOBL ·libc_mlockall_trampoline_addr(SB), RODATA, $8 DATA ·libc_mlockall_trampoline_addr(SB)/8, $libc_mlockall_trampoline<>(SB) TEXT libc_mprotect_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_mprotect(SB) - GLOBL ·libc_mprotect_trampoline_addr(SB), RODATA, $8 DATA ·libc_mprotect_trampoline_addr(SB)/8, $libc_mprotect_trampoline<>(SB) TEXT libc_msync_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_msync(SB) - GLOBL ·libc_msync_trampoline_addr(SB), RODATA, $8 DATA ·libc_msync_trampoline_addr(SB)/8, $libc_msync_trampoline<>(SB) TEXT libc_munlock_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_munlock(SB) - GLOBL ·libc_munlock_trampoline_addr(SB), RODATA, $8 DATA ·libc_munlock_trampoline_addr(SB)/8, $libc_munlock_trampoline<>(SB) TEXT libc_munlockall_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_munlockall(SB) - GLOBL ·libc_munlockall_trampoline_addr(SB), RODATA, $8 DATA ·libc_munlockall_trampoline_addr(SB)/8, $libc_munlockall_trampoline<>(SB) TEXT libc_closedir_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_closedir(SB) - GLOBL ·libc_closedir_trampoline_addr(SB), RODATA, $8 DATA ·libc_closedir_trampoline_addr(SB)/8, $libc_closedir_trampoline<>(SB) TEXT libc_readdir_r_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_readdir_r(SB) - GLOBL ·libc_readdir_r_trampoline_addr(SB), RODATA, $8 DATA ·libc_readdir_r_trampoline_addr(SB)/8, $libc_readdir_r_trampoline<>(SB) TEXT libc_pipe_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_pipe(SB) - GLOBL ·libc_pipe_trampoline_addr(SB), RODATA, $8 DATA ·libc_pipe_trampoline_addr(SB)/8, $libc_pipe_trampoline<>(SB) TEXT libc_getxattr_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_getxattr(SB) - GLOBL ·libc_getxattr_trampoline_addr(SB), RODATA, $8 DATA ·libc_getxattr_trampoline_addr(SB)/8, $libc_getxattr_trampoline<>(SB) TEXT libc_fgetxattr_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_fgetxattr(SB) - GLOBL ·libc_fgetxattr_trampoline_addr(SB), RODATA, $8 DATA ·libc_fgetxattr_trampoline_addr(SB)/8, $libc_fgetxattr_trampoline<>(SB) TEXT libc_setxattr_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_setxattr(SB) - GLOBL ·libc_setxattr_trampoline_addr(SB), RODATA, $8 DATA ·libc_setxattr_trampoline_addr(SB)/8, $libc_setxattr_trampoline<>(SB) TEXT libc_fsetxattr_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_fsetxattr(SB) - GLOBL ·libc_fsetxattr_trampoline_addr(SB), RODATA, $8 DATA ·libc_fsetxattr_trampoline_addr(SB)/8, $libc_fsetxattr_trampoline<>(SB) TEXT libc_removexattr_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_removexattr(SB) - GLOBL ·libc_removexattr_trampoline_addr(SB), RODATA, $8 DATA ·libc_removexattr_trampoline_addr(SB)/8, $libc_removexattr_trampoline<>(SB) TEXT libc_fremovexattr_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_fremovexattr(SB) - GLOBL ·libc_fremovexattr_trampoline_addr(SB), RODATA, $8 DATA ·libc_fremovexattr_trampoline_addr(SB)/8, $libc_fremovexattr_trampoline<>(SB) TEXT libc_listxattr_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_listxattr(SB) - GLOBL ·libc_listxattr_trampoline_addr(SB), RODATA, $8 DATA ·libc_listxattr_trampoline_addr(SB)/8, $libc_listxattr_trampoline<>(SB) TEXT libc_flistxattr_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_flistxattr(SB) - GLOBL ·libc_flistxattr_trampoline_addr(SB), RODATA, $8 DATA ·libc_flistxattr_trampoline_addr(SB)/8, $libc_flistxattr_trampoline<>(SB) TEXT libc_utimensat_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_utimensat(SB) - GLOBL ·libc_utimensat_trampoline_addr(SB), RODATA, $8 DATA ·libc_utimensat_trampoline_addr(SB)/8, $libc_utimensat_trampoline<>(SB) TEXT libc_fcntl_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_fcntl(SB) - GLOBL ·libc_fcntl_trampoline_addr(SB), RODATA, $8 DATA ·libc_fcntl_trampoline_addr(SB)/8, $libc_fcntl_trampoline<>(SB) TEXT libc_kill_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_kill(SB) - GLOBL ·libc_kill_trampoline_addr(SB), RODATA, $8 DATA ·libc_kill_trampoline_addr(SB)/8, $libc_kill_trampoline<>(SB) TEXT libc_ioctl_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_ioctl(SB) - GLOBL ·libc_ioctl_trampoline_addr(SB), RODATA, $8 DATA ·libc_ioctl_trampoline_addr(SB)/8, $libc_ioctl_trampoline<>(SB) TEXT libc_sysctl_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_sysctl(SB) - GLOBL ·libc_sysctl_trampoline_addr(SB), RODATA, $8 DATA ·libc_sysctl_trampoline_addr(SB)/8, $libc_sysctl_trampoline<>(SB) TEXT libc_sendfile_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_sendfile(SB) - GLOBL ·libc_sendfile_trampoline_addr(SB), RODATA, $8 DATA ·libc_sendfile_trampoline_addr(SB)/8, $libc_sendfile_trampoline<>(SB) TEXT libc_shmat_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_shmat(SB) - GLOBL ·libc_shmat_trampoline_addr(SB), RODATA, $8 DATA ·libc_shmat_trampoline_addr(SB)/8, $libc_shmat_trampoline<>(SB) TEXT libc_shmctl_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_shmctl(SB) - GLOBL ·libc_shmctl_trampoline_addr(SB), RODATA, $8 DATA ·libc_shmctl_trampoline_addr(SB)/8, $libc_shmctl_trampoline<>(SB) TEXT libc_shmdt_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_shmdt(SB) - GLOBL ·libc_shmdt_trampoline_addr(SB), RODATA, $8 DATA ·libc_shmdt_trampoline_addr(SB)/8, $libc_shmdt_trampoline<>(SB) TEXT libc_shmget_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_shmget(SB) - GLOBL ·libc_shmget_trampoline_addr(SB), RODATA, $8 DATA ·libc_shmget_trampoline_addr(SB)/8, $libc_shmget_trampoline<>(SB) TEXT libc_access_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_access(SB) - GLOBL ·libc_access_trampoline_addr(SB), RODATA, $8 DATA ·libc_access_trampoline_addr(SB)/8, $libc_access_trampoline<>(SB) TEXT libc_adjtime_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_adjtime(SB) - GLOBL ·libc_adjtime_trampoline_addr(SB), RODATA, $8 DATA ·libc_adjtime_trampoline_addr(SB)/8, $libc_adjtime_trampoline<>(SB) TEXT libc_chdir_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_chdir(SB) - GLOBL ·libc_chdir_trampoline_addr(SB), RODATA, $8 DATA ·libc_chdir_trampoline_addr(SB)/8, $libc_chdir_trampoline<>(SB) TEXT libc_chflags_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_chflags(SB) - GLOBL ·libc_chflags_trampoline_addr(SB), RODATA, $8 DATA ·libc_chflags_trampoline_addr(SB)/8, $libc_chflags_trampoline<>(SB) TEXT libc_chmod_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_chmod(SB) - GLOBL ·libc_chmod_trampoline_addr(SB), RODATA, $8 DATA ·libc_chmod_trampoline_addr(SB)/8, $libc_chmod_trampoline<>(SB) TEXT libc_chown_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_chown(SB) - GLOBL ·libc_chown_trampoline_addr(SB), RODATA, $8 DATA ·libc_chown_trampoline_addr(SB)/8, $libc_chown_trampoline<>(SB) TEXT libc_chroot_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_chroot(SB) - GLOBL ·libc_chroot_trampoline_addr(SB), RODATA, $8 DATA ·libc_chroot_trampoline_addr(SB)/8, $libc_chroot_trampoline<>(SB) TEXT libc_clock_gettime_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_clock_gettime(SB) - GLOBL ·libc_clock_gettime_trampoline_addr(SB), RODATA, $8 DATA ·libc_clock_gettime_trampoline_addr(SB)/8, $libc_clock_gettime_trampoline<>(SB) TEXT libc_close_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_close(SB) - GLOBL ·libc_close_trampoline_addr(SB), RODATA, $8 DATA ·libc_close_trampoline_addr(SB)/8, $libc_close_trampoline<>(SB) TEXT libc_clonefile_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_clonefile(SB) - GLOBL ·libc_clonefile_trampoline_addr(SB), RODATA, $8 DATA ·libc_clonefile_trampoline_addr(SB)/8, $libc_clonefile_trampoline<>(SB) TEXT libc_clonefileat_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_clonefileat(SB) - GLOBL ·libc_clonefileat_trampoline_addr(SB), RODATA, $8 DATA ·libc_clonefileat_trampoline_addr(SB)/8, $libc_clonefileat_trampoline<>(SB) TEXT libc_dup_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_dup(SB) - GLOBL ·libc_dup_trampoline_addr(SB), RODATA, $8 DATA ·libc_dup_trampoline_addr(SB)/8, $libc_dup_trampoline<>(SB) TEXT libc_dup2_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_dup2(SB) - GLOBL ·libc_dup2_trampoline_addr(SB), RODATA, $8 DATA ·libc_dup2_trampoline_addr(SB)/8, $libc_dup2_trampoline<>(SB) TEXT libc_exchangedata_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_exchangedata(SB) - GLOBL ·libc_exchangedata_trampoline_addr(SB), RODATA, $8 DATA ·libc_exchangedata_trampoline_addr(SB)/8, $libc_exchangedata_trampoline<>(SB) TEXT libc_exit_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_exit(SB) - GLOBL ·libc_exit_trampoline_addr(SB), RODATA, $8 DATA ·libc_exit_trampoline_addr(SB)/8, $libc_exit_trampoline<>(SB) TEXT libc_faccessat_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_faccessat(SB) - GLOBL ·libc_faccessat_trampoline_addr(SB), RODATA, $8 DATA ·libc_faccessat_trampoline_addr(SB)/8, $libc_faccessat_trampoline<>(SB) TEXT libc_fchdir_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_fchdir(SB) - GLOBL ·libc_fchdir_trampoline_addr(SB), RODATA, $8 DATA ·libc_fchdir_trampoline_addr(SB)/8, $libc_fchdir_trampoline<>(SB) TEXT libc_fchflags_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_fchflags(SB) - GLOBL ·libc_fchflags_trampoline_addr(SB), RODATA, $8 DATA ·libc_fchflags_trampoline_addr(SB)/8, $libc_fchflags_trampoline<>(SB) TEXT libc_fchmod_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_fchmod(SB) - GLOBL ·libc_fchmod_trampoline_addr(SB), RODATA, $8 DATA ·libc_fchmod_trampoline_addr(SB)/8, $libc_fchmod_trampoline<>(SB) TEXT libc_fchmodat_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_fchmodat(SB) - GLOBL ·libc_fchmodat_trampoline_addr(SB), RODATA, $8 DATA ·libc_fchmodat_trampoline_addr(SB)/8, $libc_fchmodat_trampoline<>(SB) TEXT libc_fchown_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_fchown(SB) - GLOBL ·libc_fchown_trampoline_addr(SB), RODATA, $8 DATA ·libc_fchown_trampoline_addr(SB)/8, $libc_fchown_trampoline<>(SB) TEXT libc_fchownat_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_fchownat(SB) - GLOBL ·libc_fchownat_trampoline_addr(SB), RODATA, $8 DATA ·libc_fchownat_trampoline_addr(SB)/8, $libc_fchownat_trampoline<>(SB) TEXT libc_fclonefileat_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_fclonefileat(SB) - GLOBL ·libc_fclonefileat_trampoline_addr(SB), RODATA, $8 DATA ·libc_fclonefileat_trampoline_addr(SB)/8, $libc_fclonefileat_trampoline<>(SB) TEXT libc_flock_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_flock(SB) - GLOBL ·libc_flock_trampoline_addr(SB), RODATA, $8 DATA ·libc_flock_trampoline_addr(SB)/8, $libc_flock_trampoline<>(SB) TEXT libc_fpathconf_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_fpathconf(SB) - GLOBL ·libc_fpathconf_trampoline_addr(SB), RODATA, $8 DATA ·libc_fpathconf_trampoline_addr(SB)/8, $libc_fpathconf_trampoline<>(SB) TEXT libc_fsync_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_fsync(SB) - GLOBL ·libc_fsync_trampoline_addr(SB), RODATA, $8 DATA ·libc_fsync_trampoline_addr(SB)/8, $libc_fsync_trampoline<>(SB) TEXT libc_ftruncate_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_ftruncate(SB) - GLOBL ·libc_ftruncate_trampoline_addr(SB), RODATA, $8 DATA ·libc_ftruncate_trampoline_addr(SB)/8, $libc_ftruncate_trampoline<>(SB) TEXT libc_getcwd_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_getcwd(SB) - GLOBL ·libc_getcwd_trampoline_addr(SB), RODATA, $8 DATA ·libc_getcwd_trampoline_addr(SB)/8, $libc_getcwd_trampoline<>(SB) TEXT libc_getdtablesize_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_getdtablesize(SB) - GLOBL ·libc_getdtablesize_trampoline_addr(SB), RODATA, $8 DATA ·libc_getdtablesize_trampoline_addr(SB)/8, $libc_getdtablesize_trampoline<>(SB) TEXT libc_getegid_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_getegid(SB) - GLOBL ·libc_getegid_trampoline_addr(SB), RODATA, $8 DATA ·libc_getegid_trampoline_addr(SB)/8, $libc_getegid_trampoline<>(SB) TEXT libc_geteuid_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_geteuid(SB) - GLOBL ·libc_geteuid_trampoline_addr(SB), RODATA, $8 DATA ·libc_geteuid_trampoline_addr(SB)/8, $libc_geteuid_trampoline<>(SB) TEXT libc_getgid_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_getgid(SB) - GLOBL ·libc_getgid_trampoline_addr(SB), RODATA, $8 DATA ·libc_getgid_trampoline_addr(SB)/8, $libc_getgid_trampoline<>(SB) TEXT libc_getpgid_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_getpgid(SB) - GLOBL ·libc_getpgid_trampoline_addr(SB), RODATA, $8 DATA ·libc_getpgid_trampoline_addr(SB)/8, $libc_getpgid_trampoline<>(SB) TEXT libc_getpgrp_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_getpgrp(SB) - GLOBL ·libc_getpgrp_trampoline_addr(SB), RODATA, $8 DATA ·libc_getpgrp_trampoline_addr(SB)/8, $libc_getpgrp_trampoline<>(SB) TEXT libc_getpid_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_getpid(SB) - GLOBL ·libc_getpid_trampoline_addr(SB), RODATA, $8 DATA ·libc_getpid_trampoline_addr(SB)/8, $libc_getpid_trampoline<>(SB) TEXT libc_getppid_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_getppid(SB) - GLOBL ·libc_getppid_trampoline_addr(SB), RODATA, $8 DATA ·libc_getppid_trampoline_addr(SB)/8, $libc_getppid_trampoline<>(SB) TEXT libc_getpriority_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_getpriority(SB) - GLOBL ·libc_getpriority_trampoline_addr(SB), RODATA, $8 DATA ·libc_getpriority_trampoline_addr(SB)/8, $libc_getpriority_trampoline<>(SB) TEXT libc_getrlimit_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_getrlimit(SB) - GLOBL ·libc_getrlimit_trampoline_addr(SB), RODATA, $8 DATA ·libc_getrlimit_trampoline_addr(SB)/8, $libc_getrlimit_trampoline<>(SB) TEXT libc_getrusage_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_getrusage(SB) - GLOBL ·libc_getrusage_trampoline_addr(SB), RODATA, $8 DATA ·libc_getrusage_trampoline_addr(SB)/8, $libc_getrusage_trampoline<>(SB) TEXT libc_getsid_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_getsid(SB) - GLOBL ·libc_getsid_trampoline_addr(SB), RODATA, $8 DATA ·libc_getsid_trampoline_addr(SB)/8, $libc_getsid_trampoline<>(SB) TEXT libc_gettimeofday_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_gettimeofday(SB) - GLOBL ·libc_gettimeofday_trampoline_addr(SB), RODATA, $8 DATA ·libc_gettimeofday_trampoline_addr(SB)/8, $libc_gettimeofday_trampoline<>(SB) TEXT libc_getuid_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_getuid(SB) - GLOBL ·libc_getuid_trampoline_addr(SB), RODATA, $8 DATA ·libc_getuid_trampoline_addr(SB)/8, $libc_getuid_trampoline<>(SB) TEXT libc_issetugid_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_issetugid(SB) - GLOBL ·libc_issetugid_trampoline_addr(SB), RODATA, $8 DATA ·libc_issetugid_trampoline_addr(SB)/8, $libc_issetugid_trampoline<>(SB) TEXT libc_kqueue_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_kqueue(SB) - GLOBL ·libc_kqueue_trampoline_addr(SB), RODATA, $8 DATA ·libc_kqueue_trampoline_addr(SB)/8, $libc_kqueue_trampoline<>(SB) TEXT libc_lchown_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_lchown(SB) - GLOBL ·libc_lchown_trampoline_addr(SB), RODATA, $8 DATA ·libc_lchown_trampoline_addr(SB)/8, $libc_lchown_trampoline<>(SB) TEXT libc_link_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_link(SB) - GLOBL ·libc_link_trampoline_addr(SB), RODATA, $8 DATA ·libc_link_trampoline_addr(SB)/8, $libc_link_trampoline<>(SB) TEXT libc_linkat_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_linkat(SB) - GLOBL ·libc_linkat_trampoline_addr(SB), RODATA, $8 DATA ·libc_linkat_trampoline_addr(SB)/8, $libc_linkat_trampoline<>(SB) TEXT libc_listen_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_listen(SB) - GLOBL ·libc_listen_trampoline_addr(SB), RODATA, $8 DATA ·libc_listen_trampoline_addr(SB)/8, $libc_listen_trampoline<>(SB) TEXT libc_mkdir_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_mkdir(SB) - GLOBL ·libc_mkdir_trampoline_addr(SB), RODATA, $8 DATA ·libc_mkdir_trampoline_addr(SB)/8, $libc_mkdir_trampoline<>(SB) TEXT libc_mkdirat_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_mkdirat(SB) - GLOBL ·libc_mkdirat_trampoline_addr(SB), RODATA, $8 DATA ·libc_mkdirat_trampoline_addr(SB)/8, $libc_mkdirat_trampoline<>(SB) TEXT libc_mkfifo_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_mkfifo(SB) - GLOBL ·libc_mkfifo_trampoline_addr(SB), RODATA, $8 DATA ·libc_mkfifo_trampoline_addr(SB)/8, $libc_mkfifo_trampoline<>(SB) TEXT libc_mknod_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_mknod(SB) - GLOBL ·libc_mknod_trampoline_addr(SB), RODATA, $8 DATA ·libc_mknod_trampoline_addr(SB)/8, $libc_mknod_trampoline<>(SB) TEXT libc_mount_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_mount(SB) - GLOBL ·libc_mount_trampoline_addr(SB), RODATA, $8 DATA ·libc_mount_trampoline_addr(SB)/8, $libc_mount_trampoline<>(SB) TEXT libc_open_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_open(SB) - GLOBL ·libc_open_trampoline_addr(SB), RODATA, $8 DATA ·libc_open_trampoline_addr(SB)/8, $libc_open_trampoline<>(SB) TEXT libc_openat_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_openat(SB) - GLOBL ·libc_openat_trampoline_addr(SB), RODATA, $8 DATA ·libc_openat_trampoline_addr(SB)/8, $libc_openat_trampoline<>(SB) TEXT libc_pathconf_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_pathconf(SB) - GLOBL ·libc_pathconf_trampoline_addr(SB), RODATA, $8 DATA ·libc_pathconf_trampoline_addr(SB)/8, $libc_pathconf_trampoline<>(SB) TEXT libc_pread_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_pread(SB) - GLOBL ·libc_pread_trampoline_addr(SB), RODATA, $8 DATA ·libc_pread_trampoline_addr(SB)/8, $libc_pread_trampoline<>(SB) TEXT libc_pwrite_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_pwrite(SB) - GLOBL ·libc_pwrite_trampoline_addr(SB), RODATA, $8 DATA ·libc_pwrite_trampoline_addr(SB)/8, $libc_pwrite_trampoline<>(SB) TEXT libc_read_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_read(SB) - GLOBL ·libc_read_trampoline_addr(SB), RODATA, $8 DATA ·libc_read_trampoline_addr(SB)/8, $libc_read_trampoline<>(SB) TEXT libc_readlink_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_readlink(SB) - GLOBL ·libc_readlink_trampoline_addr(SB), RODATA, $8 DATA ·libc_readlink_trampoline_addr(SB)/8, $libc_readlink_trampoline<>(SB) TEXT libc_readlinkat_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_readlinkat(SB) - GLOBL ·libc_readlinkat_trampoline_addr(SB), RODATA, $8 DATA ·libc_readlinkat_trampoline_addr(SB)/8, $libc_readlinkat_trampoline<>(SB) TEXT libc_rename_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_rename(SB) - GLOBL ·libc_rename_trampoline_addr(SB), RODATA, $8 DATA ·libc_rename_trampoline_addr(SB)/8, $libc_rename_trampoline<>(SB) TEXT libc_renameat_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_renameat(SB) - GLOBL ·libc_renameat_trampoline_addr(SB), RODATA, $8 DATA ·libc_renameat_trampoline_addr(SB)/8, $libc_renameat_trampoline<>(SB) TEXT libc_revoke_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_revoke(SB) - GLOBL ·libc_revoke_trampoline_addr(SB), RODATA, $8 DATA ·libc_revoke_trampoline_addr(SB)/8, $libc_revoke_trampoline<>(SB) TEXT libc_rmdir_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_rmdir(SB) - GLOBL ·libc_rmdir_trampoline_addr(SB), RODATA, $8 DATA ·libc_rmdir_trampoline_addr(SB)/8, $libc_rmdir_trampoline<>(SB) TEXT libc_lseek_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_lseek(SB) - GLOBL ·libc_lseek_trampoline_addr(SB), RODATA, $8 DATA ·libc_lseek_trampoline_addr(SB)/8, $libc_lseek_trampoline<>(SB) TEXT libc_select_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_select(SB) - GLOBL ·libc_select_trampoline_addr(SB), RODATA, $8 DATA ·libc_select_trampoline_addr(SB)/8, $libc_select_trampoline<>(SB) @@ -712,192 +595,160 @@ DATA ·libc_setattrlist_trampoline_addr(SB)/8, $libc_setattrlist_trampoline<>(SB TEXT libc_setegid_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_setegid(SB) - GLOBL ·libc_setegid_trampoline_addr(SB), RODATA, $8 DATA ·libc_setegid_trampoline_addr(SB)/8, $libc_setegid_trampoline<>(SB) TEXT libc_seteuid_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_seteuid(SB) - GLOBL ·libc_seteuid_trampoline_addr(SB), RODATA, $8 DATA ·libc_seteuid_trampoline_addr(SB)/8, $libc_seteuid_trampoline<>(SB) TEXT libc_setgid_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_setgid(SB) - GLOBL ·libc_setgid_trampoline_addr(SB), RODATA, $8 DATA ·libc_setgid_trampoline_addr(SB)/8, $libc_setgid_trampoline<>(SB) TEXT libc_setlogin_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_setlogin(SB) - GLOBL ·libc_setlogin_trampoline_addr(SB), RODATA, $8 DATA ·libc_setlogin_trampoline_addr(SB)/8, $libc_setlogin_trampoline<>(SB) TEXT libc_setpgid_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_setpgid(SB) - GLOBL ·libc_setpgid_trampoline_addr(SB), RODATA, $8 DATA ·libc_setpgid_trampoline_addr(SB)/8, $libc_setpgid_trampoline<>(SB) TEXT libc_setpriority_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_setpriority(SB) - GLOBL ·libc_setpriority_trampoline_addr(SB), RODATA, $8 DATA ·libc_setpriority_trampoline_addr(SB)/8, $libc_setpriority_trampoline<>(SB) TEXT libc_setprivexec_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_setprivexec(SB) - GLOBL ·libc_setprivexec_trampoline_addr(SB), RODATA, $8 DATA ·libc_setprivexec_trampoline_addr(SB)/8, $libc_setprivexec_trampoline<>(SB) TEXT libc_setregid_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_setregid(SB) - GLOBL ·libc_setregid_trampoline_addr(SB), RODATA, $8 DATA ·libc_setregid_trampoline_addr(SB)/8, $libc_setregid_trampoline<>(SB) TEXT libc_setreuid_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_setreuid(SB) - GLOBL ·libc_setreuid_trampoline_addr(SB), RODATA, $8 DATA ·libc_setreuid_trampoline_addr(SB)/8, $libc_setreuid_trampoline<>(SB) TEXT libc_setsid_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_setsid(SB) - GLOBL ·libc_setsid_trampoline_addr(SB), RODATA, $8 DATA ·libc_setsid_trampoline_addr(SB)/8, $libc_setsid_trampoline<>(SB) TEXT libc_settimeofday_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_settimeofday(SB) - GLOBL ·libc_settimeofday_trampoline_addr(SB), RODATA, $8 DATA ·libc_settimeofday_trampoline_addr(SB)/8, $libc_settimeofday_trampoline<>(SB) TEXT libc_setuid_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_setuid(SB) - GLOBL ·libc_setuid_trampoline_addr(SB), RODATA, $8 DATA ·libc_setuid_trampoline_addr(SB)/8, $libc_setuid_trampoline<>(SB) TEXT libc_symlink_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_symlink(SB) - GLOBL ·libc_symlink_trampoline_addr(SB), RODATA, $8 DATA ·libc_symlink_trampoline_addr(SB)/8, $libc_symlink_trampoline<>(SB) TEXT libc_symlinkat_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_symlinkat(SB) - GLOBL ·libc_symlinkat_trampoline_addr(SB), RODATA, $8 DATA ·libc_symlinkat_trampoline_addr(SB)/8, $libc_symlinkat_trampoline<>(SB) TEXT libc_sync_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_sync(SB) - GLOBL ·libc_sync_trampoline_addr(SB), RODATA, $8 DATA ·libc_sync_trampoline_addr(SB)/8, $libc_sync_trampoline<>(SB) TEXT libc_truncate_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_truncate(SB) - GLOBL ·libc_truncate_trampoline_addr(SB), RODATA, $8 DATA ·libc_truncate_trampoline_addr(SB)/8, $libc_truncate_trampoline<>(SB) TEXT libc_umask_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_umask(SB) - GLOBL ·libc_umask_trampoline_addr(SB), RODATA, $8 DATA ·libc_umask_trampoline_addr(SB)/8, $libc_umask_trampoline<>(SB) TEXT libc_undelete_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_undelete(SB) - GLOBL ·libc_undelete_trampoline_addr(SB), RODATA, $8 DATA ·libc_undelete_trampoline_addr(SB)/8, $libc_undelete_trampoline<>(SB) TEXT libc_unlink_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_unlink(SB) - GLOBL ·libc_unlink_trampoline_addr(SB), RODATA, $8 DATA ·libc_unlink_trampoline_addr(SB)/8, $libc_unlink_trampoline<>(SB) TEXT libc_unlinkat_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_unlinkat(SB) - GLOBL ·libc_unlinkat_trampoline_addr(SB), RODATA, $8 DATA ·libc_unlinkat_trampoline_addr(SB)/8, $libc_unlinkat_trampoline<>(SB) TEXT libc_unmount_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_unmount(SB) - GLOBL ·libc_unmount_trampoline_addr(SB), RODATA, $8 DATA ·libc_unmount_trampoline_addr(SB)/8, $libc_unmount_trampoline<>(SB) TEXT libc_write_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_write(SB) - GLOBL ·libc_write_trampoline_addr(SB), RODATA, $8 DATA ·libc_write_trampoline_addr(SB)/8, $libc_write_trampoline<>(SB) TEXT libc_mmap_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_mmap(SB) - GLOBL ·libc_mmap_trampoline_addr(SB), RODATA, $8 DATA ·libc_mmap_trampoline_addr(SB)/8, $libc_mmap_trampoline<>(SB) TEXT libc_munmap_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_munmap(SB) - GLOBL ·libc_munmap_trampoline_addr(SB), RODATA, $8 DATA ·libc_munmap_trampoline_addr(SB)/8, $libc_munmap_trampoline<>(SB) TEXT libc_fstat_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_fstat(SB) - GLOBL ·libc_fstat_trampoline_addr(SB), RODATA, $8 DATA ·libc_fstat_trampoline_addr(SB)/8, $libc_fstat_trampoline<>(SB) TEXT libc_fstatat_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_fstatat(SB) - GLOBL ·libc_fstatat_trampoline_addr(SB), RODATA, $8 DATA ·libc_fstatat_trampoline_addr(SB)/8, $libc_fstatat_trampoline<>(SB) TEXT libc_fstatfs_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_fstatfs(SB) - GLOBL ·libc_fstatfs_trampoline_addr(SB), RODATA, $8 DATA ·libc_fstatfs_trampoline_addr(SB)/8, $libc_fstatfs_trampoline<>(SB) TEXT libc_getfsstat_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_getfsstat(SB) - GLOBL ·libc_getfsstat_trampoline_addr(SB), RODATA, $8 DATA ·libc_getfsstat_trampoline_addr(SB)/8, $libc_getfsstat_trampoline<>(SB) TEXT libc_lstat_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_lstat(SB) - GLOBL ·libc_lstat_trampoline_addr(SB), RODATA, $8 DATA ·libc_lstat_trampoline_addr(SB)/8, $libc_lstat_trampoline<>(SB) TEXT libc_ptrace_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_ptrace(SB) - GLOBL ·libc_ptrace_trampoline_addr(SB), RODATA, $8 DATA ·libc_ptrace_trampoline_addr(SB)/8, $libc_ptrace_trampoline<>(SB) TEXT libc_stat_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_stat(SB) - GLOBL ·libc_stat_trampoline_addr(SB), RODATA, $8 DATA ·libc_stat_trampoline_addr(SB)/8, $libc_stat_trampoline<>(SB) TEXT libc_statfs_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_statfs(SB) - GLOBL ·libc_statfs_trampoline_addr(SB), RODATA, $8 DATA ·libc_statfs_trampoline_addr(SB)/8, $libc_statfs_trampoline<>(SB) diff --git a/vendor/golang.org/x/sys/unix/zsyscall_dragonfly_amd64.go b/vendor/golang.org/x/sys/unix/zsyscall_dragonfly_amd64.go index 0eabac7..0c67df6 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_dragonfly_amd64.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_dragonfly_amd64.go @@ -1642,28 +1642,6 @@ func munmap(addr uintptr, length uintptr) (err error) { // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT -func readlen(fd int, buf *byte, nbuf int) (n int, err error) { - r0, _, e1 := Syscall(SYS_READ, uintptr(fd), uintptr(unsafe.Pointer(buf)), uintptr(nbuf)) - n = int(r0) - if e1 != 0 { - err = errnoErr(e1) - } - return -} - -// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT - -func writelen(fd int, buf *byte, nbuf int) (n int, err error) { - r0, _, e1 := Syscall(SYS_WRITE, uintptr(fd), uintptr(unsafe.Pointer(buf)), uintptr(nbuf)) - n = int(r0) - if e1 != 0 { - err = errnoErr(e1) - } - return -} - -// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT - func accept4(fd int, rsa *RawSockaddrAny, addrlen *_Socklen, flags int) (nfd int, err error) { r0, _, e1 := Syscall6(SYS_ACCEPT4, uintptr(fd), uintptr(unsafe.Pointer(rsa)), uintptr(unsafe.Pointer(addrlen)), uintptr(flags), 0, 0) nfd = int(r0) diff --git a/vendor/golang.org/x/sys/unix/zsyscall_freebsd_386.go b/vendor/golang.org/x/sys/unix/zsyscall_freebsd_386.go index ee313eb..e6e05d1 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_freebsd_386.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_freebsd_386.go @@ -1862,28 +1862,6 @@ func munmap(addr uintptr, length uintptr) (err error) { // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT -func readlen(fd int, buf *byte, nbuf int) (n int, err error) { - r0, _, e1 := Syscall(SYS_READ, uintptr(fd), uintptr(unsafe.Pointer(buf)), uintptr(nbuf)) - n = int(r0) - if e1 != 0 { - err = errnoErr(e1) - } - return -} - -// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT - -func writelen(fd int, buf *byte, nbuf int) (n int, err error) { - r0, _, e1 := Syscall(SYS_WRITE, uintptr(fd), uintptr(unsafe.Pointer(buf)), uintptr(nbuf)) - n = int(r0) - if e1 != 0 { - err = errnoErr(e1) - } - return -} - -// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT - func accept4(fd int, rsa *RawSockaddrAny, addrlen *_Socklen, flags int) (nfd int, err error) { r0, _, e1 := Syscall6(SYS_ACCEPT4, uintptr(fd), uintptr(unsafe.Pointer(rsa)), uintptr(unsafe.Pointer(addrlen)), uintptr(flags), 0, 0) nfd = int(r0) diff --git a/vendor/golang.org/x/sys/unix/zsyscall_freebsd_amd64.go b/vendor/golang.org/x/sys/unix/zsyscall_freebsd_amd64.go index 4c986e4..7508acc 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_freebsd_amd64.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_freebsd_amd64.go @@ -1862,28 +1862,6 @@ func munmap(addr uintptr, length uintptr) (err error) { // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT -func readlen(fd int, buf *byte, nbuf int) (n int, err error) { - r0, _, e1 := Syscall(SYS_READ, uintptr(fd), uintptr(unsafe.Pointer(buf)), uintptr(nbuf)) - n = int(r0) - if e1 != 0 { - err = errnoErr(e1) - } - return -} - -// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT - -func writelen(fd int, buf *byte, nbuf int) (n int, err error) { - r0, _, e1 := Syscall(SYS_WRITE, uintptr(fd), uintptr(unsafe.Pointer(buf)), uintptr(nbuf)) - n = int(r0) - if e1 != 0 { - err = errnoErr(e1) - } - return -} - -// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT - func accept4(fd int, rsa *RawSockaddrAny, addrlen *_Socklen, flags int) (nfd int, err error) { r0, _, e1 := Syscall6(SYS_ACCEPT4, uintptr(fd), uintptr(unsafe.Pointer(rsa)), uintptr(unsafe.Pointer(addrlen)), uintptr(flags), 0, 0) nfd = int(r0) diff --git a/vendor/golang.org/x/sys/unix/zsyscall_freebsd_arm.go b/vendor/golang.org/x/sys/unix/zsyscall_freebsd_arm.go index 5552169..7b56aea 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_freebsd_arm.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_freebsd_arm.go @@ -1862,28 +1862,6 @@ func munmap(addr uintptr, length uintptr) (err error) { // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT -func readlen(fd int, buf *byte, nbuf int) (n int, err error) { - r0, _, e1 := Syscall(SYS_READ, uintptr(fd), uintptr(unsafe.Pointer(buf)), uintptr(nbuf)) - n = int(r0) - if e1 != 0 { - err = errnoErr(e1) - } - return -} - -// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT - -func writelen(fd int, buf *byte, nbuf int) (n int, err error) { - r0, _, e1 := Syscall(SYS_WRITE, uintptr(fd), uintptr(unsafe.Pointer(buf)), uintptr(nbuf)) - n = int(r0) - if e1 != 0 { - err = errnoErr(e1) - } - return -} - -// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT - func accept4(fd int, rsa *RawSockaddrAny, addrlen *_Socklen, flags int) (nfd int, err error) { r0, _, e1 := Syscall6(SYS_ACCEPT4, uintptr(fd), uintptr(unsafe.Pointer(rsa)), uintptr(unsafe.Pointer(addrlen)), uintptr(flags), 0, 0) nfd = int(r0) diff --git a/vendor/golang.org/x/sys/unix/zsyscall_freebsd_arm64.go b/vendor/golang.org/x/sys/unix/zsyscall_freebsd_arm64.go index 67a226f..cc623dc 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_freebsd_arm64.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_freebsd_arm64.go @@ -1862,28 +1862,6 @@ func munmap(addr uintptr, length uintptr) (err error) { // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT -func readlen(fd int, buf *byte, nbuf int) (n int, err error) { - r0, _, e1 := Syscall(SYS_READ, uintptr(fd), uintptr(unsafe.Pointer(buf)), uintptr(nbuf)) - n = int(r0) - if e1 != 0 { - err = errnoErr(e1) - } - return -} - -// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT - -func writelen(fd int, buf *byte, nbuf int) (n int, err error) { - r0, _, e1 := Syscall(SYS_WRITE, uintptr(fd), uintptr(unsafe.Pointer(buf)), uintptr(nbuf)) - n = int(r0) - if e1 != 0 { - err = errnoErr(e1) - } - return -} - -// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT - func accept4(fd int, rsa *RawSockaddrAny, addrlen *_Socklen, flags int) (nfd int, err error) { r0, _, e1 := Syscall6(SYS_ACCEPT4, uintptr(fd), uintptr(unsafe.Pointer(rsa)), uintptr(unsafe.Pointer(addrlen)), uintptr(flags), 0, 0) nfd = int(r0) diff --git a/vendor/golang.org/x/sys/unix/zsyscall_freebsd_riscv64.go b/vendor/golang.org/x/sys/unix/zsyscall_freebsd_riscv64.go index f0b9dda..5818491 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_freebsd_riscv64.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_freebsd_riscv64.go @@ -1862,28 +1862,6 @@ func munmap(addr uintptr, length uintptr) (err error) { // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT -func readlen(fd int, buf *byte, nbuf int) (n int, err error) { - r0, _, e1 := Syscall(SYS_READ, uintptr(fd), uintptr(unsafe.Pointer(buf)), uintptr(nbuf)) - n = int(r0) - if e1 != 0 { - err = errnoErr(e1) - } - return -} - -// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT - -func writelen(fd int, buf *byte, nbuf int) (n int, err error) { - r0, _, e1 := Syscall(SYS_WRITE, uintptr(fd), uintptr(unsafe.Pointer(buf)), uintptr(nbuf)) - n = int(r0) - if e1 != 0 { - err = errnoErr(e1) - } - return -} - -// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT - func accept4(fd int, rsa *RawSockaddrAny, addrlen *_Socklen, flags int) (nfd int, err error) { r0, _, e1 := Syscall6(SYS_ACCEPT4, uintptr(fd), uintptr(unsafe.Pointer(rsa)), uintptr(unsafe.Pointer(addrlen)), uintptr(flags), 0, 0) nfd = int(r0) diff --git a/vendor/golang.org/x/sys/unix/zsyscall_illumos_amd64.go b/vendor/golang.org/x/sys/unix/zsyscall_illumos_amd64.go index b57c705..6be25cd 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_illumos_amd64.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_illumos_amd64.go @@ -40,7 +40,7 @@ func readv(fd int, iovs []Iovec) (n int, err error) { r0, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procreadv)), 3, uintptr(fd), uintptr(unsafe.Pointer(_p0)), uintptr(len(iovs)), 0, 0, 0) n = int(r0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -55,7 +55,7 @@ func preadv(fd int, iovs []Iovec, off int64) (n int, err error) { r0, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procpreadv)), 4, uintptr(fd), uintptr(unsafe.Pointer(_p0)), uintptr(len(iovs)), uintptr(off), 0, 0) n = int(r0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -70,7 +70,7 @@ func writev(fd int, iovs []Iovec) (n int, err error) { r0, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procwritev)), 3, uintptr(fd), uintptr(unsafe.Pointer(_p0)), uintptr(len(iovs)), 0, 0, 0) n = int(r0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -85,7 +85,7 @@ func pwritev(fd int, iovs []Iovec, off int64) (n int, err error) { r0, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procpwritev)), 4, uintptr(fd), uintptr(unsafe.Pointer(_p0)), uintptr(len(iovs)), uintptr(off), 0, 0) n = int(r0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -96,7 +96,7 @@ func accept4(s int, rsa *RawSockaddrAny, addrlen *_Socklen, flags int) (fd int, r0, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procaccept4)), 4, uintptr(s), uintptr(unsafe.Pointer(rsa)), uintptr(unsafe.Pointer(addrlen)), uintptr(flags), 0, 0) fd = int(r0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } diff --git a/vendor/golang.org/x/sys/unix/zsyscall_linux.go b/vendor/golang.org/x/sys/unix/zsyscall_linux.go index 14ab34a..1ff3aec 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_linux.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_linux.go @@ -1734,28 +1734,6 @@ func exitThread(code int) (err error) { // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT -func readlen(fd int, p *byte, np int) (n int, err error) { - r0, _, e1 := Syscall(SYS_READ, uintptr(fd), uintptr(unsafe.Pointer(p)), uintptr(np)) - n = int(r0) - if e1 != 0 { - err = errnoErr(e1) - } - return -} - -// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT - -func writelen(fd int, p *byte, np int) (n int, err error) { - r0, _, e1 := Syscall(SYS_WRITE, uintptr(fd), uintptr(unsafe.Pointer(p)), uintptr(np)) - n = int(r0) - if e1 != 0 { - err = errnoErr(e1) - } - return -} - -// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT - func readv(fd int, iovs []Iovec) (n int, err error) { var _p0 unsafe.Pointer if len(iovs) > 0 { diff --git a/vendor/golang.org/x/sys/unix/zsyscall_netbsd_386.go b/vendor/golang.org/x/sys/unix/zsyscall_netbsd_386.go index 35f499b..2df3c5b 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_netbsd_386.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_netbsd_386.go @@ -1824,28 +1824,6 @@ func munmap(addr uintptr, length uintptr) (err error) { // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT -func readlen(fd int, buf *byte, nbuf int) (n int, err error) { - r0, _, e1 := Syscall(SYS_READ, uintptr(fd), uintptr(unsafe.Pointer(buf)), uintptr(nbuf)) - n = int(r0) - if e1 != 0 { - err = errnoErr(e1) - } - return -} - -// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT - -func writelen(fd int, buf *byte, nbuf int) (n int, err error) { - r0, _, e1 := Syscall(SYS_WRITE, uintptr(fd), uintptr(unsafe.Pointer(buf)), uintptr(nbuf)) - n = int(r0) - if e1 != 0 { - err = errnoErr(e1) - } - return -} - -// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT - func utimensat(dirfd int, path string, times *[2]Timespec, flags int) (err error) { var _p0 *byte _p0, err = BytePtrFromString(path) diff --git a/vendor/golang.org/x/sys/unix/zsyscall_netbsd_amd64.go b/vendor/golang.org/x/sys/unix/zsyscall_netbsd_amd64.go index 3cda65b..a60556b 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_netbsd_amd64.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_netbsd_amd64.go @@ -1824,28 +1824,6 @@ func munmap(addr uintptr, length uintptr) (err error) { // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT -func readlen(fd int, buf *byte, nbuf int) (n int, err error) { - r0, _, e1 := Syscall(SYS_READ, uintptr(fd), uintptr(unsafe.Pointer(buf)), uintptr(nbuf)) - n = int(r0) - if e1 != 0 { - err = errnoErr(e1) - } - return -} - -// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT - -func writelen(fd int, buf *byte, nbuf int) (n int, err error) { - r0, _, e1 := Syscall(SYS_WRITE, uintptr(fd), uintptr(unsafe.Pointer(buf)), uintptr(nbuf)) - n = int(r0) - if e1 != 0 { - err = errnoErr(e1) - } - return -} - -// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT - func utimensat(dirfd int, path string, times *[2]Timespec, flags int) (err error) { var _p0 *byte _p0, err = BytePtrFromString(path) diff --git a/vendor/golang.org/x/sys/unix/zsyscall_netbsd_arm.go b/vendor/golang.org/x/sys/unix/zsyscall_netbsd_arm.go index 1e1fea9..9f78891 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_netbsd_arm.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_netbsd_arm.go @@ -1824,28 +1824,6 @@ func munmap(addr uintptr, length uintptr) (err error) { // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT -func readlen(fd int, buf *byte, nbuf int) (n int, err error) { - r0, _, e1 := Syscall(SYS_READ, uintptr(fd), uintptr(unsafe.Pointer(buf)), uintptr(nbuf)) - n = int(r0) - if e1 != 0 { - err = errnoErr(e1) - } - return -} - -// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT - -func writelen(fd int, buf *byte, nbuf int) (n int, err error) { - r0, _, e1 := Syscall(SYS_WRITE, uintptr(fd), uintptr(unsafe.Pointer(buf)), uintptr(nbuf)) - n = int(r0) - if e1 != 0 { - err = errnoErr(e1) - } - return -} - -// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT - func utimensat(dirfd int, path string, times *[2]Timespec, flags int) (err error) { var _p0 *byte _p0, err = BytePtrFromString(path) diff --git a/vendor/golang.org/x/sys/unix/zsyscall_netbsd_arm64.go b/vendor/golang.org/x/sys/unix/zsyscall_netbsd_arm64.go index 3b77da1..82a4cb2 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_netbsd_arm64.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_netbsd_arm64.go @@ -1824,28 +1824,6 @@ func munmap(addr uintptr, length uintptr) (err error) { // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT -func readlen(fd int, buf *byte, nbuf int) (n int, err error) { - r0, _, e1 := Syscall(SYS_READ, uintptr(fd), uintptr(unsafe.Pointer(buf)), uintptr(nbuf)) - n = int(r0) - if e1 != 0 { - err = errnoErr(e1) - } - return -} - -// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT - -func writelen(fd int, buf *byte, nbuf int) (n int, err error) { - r0, _, e1 := Syscall(SYS_WRITE, uintptr(fd), uintptr(unsafe.Pointer(buf)), uintptr(nbuf)) - n = int(r0) - if e1 != 0 { - err = errnoErr(e1) - } - return -} - -// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT - func utimensat(dirfd int, path string, times *[2]Timespec, flags int) (err error) { var _p0 *byte _p0, err = BytePtrFromString(path) diff --git a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_386.go b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_386.go index 9ab9abf..66b3b64 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_386.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_386.go @@ -549,6 +549,12 @@ func ioctl(fd int, req uint, arg uintptr) (err error) { return } +var libc_ioctl_trampoline_addr uintptr + +//go:cgo_import_dynamic libc_ioctl ioctl "libc.so" + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + func ioctlPtr(fd int, req uint, arg unsafe.Pointer) (err error) { _, _, e1 := syscall_syscall(libc_ioctl_trampoline_addr, uintptr(fd), uintptr(req), uintptr(arg)) if e1 != 0 { @@ -557,10 +563,6 @@ func ioctlPtr(fd int, req uint, arg unsafe.Pointer) (err error) { return } -var libc_ioctl_trampoline_addr uintptr - -//go:cgo_import_dynamic libc_ioctl ioctl "libc.so" - // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT func sysctl(mib []_C_int, old *byte, oldlen *uintptr, new *byte, newlen uintptr) (err error) { @@ -2211,28 +2213,6 @@ var libc_munmap_trampoline_addr uintptr // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT -func readlen(fd int, buf *byte, nbuf int) (n int, err error) { - r0, _, e1 := syscall_syscall(libc_read_trampoline_addr, uintptr(fd), uintptr(unsafe.Pointer(buf)), uintptr(nbuf)) - n = int(r0) - if e1 != 0 { - err = errnoErr(e1) - } - return -} - -// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT - -func writelen(fd int, buf *byte, nbuf int) (n int, err error) { - r0, _, e1 := syscall_syscall(libc_write_trampoline_addr, uintptr(fd), uintptr(unsafe.Pointer(buf)), uintptr(nbuf)) - n = int(r0) - if e1 != 0 { - err = errnoErr(e1) - } - return -} - -// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT - func utimensat(dirfd int, path string, times *[2]Timespec, flags int) (err error) { var _p0 *byte _p0, err = BytePtrFromString(path) diff --git a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_amd64.go b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_amd64.go index 915761e..c5c4cc1 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_amd64.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_amd64.go @@ -2213,28 +2213,6 @@ var libc_munmap_trampoline_addr uintptr // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT -func readlen(fd int, buf *byte, nbuf int) (n int, err error) { - r0, _, e1 := syscall_syscall(libc_read_trampoline_addr, uintptr(fd), uintptr(unsafe.Pointer(buf)), uintptr(nbuf)) - n = int(r0) - if e1 != 0 { - err = errnoErr(e1) - } - return -} - -// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT - -func writelen(fd int, buf *byte, nbuf int) (n int, err error) { - r0, _, e1 := syscall_syscall(libc_write_trampoline_addr, uintptr(fd), uintptr(unsafe.Pointer(buf)), uintptr(nbuf)) - n = int(r0) - if e1 != 0 { - err = errnoErr(e1) - } - return -} - -// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT - func utimensat(dirfd int, path string, times *[2]Timespec, flags int) (err error) { var _p0 *byte _p0, err = BytePtrFromString(path) diff --git a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_arm.go b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_arm.go index 8e87fdf..93bfbb3 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_arm.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_arm.go @@ -549,6 +549,12 @@ func ioctl(fd int, req uint, arg uintptr) (err error) { return } +var libc_ioctl_trampoline_addr uintptr + +//go:cgo_import_dynamic libc_ioctl ioctl "libc.so" + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + func ioctlPtr(fd int, req uint, arg unsafe.Pointer) (err error) { _, _, e1 := syscall_syscall(libc_ioctl_trampoline_addr, uintptr(fd), uintptr(req), uintptr(arg)) if e1 != 0 { @@ -557,10 +563,6 @@ func ioctlPtr(fd int, req uint, arg unsafe.Pointer) (err error) { return } -var libc_ioctl_trampoline_addr uintptr - -//go:cgo_import_dynamic libc_ioctl ioctl "libc.so" - // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT func sysctl(mib []_C_int, old *byte, oldlen *uintptr, new *byte, newlen uintptr) (err error) { @@ -2211,28 +2213,6 @@ var libc_munmap_trampoline_addr uintptr // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT -func readlen(fd int, buf *byte, nbuf int) (n int, err error) { - r0, _, e1 := syscall_syscall(libc_read_trampoline_addr, uintptr(fd), uintptr(unsafe.Pointer(buf)), uintptr(nbuf)) - n = int(r0) - if e1 != 0 { - err = errnoErr(e1) - } - return -} - -// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT - -func writelen(fd int, buf *byte, nbuf int) (n int, err error) { - r0, _, e1 := syscall_syscall(libc_write_trampoline_addr, uintptr(fd), uintptr(unsafe.Pointer(buf)), uintptr(nbuf)) - n = int(r0) - if e1 != 0 { - err = errnoErr(e1) - } - return -} - -// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT - func utimensat(dirfd int, path string, times *[2]Timespec, flags int) (err error) { var _p0 *byte _p0, err = BytePtrFromString(path) diff --git a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_arm64.go b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_arm64.go index 12a7a21..a107b8f 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_arm64.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_arm64.go @@ -549,6 +549,12 @@ func ioctl(fd int, req uint, arg uintptr) (err error) { return } +var libc_ioctl_trampoline_addr uintptr + +//go:cgo_import_dynamic libc_ioctl ioctl "libc.so" + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + func ioctlPtr(fd int, req uint, arg unsafe.Pointer) (err error) { _, _, e1 := syscall_syscall(libc_ioctl_trampoline_addr, uintptr(fd), uintptr(req), uintptr(arg)) if e1 != 0 { @@ -557,10 +563,6 @@ func ioctlPtr(fd int, req uint, arg unsafe.Pointer) (err error) { return } -var libc_ioctl_trampoline_addr uintptr - -//go:cgo_import_dynamic libc_ioctl ioctl "libc.so" - // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT func sysctl(mib []_C_int, old *byte, oldlen *uintptr, new *byte, newlen uintptr) (err error) { @@ -2211,28 +2213,6 @@ var libc_munmap_trampoline_addr uintptr // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT -func readlen(fd int, buf *byte, nbuf int) (n int, err error) { - r0, _, e1 := syscall_syscall(libc_read_trampoline_addr, uintptr(fd), uintptr(unsafe.Pointer(buf)), uintptr(nbuf)) - n = int(r0) - if e1 != 0 { - err = errnoErr(e1) - } - return -} - -// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT - -func writelen(fd int, buf *byte, nbuf int) (n int, err error) { - r0, _, e1 := syscall_syscall(libc_write_trampoline_addr, uintptr(fd), uintptr(unsafe.Pointer(buf)), uintptr(nbuf)) - n = int(r0) - if e1 != 0 { - err = errnoErr(e1) - } - return -} - -// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT - func utimensat(dirfd int, path string, times *[2]Timespec, flags int) (err error) { var _p0 *byte _p0, err = BytePtrFromString(path) diff --git a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_mips64.go b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_mips64.go index b19e8aa..c427de5 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_mips64.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_mips64.go @@ -549,6 +549,12 @@ func ioctl(fd int, req uint, arg uintptr) (err error) { return } +var libc_ioctl_trampoline_addr uintptr + +//go:cgo_import_dynamic libc_ioctl ioctl "libc.so" + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + func ioctlPtr(fd int, req uint, arg unsafe.Pointer) (err error) { _, _, e1 := syscall_syscall(libc_ioctl_trampoline_addr, uintptr(fd), uintptr(req), uintptr(arg)) if e1 != 0 { @@ -557,10 +563,6 @@ func ioctlPtr(fd int, req uint, arg unsafe.Pointer) (err error) { return } -var libc_ioctl_trampoline_addr uintptr - -//go:cgo_import_dynamic libc_ioctl ioctl "libc.so" - // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT func sysctl(mib []_C_int, old *byte, oldlen *uintptr, new *byte, newlen uintptr) (err error) { @@ -2211,28 +2213,6 @@ var libc_munmap_trampoline_addr uintptr // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT -func readlen(fd int, buf *byte, nbuf int) (n int, err error) { - r0, _, e1 := syscall_syscall(libc_read_trampoline_addr, uintptr(fd), uintptr(unsafe.Pointer(buf)), uintptr(nbuf)) - n = int(r0) - if e1 != 0 { - err = errnoErr(e1) - } - return -} - -// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT - -func writelen(fd int, buf *byte, nbuf int) (n int, err error) { - r0, _, e1 := syscall_syscall(libc_write_trampoline_addr, uintptr(fd), uintptr(unsafe.Pointer(buf)), uintptr(nbuf)) - n = int(r0) - if e1 != 0 { - err = errnoErr(e1) - } - return -} - -// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT - func utimensat(dirfd int, path string, times *[2]Timespec, flags int) (err error) { var _p0 *byte _p0, err = BytePtrFromString(path) diff --git a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_ppc64.go b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_ppc64.go index fb99594..60c1a99 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_ppc64.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_ppc64.go @@ -549,6 +549,12 @@ func ioctl(fd int, req uint, arg uintptr) (err error) { return } +var libc_ioctl_trampoline_addr uintptr + +//go:cgo_import_dynamic libc_ioctl ioctl "libc.so" + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + func ioctlPtr(fd int, req uint, arg unsafe.Pointer) (err error) { _, _, e1 := syscall_syscall(libc_ioctl_trampoline_addr, uintptr(fd), uintptr(req), uintptr(arg)) if e1 != 0 { @@ -557,10 +563,6 @@ func ioctlPtr(fd int, req uint, arg unsafe.Pointer) (err error) { return } -var libc_ioctl_trampoline_addr uintptr - -//go:cgo_import_dynamic libc_ioctl ioctl "libc.so" - // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT func sysctl(mib []_C_int, old *byte, oldlen *uintptr, new *byte, newlen uintptr) (err error) { @@ -2211,28 +2213,6 @@ var libc_munmap_trampoline_addr uintptr // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT -func readlen(fd int, buf *byte, nbuf int) (n int, err error) { - r0, _, e1 := syscall_syscall(libc_read_trampoline_addr, uintptr(fd), uintptr(unsafe.Pointer(buf)), uintptr(nbuf)) - n = int(r0) - if e1 != 0 { - err = errnoErr(e1) - } - return -} - -// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT - -func writelen(fd int, buf *byte, nbuf int) (n int, err error) { - r0, _, e1 := syscall_syscall(libc_write_trampoline_addr, uintptr(fd), uintptr(unsafe.Pointer(buf)), uintptr(nbuf)) - n = int(r0) - if e1 != 0 { - err = errnoErr(e1) - } - return -} - -// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT - func utimensat(dirfd int, path string, times *[2]Timespec, flags int) (err error) { var _p0 *byte _p0, err = BytePtrFromString(path) diff --git a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_riscv64.go b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_riscv64.go index 32cbbbc..52eba36 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_riscv64.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_riscv64.go @@ -549,6 +549,12 @@ func ioctl(fd int, req uint, arg uintptr) (err error) { return } +var libc_ioctl_trampoline_addr uintptr + +//go:cgo_import_dynamic libc_ioctl ioctl "libc.so" + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + func ioctlPtr(fd int, req uint, arg unsafe.Pointer) (err error) { _, _, e1 := syscall_syscall(libc_ioctl_trampoline_addr, uintptr(fd), uintptr(req), uintptr(arg)) if e1 != 0 { @@ -557,10 +563,6 @@ func ioctlPtr(fd int, req uint, arg unsafe.Pointer) (err error) { return } -var libc_ioctl_trampoline_addr uintptr - -//go:cgo_import_dynamic libc_ioctl ioctl "libc.so" - // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT func sysctl(mib []_C_int, old *byte, oldlen *uintptr, new *byte, newlen uintptr) (err error) { @@ -2211,28 +2213,6 @@ var libc_munmap_trampoline_addr uintptr // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT -func readlen(fd int, buf *byte, nbuf int) (n int, err error) { - r0, _, e1 := syscall_syscall(libc_read_trampoline_addr, uintptr(fd), uintptr(unsafe.Pointer(buf)), uintptr(nbuf)) - n = int(r0) - if e1 != 0 { - err = errnoErr(e1) - } - return -} - -// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT - -func writelen(fd int, buf *byte, nbuf int) (n int, err error) { - r0, _, e1 := syscall_syscall(libc_write_trampoline_addr, uintptr(fd), uintptr(unsafe.Pointer(buf)), uintptr(nbuf)) - n = int(r0) - if e1 != 0 { - err = errnoErr(e1) - } - return -} - -// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT - func utimensat(dirfd int, path string, times *[2]Timespec, flags int) (err error) { var _p0 *byte _p0, err = BytePtrFromString(path) diff --git a/vendor/golang.org/x/sys/unix/zsyscall_solaris_amd64.go b/vendor/golang.org/x/sys/unix/zsyscall_solaris_amd64.go index 609d1c5..b401894 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_solaris_amd64.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_solaris_amd64.go @@ -436,7 +436,7 @@ func pipe(p *[2]_C_int) (n int, err error) { r0, _, e1 := rawSysvicall6(uintptr(unsafe.Pointer(&procpipe)), 1, uintptr(unsafe.Pointer(p)), 0, 0, 0, 0, 0) n = int(r0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -446,7 +446,7 @@ func pipe(p *[2]_C_int) (n int, err error) { func pipe2(p *[2]_C_int, flags int) (err error) { _, _, e1 := rawSysvicall6(uintptr(unsafe.Pointer(&procpipe2)), 2, uintptr(unsafe.Pointer(p)), uintptr(flags), 0, 0, 0, 0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -456,7 +456,7 @@ func pipe2(p *[2]_C_int, flags int) (err error) { func getsockname(fd int, rsa *RawSockaddrAny, addrlen *_Socklen) (err error) { _, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procgetsockname)), 3, uintptr(fd), uintptr(unsafe.Pointer(rsa)), uintptr(unsafe.Pointer(addrlen)), 0, 0, 0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -471,7 +471,7 @@ func Getcwd(buf []byte) (n int, err error) { r0, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procGetcwd)), 2, uintptr(unsafe.Pointer(_p0)), uintptr(len(buf)), 0, 0, 0, 0) n = int(r0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -482,7 +482,7 @@ func getgroups(ngid int, gid *_Gid_t) (n int, err error) { r0, _, e1 := rawSysvicall6(uintptr(unsafe.Pointer(&procgetgroups)), 2, uintptr(ngid), uintptr(unsafe.Pointer(gid)), 0, 0, 0, 0) n = int(r0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -492,7 +492,7 @@ func getgroups(ngid int, gid *_Gid_t) (n int, err error) { func setgroups(ngid int, gid *_Gid_t) (err error) { _, _, e1 := rawSysvicall6(uintptr(unsafe.Pointer(&procsetgroups)), 2, uintptr(ngid), uintptr(unsafe.Pointer(gid)), 0, 0, 0, 0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -503,7 +503,7 @@ func wait4(pid int32, statusp *_C_int, options int, rusage *Rusage) (wpid int32, r0, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procwait4)), 4, uintptr(pid), uintptr(unsafe.Pointer(statusp)), uintptr(options), uintptr(unsafe.Pointer(rusage)), 0, 0) wpid = int32(r0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -518,7 +518,7 @@ func gethostname(buf []byte) (n int, err error) { r0, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procgethostname)), 2, uintptr(unsafe.Pointer(_p0)), uintptr(len(buf)), 0, 0, 0, 0) n = int(r0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -533,7 +533,7 @@ func utimes(path string, times *[2]Timeval) (err error) { } _, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procutimes)), 2, uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(times)), 0, 0, 0, 0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -548,7 +548,7 @@ func utimensat(fd int, path string, times *[2]Timespec, flag int) (err error) { } _, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procutimensat)), 4, uintptr(fd), uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(times)), uintptr(flag), 0, 0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -559,7 +559,7 @@ func fcntl(fd int, cmd int, arg int) (val int, err error) { r0, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procfcntl)), 3, uintptr(fd), uintptr(cmd), uintptr(arg), 0, 0, 0) val = int(r0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -569,7 +569,7 @@ func fcntl(fd int, cmd int, arg int) (val int, err error) { func futimesat(fildes int, path *byte, times *[2]Timeval) (err error) { _, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procfutimesat)), 3, uintptr(fildes), uintptr(unsafe.Pointer(path)), uintptr(unsafe.Pointer(times)), 0, 0, 0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -580,7 +580,7 @@ func accept(s int, rsa *RawSockaddrAny, addrlen *_Socklen) (fd int, err error) { r0, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procaccept)), 3, uintptr(s), uintptr(unsafe.Pointer(rsa)), uintptr(unsafe.Pointer(addrlen)), 0, 0, 0) fd = int(r0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -591,7 +591,7 @@ func recvmsg(s int, msg *Msghdr, flags int) (n int, err error) { r0, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&proc__xnet_recvmsg)), 3, uintptr(s), uintptr(unsafe.Pointer(msg)), uintptr(flags), 0, 0, 0) n = int(r0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -602,7 +602,7 @@ func sendmsg(s int, msg *Msghdr, flags int) (n int, err error) { r0, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&proc__xnet_sendmsg)), 3, uintptr(s), uintptr(unsafe.Pointer(msg)), uintptr(flags), 0, 0, 0) n = int(r0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -612,7 +612,7 @@ func sendmsg(s int, msg *Msghdr, flags int) (n int, err error) { func acct(path *byte) (err error) { _, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procacct)), 1, uintptr(unsafe.Pointer(path)), 0, 0, 0, 0, 0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -647,7 +647,7 @@ func ioctlRet(fd int, req int, arg uintptr) (ret int, err error) { r0, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procioctl)), 3, uintptr(fd), uintptr(req), uintptr(arg), 0, 0, 0) ret = int(r0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -658,7 +658,7 @@ func ioctlPtrRet(fd int, req int, arg unsafe.Pointer) (ret int, err error) { r0, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procioctl)), 3, uintptr(fd), uintptr(req), uintptr(arg), 0, 0, 0) ret = int(r0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -669,7 +669,7 @@ func poll(fds *PollFd, nfds int, timeout int) (n int, err error) { r0, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procpoll)), 3, uintptr(unsafe.Pointer(fds)), uintptr(nfds), uintptr(timeout), 0, 0, 0) n = int(r0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -684,7 +684,7 @@ func Access(path string, mode uint32) (err error) { } _, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procAccess)), 2, uintptr(unsafe.Pointer(_p0)), uintptr(mode), 0, 0, 0, 0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -694,7 +694,7 @@ func Access(path string, mode uint32) (err error) { func Adjtime(delta *Timeval, olddelta *Timeval) (err error) { _, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procAdjtime)), 2, uintptr(unsafe.Pointer(delta)), uintptr(unsafe.Pointer(olddelta)), 0, 0, 0, 0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -709,7 +709,7 @@ func Chdir(path string) (err error) { } _, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procChdir)), 1, uintptr(unsafe.Pointer(_p0)), 0, 0, 0, 0, 0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -724,7 +724,7 @@ func Chmod(path string, mode uint32) (err error) { } _, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procChmod)), 2, uintptr(unsafe.Pointer(_p0)), uintptr(mode), 0, 0, 0, 0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -739,7 +739,7 @@ func Chown(path string, uid int, gid int) (err error) { } _, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procChown)), 3, uintptr(unsafe.Pointer(_p0)), uintptr(uid), uintptr(gid), 0, 0, 0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -754,7 +754,7 @@ func Chroot(path string) (err error) { } _, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procChroot)), 1, uintptr(unsafe.Pointer(_p0)), 0, 0, 0, 0, 0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -764,7 +764,7 @@ func Chroot(path string) (err error) { func ClockGettime(clockid int32, time *Timespec) (err error) { _, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procClockGettime)), 2, uintptr(clockid), uintptr(unsafe.Pointer(time)), 0, 0, 0, 0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -774,7 +774,7 @@ func ClockGettime(clockid int32, time *Timespec) (err error) { func Close(fd int) (err error) { _, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procClose)), 1, uintptr(fd), 0, 0, 0, 0, 0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -790,7 +790,7 @@ func Creat(path string, mode uint32) (fd int, err error) { r0, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procCreat)), 2, uintptr(unsafe.Pointer(_p0)), uintptr(mode), 0, 0, 0, 0) fd = int(r0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -801,7 +801,7 @@ func Dup(fd int) (nfd int, err error) { r0, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procDup)), 1, uintptr(fd), 0, 0, 0, 0, 0) nfd = int(r0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -811,7 +811,7 @@ func Dup(fd int) (nfd int, err error) { func Dup2(oldfd int, newfd int) (err error) { _, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procDup2)), 2, uintptr(oldfd), uintptr(newfd), 0, 0, 0, 0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -833,7 +833,7 @@ func Faccessat(dirfd int, path string, mode uint32, flags int) (err error) { } _, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procFaccessat)), 4, uintptr(dirfd), uintptr(unsafe.Pointer(_p0)), uintptr(mode), uintptr(flags), 0, 0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -843,7 +843,7 @@ func Faccessat(dirfd int, path string, mode uint32, flags int) (err error) { func Fchdir(fd int) (err error) { _, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procFchdir)), 1, uintptr(fd), 0, 0, 0, 0, 0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -853,7 +853,7 @@ func Fchdir(fd int) (err error) { func Fchmod(fd int, mode uint32) (err error) { _, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procFchmod)), 2, uintptr(fd), uintptr(mode), 0, 0, 0, 0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -868,7 +868,7 @@ func Fchmodat(dirfd int, path string, mode uint32, flags int) (err error) { } _, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procFchmodat)), 4, uintptr(dirfd), uintptr(unsafe.Pointer(_p0)), uintptr(mode), uintptr(flags), 0, 0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -878,7 +878,7 @@ func Fchmodat(dirfd int, path string, mode uint32, flags int) (err error) { func Fchown(fd int, uid int, gid int) (err error) { _, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procFchown)), 3, uintptr(fd), uintptr(uid), uintptr(gid), 0, 0, 0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -893,7 +893,7 @@ func Fchownat(dirfd int, path string, uid int, gid int, flags int) (err error) { } _, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procFchownat)), 5, uintptr(dirfd), uintptr(unsafe.Pointer(_p0)), uintptr(uid), uintptr(gid), uintptr(flags), 0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -903,7 +903,7 @@ func Fchownat(dirfd int, path string, uid int, gid int, flags int) (err error) { func Fdatasync(fd int) (err error) { _, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procFdatasync)), 1, uintptr(fd), 0, 0, 0, 0, 0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -913,7 +913,7 @@ func Fdatasync(fd int) (err error) { func Flock(fd int, how int) (err error) { _, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procFlock)), 2, uintptr(fd), uintptr(how), 0, 0, 0, 0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -924,7 +924,7 @@ func Fpathconf(fd int, name int) (val int, err error) { r0, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procFpathconf)), 2, uintptr(fd), uintptr(name), 0, 0, 0, 0) val = int(r0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -934,7 +934,7 @@ func Fpathconf(fd int, name int) (val int, err error) { func Fstat(fd int, stat *Stat_t) (err error) { _, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procFstat)), 2, uintptr(fd), uintptr(unsafe.Pointer(stat)), 0, 0, 0, 0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -949,7 +949,7 @@ func Fstatat(fd int, path string, stat *Stat_t, flags int) (err error) { } _, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procFstatat)), 4, uintptr(fd), uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(stat)), uintptr(flags), 0, 0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -959,7 +959,7 @@ func Fstatat(fd int, path string, stat *Stat_t, flags int) (err error) { func Fstatvfs(fd int, vfsstat *Statvfs_t) (err error) { _, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procFstatvfs)), 2, uintptr(fd), uintptr(unsafe.Pointer(vfsstat)), 0, 0, 0, 0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -974,7 +974,7 @@ func Getdents(fd int, buf []byte, basep *uintptr) (n int, err error) { r0, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procGetdents)), 4, uintptr(fd), uintptr(unsafe.Pointer(_p0)), uintptr(len(buf)), uintptr(unsafe.Pointer(basep)), 0, 0) n = int(r0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -1001,7 +1001,7 @@ func Getpgid(pid int) (pgid int, err error) { r0, _, e1 := rawSysvicall6(uintptr(unsafe.Pointer(&procGetpgid)), 1, uintptr(pid), 0, 0, 0, 0, 0) pgid = int(r0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -1012,7 +1012,7 @@ func Getpgrp() (pgid int, err error) { r0, _, e1 := rawSysvicall6(uintptr(unsafe.Pointer(&procGetpgrp)), 0, 0, 0, 0, 0, 0, 0) pgid = int(r0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -1047,7 +1047,7 @@ func Getpriority(which int, who int) (n int, err error) { r0, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procGetpriority)), 2, uintptr(which), uintptr(who), 0, 0, 0, 0) n = int(r0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -1057,7 +1057,7 @@ func Getpriority(which int, who int) (n int, err error) { func Getrlimit(which int, lim *Rlimit) (err error) { _, _, e1 := rawSysvicall6(uintptr(unsafe.Pointer(&procGetrlimit)), 2, uintptr(which), uintptr(unsafe.Pointer(lim)), 0, 0, 0, 0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -1067,7 +1067,7 @@ func Getrlimit(which int, lim *Rlimit) (err error) { func Getrusage(who int, rusage *Rusage) (err error) { _, _, e1 := rawSysvicall6(uintptr(unsafe.Pointer(&procGetrusage)), 2, uintptr(who), uintptr(unsafe.Pointer(rusage)), 0, 0, 0, 0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -1078,7 +1078,7 @@ func Getsid(pid int) (sid int, err error) { r0, _, e1 := rawSysvicall6(uintptr(unsafe.Pointer(&procGetsid)), 1, uintptr(pid), 0, 0, 0, 0, 0) sid = int(r0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -1088,7 +1088,7 @@ func Getsid(pid int) (sid int, err error) { func Gettimeofday(tv *Timeval) (err error) { _, _, e1 := rawSysvicall6(uintptr(unsafe.Pointer(&procGettimeofday)), 1, uintptr(unsafe.Pointer(tv)), 0, 0, 0, 0, 0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -1106,7 +1106,7 @@ func Getuid() (uid int) { func Kill(pid int, signum syscall.Signal) (err error) { _, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procKill)), 2, uintptr(pid), uintptr(signum), 0, 0, 0, 0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -1121,7 +1121,7 @@ func Lchown(path string, uid int, gid int) (err error) { } _, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procLchown)), 3, uintptr(unsafe.Pointer(_p0)), uintptr(uid), uintptr(gid), 0, 0, 0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -1141,7 +1141,7 @@ func Link(path string, link string) (err error) { } _, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procLink)), 2, uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(_p1)), 0, 0, 0, 0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -1151,7 +1151,7 @@ func Link(path string, link string) (err error) { func Listen(s int, backlog int) (err error) { _, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&proc__xnet_llisten)), 2, uintptr(s), uintptr(backlog), 0, 0, 0, 0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -1166,7 +1166,7 @@ func Lstat(path string, stat *Stat_t) (err error) { } _, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procLstat)), 2, uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(stat)), 0, 0, 0, 0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -1180,7 +1180,7 @@ func Madvise(b []byte, advice int) (err error) { } _, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procMadvise)), 3, uintptr(unsafe.Pointer(_p0)), uintptr(len(b)), uintptr(advice), 0, 0, 0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -1195,7 +1195,7 @@ func Mkdir(path string, mode uint32) (err error) { } _, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procMkdir)), 2, uintptr(unsafe.Pointer(_p0)), uintptr(mode), 0, 0, 0, 0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -1210,7 +1210,7 @@ func Mkdirat(dirfd int, path string, mode uint32) (err error) { } _, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procMkdirat)), 3, uintptr(dirfd), uintptr(unsafe.Pointer(_p0)), uintptr(mode), 0, 0, 0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -1225,7 +1225,7 @@ func Mkfifo(path string, mode uint32) (err error) { } _, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procMkfifo)), 2, uintptr(unsafe.Pointer(_p0)), uintptr(mode), 0, 0, 0, 0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -1240,7 +1240,7 @@ func Mkfifoat(dirfd int, path string, mode uint32) (err error) { } _, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procMkfifoat)), 3, uintptr(dirfd), uintptr(unsafe.Pointer(_p0)), uintptr(mode), 0, 0, 0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -1255,7 +1255,7 @@ func Mknod(path string, mode uint32, dev int) (err error) { } _, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procMknod)), 3, uintptr(unsafe.Pointer(_p0)), uintptr(mode), uintptr(dev), 0, 0, 0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -1270,7 +1270,7 @@ func Mknodat(dirfd int, path string, mode uint32, dev int) (err error) { } _, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procMknodat)), 4, uintptr(dirfd), uintptr(unsafe.Pointer(_p0)), uintptr(mode), uintptr(dev), 0, 0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -1284,7 +1284,7 @@ func Mlock(b []byte) (err error) { } _, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procMlock)), 2, uintptr(unsafe.Pointer(_p0)), uintptr(len(b)), 0, 0, 0, 0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -1294,7 +1294,7 @@ func Mlock(b []byte) (err error) { func Mlockall(flags int) (err error) { _, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procMlockall)), 1, uintptr(flags), 0, 0, 0, 0, 0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -1308,7 +1308,7 @@ func Mprotect(b []byte, prot int) (err error) { } _, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procMprotect)), 3, uintptr(unsafe.Pointer(_p0)), uintptr(len(b)), uintptr(prot), 0, 0, 0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -1322,7 +1322,7 @@ func Msync(b []byte, flags int) (err error) { } _, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procMsync)), 3, uintptr(unsafe.Pointer(_p0)), uintptr(len(b)), uintptr(flags), 0, 0, 0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -1336,7 +1336,7 @@ func Munlock(b []byte) (err error) { } _, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procMunlock)), 2, uintptr(unsafe.Pointer(_p0)), uintptr(len(b)), 0, 0, 0, 0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -1346,7 +1346,7 @@ func Munlock(b []byte) (err error) { func Munlockall() (err error) { _, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procMunlockall)), 0, 0, 0, 0, 0, 0, 0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -1356,7 +1356,7 @@ func Munlockall() (err error) { func Nanosleep(time *Timespec, leftover *Timespec) (err error) { _, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procNanosleep)), 2, uintptr(unsafe.Pointer(time)), uintptr(unsafe.Pointer(leftover)), 0, 0, 0, 0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -1372,7 +1372,7 @@ func Open(path string, mode int, perm uint32) (fd int, err error) { r0, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procOpen)), 3, uintptr(unsafe.Pointer(_p0)), uintptr(mode), uintptr(perm), 0, 0, 0) fd = int(r0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -1388,7 +1388,7 @@ func Openat(dirfd int, path string, flags int, mode uint32) (fd int, err error) r0, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procOpenat)), 4, uintptr(dirfd), uintptr(unsafe.Pointer(_p0)), uintptr(flags), uintptr(mode), 0, 0) fd = int(r0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -1404,7 +1404,7 @@ func Pathconf(path string, name int) (val int, err error) { r0, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procPathconf)), 2, uintptr(unsafe.Pointer(_p0)), uintptr(name), 0, 0, 0, 0) val = int(r0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -1414,7 +1414,7 @@ func Pathconf(path string, name int) (val int, err error) { func Pause() (err error) { _, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procPause)), 0, 0, 0, 0, 0, 0, 0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -1429,7 +1429,7 @@ func pread(fd int, p []byte, offset int64) (n int, err error) { r0, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procpread)), 4, uintptr(fd), uintptr(unsafe.Pointer(_p0)), uintptr(len(p)), uintptr(offset), 0, 0) n = int(r0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -1444,7 +1444,7 @@ func pwrite(fd int, p []byte, offset int64) (n int, err error) { r0, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procpwrite)), 4, uintptr(fd), uintptr(unsafe.Pointer(_p0)), uintptr(len(p)), uintptr(offset), 0, 0) n = int(r0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -1459,7 +1459,7 @@ func read(fd int, p []byte) (n int, err error) { r0, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procread)), 3, uintptr(fd), uintptr(unsafe.Pointer(_p0)), uintptr(len(p)), 0, 0, 0) n = int(r0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -1479,7 +1479,7 @@ func Readlink(path string, buf []byte) (n int, err error) { r0, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procReadlink)), 3, uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(_p1)), uintptr(len(buf)), 0, 0, 0) n = int(r0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -1499,7 +1499,7 @@ func Rename(from string, to string) (err error) { } _, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procRename)), 2, uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(_p1)), 0, 0, 0, 0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -1519,7 +1519,7 @@ func Renameat(olddirfd int, oldpath string, newdirfd int, newpath string) (err e } _, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procRenameat)), 4, uintptr(olddirfd), uintptr(unsafe.Pointer(_p0)), uintptr(newdirfd), uintptr(unsafe.Pointer(_p1)), 0, 0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -1534,7 +1534,7 @@ func Rmdir(path string) (err error) { } _, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procRmdir)), 1, uintptr(unsafe.Pointer(_p0)), 0, 0, 0, 0, 0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -1545,7 +1545,7 @@ func Seek(fd int, offset int64, whence int) (newoffset int64, err error) { r0, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&proclseek)), 3, uintptr(fd), uintptr(offset), uintptr(whence), 0, 0, 0) newoffset = int64(r0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -1556,7 +1556,7 @@ func Select(nfd int, r *FdSet, w *FdSet, e *FdSet, timeout *Timeval) (n int, err r0, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procSelect)), 5, uintptr(nfd), uintptr(unsafe.Pointer(r)), uintptr(unsafe.Pointer(w)), uintptr(unsafe.Pointer(e)), uintptr(unsafe.Pointer(timeout)), 0) n = int(r0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -1566,7 +1566,7 @@ func Select(nfd int, r *FdSet, w *FdSet, e *FdSet, timeout *Timeval) (n int, err func Setegid(egid int) (err error) { _, _, e1 := rawSysvicall6(uintptr(unsafe.Pointer(&procSetegid)), 1, uintptr(egid), 0, 0, 0, 0, 0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -1576,7 +1576,7 @@ func Setegid(egid int) (err error) { func Seteuid(euid int) (err error) { _, _, e1 := rawSysvicall6(uintptr(unsafe.Pointer(&procSeteuid)), 1, uintptr(euid), 0, 0, 0, 0, 0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -1586,7 +1586,7 @@ func Seteuid(euid int) (err error) { func Setgid(gid int) (err error) { _, _, e1 := rawSysvicall6(uintptr(unsafe.Pointer(&procSetgid)), 1, uintptr(gid), 0, 0, 0, 0, 0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -1600,7 +1600,7 @@ func Sethostname(p []byte) (err error) { } _, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procSethostname)), 2, uintptr(unsafe.Pointer(_p0)), uintptr(len(p)), 0, 0, 0, 0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -1610,7 +1610,7 @@ func Sethostname(p []byte) (err error) { func Setpgid(pid int, pgid int) (err error) { _, _, e1 := rawSysvicall6(uintptr(unsafe.Pointer(&procSetpgid)), 2, uintptr(pid), uintptr(pgid), 0, 0, 0, 0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -1620,7 +1620,7 @@ func Setpgid(pid int, pgid int) (err error) { func Setpriority(which int, who int, prio int) (err error) { _, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procSetpriority)), 3, uintptr(which), uintptr(who), uintptr(prio), 0, 0, 0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -1630,7 +1630,7 @@ func Setpriority(which int, who int, prio int) (err error) { func Setregid(rgid int, egid int) (err error) { _, _, e1 := rawSysvicall6(uintptr(unsafe.Pointer(&procSetregid)), 2, uintptr(rgid), uintptr(egid), 0, 0, 0, 0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -1640,7 +1640,7 @@ func Setregid(rgid int, egid int) (err error) { func Setreuid(ruid int, euid int) (err error) { _, _, e1 := rawSysvicall6(uintptr(unsafe.Pointer(&procSetreuid)), 2, uintptr(ruid), uintptr(euid), 0, 0, 0, 0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -1651,7 +1651,7 @@ func Setsid() (pid int, err error) { r0, _, e1 := rawSysvicall6(uintptr(unsafe.Pointer(&procSetsid)), 0, 0, 0, 0, 0, 0, 0) pid = int(r0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -1661,7 +1661,7 @@ func Setsid() (pid int, err error) { func Setuid(uid int) (err error) { _, _, e1 := rawSysvicall6(uintptr(unsafe.Pointer(&procSetuid)), 1, uintptr(uid), 0, 0, 0, 0, 0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -1671,7 +1671,7 @@ func Setuid(uid int) (err error) { func Shutdown(s int, how int) (err error) { _, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procshutdown)), 2, uintptr(s), uintptr(how), 0, 0, 0, 0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -1686,7 +1686,7 @@ func Stat(path string, stat *Stat_t) (err error) { } _, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procStat)), 2, uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(stat)), 0, 0, 0, 0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -1701,7 +1701,7 @@ func Statvfs(path string, vfsstat *Statvfs_t) (err error) { } _, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procStatvfs)), 2, uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(vfsstat)), 0, 0, 0, 0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -1721,7 +1721,7 @@ func Symlink(path string, link string) (err error) { } _, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procSymlink)), 2, uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(_p1)), 0, 0, 0, 0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -1731,7 +1731,7 @@ func Symlink(path string, link string) (err error) { func Sync() (err error) { _, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procSync)), 0, 0, 0, 0, 0, 0, 0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -1742,7 +1742,7 @@ func Sysconf(which int) (n int64, err error) { r0, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procSysconf)), 1, uintptr(which), 0, 0, 0, 0, 0) n = int64(r0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -1753,7 +1753,7 @@ func Times(tms *Tms) (ticks uintptr, err error) { r0, _, e1 := rawSysvicall6(uintptr(unsafe.Pointer(&procTimes)), 1, uintptr(unsafe.Pointer(tms)), 0, 0, 0, 0, 0) ticks = uintptr(r0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -1768,7 +1768,7 @@ func Truncate(path string, length int64) (err error) { } _, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procTruncate)), 2, uintptr(unsafe.Pointer(_p0)), uintptr(length), 0, 0, 0, 0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -1778,7 +1778,7 @@ func Truncate(path string, length int64) (err error) { func Fsync(fd int) (err error) { _, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procFsync)), 1, uintptr(fd), 0, 0, 0, 0, 0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -1788,7 +1788,7 @@ func Fsync(fd int) (err error) { func Ftruncate(fd int, length int64) (err error) { _, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procFtruncate)), 2, uintptr(fd), uintptr(length), 0, 0, 0, 0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -1806,7 +1806,7 @@ func Umask(mask int) (oldmask int) { func Uname(buf *Utsname) (err error) { _, _, e1 := rawSysvicall6(uintptr(unsafe.Pointer(&procUname)), 1, uintptr(unsafe.Pointer(buf)), 0, 0, 0, 0, 0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -1821,7 +1821,7 @@ func Unmount(target string, flags int) (err error) { } _, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procumount)), 2, uintptr(unsafe.Pointer(_p0)), uintptr(flags), 0, 0, 0, 0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -1836,7 +1836,7 @@ func Unlink(path string) (err error) { } _, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procUnlink)), 1, uintptr(unsafe.Pointer(_p0)), 0, 0, 0, 0, 0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -1851,7 +1851,7 @@ func Unlinkat(dirfd int, path string, flags int) (err error) { } _, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procUnlinkat)), 3, uintptr(dirfd), uintptr(unsafe.Pointer(_p0)), uintptr(flags), 0, 0, 0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -1861,7 +1861,7 @@ func Unlinkat(dirfd int, path string, flags int) (err error) { func Ustat(dev int, ubuf *Ustat_t) (err error) { _, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procUstat)), 2, uintptr(dev), uintptr(unsafe.Pointer(ubuf)), 0, 0, 0, 0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -1876,7 +1876,7 @@ func Utime(path string, buf *Utimbuf) (err error) { } _, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procUtime)), 2, uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(buf)), 0, 0, 0, 0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -1886,7 +1886,7 @@ func Utime(path string, buf *Utimbuf) (err error) { func bind(s int, addr unsafe.Pointer, addrlen _Socklen) (err error) { _, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&proc__xnet_bind)), 3, uintptr(s), uintptr(addr), uintptr(addrlen), 0, 0, 0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -1896,7 +1896,7 @@ func bind(s int, addr unsafe.Pointer, addrlen _Socklen) (err error) { func connect(s int, addr unsafe.Pointer, addrlen _Socklen) (err error) { _, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&proc__xnet_connect)), 3, uintptr(s), uintptr(addr), uintptr(addrlen), 0, 0, 0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -1907,7 +1907,7 @@ func mmap(addr uintptr, length uintptr, prot int, flag int, fd int, pos int64) ( r0, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procmmap)), 6, uintptr(addr), uintptr(length), uintptr(prot), uintptr(flag), uintptr(fd), uintptr(pos)) ret = uintptr(r0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -1917,7 +1917,7 @@ func mmap(addr uintptr, length uintptr, prot int, flag int, fd int, pos int64) ( func munmap(addr uintptr, length uintptr) (err error) { _, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procmunmap)), 2, uintptr(addr), uintptr(length), 0, 0, 0, 0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -1928,7 +1928,7 @@ func sendfile(outfd int, infd int, offset *int64, count int) (written int, err e r0, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procsendfile)), 4, uintptr(outfd), uintptr(infd), uintptr(unsafe.Pointer(offset)), uintptr(count), 0, 0) written = int(r0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -1942,7 +1942,7 @@ func sendto(s int, buf []byte, flags int, to unsafe.Pointer, addrlen _Socklen) ( } _, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&proc__xnet_sendto)), 6, uintptr(s), uintptr(unsafe.Pointer(_p0)), uintptr(len(buf)), uintptr(flags), uintptr(to), uintptr(addrlen)) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -1953,7 +1953,7 @@ func socket(domain int, typ int, proto int) (fd int, err error) { r0, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&proc__xnet_socket)), 3, uintptr(domain), uintptr(typ), uintptr(proto), 0, 0, 0) fd = int(r0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -1963,7 +1963,7 @@ func socket(domain int, typ int, proto int) (fd int, err error) { func socketpair(domain int, typ int, proto int, fd *[2]int32) (err error) { _, _, e1 := rawSysvicall6(uintptr(unsafe.Pointer(&proc__xnet_socketpair)), 4, uintptr(domain), uintptr(typ), uintptr(proto), uintptr(unsafe.Pointer(fd)), 0, 0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -1978,7 +1978,7 @@ func write(fd int, p []byte) (n int, err error) { r0, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procwrite)), 3, uintptr(fd), uintptr(unsafe.Pointer(_p0)), uintptr(len(p)), 0, 0, 0) n = int(r0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -1988,7 +1988,7 @@ func write(fd int, p []byte) (n int, err error) { func getsockopt(s int, level int, name int, val unsafe.Pointer, vallen *_Socklen) (err error) { _, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&proc__xnet_getsockopt)), 5, uintptr(s), uintptr(level), uintptr(name), uintptr(val), uintptr(unsafe.Pointer(vallen)), 0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -1998,7 +1998,7 @@ func getsockopt(s int, level int, name int, val unsafe.Pointer, vallen *_Socklen func getpeername(fd int, rsa *RawSockaddrAny, addrlen *_Socklen) (err error) { _, _, e1 := rawSysvicall6(uintptr(unsafe.Pointer(&procgetpeername)), 3, uintptr(fd), uintptr(unsafe.Pointer(rsa)), uintptr(unsafe.Pointer(addrlen)), 0, 0, 0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -2008,7 +2008,7 @@ func getpeername(fd int, rsa *RawSockaddrAny, addrlen *_Socklen) (err error) { func setsockopt(s int, level int, name int, val unsafe.Pointer, vallen uintptr) (err error) { _, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procsetsockopt)), 5, uintptr(s), uintptr(level), uintptr(name), uintptr(val), uintptr(vallen), 0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -2023,7 +2023,7 @@ func recvfrom(fd int, p []byte, flags int, from *RawSockaddrAny, fromlen *_Sockl r0, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procrecvfrom)), 6, uintptr(fd), uintptr(unsafe.Pointer(_p0)), uintptr(len(p)), uintptr(flags), uintptr(unsafe.Pointer(from)), uintptr(unsafe.Pointer(fromlen))) n = int(r0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -2034,7 +2034,7 @@ func port_create() (n int, err error) { r0, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procport_create)), 0, 0, 0, 0, 0, 0, 0) n = int(r0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -2045,7 +2045,7 @@ func port_associate(port int, source int, object uintptr, events int, user *byte r0, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procport_associate)), 5, uintptr(port), uintptr(source), uintptr(object), uintptr(events), uintptr(unsafe.Pointer(user)), 0) n = int(r0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -2056,7 +2056,7 @@ func port_dissociate(port int, source int, object uintptr) (n int, err error) { r0, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procport_dissociate)), 3, uintptr(port), uintptr(source), uintptr(object), 0, 0, 0) n = int(r0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -2067,7 +2067,7 @@ func port_get(port int, pe *portEvent, timeout *Timespec) (n int, err error) { r0, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procport_get)), 3, uintptr(port), uintptr(unsafe.Pointer(pe)), uintptr(unsafe.Pointer(timeout)), 0, 0, 0) n = int(r0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -2078,7 +2078,7 @@ func port_getn(port int, pe *portEvent, max uint32, nget *uint32, timeout *Times r0, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procport_getn)), 5, uintptr(port), uintptr(unsafe.Pointer(pe)), uintptr(max), uintptr(unsafe.Pointer(nget)), uintptr(unsafe.Pointer(timeout)), 0) n = int(r0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -2088,7 +2088,7 @@ func port_getn(port int, pe *portEvent, max uint32, nget *uint32, timeout *Times func putmsg(fd int, clptr *strbuf, dataptr *strbuf, flags int) (err error) { _, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procputmsg)), 4, uintptr(fd), uintptr(unsafe.Pointer(clptr)), uintptr(unsafe.Pointer(dataptr)), uintptr(flags), 0, 0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } @@ -2098,7 +2098,7 @@ func putmsg(fd int, clptr *strbuf, dataptr *strbuf, flags int) (err error) { func getmsg(fd int, clptr *strbuf, dataptr *strbuf, flags *int) (err error) { _, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procgetmsg)), 4, uintptr(fd), uintptr(unsafe.Pointer(clptr)), uintptr(unsafe.Pointer(dataptr)), uintptr(unsafe.Pointer(flags)), 0, 0) if e1 != 0 { - err = e1 + err = errnoErr(e1) } return } diff --git a/vendor/golang.org/x/sys/unix/zsyscall_zos_s390x.go b/vendor/golang.org/x/sys/unix/zsyscall_zos_s390x.go index c316817..1d8fe1d 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_zos_s390x.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_zos_s390x.go @@ -40,17 +40,6 @@ func read(fd int, p []byte) (n int, err error) { // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT -func readlen(fd int, buf *byte, nbuf int) (n int, err error) { - r0, _, e1 := syscall_syscall(SYS_READ, uintptr(fd), uintptr(unsafe.Pointer(buf)), uintptr(nbuf)) - n = int(r0) - if e1 != 0 { - err = errnoErr(e1) - } - return -} - -// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT - func write(fd int, p []byte) (n int, err error) { var _p0 unsafe.Pointer if len(p) > 0 { diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_386.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_386.go index c9c4ad0..9862853 100644 --- a/vendor/golang.org/x/sys/unix/zsysnum_linux_386.go +++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_386.go @@ -447,4 +447,5 @@ const ( SYS_PROCESS_MRELEASE = 448 SYS_FUTEX_WAITV = 449 SYS_SET_MEMPOLICY_HOME_NODE = 450 + SYS_CACHESTAT = 451 ) diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_amd64.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_amd64.go index 12ff341..8901f0f 100644 --- a/vendor/golang.org/x/sys/unix/zsysnum_linux_amd64.go +++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_amd64.go @@ -369,4 +369,5 @@ const ( SYS_PROCESS_MRELEASE = 448 SYS_FUTEX_WAITV = 449 SYS_SET_MEMPOLICY_HOME_NODE = 450 + SYS_CACHESTAT = 451 ) diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_arm.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_arm.go index c3fb5e7..6902c37 100644 --- a/vendor/golang.org/x/sys/unix/zsysnum_linux_arm.go +++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_arm.go @@ -411,4 +411,5 @@ const ( SYS_PROCESS_MRELEASE = 448 SYS_FUTEX_WAITV = 449 SYS_SET_MEMPOLICY_HOME_NODE = 450 + SYS_CACHESTAT = 451 ) diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_arm64.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_arm64.go index 358c847..a6d3dff 100644 --- a/vendor/golang.org/x/sys/unix/zsysnum_linux_arm64.go +++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_arm64.go @@ -314,4 +314,5 @@ const ( SYS_PROCESS_MRELEASE = 448 SYS_FUTEX_WAITV = 449 SYS_SET_MEMPOLICY_HOME_NODE = 450 + SYS_CACHESTAT = 451 ) diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_loong64.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_loong64.go index 81c4849..b18f3f7 100644 --- a/vendor/golang.org/x/sys/unix/zsysnum_linux_loong64.go +++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_loong64.go @@ -308,4 +308,5 @@ const ( SYS_PROCESS_MRELEASE = 448 SYS_FUTEX_WAITV = 449 SYS_SET_MEMPOLICY_HOME_NODE = 450 + SYS_CACHESTAT = 451 ) diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_mips.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_mips.go index 202a57e..0302e5e 100644 --- a/vendor/golang.org/x/sys/unix/zsysnum_linux_mips.go +++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_mips.go @@ -431,4 +431,5 @@ const ( SYS_PROCESS_MRELEASE = 4448 SYS_FUTEX_WAITV = 4449 SYS_SET_MEMPOLICY_HOME_NODE = 4450 + SYS_CACHESTAT = 4451 ) diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_mips64.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_mips64.go index 1fbceb5..6693ba4 100644 --- a/vendor/golang.org/x/sys/unix/zsysnum_linux_mips64.go +++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_mips64.go @@ -361,4 +361,5 @@ const ( SYS_PROCESS_MRELEASE = 5448 SYS_FUTEX_WAITV = 5449 SYS_SET_MEMPOLICY_HOME_NODE = 5450 + SYS_CACHESTAT = 5451 ) diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_mips64le.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_mips64le.go index b4ffb7a..fd93f49 100644 --- a/vendor/golang.org/x/sys/unix/zsysnum_linux_mips64le.go +++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_mips64le.go @@ -361,4 +361,5 @@ const ( SYS_PROCESS_MRELEASE = 5448 SYS_FUTEX_WAITV = 5449 SYS_SET_MEMPOLICY_HOME_NODE = 5450 + SYS_CACHESTAT = 5451 ) diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_mipsle.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_mipsle.go index 867985f..760ddca 100644 --- a/vendor/golang.org/x/sys/unix/zsysnum_linux_mipsle.go +++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_mipsle.go @@ -431,4 +431,5 @@ const ( SYS_PROCESS_MRELEASE = 4448 SYS_FUTEX_WAITV = 4449 SYS_SET_MEMPOLICY_HOME_NODE = 4450 + SYS_CACHESTAT = 4451 ) diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_ppc.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_ppc.go index a8cce69..cff2b25 100644 --- a/vendor/golang.org/x/sys/unix/zsysnum_linux_ppc.go +++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_ppc.go @@ -438,4 +438,5 @@ const ( SYS_PROCESS_MRELEASE = 448 SYS_FUTEX_WAITV = 449 SYS_SET_MEMPOLICY_HOME_NODE = 450 + SYS_CACHESTAT = 451 ) diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_ppc64.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_ppc64.go index d44c5b3..a4b2405 100644 --- a/vendor/golang.org/x/sys/unix/zsysnum_linux_ppc64.go +++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_ppc64.go @@ -410,4 +410,5 @@ const ( SYS_PROCESS_MRELEASE = 448 SYS_FUTEX_WAITV = 449 SYS_SET_MEMPOLICY_HOME_NODE = 450 + SYS_CACHESTAT = 451 ) diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_ppc64le.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_ppc64le.go index 4214dd9..aca54b4 100644 --- a/vendor/golang.org/x/sys/unix/zsysnum_linux_ppc64le.go +++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_ppc64le.go @@ -410,4 +410,5 @@ const ( SYS_PROCESS_MRELEASE = 448 SYS_FUTEX_WAITV = 449 SYS_SET_MEMPOLICY_HOME_NODE = 450 + SYS_CACHESTAT = 451 ) diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_riscv64.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_riscv64.go index ef285c5..9d1738d 100644 --- a/vendor/golang.org/x/sys/unix/zsysnum_linux_riscv64.go +++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_riscv64.go @@ -315,4 +315,5 @@ const ( SYS_PROCESS_MRELEASE = 448 SYS_FUTEX_WAITV = 449 SYS_SET_MEMPOLICY_HOME_NODE = 450 + SYS_CACHESTAT = 451 ) diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_s390x.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_s390x.go index e6ed7d6..022878d 100644 --- a/vendor/golang.org/x/sys/unix/zsysnum_linux_s390x.go +++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_s390x.go @@ -376,4 +376,5 @@ const ( SYS_PROCESS_MRELEASE = 448 SYS_FUTEX_WAITV = 449 SYS_SET_MEMPOLICY_HOME_NODE = 450 + SYS_CACHESTAT = 451 ) diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_sparc64.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_sparc64.go index 92f628e..4100a76 100644 --- a/vendor/golang.org/x/sys/unix/zsysnum_linux_sparc64.go +++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_sparc64.go @@ -389,4 +389,5 @@ const ( SYS_PROCESS_MRELEASE = 448 SYS_FUTEX_WAITV = 449 SYS_SET_MEMPOLICY_HOME_NODE = 450 + SYS_CACHESTAT = 451 ) diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux.go b/vendor/golang.org/x/sys/unix/ztypes_linux.go index 494493c..18aa70b 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux.go @@ -1977,7 +1977,7 @@ const ( NFT_MSG_GETFLOWTABLE = 0x17 NFT_MSG_DELFLOWTABLE = 0x18 NFT_MSG_GETRULE_RESET = 0x19 - NFT_MSG_MAX = 0x21 + NFT_MSG_MAX = 0x22 NFTA_LIST_UNSPEC = 0x0 NFTA_LIST_ELEM = 0x1 NFTA_HOOK_UNSPEC = 0x0 @@ -4499,7 +4499,7 @@ const ( NL80211_ATTR_MAC_HINT = 0xc8 NL80211_ATTR_MAC_MASK = 0xd7 NL80211_ATTR_MAX_AP_ASSOC_STA = 0xca - NL80211_ATTR_MAX = 0x145 + NL80211_ATTR_MAX = 0x146 NL80211_ATTR_MAX_CRIT_PROT_DURATION = 0xb4 NL80211_ATTR_MAX_CSA_COUNTERS = 0xce NL80211_ATTR_MAX_MATCH_SETS = 0x85 @@ -4869,7 +4869,7 @@ const ( NL80211_CMD_LEAVE_IBSS = 0x2c NL80211_CMD_LEAVE_MESH = 0x45 NL80211_CMD_LEAVE_OCB = 0x6d - NL80211_CMD_MAX = 0x99 + NL80211_CMD_MAX = 0x9a NL80211_CMD_MICHAEL_MIC_FAILURE = 0x29 NL80211_CMD_MODIFY_LINK_STA = 0x97 NL80211_CMD_NAN_MATCH = 0x78 @@ -5503,7 +5503,7 @@ const ( NL80211_RATE_INFO_HE_RU_ALLOC_52 = 0x1 NL80211_RATE_INFO_HE_RU_ALLOC_996 = 0x5 NL80211_RATE_INFO_HE_RU_ALLOC = 0x11 - NL80211_RATE_INFO_MAX = 0x16 + NL80211_RATE_INFO_MAX = 0x1d NL80211_RATE_INFO_MCS = 0x2 NL80211_RATE_INFO_SHORT_GI = 0x4 NL80211_RATE_INFO_VHT_MCS = 0x6 diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_riscv64.go b/vendor/golang.org/x/sys/unix/ztypes_linux_riscv64.go index 83c69c1..1b4c97c 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_riscv64.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_riscv64.go @@ -733,6 +733,10 @@ const ( RISCV_HWPROBE_KEY_IMA_EXT_0 = 0x4 RISCV_HWPROBE_IMA_FD = 0x1 RISCV_HWPROBE_IMA_C = 0x2 + RISCV_HWPROBE_IMA_V = 0x4 + RISCV_HWPROBE_EXT_ZBA = 0x8 + RISCV_HWPROBE_EXT_ZBB = 0x10 + RISCV_HWPROBE_EXT_ZBS = 0x20 RISCV_HWPROBE_KEY_CPUPERF_0 = 0x5 RISCV_HWPROBE_MISALIGNED_UNKNOWN = 0x0 RISCV_HWPROBE_MISALIGNED_EMULATED = 0x1 diff --git a/vendor/golang.org/x/sys/windows/exec_windows.go b/vendor/golang.org/x/sys/windows/exec_windows.go index a52e033..9cabbb6 100644 --- a/vendor/golang.org/x/sys/windows/exec_windows.go +++ b/vendor/golang.org/x/sys/windows/exec_windows.go @@ -22,7 +22,7 @@ import ( // but only if there is space or tab inside s. func EscapeArg(s string) string { if len(s) == 0 { - return "\"\"" + return `""` } n := len(s) hasSpace := false @@ -35,7 +35,7 @@ func EscapeArg(s string) string { } } if hasSpace { - n += 2 + n += 2 // Reserve space for quotes. } if n == len(s) { return s @@ -82,20 +82,68 @@ func EscapeArg(s string) string { // in CreateProcess's CommandLine argument, CreateService/ChangeServiceConfig's BinaryPathName argument, // or any program that uses CommandLineToArgv. func ComposeCommandLine(args []string) string { - var commandLine string - for i := range args { - if i > 0 { - commandLine += " " - } - commandLine += EscapeArg(args[i]) + if len(args) == 0 { + return "" } - return commandLine + + // Per https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-commandlinetoargvw: + // “This function accepts command lines that contain a program name; the + // program name can be enclosed in quotation marks or not.” + // + // Unfortunately, it provides no means of escaping interior quotation marks + // within that program name, and we have no way to report them here. + prog := args[0] + mustQuote := len(prog) == 0 + for i := 0; i < len(prog); i++ { + c := prog[i] + if c <= ' ' || (c == '"' && i == 0) { + // Force quotes for not only the ASCII space and tab as described in the + // MSDN article, but also ASCII control characters. + // The documentation for CommandLineToArgvW doesn't say what happens when + // the first argument is not a valid program name, but it empirically + // seems to drop unquoted control characters. + mustQuote = true + break + } + } + var commandLine []byte + if mustQuote { + commandLine = make([]byte, 0, len(prog)+2) + commandLine = append(commandLine, '"') + for i := 0; i < len(prog); i++ { + c := prog[i] + if c == '"' { + // This quote would interfere with our surrounding quotes. + // We have no way to report an error, so just strip out + // the offending character instead. + continue + } + commandLine = append(commandLine, c) + } + commandLine = append(commandLine, '"') + } else { + if len(args) == 1 { + // args[0] is a valid command line representing itself. + // No need to allocate a new slice or string for it. + return prog + } + commandLine = []byte(prog) + } + + for _, arg := range args[1:] { + commandLine = append(commandLine, ' ') + // TODO(bcmills): since we're already appending to a slice, it would be nice + // to avoid the intermediate allocations of EscapeArg. + // Perhaps we can factor out an appendEscapedArg function. + commandLine = append(commandLine, EscapeArg(arg)...) + } + return string(commandLine) } // DecomposeCommandLine breaks apart its argument command line into unescaped parts using CommandLineToArgv, // as gathered from GetCommandLine, QUERY_SERVICE_CONFIG's BinaryPathName argument, or elsewhere that // command lines are passed around. -// DecomposeCommandLine returns error if commandLine contains NUL. +// DecomposeCommandLine returns an error if commandLine contains NUL. func DecomposeCommandLine(commandLine string) ([]string, error) { if len(commandLine) == 0 { return []string{}, nil @@ -105,18 +153,35 @@ func DecomposeCommandLine(commandLine string) ([]string, error) { return nil, errorspkg.New("string with NUL passed to DecomposeCommandLine") } var argc int32 - argv, err := CommandLineToArgv(&utf16CommandLine[0], &argc) + argv, err := commandLineToArgv(&utf16CommandLine[0], &argc) if err != nil { return nil, err } defer LocalFree(Handle(unsafe.Pointer(argv))) + var args []string - for _, v := range (*argv)[:argc] { - args = append(args, UTF16ToString((*v)[:])) + for _, p := range unsafe.Slice(argv, argc) { + args = append(args, UTF16PtrToString(p)) } return args, nil } +// CommandLineToArgv parses a Unicode command line string and sets +// argc to the number of parsed arguments. +// +// The returned memory should be freed using a single call to LocalFree. +// +// Note that although the return type of CommandLineToArgv indicates 8192 +// entries of up to 8192 characters each, the actual count of parsed arguments +// may exceed 8192, and the documentation for CommandLineToArgvW does not mention +// any bound on the lengths of the individual argument strings. +// (See https://go.dev/issue/63236.) +func CommandLineToArgv(cmd *uint16, argc *int32) (argv *[8192]*[8192]uint16, err error) { + argp, err := commandLineToArgv(cmd, argc) + argv = (*[8192]*[8192]uint16)(unsafe.Pointer(argp)) + return argv, err +} + func CloseOnExec(fd Handle) { SetHandleInformation(Handle(fd), HANDLE_FLAG_INHERIT, 0) } diff --git a/vendor/golang.org/x/sys/windows/security_windows.go b/vendor/golang.org/x/sys/windows/security_windows.go index d414ef1..26be94a 100644 --- a/vendor/golang.org/x/sys/windows/security_windows.go +++ b/vendor/golang.org/x/sys/windows/security_windows.go @@ -7,8 +7,6 @@ package windows import ( "syscall" "unsafe" - - "golang.org/x/sys/internal/unsafeheader" ) const ( @@ -1341,21 +1339,14 @@ func (selfRelativeSD *SECURITY_DESCRIPTOR) copySelfRelativeSecurityDescriptor() sdLen = min } - var src []byte - h := (*unsafeheader.Slice)(unsafe.Pointer(&src)) - h.Data = unsafe.Pointer(selfRelativeSD) - h.Len = sdLen - h.Cap = sdLen - + src := unsafe.Slice((*byte)(unsafe.Pointer(selfRelativeSD)), sdLen) + // SECURITY_DESCRIPTOR has pointers in it, which means checkptr expects for it to + // be aligned properly. When we're copying a Windows-allocated struct to a + // Go-allocated one, make sure that the Go allocation is aligned to the + // pointer size. const psize = int(unsafe.Sizeof(uintptr(0))) - - var dst []byte - h = (*unsafeheader.Slice)(unsafe.Pointer(&dst)) alloc := make([]uintptr, (sdLen+psize-1)/psize) - h.Data = (*unsafeheader.Slice)(unsafe.Pointer(&alloc)).Data - h.Len = sdLen - h.Cap = sdLen - + dst := unsafe.Slice((*byte)(unsafe.Pointer(&alloc[0])), sdLen) copy(dst, src) return (*SECURITY_DESCRIPTOR)(unsafe.Pointer(&dst[0])) } diff --git a/vendor/golang.org/x/sys/windows/syscall_windows.go b/vendor/golang.org/x/sys/windows/syscall_windows.go index 67bad09..35cfc57 100644 --- a/vendor/golang.org/x/sys/windows/syscall_windows.go +++ b/vendor/golang.org/x/sys/windows/syscall_windows.go @@ -15,8 +15,6 @@ import ( "time" "unicode/utf16" "unsafe" - - "golang.org/x/sys/internal/unsafeheader" ) type Handle uintptr @@ -240,7 +238,7 @@ func NewCallbackCDecl(fn interface{}) uintptr { //sys SetFileAttributes(name *uint16, attrs uint32) (err error) = kernel32.SetFileAttributesW //sys GetFileAttributesEx(name *uint16, level uint32, info *byte) (err error) = kernel32.GetFileAttributesExW //sys GetCommandLine() (cmd *uint16) = kernel32.GetCommandLineW -//sys CommandLineToArgv(cmd *uint16, argc *int32) (argv *[8192]*[8192]uint16, err error) [failretval==nil] = shell32.CommandLineToArgvW +//sys commandLineToArgv(cmd *uint16, argc *int32) (argv **uint16, err error) [failretval==nil] = shell32.CommandLineToArgvW //sys LocalFree(hmem Handle) (handle Handle, err error) [failretval!=0] //sys LocalAlloc(flags uint32, length uint32) (ptr uintptr, err error) //sys SetHandleInformation(handle Handle, mask uint32, flags uint32) (err error) @@ -299,12 +297,15 @@ func NewCallbackCDecl(fn interface{}) uintptr { //sys RegNotifyChangeKeyValue(key Handle, watchSubtree bool, notifyFilter uint32, event Handle, asynchronous bool) (regerrno error) = advapi32.RegNotifyChangeKeyValue //sys GetCurrentProcessId() (pid uint32) = kernel32.GetCurrentProcessId //sys ProcessIdToSessionId(pid uint32, sessionid *uint32) (err error) = kernel32.ProcessIdToSessionId +//sys ClosePseudoConsole(console Handle) = kernel32.ClosePseudoConsole +//sys createPseudoConsole(size uint32, in Handle, out Handle, flags uint32, pconsole *Handle) (hr error) = kernel32.CreatePseudoConsole //sys GetConsoleMode(console Handle, mode *uint32) (err error) = kernel32.GetConsoleMode //sys SetConsoleMode(console Handle, mode uint32) (err error) = kernel32.SetConsoleMode //sys GetConsoleScreenBufferInfo(console Handle, info *ConsoleScreenBufferInfo) (err error) = kernel32.GetConsoleScreenBufferInfo //sys setConsoleCursorPosition(console Handle, position uint32) (err error) = kernel32.SetConsoleCursorPosition //sys WriteConsole(console Handle, buf *uint16, towrite uint32, written *uint32, reserved *byte) (err error) = kernel32.WriteConsoleW //sys ReadConsole(console Handle, buf *uint16, toread uint32, read *uint32, inputControl *byte) (err error) = kernel32.ReadConsoleW +//sys resizePseudoConsole(pconsole Handle, size uint32) (hr error) = kernel32.ResizePseudoConsole //sys CreateToolhelp32Snapshot(flags uint32, processId uint32) (handle Handle, err error) [failretval==InvalidHandle] = kernel32.CreateToolhelp32Snapshot //sys Module32First(snapshot Handle, moduleEntry *ModuleEntry32) (err error) = kernel32.Module32FirstW //sys Module32Next(snapshot Handle, moduleEntry *ModuleEntry32) (err error) = kernel32.Module32NextW @@ -1667,12 +1668,8 @@ func NewNTUnicodeString(s string) (*NTUnicodeString, error) { // Slice returns a uint16 slice that aliases the data in the NTUnicodeString. func (s *NTUnicodeString) Slice() []uint16 { - var slice []uint16 - hdr := (*unsafeheader.Slice)(unsafe.Pointer(&slice)) - hdr.Data = unsafe.Pointer(s.Buffer) - hdr.Len = int(s.Length) - hdr.Cap = int(s.MaximumLength) - return slice + slice := unsafe.Slice(s.Buffer, s.MaximumLength) + return slice[:s.Length] } func (s *NTUnicodeString) String() string { @@ -1695,12 +1692,8 @@ func NewNTString(s string) (*NTString, error) { // Slice returns a byte slice that aliases the data in the NTString. func (s *NTString) Slice() []byte { - var slice []byte - hdr := (*unsafeheader.Slice)(unsafe.Pointer(&slice)) - hdr.Data = unsafe.Pointer(s.Buffer) - hdr.Len = int(s.Length) - hdr.Cap = int(s.MaximumLength) - return slice + slice := unsafe.Slice(s.Buffer, s.MaximumLength) + return slice[:s.Length] } func (s *NTString) String() string { @@ -1752,10 +1745,7 @@ func LoadResourceData(module, resInfo Handle) (data []byte, err error) { if err != nil { return } - h := (*unsafeheader.Slice)(unsafe.Pointer(&data)) - h.Data = unsafe.Pointer(ptr) - h.Len = int(size) - h.Cap = int(size) + data = unsafe.Slice((*byte)(unsafe.Pointer(ptr)), size) return } @@ -1826,3 +1816,17 @@ type PSAPI_WORKING_SET_EX_INFORMATION struct { // A PSAPI_WORKING_SET_EX_BLOCK union that indicates the attributes of the page at VirtualAddress. VirtualAttributes PSAPI_WORKING_SET_EX_BLOCK } + +// CreatePseudoConsole creates a windows pseudo console. +func CreatePseudoConsole(size Coord, in Handle, out Handle, flags uint32, pconsole *Handle) error { + // We need this wrapper to manually cast Coord to uint32. The autogenerated wrappers only + // accept arguments that can be casted to uintptr, and Coord can't. + return createPseudoConsole(*((*uint32)(unsafe.Pointer(&size))), in, out, flags, pconsole) +} + +// ResizePseudoConsole resizes the internal buffers of the pseudo console to the width and height specified in `size`. +func ResizePseudoConsole(pconsole Handle, size Coord) error { + // We need this wrapper to manually cast Coord to uint32. The autogenerated wrappers only + // accept arguments that can be casted to uintptr, and Coord can't. + return resizePseudoConsole(pconsole, *((*uint32)(unsafe.Pointer(&size)))) +} diff --git a/vendor/golang.org/x/sys/windows/types_windows.go b/vendor/golang.org/x/sys/windows/types_windows.go index 88e62a6..b88dc7c 100644 --- a/vendor/golang.org/x/sys/windows/types_windows.go +++ b/vendor/golang.org/x/sys/windows/types_windows.go @@ -247,6 +247,7 @@ const ( PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY = 0x00020007 PROC_THREAD_ATTRIBUTE_UMS_THREAD = 0x00030006 PROC_THREAD_ATTRIBUTE_PROTECTION_LEVEL = 0x0002000b + PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE = 0x00020016 ) const ( @@ -2139,6 +2140,12 @@ const ( ENABLE_LVB_GRID_WORLDWIDE = 0x10 ) +// Pseudo console related constants used for the flags parameter to +// CreatePseudoConsole. See: https://learn.microsoft.com/en-us/windows/console/createpseudoconsole +const ( + PSEUDOCONSOLE_INHERIT_CURSOR = 0x1 +) + type Coord struct { X int16 Y int16 diff --git a/vendor/golang.org/x/sys/windows/zsyscall_windows.go b/vendor/golang.org/x/sys/windows/zsyscall_windows.go index 5c38558..8b1688d 100644 --- a/vendor/golang.org/x/sys/windows/zsyscall_windows.go +++ b/vendor/golang.org/x/sys/windows/zsyscall_windows.go @@ -188,6 +188,7 @@ var ( procCancelIo = modkernel32.NewProc("CancelIo") procCancelIoEx = modkernel32.NewProc("CancelIoEx") procCloseHandle = modkernel32.NewProc("CloseHandle") + procClosePseudoConsole = modkernel32.NewProc("ClosePseudoConsole") procConnectNamedPipe = modkernel32.NewProc("ConnectNamedPipe") procCreateDirectoryW = modkernel32.NewProc("CreateDirectoryW") procCreateEventExW = modkernel32.NewProc("CreateEventExW") @@ -202,6 +203,7 @@ var ( procCreateNamedPipeW = modkernel32.NewProc("CreateNamedPipeW") procCreatePipe = modkernel32.NewProc("CreatePipe") procCreateProcessW = modkernel32.NewProc("CreateProcessW") + procCreatePseudoConsole = modkernel32.NewProc("CreatePseudoConsole") procCreateSymbolicLinkW = modkernel32.NewProc("CreateSymbolicLinkW") procCreateToolhelp32Snapshot = modkernel32.NewProc("CreateToolhelp32Snapshot") procDefineDosDeviceW = modkernel32.NewProc("DefineDosDeviceW") @@ -328,6 +330,7 @@ var ( procReleaseMutex = modkernel32.NewProc("ReleaseMutex") procRemoveDirectoryW = modkernel32.NewProc("RemoveDirectoryW") procResetEvent = modkernel32.NewProc("ResetEvent") + procResizePseudoConsole = modkernel32.NewProc("ResizePseudoConsole") procResumeThread = modkernel32.NewProc("ResumeThread") procSetCommTimeouts = modkernel32.NewProc("SetCommTimeouts") procSetConsoleCursorPosition = modkernel32.NewProc("SetConsoleCursorPosition") @@ -1633,6 +1636,11 @@ func CloseHandle(handle Handle) (err error) { return } +func ClosePseudoConsole(console Handle) { + syscall.Syscall(procClosePseudoConsole.Addr(), 1, uintptr(console), 0, 0) + return +} + func ConnectNamedPipe(pipe Handle, overlapped *Overlapped) (err error) { r1, _, e1 := syscall.Syscall(procConnectNamedPipe.Addr(), 2, uintptr(pipe), uintptr(unsafe.Pointer(overlapped)), 0) if r1 == 0 { @@ -1762,6 +1770,14 @@ func CreateProcess(appName *uint16, commandLine *uint16, procSecurity *SecurityA return } +func createPseudoConsole(size uint32, in Handle, out Handle, flags uint32, pconsole *Handle) (hr error) { + r0, _, _ := syscall.Syscall6(procCreatePseudoConsole.Addr(), 5, uintptr(size), uintptr(in), uintptr(out), uintptr(flags), uintptr(unsafe.Pointer(pconsole)), 0) + if r0 != 0 { + hr = syscall.Errno(r0) + } + return +} + func CreateSymbolicLink(symlinkfilename *uint16, targetfilename *uint16, flags uint32) (err error) { r1, _, e1 := syscall.Syscall(procCreateSymbolicLinkW.Addr(), 3, uintptr(unsafe.Pointer(symlinkfilename)), uintptr(unsafe.Pointer(targetfilename)), uintptr(flags)) if r1&0xff == 0 { @@ -2862,6 +2878,14 @@ func ResetEvent(event Handle) (err error) { return } +func resizePseudoConsole(pconsole Handle, size uint32) (hr error) { + r0, _, _ := syscall.Syscall(procResizePseudoConsole.Addr(), 2, uintptr(pconsole), uintptr(size), 0) + if r0 != 0 { + hr = syscall.Errno(r0) + } + return +} + func ResumeThread(thread Handle) (ret uint32, err error) { r0, _, e1 := syscall.Syscall(procResumeThread.Addr(), 1, uintptr(thread), 0, 0) ret = uint32(r0) @@ -3820,9 +3844,9 @@ func setupUninstallOEMInf(infFileName *uint16, flags SUOI, reserved uintptr) (er return } -func CommandLineToArgv(cmd *uint16, argc *int32) (argv *[8192]*[8192]uint16, err error) { +func commandLineToArgv(cmd *uint16, argc *int32) (argv **uint16, err error) { r0, _, e1 := syscall.Syscall(procCommandLineToArgvW.Addr(), 2, uintptr(unsafe.Pointer(cmd)), uintptr(unsafe.Pointer(argc)), 0) - argv = (*[8192]*[8192]uint16)(unsafe.Pointer(r0)) + argv = (**uint16)(unsafe.Pointer(r0)) if argv == nil { err = errnoErr(e1) } diff --git a/vendor/golang.org/x/tools/go/internal/packagesdriver/sizes.go b/vendor/golang.org/x/tools/go/internal/packagesdriver/sizes.go index 18a002f..0454cdd 100644 --- a/vendor/golang.org/x/tools/go/internal/packagesdriver/sizes.go +++ b/vendor/golang.org/x/tools/go/internal/packagesdriver/sizes.go @@ -8,7 +8,6 @@ package packagesdriver import ( "context" "fmt" - "go/types" "strings" "golang.org/x/tools/internal/gocommand" @@ -16,7 +15,7 @@ import ( var debug = false -func GetSizesGolist(ctx context.Context, inv gocommand.Invocation, gocmdRunner *gocommand.Runner) (types.Sizes, error) { +func GetSizesForArgsGolist(ctx context.Context, inv gocommand.Invocation, gocmdRunner *gocommand.Runner) (string, string, error) { inv.Verb = "list" inv.Args = []string{"-f", "{{context.GOARCH}} {{context.Compiler}}", "--", "unsafe"} stdout, stderr, friendlyErr, rawErr := gocmdRunner.RunRaw(ctx, inv) @@ -29,21 +28,21 @@ func GetSizesGolist(ctx context.Context, inv gocommand.Invocation, gocmdRunner * inv.Args = []string{"GOARCH"} envout, enverr := gocmdRunner.Run(ctx, inv) if enverr != nil { - return nil, enverr + return "", "", enverr } goarch = strings.TrimSpace(envout.String()) compiler = "gc" } else { - return nil, friendlyErr + return "", "", friendlyErr } } else { fields := strings.Fields(stdout.String()) if len(fields) < 2 { - return nil, fmt.Errorf("could not parse GOARCH and Go compiler in format \" \":\nstdout: <<%s>>\nstderr: <<%s>>", + return "", "", fmt.Errorf("could not parse GOARCH and Go compiler in format \" \":\nstdout: <<%s>>\nstderr: <<%s>>", stdout.String(), stderr.String()) } goarch = fields[0] compiler = fields[1] } - return types.SizesFor(compiler, goarch), nil + return compiler, goarch, nil } diff --git a/vendor/golang.org/x/tools/go/packages/golist.go b/vendor/golang.org/x/tools/go/packages/golist.go index 5823003..b5de9cf 100644 --- a/vendor/golang.org/x/tools/go/packages/golist.go +++ b/vendor/golang.org/x/tools/go/packages/golist.go @@ -9,7 +9,6 @@ import ( "context" "encoding/json" "fmt" - "go/types" "io/ioutil" "log" "os" @@ -153,10 +152,10 @@ func goListDriver(cfg *Config, patterns ...string) (*driverResponse, error) { if cfg.Mode&NeedTypesSizes != 0 || cfg.Mode&NeedTypes != 0 { sizeswg.Add(1) go func() { - var sizes types.Sizes - sizes, sizeserr = packagesdriver.GetSizesGolist(ctx, state.cfgInvocation(), cfg.gocmdRunner) - // types.SizesFor always returns nil or a *types.StdSizes. - response.dr.Sizes, _ = sizes.(*types.StdSizes) + compiler, arch, err := packagesdriver.GetSizesForArgsGolist(ctx, state.cfgInvocation(), cfg.gocmdRunner) + sizeserr = err + response.dr.Compiler = compiler + response.dr.Arch = arch sizeswg.Done() }() } diff --git a/vendor/golang.org/x/tools/go/packages/packages.go b/vendor/golang.org/x/tools/go/packages/packages.go index da1a27e..124a6fe 100644 --- a/vendor/golang.org/x/tools/go/packages/packages.go +++ b/vendor/golang.org/x/tools/go/packages/packages.go @@ -220,8 +220,10 @@ type driverResponse struct { // lists of multiple drivers, go/packages will fall back to the next driver. NotHandled bool - // Sizes, if not nil, is the types.Sizes to use when type checking. - Sizes *types.StdSizes + // Compiler and Arch are the arguments pass of types.SizesFor + // to get a types.Sizes to use when type checking. + Compiler string + Arch string // Roots is the set of package IDs that make up the root packages. // We have to encode this separately because when we encode a single package @@ -262,7 +264,7 @@ func Load(cfg *Config, patterns ...string) ([]*Package, error) { if err != nil { return nil, err } - l.sizes = response.Sizes + l.sizes = types.SizesFor(response.Compiler, response.Arch) return l.refine(response) } diff --git a/vendor/golang.org/x/tools/go/types/objectpath/objectpath.go b/vendor/golang.org/x/tools/go/types/objectpath/objectpath.go index c725d83..fa5834b 100644 --- a/vendor/golang.org/x/tools/go/types/objectpath/objectpath.go +++ b/vendor/golang.org/x/tools/go/types/objectpath/objectpath.go @@ -32,6 +32,7 @@ import ( _ "unsafe" "golang.org/x/tools/internal/typeparams" + "golang.org/x/tools/internal/typesinternal" ) // A Path is an opaque name that identifies a types.Object @@ -127,12 +128,15 @@ type Encoder struct { skipMethodSorting bool } -// Exposed to gopls via golang.org/x/tools/internal/typesinternal -// TODO(golang/go#61443): eliminate this parameter one way or the other. +// Expose back doors so that gopls can avoid method sorting, which can dominate +// analysis on certain repositories. // -//go:linkname skipMethodSorting -func skipMethodSorting(enc *Encoder) { - enc.skipMethodSorting = true +// TODO(golang/go#61443): remove this. +func init() { + typesinternal.SkipEncoderMethodSorting = func(enc interface{}) { + enc.(*Encoder).skipMethodSorting = true + } + typesinternal.ObjectpathObject = object } // For returns the path to an object relative to its package, @@ -572,17 +576,16 @@ func findTypeParam(obj types.Object, list *typeparams.TypeParamList, path []byte // Object returns the object denoted by path p within the package pkg. func Object(pkg *types.Package, p Path) (types.Object, error) { - return object(pkg, p, false) + return object(pkg, string(p), false) } // Note: the skipMethodSorting parameter must match the value of // Encoder.skipMethodSorting used during encoding. -func object(pkg *types.Package, p Path, skipMethodSorting bool) (types.Object, error) { - if p == "" { +func object(pkg *types.Package, pathstr string, skipMethodSorting bool) (types.Object, error) { + if pathstr == "" { return nil, fmt.Errorf("empty path") } - pathstr := string(p) var pkgobj, suffix string if dot := strings.IndexByte(pathstr, opType); dot < 0 { pkgobj = pathstr diff --git a/vendor/golang.org/x/tools/internal/typeparams/coretype.go b/vendor/golang.org/x/tools/internal/typeparams/coretype.go index 993135e..7124820 100644 --- a/vendor/golang.org/x/tools/internal/typeparams/coretype.go +++ b/vendor/golang.org/x/tools/internal/typeparams/coretype.go @@ -81,13 +81,13 @@ func CoreType(T types.Type) types.Type { // restrictions may be arbitrarily complex. For example, consider the // following: // -// type A interface{ ~string|~[]byte } +// type A interface{ ~string|~[]byte } // -// type B interface{ int|string } +// type B interface{ int|string } // -// type C interface { ~string|~int } +// type C interface { ~string|~int } // -// type T[P interface{ A|B; C }] int +// type T[P interface{ A|B; C }] int // // In this example, the structural type restriction of P is ~string|int: A|B // expands to ~string|~[]byte|int|string, which reduces to ~string|~[]byte|int, diff --git a/vendor/golang.org/x/tools/internal/typeparams/termlist.go b/vendor/golang.org/x/tools/internal/typeparams/termlist.go index 933106a..cbd12f8 100644 --- a/vendor/golang.org/x/tools/internal/typeparams/termlist.go +++ b/vendor/golang.org/x/tools/internal/typeparams/termlist.go @@ -30,7 +30,7 @@ func (xl termlist) String() string { var buf bytes.Buffer for i, x := range xl { if i > 0 { - buf.WriteString(" ∪ ") + buf.WriteString(" | ") } buf.WriteString(x.String()) } diff --git a/vendor/golang.org/x/tools/internal/typeparams/typeterm.go b/vendor/golang.org/x/tools/internal/typeparams/typeterm.go index 7ddee28..7350bb7 100644 --- a/vendor/golang.org/x/tools/internal/typeparams/typeterm.go +++ b/vendor/golang.org/x/tools/internal/typeparams/typeterm.go @@ -10,11 +10,10 @@ import "go/types" // A term describes elementary type sets: // -// ∅: (*term)(nil) == ∅ // set of no types (empty set) -// 𝓤: &term{} == 𝓤 // set of all types (𝓤niverse) -// T: &term{false, T} == {T} // set of type T -// ~t: &term{true, t} == {t' | under(t') == t} // set of types with underlying type t -// +// ∅: (*term)(nil) == ∅ // set of no types (empty set) +// 𝓤: &term{} == 𝓤 // set of all types (𝓤niverse) +// T: &term{false, T} == {T} // set of type T +// ~t: &term{true, t} == {t' | under(t') == t} // set of types with underlying type t type term struct { tilde bool // valid if typ != nil typ types.Type diff --git a/vendor/golang.org/x/tools/internal/typesinternal/objectpath.go b/vendor/golang.org/x/tools/internal/typesinternal/objectpath.go new file mode 100644 index 0000000..5e96e89 --- /dev/null +++ b/vendor/golang.org/x/tools/internal/typesinternal/objectpath.go @@ -0,0 +1,24 @@ +// Copyright 2023 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. + +package typesinternal + +import "go/types" + +// This file contains back doors that allow gopls to avoid method sorting when +// using the objectpath package. +// +// This is performance-critical in certain repositories, but changing the +// behavior of the objectpath package is still being discussed in +// golang/go#61443. If we decide to remove the sorting in objectpath we can +// simply delete these back doors. Otherwise, we should add a new API to +// objectpath that allows controlling the sorting. + +// SkipEncoderMethodSorting marks enc (which must be an *objectpath.Encoder) as +// not requiring sorted methods. +var SkipEncoderMethodSorting func(enc interface{}) + +// ObjectpathObject is like objectpath.Object, but allows suppressing method +// sorting. +var ObjectpathObject func(pkg *types.Package, p string, skipMethodSorting bool) (types.Object, error) diff --git a/vendor/golang.org/x/tools/internal/typesinternal/types.go b/vendor/golang.org/x/tools/internal/typesinternal/types.go index 66e8b09..ce7d435 100644 --- a/vendor/golang.org/x/tools/internal/typesinternal/types.go +++ b/vendor/golang.org/x/tools/internal/typesinternal/types.go @@ -11,8 +11,6 @@ import ( "go/types" "reflect" "unsafe" - - "golang.org/x/tools/go/types/objectpath" ) func SetUsesCgo(conf *types.Config) bool { @@ -52,17 +50,3 @@ func ReadGo116ErrorData(err types.Error) (code ErrorCode, start, end token.Pos, } var SetGoVersion = func(conf *types.Config, version string) bool { return false } - -// SkipEncoderMethodSorting marks the encoder as not requiring sorted methods, -// as an optimization for gopls (which guarantees the order of parsed source files). -// -// TODO(golang/go#61443): eliminate this parameter one way or the other. -// -//go:linkname SkipEncoderMethodSorting golang.org/x/tools/go/types/objectpath.skipMethodSorting -func SkipEncoderMethodSorting(enc *objectpath.Encoder) - -// ObjectpathObject is like objectpath.Object, but allows suppressing method -// sorting (which is not necessary for gopls). -// -//go:linkname ObjectpathObject golang.org/x/tools/go/types/objectpath.object -func ObjectpathObject(pkg *types.Package, p objectpath.Path, skipMethodSorting bool) (types.Object, error) diff --git a/vendor/modules.txt b/vendor/modules.txt index b42cc17..e7c0797 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -11,6 +11,15 @@ github.com/golang/protobuf/ptypes/timestamp # github.com/matttproud/golang_protobuf_extensions v1.0.1 ## explicit github.com/matttproud/golang_protobuf_extensions/pbutil +# github.com/mjl-/adns v0.0.0-20231009145311-e3834995f16c +## explicit; go 1.20 +github.com/mjl-/adns +github.com/mjl-/adns/internal/bytealg +github.com/mjl-/adns/internal/itoa +github.com/mjl-/adns/internal/singleflight +# github.com/mjl-/autocert v0.0.0-20231009155929-d0d48f2f0290 +## explicit; go 1.20 +github.com/mjl-/autocert # github.com/mjl-/bstore v0.0.2 ## explicit; go 1.19 github.com/mjl-/bstore @@ -56,10 +65,9 @@ github.com/prometheus/procfs/internal/util # go.etcd.io/bbolt v1.3.7 ## explicit; go 1.17 go.etcd.io/bbolt -# golang.org/x/crypto v0.13.0 +# golang.org/x/crypto v0.14.0 ## explicit; go 1.17 golang.org/x/crypto/acme -golang.org/x/crypto/acme/autocert golang.org/x/crypto/bcrypt golang.org/x/crypto/blake2b golang.org/x/crypto/blowfish @@ -75,19 +83,19 @@ golang.org/x/mod/internal/lazyregexp golang.org/x/mod/modfile golang.org/x/mod/module golang.org/x/mod/semver -# golang.org/x/net v0.15.0 +# golang.org/x/net v0.16.0 ## explicit; go 1.17 +golang.org/x/net/dns/dnsmessage golang.org/x/net/html golang.org/x/net/html/atom golang.org/x/net/idna golang.org/x/net/internal/socks golang.org/x/net/proxy golang.org/x/net/websocket -# golang.org/x/sys v0.12.0 +# golang.org/x/sys v0.13.0 ## explicit; go 1.17 golang.org/x/sys/cpu golang.org/x/sys/execabs -golang.org/x/sys/internal/unsafeheader golang.org/x/sys/unix golang.org/x/sys/windows # golang.org/x/text v0.13.0 @@ -108,7 +116,7 @@ golang.org/x/text/secure/bidirule golang.org/x/text/transform golang.org/x/text/unicode/bidi golang.org/x/text/unicode/norm -# golang.org/x/tools v0.12.0 +# golang.org/x/tools v0.13.0 ## explicit; go 1.18 golang.org/x/tools/go/gcexportdata golang.org/x/tools/go/internal/packagesdriver diff --git a/webadmin/admin.go b/webadmin/admin.go index 6d83ddb..e9a807a 100644 --- a/webadmin/admin.go +++ b/webadmin/admin.go @@ -8,8 +8,10 @@ import ( "bufio" "bytes" "context" + "crypto" "crypto/ed25519" "crypto/rsa" + "crypto/sha256" "crypto/tls" "crypto/x509" "encoding/base64" @@ -31,6 +33,9 @@ import ( _ "embed" "golang.org/x/crypto/bcrypt" + "golang.org/x/exp/maps" + + "github.com/mjl-/adns" "github.com/mjl-/bstore" "github.com/mjl-/sherpa" @@ -243,7 +248,7 @@ type Result struct { Instructions []string } -type TLSCheckResult struct { +type DNSSECResult struct { Result } @@ -264,6 +269,14 @@ type MXCheckResult struct { Result } +type TLSCheckResult struct { + Result +} + +type DANECheckResult struct { + Result +} + type SPFRecord struct { spf.Record } @@ -345,9 +358,11 @@ type AutodiscoverCheckResult struct { // (e.g. DNS records), and warnings and errors encountered. type CheckResult struct { Domain string + DNSSEC DNSSECResult IPRev IPRevCheckResult MX MXCheckResult TLS TLSCheckResult + DANE DANECheckResult SPF SPFCheckResult DKIM DKIMCheckResult DMARC DMARCCheckResult @@ -421,7 +436,7 @@ func checkDomain(ctx context.Context, resolver dns.Resolver, dialer *net.Dialer, // host must be an absolute dns name, ending with a dot. lookupIPs := func(errors *[]string, host string) (ips []string, ourIPs, notOurIPs []net.IP, rerr error) { - addrs, err := resolver.LookupHost(ctx, host) + addrs, _, err := resolver.LookupHost(ctx, host) if err != nil { addf(errors, "Looking up %q: %s", host, err) return nil, nil, nil, err @@ -479,6 +494,36 @@ func checkDomain(ctx context.Context, resolver dns.Resolver, dialer *net.Dialer, var wg sync.WaitGroup + // DNSSEC + wg.Add(1) + go func() { + defer logPanic(ctx) + defer wg.Done() + + _, result, err := resolver.LookupNS(ctx, ".") + if err != nil { + addf(&r.DNSSEC.Errors, "Looking up NS for DNS root (.) to check support in resolver for DNSSEC-verification: %s", err) + } else if !result.Authentic { + addf(&r.DNSSEC.Warnings, `It looks like the DNS resolvers configured on your system do not verify DNSSEC, or aren't trusted (by having loopback IPs or through "options trust-ad" in /etc/resolv.conf). Without DNSSEC, outbound delivery with SMTP used unprotected MX records, and SMTP STARTTLS connections cannot verify the TLS certificate with DANE (based on a public key in DNS), and will fallback to either MTA-STS for verification, or use "opportunistic TLS" with no certificate verification.`) + } else { + _, result, _ := resolver.LookupMX(ctx, domain.ASCII+".") + if !result.Authentic { + addf(&r.DNSSEC.Warnings, `DNS records for this domain (zone) are not DNSSEC-signed. Mail servers sending email to your domain, or receiving email from your domain, cannot verify that the MX/SPF/DKIM/DMARC/MTA-STS records they see are authentic.`) + } + } + + addf(&r.DNSSEC.Instructions, `Enable DNSSEC-signing of the DNS records of your domain (zone) at your DNS hosting provider.`) + + addf(&r.DNSSEC.Instructions, `If your DNS records are already DNSSEC-signed, you may not have a DNSSEC-verifying recursive resolver in use. Install unbound, and enable support for "extended DNS errors" (EDE), for example: + +cat </etc/unbound/unbound.conf.d/ede.conf +server: + ede: yes + val-log-level: 2 +EOF +`) + }() + // IPRev wg.Add(1) go func() { @@ -488,7 +533,7 @@ func checkDomain(ctx context.Context, resolver dns.Resolver, dialer *net.Dialer, // For each mox.Conf.SpecifiedSMTPListenIPs and all NATIPs, and each IP for // mox.Conf.HostnameDomain, check if they resolve back to the host name. hostIPs := map[dns.Domain][]net.IP{} - ips, err := resolver.LookupIP(ctx, "ip", mox.Conf.Static.HostnameDomain.ASCII+".") + ips, _, err := resolver.LookupIP(ctx, "ip", mox.Conf.Static.HostnameDomain.ASCII+".") if err != nil { addf(&r.IPRev.Errors, "Looking up IPs for hostname: %s", err) } @@ -555,7 +600,7 @@ func checkDomain(ctx context.Context, resolver dns.Resolver, dialer *net.Dialer, s := ip.String() host := host go func() { - addrs, err := resolver.LookupAddr(ctx, s) + addrs, _, err := resolver.LookupAddr(ctx, s) results <- result{host, s, addrs, err} }() } @@ -606,7 +651,7 @@ func checkDomain(ctx context.Context, resolver dns.Resolver, dialer *net.Dialer, defer logPanic(ctx) defer wg.Done() - mxs, err := resolver.LookupMX(ctx, domain.ASCII+".") + mxs, _, err := resolver.LookupMX(ctx, domain.ASCII+".") if err != nil { addf(&r.MX.Errors, "Looking up MX records for %s: %s", domain, err) } @@ -713,7 +758,7 @@ func checkDomain(ctx context.Context, resolver dns.Resolver, dialer *net.Dialer, checkSMTPSTARTTLS := func() { // Initial errors are ignored, will already have been warned about by MX checks. - mxs, err := resolver.LookupMX(ctx, domain.ASCII+".") + mxs, _, err := resolver.LookupMX(ctx, domain.ASCII+".") if err != nil { return } @@ -738,6 +783,115 @@ func checkDomain(ctx context.Context, resolver dns.Resolver, dialer *net.Dialer, }() + // DANE + wg.Add(1) + go func() { + defer logPanic(ctx) + defer wg.Done() + + daneRecords := func(l config.Listener) map[string]struct{} { + if l.TLS == nil { + return nil + } + records := map[string]struct{}{} + addRecord := func(privKey crypto.Signer) { + spkiBuf, err := x509.MarshalPKIXPublicKey(privKey.Public()) + if err != nil { + addf(&r.DANE.Errors, "marshal SubjectPublicKeyInfo for DANE record: %v", err) + return + } + sum := sha256.Sum256(spkiBuf) + r := adns.TLSA{ + Usage: adns.TLSAUsageDANEEE, + Selector: adns.TLSASelectorSPKI, + MatchType: adns.TLSAMatchTypeSHA256, + CertAssoc: sum[:], + } + records[r.Record()] = struct{}{} + } + for _, privKey := range l.TLS.HostPrivateRSA2048Keys { + addRecord(privKey) + } + for _, privKey := range l.TLS.HostPrivateECDSAP256Keys { + addRecord(privKey) + } + return records + } + + expectedDANERecords := func(host string) map[string]struct{} { + for _, l := range mox.Conf.Static.Listeners { + if l.HostnameDomain.ASCII == host { + return daneRecords(l) + } + } + public := mox.Conf.Static.Listeners["public"] + if mox.Conf.Static.HostnameDomain.ASCII == host && public.HostnameDomain.ASCII == "" { + return daneRecords(public) + } + return nil + } + + mxl, result, err := resolver.LookupMX(ctx, domain.ASCII+".") + if err != nil { + addf(&r.DANE.Errors, "Looking up MX hosts to check for DANE records: %s", err) + } else { + if !result.Authentic { + addf(&r.DANE.Warnings, "DANE is inactive because MX records are not DNSSEC-signed.") + } + for _, mx := range mxl { + expect := expectedDANERecords(mx.Host) + + tlsal, tlsaResult, err := resolver.LookupTLSA(ctx, 25, "tcp", mx.Host+".") + if dns.IsNotFound(err) { + if len(expect) > 0 { + addf(&r.DANE.Errors, "No DANE records for MX host %s, expected: %s.", mx.Host, strings.Join(maps.Keys(expect), "; ")) + } + continue + } else if err != nil { + addf(&r.DANE.Errors, "Looking up DANE records for MX host %s: %v", mx.Host, err) + continue + } else if !tlsaResult.Authentic && len(tlsal) > 0 { + addf(&r.DANE.Errors, "DANE records exist for MX host %s, but are not DNSSEC-signed.", mx.Host) + } + + extra := map[string]struct{}{} + for _, e := range tlsal { + s := e.Record() + if _, ok := expect[s]; ok { + delete(expect, s) + } else { + extra[s] = struct{}{} + } + } + if len(expect) > 0 { + l := maps.Keys(expect) + sort.Strings(l) + addf(&r.DANE.Errors, "Missing DANE records of type TLSA for MX host _25._tcp.%s: %s", mx.Host, strings.Join(l, "; ")) + } + if len(extra) > 0 { + l := maps.Keys(extra) + sort.Strings(l) + addf(&r.DANE.Errors, "Unexpected DANE records of type TLSA for MX host _25._tcp.%s: %s", mx.Host, strings.Join(l, "; ")) + } + } + } + + public := mox.Conf.Static.Listeners["public"] + pubDom := public.HostnameDomain + if pubDom.ASCII == "" { + pubDom = mox.Conf.Static.HostnameDomain + } + records := maps.Keys(daneRecords(public)) + sort.Strings(records) + if len(records) > 0 { + instr := "Ensure the DNS records below exist. These records are for the whole machine, not per domain, so create them only once. Make sure DNSSEC is enabled, otherwise the records have no effect. The records indicate that a remote mail server trying to deliver email with SMTP (TCP port 25) must verify the TLS certificate with DANE-EE (3), based on the certificate public key (\"SPKI\", 1) that is SHA2-256-hashed (1) to the hexadecimal hash. DANE-EE verification means only the certificate or public key is verified, not whether the certificate is signed by a (centralized) certificate authority (CA), is expired, or matches the host name.\n\n" + for _, r := range records { + instr += fmt.Sprintf("\t_25._tcp.%s. TLSA %s\n", pubDom.ASCII, r) + } + addf(&r.DANE.Instructions, instr) + } + }() + // SPF // todo: add warnings if we have Transports with submission? admin should ensure their IPs are in the SPF record. it may be an IP(net), or an include. that means we cannot easily check for it. and should we first check the transport can be used from this domain (or an account that has this domain?). also see DKIM. wg.Add(1) @@ -747,7 +901,7 @@ func checkDomain(ctx context.Context, resolver dns.Resolver, dialer *net.Dialer, // Verify a domain with the configured IPs that do SMTP. verifySPF := func(kind string, domain dns.Domain) (string, *SPFRecord, spf.Record) { - _, txt, record, err := spf.Lookup(ctx, resolver, domain) + _, txt, record, _, err := spf.Lookup(ctx, resolver, domain) if err != nil { addf(&r.SPF.Errors, "Looking up %s SPF record: %s", kind, err) } @@ -779,7 +933,7 @@ func checkDomain(ctx context.Context, resolver dns.Resolver, dialer *net.Dialer, LocalIP: net.ParseIP("127.0.0.1"), LocalHostname: dns.Domain{ASCII: "localhost"}, } - status, mechanism, expl, err := spf.Evaluate(ctx, record, resolver, args) + status, mechanism, expl, _, err := spf.Evaluate(ctx, record, resolver, args) if err != nil { addf(&r.SPF.Errors, "Evaluating IP %q against %s SPF record: %s", ip, kind, err) } else if status != spf.StatusPass { @@ -844,7 +998,7 @@ func checkDomain(ctx context.Context, resolver dns.Resolver, dialer *net.Dialer, haveEd25519 = true } - _, record, txt, err := dkim.Lookup(ctx, resolver, selc.Domain, domain) + _, record, txt, _, err := dkim.Lookup(ctx, resolver, selc.Domain, domain) if err != nil { missing = append(missing, sel) if errors.Is(err, dkim.ErrNoRecord) { @@ -918,7 +1072,7 @@ func checkDomain(ctx context.Context, resolver dns.Resolver, dialer *net.Dialer, defer logPanic(ctx) defer wg.Done() - _, dmarcDomain, record, txt, err := dmarc.Lookup(ctx, resolver, domain) + _, dmarcDomain, record, txt, _, err := dmarc.Lookup(ctx, resolver, domain) if err != nil { addf(&r.DMARC.Errors, "Looking up DMARC record: %s", err) } else if record == nil { @@ -951,7 +1105,7 @@ func checkDomain(ctx context.Context, resolver dns.Resolver, dialer *net.Dialer, orgDom := publicsuffix.Lookup(ctx, domain) destOrgDom := publicsuffix.Lookup(ctx, domConf.DMARC.DNSDomain) if orgDom != destOrgDom { - accepts, status, _, _, err := dmarc.LookupExternalReportsAccepted(ctx, resolver, domain, domConf.DMARC.DNSDomain) + accepts, status, _, _, _, err := dmarc.LookupExternalReportsAccepted(ctx, resolver, domain, domConf.DMARC.DNSDomain) if status != dmarc.StatusNone { addf(&r.DMARC.Errors, "Checking if external destination accepts reports: %s", err) } else if !accepts { @@ -1058,7 +1212,7 @@ Ensure a DNS TXT record like the following exists: addf(&r.MTASTS.Warnings, "Policy has a MaxAge of less than 1 day. For stable configurations, the recommended period is in weeks.") } - mxl, _ := resolver.LookupMX(ctx, domain.ASCII+".") + mxl, _, _ := resolver.LookupMX(ctx, domain.ASCII+".") // We do not check for errors, the MX check will complain about mx errors, we assume we will get the same error here. mxs := map[dns.Domain]struct{}{} for _, mx := range mxl { @@ -1155,7 +1309,7 @@ When enabling MTA-STS, or updating a policy, always update the policy first (thr for i := range reqs { go func(i int) { defer srvwg.Done() - _, reqs[i].srvs, reqs[i].err = resolver.LookupSRV(ctx, reqs[i].name[1:], "tcp", domain.ASCII+".") + _, reqs[i].srvs, _, reqs[i].err = resolver.LookupSRV(ctx, reqs[i].name[1:], "tcp", domain.ASCII+".") }(i) } srvwg.Wait() @@ -1212,7 +1366,7 @@ When enabling MTA-STS, or updating a policy, always update the policy first (thr addf(&r.Autodiscover.Instructions, "Ensure DNS records like the following exist:\n\n\t_autodiscover._tcp.%s IN SRV 0 1 443 autoconfig.%s\n\tautoconfig.%s IN CNAME %s\n\nNote: the trailing dots are relevant, it makes the host names absolute instead of relative to the domain name.", domain.ASCII+".", domain.ASCII+".", domain.ASCII+".", mox.Conf.Static.HostnameDomain.ASCII+".") - _, srvs, err := resolver.LookupSRV(ctx, "autodiscover", "tcp", domain.ASCII+".") + _, srvs, _, err := resolver.LookupSRV(ctx, "autodiscover", "tcp", domain.ASCII+".") if err != nil { addf(&r.Autodiscover.Errors, "Looking up SRV record %q: %s", "autodiscover", err) return @@ -1496,7 +1650,7 @@ type Reverse struct { // LookupIP does a reverse lookup of ip. func (Admin) LookupIP(ctx context.Context, ip string) Reverse { resolver := dns.StrictResolver{Pkg: "webadmin"} - names, err := resolver.LookupAddr(ctx, ip) + names, _, err := resolver.LookupAddr(ctx, ip) xcheckuserf(ctx, err, "looking up ip") return Reverse{names} } @@ -1555,7 +1709,12 @@ func (Admin) DomainRecords(ctx context.Context, domain string) []string { if !ok { xcheckuserf(ctx, errors.New("unknown domain"), "lookup domain") } - records, err := mox.DomainRecords(dc, d) + resolver := dns.StrictResolver{Pkg: "admin"} + _, result, err := resolver.LookupTXT(ctx, domain+".") + if !dns.IsNotFound(err) { + xcheckf(ctx, err, "looking up record to determine if dnssec is implemented") + } + records, err := mox.DomainRecords(dc, d, result.Authentic) xcheckf(ctx, err, "dns records") return records } diff --git a/webadmin/admin.html b/webadmin/admin.html index dd07982..2caa04a 100644 --- a/webadmin/admin.html +++ b/webadmin/admin.html @@ -912,6 +912,7 @@ const domainDNSCheck = async (d) => { ] } + const detailsDNSSEC = '' const detailsIPRev = !checks.IPRev.IPNames || !Object.entries(checks.IPRev.IPNames).length ? [] : [ dom.div('Hostname: ' + domainString(checks.IPRev.Hostname)), dom.table( @@ -930,6 +931,7 @@ const domainDNSCheck = async (d) => { ), ] const detailsTLS = '' + const detailsDANE = '' const detailsSPF = [ checks.SPF.DomainTXT ? [dom.div('Domain TXT record: ' + checks.SPF.DomainTXT)] : [], checks.SPF.HostTXT ? [dom.div('Host TXT record: ' + checks.SPF.HostTXT)] : [], @@ -986,9 +988,11 @@ const domainDNSCheck = async (d) => { 'Check DNS', ), dom.h1('DNS records and domain configuration check'), + resultSection('DNSSEC', checks.DNSSEC, detailsDNSSEC), resultSection('IPRev', checks.IPRev, detailsIPRev), resultSection('MX', checks.MX, detailsMX), resultSection('TLS', checks.TLS, detailsTLS), + resultSection('DANE', checks.DANE, detailsDANE), resultSection('SPF', checks.SPF, detailsSPF), resultSection('DKIM', checks.DKIM, detailsDKIM), resultSection('DMARC', checks.DMARC, detailsDMARC), diff --git a/webadmin/adminapi.json b/webadmin/adminapi.json index a0cf9fd..b1b4459 100644 --- a/webadmin/adminapi.json +++ b/webadmin/adminapi.json @@ -748,6 +748,13 @@ "string" ] }, + { + "Name": "DNSSEC", + "Docs": "", + "Typewords": [ + "DNSSECResult" + ] + }, { "Name": "IPRev", "Docs": "", @@ -769,6 +776,13 @@ "TLSCheckResult" ] }, + { + "Name": "DANE", + "Docs": "", + "Typewords": [ + "DANECheckResult" + ] + }, { "Name": "SPF", "Docs": "", @@ -827,6 +841,36 @@ } ] }, + { + "Name": "DNSSECResult", + "Docs": "", + "Fields": [ + { + "Name": "Errors", + "Docs": "", + "Typewords": [ + "[]", + "string" + ] + }, + { + "Name": "Warnings", + "Docs": "", + "Typewords": [ + "[]", + "string" + ] + }, + { + "Name": "Instructions", + "Docs": "", + "Typewords": [ + "[]", + "string" + ] + } + ] + }, { "Name": "IPRevCheckResult", "Docs": "", @@ -989,6 +1033,36 @@ } ] }, + { + "Name": "DANECheckResult", + "Docs": "", + "Fields": [ + { + "Name": "Errors", + "Docs": "", + "Typewords": [ + "[]", + "string" + ] + }, + { + "Name": "Warnings", + "Docs": "", + "Typewords": [ + "[]", + "string" + ] + }, + { + "Name": "Instructions", + "Docs": "", + "Typewords": [ + "[]", + "string" + ] + } + ] + }, { "Name": "SPFCheckResult", "Docs": "",