autofs-5.1.0-beta1 - fix multi entry ldap option handling

From: Ian Kent <raven@themaw.net>

Handling new and old configurations presents special problems for
configuration entries that may have repeated values, such as ldap_uris
and ldap_search_base, which can be overridden when the old configuration
is read.

If entries exist in the new configuration they need to be saved and
restored if there's no entries in the old configuration or, if entries
exist in the old configuration, they need to be discarded.
---
 CHANGELOG      |    1 
 lib/defaults.c |  241 ++++++++++++++++++++++++++++++++++++++++++++++----------
 2 files changed, 198 insertions(+), 44 deletions(-)

diff --git a/CHANGELOG b/CHANGELOG
index 2fc9bdf..c6bcde1 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -22,6 +22,7 @@
 - fix typo in conf_load_autofs_defaults().
 - fix hash on confg option add and delete.
 - add plus to path match pattern.
+- fix multi entry ldap option handling.
 
 28/03/2014 autofs-5.0.9
 =======================
diff --git a/lib/defaults.c b/lib/defaults.c
index 3dbc13e..2b03ea2 100644
--- a/lib/defaults.c
+++ b/lib/defaults.c
@@ -916,83 +916,236 @@ static int read_config(unsigned int to_syslog, FILE *f, const char *name)
 	return 0;
 }
 
-/*
- * Read config env variables and check they have been set.
- *
- * This simple minded routine assumes the config file
- * is valid bourne shell script without spaces around "="
- * and that it has valid values.
- */
-unsigned int defaults_read_config(unsigned int to_syslog)
+struct conf_option *save_ldap_option_list(const char *key)
 {
-	FILE *f;
-	struct stat stb;
-	int ret;
+	struct conf_option *co, *head, *this, *last;
+	unsigned int size = CFG_TABLE_SIZE;
+	u_int32_t key_hash;
 
-	pthread_mutex_lock(&conf_mutex);
-	if (!config) {
-		if (conf_init()) {
-			pthread_mutex_unlock(&conf_mutex);
-			message(to_syslog, "failed to init config");
-			return 0;
+	key_hash = get_hash(key, size);
+	co = config->hash[key_hash];
+	if (!co)
+		return NULL;
+	last = co;
+
+	head = this = NULL;
+	while (co) {
+		if (strcasecmp(autofs_gbl_sec, co->section)) {
+			last = co;
+			goto next;
+		}
+
+		if (!strcasecmp(co->name, key)) {
+			/* Unlink from old */
+			if (co == config->hash[key_hash])
+				config->hash[key_hash] = co->next;
+			else
+				last->next = co->next;
+			last = co->next;
+			co->next = NULL;
+			/* Add to new */
+			if (this)
+				this->next = co;
+			this = co;
+			/* If none have been found yet */
+			if (!head)
+				head = co;
+			co = last;
+			continue;
 		}
+next:
+		co = co->next;
+	}
+
+	return head;
+}
+
+void restore_ldap_option_list(struct conf_option *list)
+{
+	struct conf_option *co, *this, *last;
+	unsigned int size = CFG_TABLE_SIZE;
+	u_int32_t key_hash;
+
+	if (!list)
+		return;
+
+	this = list;
+	while (this) {
+		last = this;
+		this = this->next;
+	}
+
+	key_hash = get_hash(list->name, size);
+	co = config->hash[key_hash];
+	config->hash[key_hash] = list;
+	if (co)
+		last->next = co;
+
+	return;
+}
+
+void free_ldap_option_list(struct conf_option *list)
+{
+	struct conf_option *next, *this;
+
+	if (!list)
+		return;
+
+	this = list;
+	while (this) {
+		next = this->next;
+		free(this->section);
+		free(this->name);
+		free(this->value);
+		free(this);
+		this = next;
 	}
 
-	/* Set configuration to defaults */
+	return;
+}
+
+static void clean_ldap_multi_option(const char *key)
+{
+	const char *sec = autofs_gbl_sec;
+	struct conf_option *co;
+
+	while ((co = conf_lookup(sec, key)))
+		conf_delete(co->section, co->name);
+
+	return;
+}
+
+static int reset_defaults(unsigned int to_syslog)
+{
+	int ret;
+
 	ret = conf_load_autofs_defaults();
 	if (!ret) {
-		pthread_mutex_unlock(&conf_mutex);
 		message(to_syslog, "failed to reset autofs default config");
 		return 0;
 	}
 
 	ret = conf_load_amd_defaults();
 	if (!ret) {
-		pthread_mutex_unlock(&conf_mutex);
 		message(to_syslog, "failed to reset amd default config");
 		return 0;
 	}
 
-	f = open_fopen_r(DEFAULT_CONFIG_FILE);
-	if (!f) {
+	return 1;
+}
+
+/*
+ * Read config env variables and check they have been set.
+ *
+ * This simple minded routine assumes the config file
+ * is valid bourne shell script without spaces around "="
+ * and that it has valid values.
+ */
+unsigned int defaults_read_config(unsigned int to_syslog)
+{
+	FILE *conf, *oldconf;
+	struct stat stb, oldstb;
+	int ret, stat, oldstat;
+
+	ret = 1;
+
+	pthread_mutex_lock(&conf_mutex);
+	if (!config) {
+		if (conf_init()) {
+			message(to_syslog, "failed to init config");
+			ret = 0;
+			goto out;
+		}
+	}
+
+	conf = open_fopen_r(DEFAULT_CONFIG_FILE);
+	if (!conf)
 		message(to_syslog, "failed to to open config %s",
 			DEFAULT_CONFIG_FILE);
+
+	oldconf = open_fopen_r(OLD_CONFIG_FILE);
+	if (!oldconf)
+		message(to_syslog, "failed to to open old config %s",
+			OLD_CONFIG_FILE);
+
+	/* Neither config has been updated */
+	stat = oldstat = -1;
+	if (conf && oldconf &&
+	    (stat = fstat(fileno(conf), &stb) != -1) &&
+	    stb.st_mtime <= config->modified &&
+	    (oldstat = fstat(fileno(oldconf), &oldstb) == -1) &&
+	    oldstb.st_mtime <= config->modified) {
+		fclose(conf);
+		fclose(oldconf);
 		goto out;
 	}
 
-	if (fstat(fileno(f), &stb) != -1) {
-		/* Config hasn't been updated */
-		if (stb.st_mtime <= config->modified) {
-			fclose(f);
+	if (conf || oldconf) {
+		if (!reset_defaults(to_syslog)) {
+			fclose(conf);
+			fclose(oldconf);
+			ret = 0;
 			goto out;
 		}
 	}
 
-	ret = read_config(to_syslog, f, DEFAULT_CONFIG_FILE);
-
-	if (fstat(fileno(f), &stb) != -1)
-		config->modified = stb.st_mtime;
-	else
-		message(to_syslog, "failed to update config modified time");
+	/* Update last modified */
+	if (stat != -1) {
+		if (oldstat == -1)
+			config->modified = stb.st_mtime;
+		else {
+			if (oldstb.st_mtime < stb.st_mtime)
+				config->modified = oldstb.st_mtime;
+			else
+				config->modified = stb.st_mtime;
+		}
+	}
 
-	fclose(f);
+	if (conf) {
+		read_config(to_syslog, conf, DEFAULT_CONFIG_FILE);
+		fclose(conf);
+	}
 
 	/*
-	 * Try to read the old config file and override the installed
-	 * defaults in case user has a stale config following updating
-	 * to the new config file location.
+	 * Read the old config file and override the installed
+	 * defaults in case user has a stale config following
+	 * updating to the new config file location.
 	 */
+	if (oldconf) {
+		struct conf_option *ldap_search_base, *ldap_uris;
+		const char *sec = amd_gbl_sec;
+		struct conf_option *co;
+
+		ldap_search_base = save_ldap_option_list(NAME_SEARCH_BASE);
+		if (ldap_search_base)
+			clean_ldap_multi_option(NAME_SEARCH_BASE);
+
+		ldap_uris = save_ldap_option_list(NAME_LDAP_URI);
+		if (ldap_uris)
+			clean_ldap_multi_option(NAME_LDAP_URI);
+
+		read_config(to_syslog, oldconf, OLD_CONFIG_FILE);
+		fclose(oldconf);
+
+		if (ldap_search_base) {
+			co = conf_lookup(sec, NAME_SEARCH_BASE);
+			if (co)
+				free_ldap_option_list(ldap_search_base);
+			else
+				restore_ldap_option_list(ldap_search_base);
+		}
 
-	f = open_fopen_r(OLD_CONFIG_FILE);
-	if (!f)
-		goto out;
-
-	read_config(to_syslog, f, OLD_CONFIG_FILE);
-
-	fclose(f);
+		if (ldap_uris) {
+			co = conf_lookup(sec, NAME_LDAP_URI);
+			if (co)
+				free_ldap_option_list(ldap_uris);
+			else
+				restore_ldap_option_list(ldap_uris);
+		}
+	}
 out:
 	pthread_mutex_unlock(&conf_mutex);
-	return 1;
+	return ret;
 }
 
 static char *conf_get_string(const char *section, const char *name)
