2016-02-14  Florian Weimer  <fweimer@redhat.com>

	* resolv/res_init.c (init_defdname): Extracted from _res_vinit.
	(__res_init_fp): Likewise.  Use parameters instead of environment
	variables, and have the caller supply the input stream.  Use
	__getline to read lines from the input file.
	(__res_vinit): Use __res_init_fp.
	* resolv/Versions (GLIBC_PRIVATE): Export __res_init_fp.
	* resolv/tst-res_init.c: New test.
	* resolv/Makefile (tests): Add it.

diff --git a/include/resolv.h b/include/resolv.h
index 4c61476..fc05484 100644
--- a/include/resolv.h
+++ b/include/resolv.h
@@ -22,6 +22,7 @@ extern __thread struct __res_state *__resp attribute_tls_model_ie;
 
 /* Now define the internal interfaces.  */
 extern int __res_vinit (res_state, int);
+extern void __res_init_fp (res_state, FILE *, bool, const char *);
 extern int __res_maybe_init (res_state, int);
 extern void _sethtent (int);
 extern void _endhtent (void);
@@ -41,6 +42,7 @@ extern void __res_iclose (res_state statp, bool free_addr);
 extern int __res_nopt(res_state statp, int n0, u_char *buf, int buflen,
 		      int anslen);
 libc_hidden_proto (__res_ninit)
+libc_hidden_proto (__res_init_fp)
 libc_hidden_proto (__res_maybe_init)
 libc_hidden_proto (__res_nclose)
 libc_hidden_proto (__res_iclose)
diff --git a/resolv/Makefile b/resolv/Makefile
index 8be41d3..08503e0 100644
--- a/resolv/Makefile
+++ b/resolv/Makefile
@@ -30,7 +30,7 @@ headers	:= resolv.h \
 routines := herror inet_addr inet_ntop inet_pton nsap_addr res_init \
 	    res_hconf res_libc res-state
 
-tests = tst-aton tst-leaks tst-inet_ntop
+tests = tst-aton tst-leaks tst-inet_ntop tst-res_init
 xtests = tst-leaks2
 
 generate := mtrace-tst-leaks.out tst-leaks.mtrace tst-leaks2.mtrace
diff --git a/resolv/Versions b/resolv/Versions
index e561bce..e3226cd 100644
--- a/resolv/Versions
+++ b/resolv/Versions
@@ -27,6 +27,9 @@ libc {
     __h_errno; __resp;
 
     __res_maybe_init; __res_iclose;
+
+    # For testing.
+    __res_init_fp;
   }
 }
 
diff --git a/resolv/res_init.c b/resolv/res_init.c
index 128004a..6fefa6d 100644
--- a/resolv/res_init.c
+++ b/resolv/res_init.c
@@ -1,3 +1,21 @@
+/* Resolver structure initialization and resolv.conf parsing.
+   Copyright (C) 1995-2016 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
 /*
  * Copyright (c) 1985, 1989, 1993
  *    The Regents of the University of California.  All rights reserved.
@@ -132,16 +150,57 @@ res_ninit(res_state statp) {
 libc_hidden_def (__res_ninit)
 #endif
 
-/* This function has to be reachable by res_data.c but not publically. */
 int
-__res_vinit(res_state statp, int preinit) {
-	FILE *fp;
+__res_vinit (res_state statp, int preinit)
+{
+  FILE *fp = fopen (_PATH_RESCONF, "rce");
+  /* LOCALDOMAIN is listed in unsecvars.h and removed in AT_SECURE
+     mode.  */
+  const char *localdomain = getenv ("LOCALDOMAIN");
+  __res_init_fp (statp, fp, preinit, localdomain);
+  if (fp != NULL)
+      fclose (fp);
+  /* RES_OPTIONS is listed in unsecvars.h, too.  */
+  const char *options = getenv ("RES_OPTIONS");
+  if (options != NULL)
+    res_setoptions (statp, options, "env");
+  /* __res_init_fp currently ignores all errors.  */
+  return 0;
+}
+
+/* Initialize the defdname member of *STATP if it is empty.  The
+   default is derived from the system host name.  */
+static void
+init_defdname (res_state statp)
+{
+  if (statp->defdname[0] != '\0')
+    return;
+  if (__gethostname (statp->defdname, sizeof (statp->defdname) - 1) != 0)
+    return;
+  const char *dot = strchr (statp->defdname, '.');
+  if (dot == NULL)
+    /* Host name does not contain a dot.  Use the root as the
+       default domain name.  */
+    statp->defdname[0] = '\0';
+  else
+    /* Use the remaining part of the host name as the domain name.  */
+    memmove (statp->defdname, dot + 1, strlen (dot + 1) + 1);
+}
+
+/* Initialize *STATP from the resolv.conf-style file FP.  If PREINIT,
+   do not set default values for the *STATP members retrans, retry,
+   options, id.  If LOCALDOMAIN is not NULL, use it to initialize the
+   search path instead of the contents of the file.  If FP is NULL,
+   just write the default values (and, possibly, the contents of
+   LOCALDOMAIN) to *STATP.  */
+void
+__res_init_fp (res_state statp, FILE *fp, bool preinit,
+	       const char *localdomain)
+{
 	char *cp, **pp;
 	int n;
-	char buf[BUFSIZ];
 	int nserv = 0;    /* number of nameservers read from file */
 	int have_serv6 = 0;
-	int haveenv = 0;
 	int havesearch = 0;
 #ifdef RESOLVSORT
 	int nsort = 0;
@@ -174,10 +233,10 @@ __res_vinit(res_state statp, int preinit) {
 	    statp->_u._ext.nsaddrs[n] = NULL;
 
 	/* Allow user to override the local domain definition */
-	if ((cp = getenv("LOCALDOMAIN")) != NULL) {
-		(void)strncpy(statp->defdname, cp, sizeof(statp->defdname) - 1);
+	if (localdomain != NULL) {
+		strncpy (statp->defdname, localdomain,
+			 sizeof (statp->defdname) - 1);
 		statp->defdname[sizeof(statp->defdname) - 1] = '\0';
-		haveenv++;
 
 		/*
 		 * Set search list to be blank-separated strings
@@ -213,17 +272,17 @@ __res_vinit(res_state statp, int preinit) {
 	(line[sizeof(name) - 1] == ' ' || \
 	 line[sizeof(name) - 1] == '\t'))
 
-	if ((fp = fopen(_PATH_RESCONF, "rce")) != NULL) {
-	    /* No threads use this stream.  */
-	    __fsetlocking (fp, FSETLOCKING_BYCALLER);
-	    /* read the config file */
-	    while (__fgets_unlocked(buf, sizeof(buf), fp) != NULL) {
+	if (fp != NULL) {
+	    char *buf = NULL;
+	    size_t buf_len = 0;
+	    while (__getline (&buf, &buf_len, fp) >= 0) {
 		/* skip comments */
 		if (*buf == ';' || *buf == '#')
 			continue;
 		/* read default domain name */
 		if (MATCH(buf, "domain")) {
-		    if (haveenv)	/* skip if have from environ */
+		    /* Do not override the environment setting.  */
+		    if (localdomain != NULL)
 			    continue;
 		    cp = buf + sizeof("domain") - 1;
 		    while (*cp == ' ' || *cp == '\t')
@@ -239,8 +298,9 @@ __res_vinit(res_state statp, int preinit) {
 		}
 		/* set search list */
 		if (MATCH(buf, "search")) {
-		    if (haveenv)	/* skip if have from environ */
-			    continue;
+		    /* Do not override the environment setting.  */
+		    if (localdomain != NULL)
+		      continue;
 		    cp = buf + sizeof("search") - 1;
 		    while (*cp == ' ' || *cp == '\t')
 			    cp++;
@@ -399,7 +459,7 @@ __res_vinit(res_state statp, int preinit) {
 #ifdef RESOLVSORT
 	    statp->nsort = nsort;
 #endif
-	    (void) fclose(fp);
+	    free (buf);
 	}
 	if (__builtin_expect(statp->nscount == 0, 0)) {
 	    statp->nsaddr.sin_addr = __inet_makeaddr(IN_LOOPBACKNET, 1);
@@ -407,10 +467,7 @@ __res_vinit(res_state statp, int preinit) {
 	    statp->nsaddr.sin_port = htons(NAMESERVER_PORT);
 	    statp->nscount = 1;
 	}
-	if (statp->defdname[0] == 0 &&
-	    __gethostname(buf, sizeof(statp->defdname) - 1) == 0 &&
-	    (cp = strchr(buf, '.')) != NULL)
-		strcpy(statp->defdname, cp + 1);
+	init_defdname (statp);
 
 	/* find components of local domain that might be searched */
 	if (havesearch == 0) {
@@ -443,11 +500,9 @@ __res_vinit(res_state statp, int preinit) {
 #endif /* !RFC1535 */
 	}
 
-	if ((cp = getenv("RES_OPTIONS")) != NULL)
-		res_setoptions(statp, cp, "env");
 	statp->options |= RES_INIT;
-	return (0);
 }
+libc_hidden_def (__res_init_fp)
 
 static void
 internal_function
diff --git a/resolv/tst-res_init.c b/resolv/tst-res_init.c
new file mode 100644
index 0000000..c1e5e03
--- /dev/null
+++ b/resolv/tst-res_init.c
@@ -0,0 +1,236 @@
+/* Test resolv.conf parsing.
+   Copyright (C) 1995-2016 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+#include <arpa/inet.h>
+#include <resolv.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+static bool errors;
+
+static char hostname[HOST_NAME_MAX + 1];
+static const char *domainname;
+
+/* Allocate a new resolver state.  */
+static res_state
+new_state (void)
+{
+  res_state statp = malloc (sizeof (*statp));
+  if (statp == NULL)
+    {
+      printf ("error: malloc: %m\n");
+      abort ();
+    }
+  return statp;
+}
+
+/* Opens string for reading.  Does not make a copy of the string.  */
+static FILE *
+open_string (const char *str)
+{
+  FILE *fp = fmemopen ((char *) str, strlen (str), "r");
+  if (fp == NULL)
+    {
+      printf ("error: fmemopen: %m\n");
+      abort ();
+    }
+  return fp;
+}
+
+/* Tests with an empty configuration.  If positive, FILL is used to
+   initialize the allocated resolver structure.  */
+static void
+test_empty (int fill)
+{
+  res_state statp = new_state ();
+  if (fill >= 0)
+    memset (statp, fill, sizeof (*statp));
+
+  {
+    FILE *fp = open_string ("");
+    __res_init_fp (statp, fp, false, NULL);
+    fclose (fp);
+  }
+
+  if ((statp->options & RES_INIT) == 0)
+    {
+        printf ("error: RES_INIT not set\n");
+        errors = true;
+    }
+  if (domainname == NULL)
+    /* System does not have a domain name as part of the host name.  */
+    {
+      if (statp->defdname[0] != '\0')
+        {
+          printf ("error: incorrect default domain name: %s\n", statp->defdname);
+          errors = true;
+        }
+      if (statp->dnsrch[0][0] != '\0')
+        {
+          printf ("error: invalid search entry 0: %s\n", statp->dnsrch[0]);
+          errors = true;
+        }
+    }
+  else
+    /* System has a domain name as part of the host name.  */
+    {
+      if (strcmp (statp->defdname, domainname) != 0)
+        {
+          printf ("info: system host name: %s\n", hostname);
+          printf ("error: incorrect default domain name: %s\n", statp->defdname);
+          errors = true;
+        }
+      if (strcmp (statp->dnsrch[0], domainname) != 0)
+        {
+          printf ("error: incorrect search entry 0: %s\n", statp->dnsrch[0]);
+          errors = true;
+        }
+    }
+  if (statp->dnsrch[1] != NULL)
+    {
+      printf ("error: invalid search entry 1: %s\n", statp->dnsrch[1]);
+      errors = true;
+    }
+
+  res_nclose (statp);
+  free (statp);
+}
+
+static void
+test_ipv6 (void)
+{
+  res_state statp = new_state ();
+
+  {
+    const char *conf
+      = "domain .\n"
+        "nameserver 2001:db8::1\n";
+    FILE *fp = open_string (conf);
+    __res_init_fp (statp, fp, false, NULL);
+    fclose (fp);
+  }
+
+  if (statp->nscount != 1)
+    {
+      printf ("error: expected one name server, got %d\n", statp->nscount);
+      errors = true;
+    }
+  else if (statp->_u._ext.nsaddrs[0] == NULL)
+    {
+      printf ("error: IPv6 name server is missing\n");
+      errors = true;
+    }
+  else
+    {
+      char buf[128];
+      if (inet_ntop (AF_INET6, statp->_u._ext.nsaddrs[0]->sin6_addr.s6_addr,
+                     buf, sizeof (buf)) == NULL)
+        {
+          printf ("error: inet_ntop: %m\n");
+          errors = true;
+        }
+      else if (strcmp (buf, "2001:db8::1") != 0)
+        {
+          printf ("error: incorrect name server %s\n", buf);
+          errors = true;
+        }
+    }
+
+  res_nclose (statp);
+  free (statp);
+}
+
+/* Check that the original settings are restored on reinitialization
+   of a resolver structure with an empty configuration.  */
+static void
+bug19369 (const char *localdomain)
+{
+  res_state statp_reference = new_state ();
+  {
+    FILE *fp = open_string ("");
+    __res_init_fp (statp_reference, fp, false, localdomain);
+    fclose (fp);
+  }
+
+  res_state statp = new_state ();
+  {
+    /* NB: No IPv6 name servers.  They would trigger memory
+       allocation, and reinitialization without an intervening
+       res_nclose leads to a memory leak.  */
+    const char *conf
+      = "search example.org\n"
+        "nameserver 192.0.2.1\n"
+        "nameserver 192.0.2.2\n";
+    FILE *fp = open_string (conf);
+    __res_init_fp (statp, fp, false, NULL);
+    fclose (fp);
+  }
+  if (statp->nscount != 2)
+    {
+      printf ("error: invalid name server count: %d\n", statp->nscount);
+      errors = true;
+    }
+  {
+    FILE *fp = open_string ("");
+    __res_init_fp (statp, fp, false, localdomain);
+    fclose (fp);
+  }
+  if (statp->nscount != 1)
+    {
+      printf ("error: invalid name server count: %d\n", statp->nscount);
+      errors = true;
+    }
+  if (ntohl (statp->nsaddr_list[0].sin_addr.s_addr) != INADDR_LOOPBACK)
+    {
+      printf ("error: incorrect loopback name server address\n");
+      errors = true;
+    }
+  if (strcmp (statp->defdname, statp_reference->defdname) != 0)
+    {
+      printf ("info: expected defdname: %s\n", statp_reference->defdname);
+      printf ("error: actual defdname: %s\n", statp->defdname);
+      errors = true;
+    }
+
+  free (statp);
+  free (statp_reference);
+}
+
+static int
+do_test (void)
+{
+  if (gethostname (hostname, sizeof (hostname) - 1) != 0)
+    hostname[0] = '\0';
+  domainname = strchr (hostname, '.');
+  if (domainname != NULL)
+    ++domainname;
+
+  for (int i = -1; i <= 255; ++i)
+    test_empty (i);
+
+  test_ipv6 ();
+  bug19369 (NULL);
+  bug19369 ("");
+
+  return errors;
+}
+
+#define TEST_FUNCTION do_test ()
+#include "../test-skeleton.c"
