net: expand nss myhostname fallback detection

Expand myhostname fallback detection to properly detect the local
hostname in addition to other supported special names and suffixes.

Fixes #17967

Change-Id: I1fe141fd9838b25886c08b6f2fd325e58be60457
Reviewed-on: https://go-review.googlesource.com/33550
Reviewed-by: Matthew Dempsky <mdempsky@google.com>
Run-TryBot: Matthew Dempsky <mdempsky@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
diff --git a/src/net/conf.go b/src/net/conf.go
index 41e62d5..c10aafe 100644
--- a/src/net/conf.go
+++ b/src/net/conf.go
@@ -179,8 +179,6 @@
 		}
 	}
 
-	hasDot := byteIndex(hostname, '.') != -1
-
 	// Canonicalize the hostname by removing any trailing dot.
 	if stringsHasSuffix(hostname, ".") {
 		hostname = hostname[:len(hostname)-1]
@@ -220,10 +218,14 @@
 	var first string
 	for _, src := range srcs {
 		if src.source == "myhostname" {
-			if hostname == "" || hasDot {
-				continue
+			if isLocalhost(hostname) || isGateway(hostname) {
+				return fallbackOrder
 			}
-			return fallbackOrder
+			hn, err := getHostname()
+			if err != nil || stringsEqualFold(hostname, hn) {
+				return fallbackOrder
+			}
+			continue
 		}
 		if src.source == "files" || src.source == "dns" {
 			if !src.standardCriteria() {
@@ -306,3 +308,15 @@
 	parsePart(goDebug)
 	return
 }
+
+// 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")
+}
diff --git a/src/net/conf_test.go b/src/net/conf_test.go
index ec8814b..17d03f4 100644
--- a/src/net/conf_test.go
+++ b/src/net/conf_test.go
@@ -13,8 +13,9 @@
 )
 
 type nssHostTest struct {
-	host string
-	want hostLookupOrder
+	host      string
+	localhost string
+	want      hostLookupOrder
 }
 
 func nssStr(s string) *nssConf { return parseNSSConf(strings.NewReader(s)) }
@@ -42,8 +43,8 @@
 				resolv:             defaultResolvConf,
 			},
 			hostTests: []nssHostTest{
-				{"foo.local", hostLookupCgo},
-				{"google.com", hostLookupCgo},
+				{"foo.local", "myhostname", hostLookupCgo},
+				{"google.com", "myhostname", hostLookupCgo},
 			},
 		},
 		{
@@ -54,7 +55,7 @@
 				resolv: defaultResolvConf,
 			},
 			hostTests: []nssHostTest{
-				{"x.com", hostLookupDNSFiles},
+				{"x.com", "myhostname", hostLookupDNSFiles},
 			},
 		},
 		{
@@ -65,7 +66,7 @@
 				resolv: defaultResolvConf,
 			},
 			hostTests: []nssHostTest{
-				{"x.com", hostLookupFilesDNS},
+				{"x.com", "myhostname", hostLookupFilesDNS},
 			},
 		},
 		{
@@ -75,11 +76,11 @@
 				resolv: defaultResolvConf,
 			},
 			hostTests: []nssHostTest{
-				{"foo.local", hostLookupCgo},
-				{"foo.local.", hostLookupCgo},
-				{"foo.LOCAL", hostLookupCgo},
-				{"foo.LOCAL.", hostLookupCgo},
-				{"google.com", hostLookupFilesDNS},
+				{"foo.local", "myhostname", hostLookupCgo},
+				{"foo.local.", "myhostname", hostLookupCgo},
+				{"foo.LOCAL", "myhostname", hostLookupCgo},
+				{"foo.LOCAL.", "myhostname", hostLookupCgo},
+				{"google.com", "myhostname", hostLookupFilesDNS},
 			},
 		},
 		{
@@ -89,7 +90,7 @@
 				nss:    nssStr("foo: bar"),
 				resolv: defaultResolvConf,
 			},
-			hostTests: []nssHostTest{{"google.com", hostLookupFilesDNS}},
+			hostTests: []nssHostTest{{"google.com", "myhostname", hostLookupFilesDNS}},
 		},
 		// On OpenBSD, no resolv.conf means no DNS.
 		{
@@ -98,7 +99,7 @@
 				goos:   "openbsd",
 				resolv: defaultResolvConf,
 			},
-			hostTests: []nssHostTest{{"google.com", hostLookupFiles}},
+			hostTests: []nssHostTest{{"google.com", "myhostname", hostLookupFiles}},
 		},
 		{
 			name: "solaris_no_nsswitch",
@@ -107,7 +108,7 @@
 				nss:    &nssConf{err: os.ErrNotExist},
 				resolv: defaultResolvConf,
 			},
-			hostTests: []nssHostTest{{"google.com", hostLookupCgo}},
+			hostTests: []nssHostTest{{"google.com", "myhostname", hostLookupCgo}},
 		},
 		{
 			name: "openbsd_lookup_bind_file",
@@ -116,8 +117,8 @@
 				resolv: &dnsConfig{lookup: []string{"bind", "file"}},
 			},
 			hostTests: []nssHostTest{
-				{"google.com", hostLookupDNSFiles},
-				{"foo.local", hostLookupDNSFiles},
+				{"google.com", "myhostname", hostLookupDNSFiles},
+				{"foo.local", "myhostname", hostLookupDNSFiles},
 			},
 		},
 		{
@@ -126,7 +127,7 @@
 				goos:   "openbsd",
 				resolv: &dnsConfig{lookup: []string{"file", "bind"}},
 			},
-			hostTests: []nssHostTest{{"google.com", hostLookupFilesDNS}},
+			hostTests: []nssHostTest{{"google.com", "myhostname", hostLookupFilesDNS}},
 		},
 		{
 			name: "openbsd_lookup_bind",
@@ -134,7 +135,7 @@
 				goos:   "openbsd",
 				resolv: &dnsConfig{lookup: []string{"bind"}},
 			},
-			hostTests: []nssHostTest{{"google.com", hostLookupDNS}},
+			hostTests: []nssHostTest{{"google.com", "myhostname", hostLookupDNS}},
 		},
 		{
 			name: "openbsd_lookup_file",
@@ -142,7 +143,7 @@
 				goos:   "openbsd",
 				resolv: &dnsConfig{lookup: []string{"file"}},
 			},
-			hostTests: []nssHostTest{{"google.com", hostLookupFiles}},
+			hostTests: []nssHostTest{{"google.com", "myhostname", hostLookupFiles}},
 		},
 		{
 			name: "openbsd_lookup_yp",
@@ -150,7 +151,7 @@
 				goos:   "openbsd",
 				resolv: &dnsConfig{lookup: []string{"file", "bind", "yp"}},
 			},
-			hostTests: []nssHostTest{{"google.com", hostLookupCgo}},
+			hostTests: []nssHostTest{{"google.com", "myhostname", hostLookupCgo}},
 		},
 		{
 			name: "openbsd_lookup_two",
@@ -158,7 +159,7 @@
 				goos:   "openbsd",
 				resolv: &dnsConfig{lookup: []string{"file", "foo"}},
 			},
-			hostTests: []nssHostTest{{"google.com", hostLookupCgo}},
+			hostTests: []nssHostTest{{"google.com", "myhostname", hostLookupCgo}},
 		},
 		{
 			name: "openbsd_lookup_empty",
@@ -166,7 +167,7 @@
 				goos:   "openbsd",
 				resolv: &dnsConfig{lookup: nil},
 			},
-			hostTests: []nssHostTest{{"google.com", hostLookupDNSFiles}},
+			hostTests: []nssHostTest{{"google.com", "myhostname", hostLookupDNSFiles}},
 		},
 		// glibc lacking an nsswitch.conf, per
 		// http://www.gnu.org/software/libc/manual/html_node/Notes-on-NSS-Configuration-File.html
@@ -177,7 +178,7 @@
 				nss:    &nssConf{err: os.ErrNotExist},
 				resolv: defaultResolvConf,
 			},
-			hostTests: []nssHostTest{{"google.com", hostLookupDNSFiles}},
+			hostTests: []nssHostTest{{"google.com", "myhostname", hostLookupDNSFiles}},
 		},
 		{
 			name: "files_mdns_dns",
@@ -186,8 +187,8 @@
 				resolv: defaultResolvConf,
 			},
 			hostTests: []nssHostTest{
-				{"x.com", hostLookupFilesDNS},
-				{"x.local", hostLookupCgo},
+				{"x.com", "myhostname", hostLookupFilesDNS},
+				{"x.local", "myhostname", hostLookupCgo},
 			},
 		},
 		{
@@ -197,9 +198,9 @@
 				resolv: defaultResolvConf,
 			},
 			hostTests: []nssHostTest{
-				{"x.com", hostLookupDNS},
-				{"x\\.com", hostLookupCgo},     // punt on weird glibc escape
-				{"foo.com%en0", hostLookupCgo}, // and IPv6 zones
+				{"x.com", "myhostname", hostLookupDNS},
+				{"x\\.com", "myhostname", hostLookupCgo},     // punt on weird glibc escape
+				{"foo.com%en0", "myhostname", hostLookupCgo}, // and IPv6 zones
 			},
 		},
 		{
@@ -210,8 +211,8 @@
 				hasMDNSAllow: true,
 			},
 			hostTests: []nssHostTest{
-				{"x.com", hostLookupCgo},
-				{"x.local", hostLookupCgo},
+				{"x.com", "myhostname", hostLookupCgo},
+				{"x.local", "myhostname", hostLookupCgo},
 			},
 		},
 		{
@@ -221,9 +222,9 @@
 				resolv: defaultResolvConf,
 			},
 			hostTests: []nssHostTest{
-				{"x.com", hostLookupFilesDNS},
-				{"x", hostLookupFilesDNS},
-				{"x.local", hostLookupCgo},
+				{"x.com", "myhostname", hostLookupFilesDNS},
+				{"x", "myhostname", hostLookupFilesDNS},
+				{"x.local", "myhostname", hostLookupCgo},
 			},
 		},
 		{
@@ -233,9 +234,9 @@
 				resolv: defaultResolvConf,
 			},
 			hostTests: []nssHostTest{
-				{"x.com", hostLookupDNSFiles},
-				{"x", hostLookupDNSFiles},
-				{"x.local", hostLookupCgo},
+				{"x.com", "myhostname", hostLookupDNSFiles},
+				{"x", "myhostname", hostLookupDNSFiles},
+				{"x.local", "myhostname", hostLookupCgo},
 			},
 		},
 		{
@@ -245,7 +246,7 @@
 				resolv: defaultResolvConf,
 			},
 			hostTests: []nssHostTest{
-				{"x.com", hostLookupCgo},
+				{"x.com", "myhostname", hostLookupCgo},
 			},
 		},
 		{
@@ -255,9 +256,23 @@
 				resolv: defaultResolvConf,
 			},
 			hostTests: []nssHostTest{
-				{"x.com", hostLookupFilesDNS},
-				{"somehostname", hostLookupCgo},
-				{"", hostLookupFilesDNS}, // Issue 13623
+				{"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
 			},
 		},
 		{
@@ -267,8 +282,9 @@
 				resolv: defaultResolvConf,
 			},
 			hostTests: []nssHostTest{
-				{"x.com", hostLookupFilesDNS},
-				{"somehostname", hostLookupCgo},
+				{"x.com", "myhostname", hostLookupFilesDNS},
+				{"somehostname", "myhostname", hostLookupFilesDNS},
+				{"myhostname", "myhostname", hostLookupCgo},
 			},
 		},
 		// Debian Squeeze is just "dns,files", but lists all
@@ -282,8 +298,8 @@
 				resolv: defaultResolvConf,
 			},
 			hostTests: []nssHostTest{
-				{"x.com", hostLookupDNSFiles},
-				{"somehostname", hostLookupDNSFiles},
+				{"x.com", "myhostname", hostLookupDNSFiles},
+				{"somehostname", "myhostname", hostLookupDNSFiles},
 			},
 		},
 		{
@@ -292,7 +308,7 @@
 				nss:    nssStr("foo: bar"),
 				resolv: &dnsConfig{servers: defaultNS, ndots: 1, timeout: 5, attempts: 2, unknownOpt: true},
 			},
-			hostTests: []nssHostTest{{"google.com", hostLookupCgo}},
+			hostTests: []nssHostTest{{"google.com", "myhostname", hostLookupCgo}},
 		},
 		// Android should always use cgo.
 		{
@@ -303,12 +319,18 @@
 				resolv: defaultResolvConf,
 			},
 			hostTests: []nssHostTest{
-				{"x.com", hostLookupCgo},
+				{"x.com", "myhostname", hostLookupCgo},
 			},
 		},
 	}
+
+	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(ht.host)
 			if gotOrder != ht.want {
 				t.Errorf("%s: hostLookupOrder(%q) = %v; want %v", tt.name, ht.host, gotOrder, ht.want)
diff --git a/src/net/parse.go b/src/net/parse.go
index d615eb2..5826984 100644
--- a/src/net/parse.go
+++ b/src/net/parse.go
@@ -334,15 +334,7 @@
 // stringsHasSuffixFold reports whether s ends in suffix,
 // ASCII-case-insensitively.
 func stringsHasSuffixFold(s, suffix string) bool {
-	if len(suffix) > len(s) {
-		return false
-	}
-	for i := 0; i < len(suffix); i++ {
-		if lowerASCII(suffix[i]) != lowerASCII(s[len(s)-len(suffix)+i]) {
-			return false
-		}
-	}
-	return true
+	return len(s) >= len(suffix) && stringsEqualFold(s[len(s)-len(suffix):], suffix)
 }
 
 // stringsHasPrefix is strings.HasPrefix. It reports whether s begins with prefix.
@@ -350,6 +342,20 @@
 	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
+}
+
 func readFull(r io.Reader) (all []byte, err error) {
 	buf := make([]byte, 1024)
 	for {