// 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 darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris
// +build darwin dragonfly freebsd linux netbsd openbsd solaris

package net

import (
	"io/fs"
	"strings"
	"testing"
)

type nssHostTest struct {
	host      string
	localhost string
	want      hostLookupOrder
}

func nssStr(s string) *nssConf { return parseNSSConf(strings.NewReader(s)) }

// represents a dnsConfig returned by parsing a nonexistent resolv.conf
var defaultResolvConf = &dnsConfig{
	servers:  defaultNS,
	ndots:    1,
	timeout:  5,
	attempts: 2,
	err:      fs.ErrNotExist,
}

func TestConfHostLookupOrder(t *testing.T) {
	tests := []struct {
		name      string
		c         *conf
		resolver  *Resolver
		hostTests []nssHostTest
	}{
		{
			name: "force",
			c: &conf{
				forceCgoLookupHost: true,
				nss:                nssStr("foo: bar"),
				resolv:             defaultResolvConf,
			},
			hostTests: []nssHostTest{
				{"foo.local", "myhostname", hostLookupCgo},
				{"google.com", "myhostname", hostLookupCgo},
			},
		},
		{
			name: "netgo_dns_before_files",
			c: &conf{
				netGo:  true,
				nss:    nssStr("hosts: dns files"),
				resolv: defaultResolvConf,
			},
			hostTests: []nssHostTest{
				{"x.com", "myhostname", hostLookupDNSFiles},
			},
		},
		{
			name: "netgo_fallback_on_cgo",
			c: &conf{
				netGo:  true,
				nss:    nssStr("hosts: dns files something_custom"),
				resolv: defaultResolvConf,
			},
			hostTests: []nssHostTest{
				{"x.com", "myhostname", hostLookupFilesDNS},
			},
		},
		{
			name: "ubuntu_trusty_avahi",
			c: &conf{
				nss:    nssStr("hosts: files mdns4_minimal [NOTFOUND=return] dns mdns4"),
				resolv: defaultResolvConf,
			},
			hostTests: []nssHostTest{
				{"foo.local", "myhostname", hostLookupCgo},
				{"foo.local.", "myhostname", hostLookupCgo},
				{"foo.LOCAL", "myhostname", hostLookupCgo},
				{"foo.LOCAL.", "myhostname", hostLookupCgo},
				{"google.com", "myhostname", hostLookupFilesDNS},
			},
		},
		{
			name: "freebsdlinux_no_resolv_conf",
			c: &conf{
				goos:   "freebsd",
				nss:    nssStr("foo: bar"),
				resolv: defaultResolvConf,
			},
			hostTests: []nssHostTest{{"google.com", "myhostname", hostLookupFilesDNS}},
		},
		// On OpenBSD, no resolv.conf means no DNS.
		{
			name: "openbsd_no_resolv_conf",
			c: &conf{
				goos:   "openbsd",
				resolv: defaultResolvConf,
			},
			hostTests: []nssHostTest{{"google.com", "myhostname", hostLookupFiles}},
		},
		{
			name: "solaris_no_nsswitch",
			c: &conf{
				goos:   "solaris",
				nss:    &nssConf{err: fs.ErrNotExist},
				resolv: defaultResolvConf,
			},
			hostTests: []nssHostTest{{"google.com", "myhostname", hostLookupCgo}},
		},
		{
			name: "openbsd_lookup_bind_file",
			c: &conf{
				goos:   "openbsd",
				resolv: &dnsConfig{lookup: []string{"bind", "file"}},
			},
			hostTests: []nssHostTest{
				{"google.com", "myhostname", hostLookupDNSFiles},
				{"foo.local", "myhostname", hostLookupDNSFiles},
			},
		},
		{
			name: "openbsd_lookup_file_bind",
			c: &conf{
				goos:   "openbsd",
				resolv: &dnsConfig{lookup: []string{"file", "bind"}},
			},
			hostTests: []nssHostTest{{"google.com", "myhostname", hostLookupFilesDNS}},
		},
		{
			name: "openbsd_lookup_bind",
			c: &conf{
				goos:   "openbsd",
				resolv: &dnsConfig{lookup: []string{"bind"}},
			},
			hostTests: []nssHostTest{{"google.com", "myhostname", hostLookupDNS}},
		},
		{
			name: "openbsd_lookup_file",
			c: &conf{
				goos:   "openbsd",
				resolv: &dnsConfig{lookup: []string{"file"}},
			},
			hostTests: []nssHostTest{{"google.com", "myhostname", hostLookupFiles}},
		},
		{
			name: "openbsd_lookup_yp",
			c: &conf{
				goos:   "openbsd",
				resolv: &dnsConfig{lookup: []string{"file", "bind", "yp"}},
			},
			hostTests: []nssHostTest{{"google.com", "myhostname", hostLookupCgo}},
		},
		{
			name: "openbsd_lookup_two",
			c: &conf{
				goos:   "openbsd",
				resolv: &dnsConfig{lookup: []string{"file", "foo"}},
			},
			hostTests: []nssHostTest{{"google.com", "myhostname", hostLookupCgo}},
		},
		{
			name: "openbsd_lookup_empty",
			c: &conf{
				goos:   "openbsd",
				resolv: &dnsConfig{lookup: nil},
			},
			hostTests: []nssHostTest{{"google.com", "myhostname", hostLookupDNSFiles}},
		},
		{
			name: "linux_no_nsswitch.conf",
			c: &conf{
				goos:   "linux",
				nss:    &nssConf{err: fs.ErrNotExist},
				resolv: defaultResolvConf,
			},
			hostTests: []nssHostTest{{"google.com", "myhostname", hostLookupFilesDNS}},
		},
		{
			name: "linux_empty_nsswitch.conf",
			c: &conf{
				goos:   "linux",
				nss:    nssStr(""),
				resolv: defaultResolvConf,
			},
			hostTests: []nssHostTest{{"google.com", "myhostname", hostLookupFilesDNS}},
		},
		{
			name: "files_mdns_dns",
			c: &conf{
				nss:    nssStr("hosts: files mdns dns"),
				resolv: defaultResolvConf,
			},
			hostTests: []nssHostTest{
				{"x.com", "myhostname", hostLookupFilesDNS},
				{"x.local", "myhostname", hostLookupCgo},
			},
		},
		{
			name: "dns_special_hostnames",
			c: &conf{
				nss:    nssStr("hosts: dns"),
				resolv: defaultResolvConf,
			},
			hostTests: []nssHostTest{
				{"x.com", "myhostname", hostLookupDNS},
				{"x\\.com", "myhostname", hostLookupCgo},     // punt on weird glibc escape
				{"foo.com%en0", "myhostname", hostLookupCgo}, // and IPv6 zones
			},
		},
		{
			name: "mdns_allow",
			c: &conf{
				nss:          nssStr("hosts: files mdns dns"),
				resolv:       defaultResolvConf,
				hasMDNSAllow: true,
			},
			hostTests: []nssHostTest{
				{"x.com", "myhostname", hostLookupCgo},
				{"x.local", "myhostname", hostLookupCgo},
			},
		},
		{
			name: "files_dns",
			c: &conf{
				nss:    nssStr("hosts: files dns"),
				resolv: defaultResolvConf,
			},
			hostTests: []nssHostTest{
				{"x.com", "myhostname", hostLookupFilesDNS},
				{"x", "myhostname", hostLookupFilesDNS},
				{"x.local", "myhostname", hostLookupCgo},
			},
		},
		{
			name: "dns_files",
			c: &conf{
				nss:    nssStr("hosts: dns files"),
				resolv: defaultResolvConf,
			},
			hostTests: []nssHostTest{
				{"x.com", "myhostname", hostLookupDNSFiles},
				{"x", "myhostname", hostLookupDNSFiles},
				{"x.local", "myhostname", hostLookupCgo},
			},
		},
		{
			name: "something_custom",
			c: &conf{
				nss:    nssStr("hosts: dns files something_custom"),
				resolv: defaultResolvConf,
			},
			hostTests: []nssHostTest{
				{"x.com", "myhostname", hostLookupCgo},
			},
		},
		{
			name: "myhostname",
			c: &conf{
				nss:    nssStr("hosts: files dns myhostname"),
				resolv: defaultResolvConf,
			},
			hostTests: []nssHostTest{
				{"x.com", "myhostname", hostLookupFilesDNS},
				{"myhostname", "myhostname", hostLookupCgo},
				{"myHostname", "myhostname", hostLookupCgo},
				{"myhostname.dot", "myhostname.dot", hostLookupCgo},
				{"myHostname.dot", "myhostname.dot", hostLookupCgo},
				{"gateway", "myhostname", hostLookupCgo},
				{"Gateway", "myhostname", hostLookupCgo},
				{"localhost", "myhostname", hostLookupCgo},
				{"Localhost", "myhostname", hostLookupCgo},
				{"anything.localhost", "myhostname", hostLookupCgo},
				{"Anything.localhost", "myhostname", hostLookupCgo},
				{"localhost.localdomain", "myhostname", hostLookupCgo},
				{"Localhost.Localdomain", "myhostname", hostLookupCgo},
				{"anything.localhost.localdomain", "myhostname", hostLookupCgo},
				{"Anything.Localhost.Localdomain", "myhostname", hostLookupCgo},
				{"somehostname", "myhostname", hostLookupFilesDNS},
				{"", "myhostname", hostLookupFilesDNS}, // Issue 13623
			},
		},
		{
			name: "ubuntu14.04.02",
			c: &conf{
				nss:    nssStr("hosts: files myhostname mdns4_minimal [NOTFOUND=return] dns mdns4"),
				resolv: defaultResolvConf,
			},
			hostTests: []nssHostTest{
				{"x.com", "myhostname", hostLookupFilesDNS},
				{"somehostname", "myhostname", hostLookupFilesDNS},
				{"myhostname", "myhostname", hostLookupCgo},
			},
		},
		// Debian Squeeze is just "dns,files", but lists all
		// the default criteria for dns, but then has a
		// non-standard but redundant notfound=return for the
		// files.
		{
			name: "debian_squeeze",
			c: &conf{
				nss:    nssStr("hosts: dns [success=return notfound=continue unavail=continue tryagain=continue] files [notfound=return]"),
				resolv: defaultResolvConf,
			},
			hostTests: []nssHostTest{
				{"x.com", "myhostname", hostLookupDNSFiles},
				{"somehostname", "myhostname", hostLookupDNSFiles},
			},
		},
		{
			name: "resolv.conf-unknown",
			c: &conf{
				nss:    nssStr("foo: bar"),
				resolv: &dnsConfig{servers: defaultNS, ndots: 1, timeout: 5, attempts: 2, unknownOpt: true},
			},
			hostTests: []nssHostTest{{"google.com", "myhostname", hostLookupCgo}},
		},
		// Android should always use cgo.
		{
			name: "android",
			c: &conf{
				goos:   "android",
				nss:    nssStr(""),
				resolv: defaultResolvConf,
			},
			hostTests: []nssHostTest{
				{"x.com", "myhostname", hostLookupCgo},
			},
		},
		// Issue 24393: make sure "Resolver.PreferGo = true" acts like netgo.
		{
			name:     "resolver-prefergo",
			resolver: &Resolver{PreferGo: true},
			c: &conf{
				goos:               "darwin",
				forceCgoLookupHost: true, // always true for darwin
				resolv:             defaultResolvConf,
				nss:                nssStr(""),
				netCgo:             true,
			},
			hostTests: []nssHostTest{
				{"localhost", "myhostname", hostLookupFilesDNS},
			},
		},
	}

	origGetHostname := getHostname
	defer func() { getHostname = origGetHostname }()

	for _, tt := range tests {
		for _, ht := range tt.hostTests {
			getHostname = func() (string, error) { return ht.localhost, nil }

			gotOrder := tt.c.hostLookupOrder(tt.resolver, ht.host)
			if gotOrder != ht.want {
				t.Errorf("%s: hostLookupOrder(%q) = %v; want %v", tt.name, ht.host, gotOrder, ht.want)
			}
		}
	}

}

func TestSystemConf(t *testing.T) {
	systemConf()
}
