From 45dd12dc46a9368a528c652b69cafa78066470fd Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Petr=20Men=C5=A1=C3=ADk?= <pemensik@redhat.com>
Date: Thu, 9 Mar 2017 20:37:44 +0100
Subject: [PATCH 1/2] Add new-zones-directory option, write .nzf there

Allow unattended config upgrade.
If file does not exist in directory but does exist in working directory,
use file from working directory.
Fix case when new-zones-directory is not used.
---
 bin/named/server.c     | 137 +++++++++++++++++++++++++++++++++++++++++++++++--
 lib/dns/view.c         |  62 ++--------------------
 lib/isccfg/namedconf.c |   1 +
 3 files changed, 139 insertions(+), 61 deletions(-)

diff --git a/bin/named/server.c b/bin/named/server.c
index f7c8cc1..3cf7c76 100644
--- a/bin/named/server.c
+++ b/bin/named/server.c
@@ -5715,7 +5715,6 @@ configure_zone(const cfg_obj_t *config, const cfg_obj_t *zconfig,
 /*
  * Configure built-in zone for storing managed-key data.
  */
-
 static isc_result_t
 add_keydata_zone(dns_view_t *view, const char *directory, isc_mem_t *mctx) {
 	isc_result_t result;
@@ -6614,6 +6613,120 @@ configure_session_key(const cfg_obj_t **maps, ns_server_t *server,
 	return (result);
 }
 
+/*
+ * Create path to a directory and a filename contructed from viewname.
+ * Similar to isc_file_sanitize, but allows easy config upgrade
+ * without moving files.
+ * It tests file existence in the directory first. If no such file
+ * exists, try without the directory. If it does not exist again,
+ * return the path inside the directory.
+ */
+static isc_result_t
+file_sanitize_legacy(isc_mem_t *mctx,
+		     const char *directory, const char *viewname,
+		     const char *suffix,
+		     char *buffer, size_t buflen) {
+	isc_result_t result;
+	char *file_in_dir;
+
+	result = isc_file_sanitize(directory, viewname, suffix,
+				   buffer, buflen);
+	if (result != ISC_R_SUCCESS
+	    || directory == NULL
+	    || isc_file_exists(buffer))
+		return result;
+
+	file_in_dir = isc_mem_strdup(mctx, buffer);
+	/* It is not in the specified directory, maybe it is an old file
+	 * still in the current directory */
+	result = isc_file_sanitize(NULL, viewname, suffix,
+				   buffer, buflen);
+	if (result != ISC_R_SUCCESS || isc_file_exists(buffer)) {
+		isc_mem_free(mctx, file_in_dir);
+		return result;
+	}
+
+	/* Does not exist in the current directory yet,
+	 * use the path in the directory. */
+	strncpy(buffer, file_in_dir, buflen-1);
+	isc_mem_free(mctx, file_in_dir);
+	return result;
+}
+
+/*
+ * Called only when new-zones option is enabled.
+ * Moved here from dns_view_setnewzones.
+ */
+static isc_result_t
+allow_setnewzones(dns_view_t *view,
+		  const char *directory, void *cfgctx,
+		  void (*cfg_destroy)(void **)) {
+	isc_result_t result;
+	char buffer[1024];
+#ifdef HAVE_LMDB
+	MDB_env *env = NULL;
+	int status;
+#endif
+	REQUIRE(DNS_VIEW_VALID(view));
+	REQUIRE(cfgctx != NULL && cfg_destroy != NULL);
+
+	dns_view_setnewzones(view, ISC_FALSE, NULL, NULL);
+	result = file_sanitize_legacy(view->mctx, directory, view->name, "nzf",
+				   buffer, sizeof(buffer));
+	if (result != ISC_R_SUCCESS)
+		goto out;
+	view->new_zone_file = isc_mem_strdup(view->mctx, buffer);
+
+#ifdef HAVE_LMDB
+	result = file_sanitize_legacy(directory, view->name, "nzd",
+				   buffer, sizeof(buffer));
+	if (result != ISC_R_SUCCESS)
+		goto out;
+	view->new_zone_db = isc_mem_strdup(view->mctx, buffer);
+
+	status = mdb_env_create(&env);
+	if (status != 0) {
+		result = ISC_R_FAILURE;
+		goto out;
+	}
+
+	status = mdb_env_open(env, view->new_zone_db,
+			      MDB_NOSUBDIR|MDB_CREATE, 0600);
+	if (status != 0) {
+		result = ISC_R_FAILURE;
+		goto out;
+	}
+
+	view->new_zone_dbenv = env;
+	env = NULL;
+#endif /* HAVE_LMDB */
+
+	view->new_zone_config = cfgctx;
+	view->cfg_destroy = cfg_destroy;
+
+ out:
+	if (result != ISC_R_SUCCESS) {
+		if (view->new_zone_file != NULL) {
+			isc_mem_free(view->mctx, view->new_zone_file);
+			view->new_zone_file = NULL;
+		}
+
+#ifdef HAVE_LMDB
+		if (view->new_zone_db != NULL) {
+			isc_mem_free(view->mctx, view->new_zone_db);
+			view->new_zone_db = NULL;
+		}
+		if (env != NULL)
+			mdb_env_close(env);
+#endif /* HAVE_LMDB */
+		view->new_zone_config = NULL;
+		view->cfg_destroy = NULL;
+	}
+
+	return (result);
+}
+
+
 #ifndef HAVE_LMDB
 static isc_result_t
 count_newzones(dns_view_t *view, ns_cfgctx_t *nzcfg, int *num_zonesp) {
@@ -6706,7 +6819,9 @@ setup_newzones(dns_view_t *view, cfg_obj_t *config, cfg_obj_t *vconfig,
 	const cfg_obj_t *maps[4];
 	const cfg_obj_t *options = NULL, *voptions = NULL;
 	const cfg_obj_t *nz = NULL;
+	const cfg_obj_t *nzdir = NULL;
 	int i = 0;
+	const char *directory = NULL;
 
 	REQUIRE (config != NULL);
 
@@ -6723,6 +6838,20 @@ setup_newzones(dns_view_t *view, cfg_obj_t *config, cfg_obj_t *vconfig,
 	result = ns_config_get(maps, "allow-new-zones", &nz);
 	if (result == ISC_R_SUCCESS)
 		allow = cfg_obj_asboolean(nz);
+	result = ns_config_get(maps, "new-zones-directory", &nzdir);
+	if (result == ISC_R_SUCCESS) {
+		directory = cfg_obj_asstring(nzdir);
+		if (directory != NULL)
+			result = isc_file_isdirectory(directory);
+		if (result != ISC_R_SUCCESS) {
+			isc_log_write(ns_g_lctx, DNS_LOGCATEGORY_SECURITY,
+				      NS_LOGMODULE_SERVER, ISC_LOG_ERROR,
+				      "invalid new-zones-directory %s: %s",
+				      directory, isc_result_totext(result));
+			dns_view_setnewzones(view, ISC_FALSE, NULL, NULL);
+			return (result);
+		}
+	}
 
 	/*
 	 * A non-empty catalog-zones statement implies allow-new-zones
@@ -6753,8 +6882,8 @@ setup_newzones(dns_view_t *view, cfg_obj_t *config, cfg_obj_t *vconfig,
 
 	memset(nzcfg, 0, sizeof(*nzcfg));
 
-	result = dns_view_setnewzones(view, allow, nzcfg,
-				      newzone_cfgctx_destroy);
+	result = allow_setnewzones(view, directory, nzcfg,
+				   newzone_cfgctx_destroy);
 	if (result != ISC_R_SUCCESS) {
 		isc_mem_free(view->mctx, nzcfg);
 		return (result);
@@ -11114,7 +11243,7 @@ nzf_writeconf(const cfg_obj_t *config, dns_view_t *view) {
 	char tmp[1024];
 	isc_result_t result;
 
-	result = isc_file_template("", "nzf-XXXXXXXX", tmp, sizeof(tmp));
+	result = isc_file_template(view->new_zone_file, "nzf-XXXXXXXX", tmp, sizeof(tmp));
 	if (result == ISC_R_SUCCESS)
 		result = isc_file_openunique(tmp, &fp);
 	if (result != ISC_R_SUCCESS)
diff --git a/lib/dns/view.c b/lib/dns/view.c
index 0e02cca..7d82f76 100644
--- a/lib/dns/view.c
+++ b/lib/dns/view.c
@@ -2006,7 +2006,9 @@ dns_view_setnewzones(dns_view_t *view, isc_boolean_t allow, void *cfgctx,
 #endif
 
 	REQUIRE(DNS_VIEW_VALID(view));
-	REQUIRE((cfgctx != NULL && cfg_destroy != NULL) || !allow);
+	REQUIRE(!allow);
+	UNUSED(cfgctx);
+	UNUSED(cfg_destroy);
 
 	if (view->new_zone_file != NULL) {
 		isc_mem_free(view->mctx, view->new_zone_file);
@@ -2030,62 +2032,8 @@ dns_view_setnewzones(dns_view_t *view, isc_boolean_t allow, void *cfgctx,
 		view->cfg_destroy = NULL;
 	}
 
-	if (!allow)
-		return (ISC_R_SUCCESS);
-
-	result = isc_file_sanitize(NULL, view->name, "nzf",
-				   buffer, sizeof(buffer));
-	if (result != ISC_R_SUCCESS)
-		goto out;
-	view->new_zone_file = isc_mem_strdup(view->mctx, buffer);
-
-#ifdef HAVE_LMDB
-	result = isc_file_sanitize(NULL, view->name, "nzd",
-				   buffer, sizeof(buffer));
-	if (result != ISC_R_SUCCESS)
-		goto out;
-	view->new_zone_db = isc_mem_strdup(view->mctx, buffer);
-
-	status = mdb_env_create(&env);
-	if (status != 0) {
-		result = ISC_R_FAILURE;
-		goto out;
-	}
-
-	status = mdb_env_open(env, view->new_zone_db,
-			      MDB_NOSUBDIR|MDB_CREATE, 0600);
-	if (status != 0) {
-		result = ISC_R_FAILURE;
-		goto out;
-	}
-
-	view->new_zone_dbenv = env;
-	env = NULL;
-#endif /* HAVE_LMDB */
-
-	view->new_zone_config = cfgctx;
-	view->cfg_destroy = cfg_destroy;
-
- out:
-	if (result != ISC_R_SUCCESS) {
-		if (view->new_zone_file != NULL) {
-			isc_mem_free(view->mctx, view->new_zone_file);
-			view->new_zone_file = NULL;
-		}
-
-#ifdef HAVE_LMDB
-		if (view->new_zone_db != NULL) {
-			isc_mem_free(view->mctx, view->new_zone_db);
-			view->new_zone_db = NULL;
-		}
-		if (env != NULL)
-			mdb_env_close(env);
-#endif /* HAVE_LMDB */
-		view->new_zone_config = NULL;
-		view->cfg_destroy = NULL;
-	}
-
-	return (result);
+	// allow code moved to bin/named/server.c
+	return (ISC_R_SUCCESS);
 }
 
 isc_result_t
diff --git a/lib/isccfg/namedconf.c b/lib/isccfg/namedconf.c
index ca83423..72af787 100644
--- a/lib/isccfg/namedconf.c
+++ b/lib/isccfg/namedconf.c
@@ -1100,6 +1100,7 @@ options_clauses[] = {
 	{ "memstatistics", &cfg_type_boolean, 0 },
 	{ "multiple-cnames", &cfg_type_boolean, CFG_CLAUSEFLAG_OBSOLETE },
 	{ "named-xfer", &cfg_type_qstring, CFG_CLAUSEFLAG_OBSOLETE },
+	{ "new-zones-directory", &cfg_type_qstring, 0 },
 	{ "notify-rate", &cfg_type_uint32, 0 },
 	{ "pid-file", &cfg_type_qstringornone, 0 },
 	{ "port", &cfg_type_uint32, 0 },
-- 
2.9.3

