changeset 6666:b61097837011

6633347 nscd (sparks) can give inconsistent name resolution if started without a resolv.conf file
author sm26363
date Tue, 20 May 2008 09:43:20 -0700
parents 1515bc919a93
children 2d6c366a5d6c
files usr/src/cmd/nscd/nscd_frontend.c usr/src/cmd/nscd/nscd_switch.c usr/src/lib/nsswitch/dns/common/dns_common.c
diffstat 3 files changed, 127 insertions(+), 36 deletions(-) [+]
line wrap: on
line diff
--- a/usr/src/cmd/nscd/nscd_frontend.c	Tue May 20 07:37:47 2008 -0700
+++ b/usr/src/cmd/nscd/nscd_frontend.c	Tue May 20 09:43:20 2008 -0700
@@ -171,6 +171,24 @@
 	/* not much here */
 }
 
+/*
+ * _nscd_restart_if_cfgfile_changed()
+ * Restart if modification times of nsswitch.conf or resolv.conf have changed.
+ *
+ * If nsswitch.conf has changed then it is possible that sources for
+ * various backends have changed and therefore the current cached
+ * data may not be consistent with the new data sources.  By
+ * restarting the cache will be cleared and the new configuration will
+ * be used.
+ *
+ * The check for resolv.conf is made as only the first call to
+ * res_gethostbyname() or res_getaddrbyname() causes a call to
+ * res_ninit() to occur which in turn parses resolv.conf.  Therefore
+ * to benefit from changes to resolv.conf nscd must be restarted when
+ * resolv.conf is updated, removed or created.  If res_getXbyY calls
+ * are removed from NSS then this check could be removed.
+ *
+ */
 void
 _nscd_restart_if_cfgfile_changed()
 {
@@ -178,13 +196,22 @@
 	static mutex_t		nsswitch_lock = DEFAULTMUTEX;
 	static timestruc_t	last_nsswitch_check = { 0 };
 	static timestruc_t	last_nsswitch_modified = { 0 };
-	static timestruc_t	last_resolv_modified = { 0 };
+	static timestruc_t	last_resolv_modified = { -1, 0 };
 	static mutex_t		restarting_lock = DEFAULTMUTEX;
 	static int 		restarting = 0;
 	int			restart = 0;
 	time_t			now = time(NULL);
 	char			*me = "_nscd_restart_if_cfgfile_changed";
 
+#define	FLAG_RESTART_REQUIRED	if (restarting == 0) {\
+					(void) mutex_lock(&restarting_lock);\
+					if (restarting == 0) {\
+						restarting = 1;\
+						restart = 1;\
+					}\
+					(void) mutex_unlock(&restarting_lock);\
+				}
+
 	if (restarting == 1)
 		return;
 
@@ -202,24 +229,6 @@
 
 		(void) mutex_unlock(&nsswitch_lock); /* let others continue */
 
-		/*
-		 *  This code keeps us from statting resolv.conf
-		 *  if it doesn't exist, yet prevents us from ignoring
-		 *  it if it happens to disappear later on for a bit.
-		 */
-
-		if (last_resolv_modified.tv_sec >= 0) {
-			if (stat("/etc/resolv.conf", &res_buf) < 0) {
-				if (last_resolv_modified.tv_sec == 0) {
-					last_resolv_modified.tv_sec = -1;
-					last_resolv_modified.tv_nsec = 0;
-				} else
-					res_buf.st_mtim = last_resolv_modified;
-			} else if (last_resolv_modified.tv_sec == 0) {
-				last_resolv_modified = res_buf.st_mtim;
-			}
-		}
-
 		if (stat("/etc/nsswitch.conf", &nss_buf) < 0) {
 			return;
 		} else if (last_nsswitch_modified.tv_sec == 0) {
@@ -228,19 +237,33 @@
 
 		if (last_nsswitch_modified.tv_sec < nss_buf.st_mtim.tv_sec ||
 		    (last_nsswitch_modified.tv_sec == nss_buf.st_mtim.tv_sec &&
-		    last_nsswitch_modified.tv_nsec < nss_buf.st_mtim.tv_nsec) ||
-		    (last_resolv_modified.tv_sec > 0 &&
-		    (last_resolv_modified.tv_sec < res_buf.st_mtim.tv_sec ||
-		    (last_resolv_modified.tv_sec == res_buf.st_mtim.tv_sec &&
-		    last_resolv_modified.tv_nsec < res_buf.st_mtim.tv_nsec)))) {
+		    last_nsswitch_modified.tv_nsec < nss_buf.st_mtim.tv_nsec)) {
+			FLAG_RESTART_REQUIRED;
+		}
 
-			if (restarting == 0) {
-				(void) mutex_lock(&restarting_lock);
-					if (restarting == 0) {
-						restarting = 1;
-						restart = 1;
-					}
-				(void) mutex_unlock(&restarting_lock);
+		if (restart == 0) {
+			if (stat("/etc/resolv.conf", &res_buf) < 0) {
+				/* Unable to stat file, were we previously? */
+				if (last_resolv_modified.tv_sec > 0) {
+					/* Yes, it must have been removed. */
+					FLAG_RESTART_REQUIRED;
+				} else if (last_resolv_modified.tv_sec == -1) {
+					/* No, then we've never seen it. */
+					last_resolv_modified.tv_sec = 0;
+				}
+			} else if (last_resolv_modified.tv_sec == -1) {
+				/* We've just started and file is present. */
+				last_resolv_modified = res_buf.st_mtim;
+			} else if (last_resolv_modified.tv_sec == 0) {
+				/* Wasn't there at start-up. */
+				FLAG_RESTART_REQUIRED;
+			} else if (last_resolv_modified.tv_sec <
+			    res_buf.st_mtim.tv_sec ||
+			    (last_resolv_modified.tv_sec ==
+			    res_buf.st_mtim.tv_sec &&
+			    last_resolv_modified.tv_nsec <
+			    res_buf.st_mtim.tv_nsec)) {
+				FLAG_RESTART_REQUIRED;
 			}
 		}
 
--- a/usr/src/cmd/nscd/nscd_switch.c	Tue May 20 07:37:47 2008 -0700
+++ b/usr/src/cmd/nscd/nscd_switch.c	Tue May 20 09:43:20 2008 -0700
@@ -377,8 +377,9 @@
 	}
 
 	_nscd_logit(me, "%s: database: %s, operation: %d, source: %s, "
-	    "erange= %d, errno: %s \n",
-	    res_str, db, op, src, arg->erange, strerror(arg->h_errno));
+	    "erange= %d, herrno: %s (%d)\n",
+	    res_str, db, op, src, arg->erange, hstrerror(arg->h_errno),
+	    arg->h_errno);
 }
 
 /*
--- a/usr/src/lib/nsswitch/dns/common/dns_common.c	Tue May 20 07:37:47 2008 -0700
+++ b/usr/src/lib/nsswitch/dns/common/dns_common.c	Tue May 20 09:43:20 2008 -0700
@@ -19,7 +19,7 @@
  * CDDL HEADER END
  */
 /*
- * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
+ * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
  * Use is subject to license terms.
  */
 
@@ -44,6 +44,10 @@
 #define	DNS_ADDRLIST	1
 #define	DNS_MAPDLIST	2
 
+#ifndef	tolower
+#define	tolower(c) ((c) >= 'A' && (c) <= 'Z' ? (c) | 0x20 : (c))
+#endif
+
 static int
 dns_netdb_aliases(from_list, to_list, aliaspp, type, count, af_type)
 	char	**from_list, **to_list,	**aliaspp;
@@ -283,6 +287,55 @@
 }
 
 /*
+ * name_is_alias(aliases_ptr, name_ptr)
+ * Verify name matches an alias in the provided aliases list.
+ *
+ * Within DNS there should be only one canonical name, aliases should
+ * all refer to the one canonical.  However alias chains do occur and
+ * pre BIND 9 servers may also respond with multiple CNAMEs.  This
+ * routine checks if a given name has been provided as a CNAME in the
+ * response.  This assumes that the chains have been sent in-order.
+ *
+ * INPUT:
+ *  aliases_ptr: space separated list of alias names.
+ *  name_ptr: name to look for in aliases_ptr list.
+ * RETURNS: NSS_SUCCESS or NSS_ERROR
+ *  NSS_SUCCESS indicates that the name is listed in the collected aliases.
+ */
+static nss_status_t
+name_is_alias(char *aliases_ptr, char *name_ptr) {
+	char *host_ptr;
+	/* Loop through alias string and compare it against host string. */
+	while (*aliases_ptr != '\0') {
+		host_ptr = name_ptr;
+
+		/* Compare name with alias. */
+		while (tolower(*host_ptr) == tolower(*aliases_ptr) &&
+		    *host_ptr != '\0') {
+			host_ptr++;
+			aliases_ptr++;
+		}
+
+		/*
+		 * If name was exhausted and the next character in the
+		 * alias is either the end-of-string or space
+		 * character then we have a match.
+		 */
+		if (*host_ptr == '\0' &&
+		    (*aliases_ptr == '\0' || *aliases_ptr == ' ')) {
+			return (NSS_SUCCESS);
+		}
+
+		/* Alias did not match, step over remainder of alias. */
+		while (*aliases_ptr != ' ' && *aliases_ptr != '\0')
+			aliases_ptr++;
+		/* Step over separator character. */
+		while (*aliases_ptr == ' ') aliases_ptr++;
+	}
+	return (NSS_ERROR);
+}
+
+/*
  * nss_dns_gethost_withttl(void *buffer, size_t bufsize, int ipnode)
  *      nss2 get hosts/ipnodes with ttl backend DNS search engine.
  *
@@ -425,7 +478,12 @@
 	while (ancount-- > 0 && cp < eom && blen < bsize) {
 		n = dn_expand(bom, eom, cp, ans, MAXHOSTNAMELEN);
 		if (n > 0) {
-			if (strncasecmp(host, ans, hlen) != 0) {
+			/*
+			 * Check that the expanded name is either the
+			 * name we asked for or a learned alias.
+			 */
+			if (strncasecmp(host, ans, hlen) != 0 && (alen == 0 ||
+			    name_is_alias(aliases, ans) == NSS_ERROR)) {
 				__res_ndestroy(statp);
 				return (NSS_ERROR);	/* spoof? */
 			}
@@ -448,7 +506,16 @@
 		}
 		eor = cp + n;
 		if (type == T_CNAME) {
-			/* add an alias to the alias list */
+			/*
+			 * The name we looked up is really an alias
+			 * and the canonical name should be in the
+			 * RDATA.  A canonical name may have several
+			 * aliases but an alias should only have one
+			 * canonical name. However multiple CNAMEs and
+			 * CNAME chains do exist!  So for caching
+			 * purposes maintain the alias as the host
+			 * name, and the CNAME as an alias.
+			 */
 			n = dn_expand(bom, eor, cp, aname, MAXHOSTNAMELEN);
 			if (n > 0) {
 				len = strlen(aname);