Commit 24197ff742 for bind

commit 24197ff74290f66fa1eeedc3fc6a65e354393017
Author: Alessio Podda <alessio@isc.org>
Date:   Wed Oct 28 14:22:42 2026 +0100

    Linear time zone options check

    Make the check of zone options validity linear in the number of
    zone options + zone template options present.

diff --git a/lib/isccfg/check.c b/lib/isccfg/check.c
index 6beb56bda9..d13e8d92e1 100644
--- a/lib/isccfg/check.c
+++ b/lib/isccfg/check.c
@@ -3354,6 +3354,37 @@ get_zoneopt(const cfg_obj_t *opts1, const cfg_obj_t *opts2,
 	return result;
 }

+typedef struct {
+	unsigned int ztype;
+	const char *typestr;
+	const char *znamestr;
+	const char *tmplname;
+} zone_options_check_t;
+
+static bool
+check_zone_option(char *key ISC_ATTR_UNUSED, unsigned int type,
+		  isc_symvalue_t value, void *arg) {
+	const zone_options_check_t *check = arg;
+
+	if (cfg_clause_validforzone(type, check->ztype)) {
+		return false;
+	}
+
+	if (check->tmplname == NULL) {
+		cfg_obj_log(value.as_pointer, ISC_LOG_WARNING,
+			    "option '%s' is not allowed in '%s' zone '%s'",
+			    cfg_clause_as_string[type], check->typestr,
+			    check->znamestr);
+	} else {
+		cfg_obj_log(value.as_pointer, ISC_LOG_WARNING,
+			    "option '%s' is not allowed in '%s' zone '%s' "
+			    "(referencing template '%s')",
+			    cfg_clause_as_string[type], check->typestr,
+			    check->znamestr, check->tmplname);
+	}
+	return false;
+}
+
 isc_result_t
 isccfg_check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions,
 		      const cfg_obj_t *config, isc_symtab_t *symtab,
@@ -3383,8 +3414,6 @@ isccfg_check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions,
 	bool kasp_inlinesigning = false;
 	bool inline_signing = false;
 	bool check_keys = (flags & BIND_CHECK_KEYS) != 0;
-	const void *clauses = NULL;
-	const cfg_clausedef_t *option = NULL;
 	const char *kaspname = NULL;
 	const char *dir = NULL;
 	static const enum cfg_clause acls[] = {
@@ -3711,28 +3740,21 @@ isccfg_check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions,
 	/*
 	 * Check validity of the zone options.
 	 */
-	option = cfg_map_firstclause(&cfg_type_zoneopts, &clauses, &i);
-	while (option != NULL) {
-		obj = NULL;
-		bool topt = false;
-		(void)cfg_map_get(zoptions, option->name, &obj);
-		if (obj == NULL && toptions != NULL) {
-			(void)cfg_map_get(toptions, option->name, &obj);
-			topt = true;
-		}
-		if (obj != NULL &&
-		    !cfg_clause_validforzone(option->name, ztype))
-		{
-			cfg_obj_log(obj, ISC_LOG_WARNING,
-				    "option '%s' is not allowed "
-				    "in '%s' zone '%s'%s%s%s",
-				    cfg_clause_as_string[option->name], typestr,
-				    znamestr,
-				    topt ? " (referencing template '" : "",
-				    topt ? tmplname : "", topt ? "')" : "");
-			result = ISC_R_FAILURE;
+	if (!cfg_zoneopts_validforzone(zoptions, toptions, ztype)) {
+		zone_options_check_t check = {
+			.ztype = ztype,
+			.typestr = typestr,
+			.znamestr = znamestr,
+		};
+
+		isc_symtab_foreach(zoptions->value.map->symtab,
+				   check_zone_option, &check);
+		if (toptions != NULL) {
+			check.tmplname = tmplname;
+			isc_symtab_foreach(toptions->value.map->symtab,
+					   check_zone_option, &check);
 		}
-		option = cfg_map_nextclause(&cfg_type_zoneopts, &clauses, &i);
+		result = ISC_R_FAILURE;
 	}

 	/*
diff --git a/lib/isccfg/include/isccfg/grammar.h b/lib/isccfg/include/isccfg/grammar.h
index 1ce2a4d598..24639c778e 100644
--- a/lib/isccfg/include/isccfg/grammar.h
+++ b/lib/isccfg/include/isccfg/grammar.h
@@ -630,6 +630,11 @@ cfg_clause_validforzone(enum cfg_clause name, unsigned int ztype);
  * Check whether an option is legal for the specified zone type.
  */

+bool
+cfg_zoneopts_validforzone(const cfg_obj_t *zoptions, const cfg_obj_t *toptions,
+			  unsigned int ztype);
+/*%< Check whether all zone and template options allow the zone type. */
+
 void
 cfg_print_zonegrammar(const unsigned int zonetype, unsigned int flags,
 		      void (*f)(void *closure, const char *text, int textlen),
diff --git a/lib/isccfg/namedconf.c b/lib/isccfg/namedconf.c
index 37e6974225..4559c70c13 100644
--- a/lib/isccfg/namedconf.c
+++ b/lib/isccfg/namedconf.c
@@ -21,8 +21,10 @@
 #include <isc/lex.h>
 #include <isc/log.h>
 #include <isc/mem.h>
+#include <isc/once.h>
 #include <isc/result.h>
 #include <isc/string.h>
+#include <isc/symtab.h>
 #include <isc/util.h>

 #include <dns/ttl.h>
@@ -4320,36 +4322,62 @@ cmp_clause(const void *ap, const void *bp) {
 		      cfg_clause_as_string[b->name]);
 }

-bool
-cfg_clause_validforzone(enum cfg_clause name, unsigned int ztype) {
-	const cfg_clausedef_t *clause;
-	bool valid = false;
+static unsigned int zone_flags[CFG_CLAUSE__COUNT];
+static isc_once_t zone_flags_once = ISC_ONCE_INITIALIZER;

-	for (clause = zone_clauses; clause->name != CFG_CLAUSE__NONE; clause++)
-	{
-		if ((clause->flags & ztype) == 0 || clause->name != name) {
-			continue;
-		}
-		valid = true;
-	}
-	for (clause = zone_only_clauses; clause->name != CFG_CLAUSE__NONE;
-	     clause++)
+static void
+initialize_zone_flags(void) {
+	const void *clauses = NULL;
+	unsigned int i = 0;
+
+	for (const cfg_clausedef_t *clause =
+		     cfg_map_firstclause(&cfg_type_zoneopts, &clauses, &i);
+	     clause != NULL;
+	     clause = cfg_map_nextclause(&cfg_type_zoneopts, &clauses, &i))
 	{
-		if ((clause->flags & ztype) == 0 || clause->name != name) {
-			continue;
-		}
-		valid = true;
+		INSIST(zone_flags[clause->name] == 0);
+		INSIST(clause->flags != 0);
+		zone_flags[clause->name] = clause->flags;
 	}
-	for (clause = non_template_clauses; clause->name != CFG_CLAUSE__NONE;
-	     clause++)
-	{
-		if ((clause->flags & ztype) == 0 || clause->name != name) {
+}
+
+bool
+cfg_clause_validforzone(enum cfg_clause name, unsigned int ztype) {
+	REQUIRE(name > CFG_CLAUSE__NONE && name < CFG_CLAUSE__COUNT);
+
+	isc_once_do(&zone_flags_once, initialize_zone_flags);
+	return (zone_flags[name] & ztype) != 0;
+}
+
+static bool
+intersect_zone_flags(char *key ISC_ATTR_UNUSED, unsigned int type,
+		     isc_symvalue_t value ISC_ATTR_UNUSED, void *arg) {
+	unsigned int *allowed = arg;
+
+	*allowed &= zone_flags[type];
+	return false;
+}
+
+bool
+cfg_zoneopts_validforzone(const cfg_obj_t *zoptions, const cfg_obj_t *toptions,
+			  unsigned int ztype) {
+	const cfg_obj_t *maps[] = { zoptions, toptions };
+	unsigned int allowed = ~0U;
+
+	REQUIRE(zoptions != NULL);
+
+	isc_once_do(&zone_flags_once, initialize_zone_flags);
+	for (size_t i = 0; i < ARRAY_SIZE(maps); i++) {
+		if (maps[i] == NULL) {
 			continue;
 		}
-		valid = true;
+		REQUIRE(cfg_obj_ismap(maps[i]));
+
+		isc_symtab_foreach(maps[i]->value.map->symtab,
+				   intersect_zone_flags, &allowed);
 	}

-	return valid;
+	return (allowed & ztype) != 0;
 }

 void
diff --git a/tests/isccfg/grammar_test.c b/tests/isccfg/grammar_test.c
index 13520248a8..03a7fd415d 100644
--- a/tests/isccfg/grammar_test.c
+++ b/tests/isccfg/grammar_test.c
@@ -153,8 +153,62 @@ ISC_RUN_TEST_IMPL(query_source_print_addr) {
 				 "options { query-source-v6 ::1; }; ");
 }

+ISC_RUN_TEST_IMPL(zone_options_validity) {
+	const struct {
+		const char *zone;
+		const char *template;
+		unsigned int ztype;
+		bool valid;
+	} cases[] = {
+		{ "{}", NULL, CFG_ZONE_PRIMARY, true },
+		{ "{ type primary; file \"example.db\"; }", NULL,
+		  CFG_ZONE_PRIMARY, true },
+		{ "{ type primary; allow-update { none; }; }", NULL,
+		  CFG_ZONE_SECONDARY, false },
+		{ "{}", "{ allow-update { none; }; }", CFG_ZONE_SECONDARY,
+		  false },
+		{ "{ notify yes; }", "{ notify no; }", CFG_ZONE_PRIMARY, true },
+		{ "{ allow-update { none; }; }", "{ allow-update { any; }; }",
+		  CFG_ZONE_SECONDARY, false },
+		{ "{ in-view \"other\"; }", NULL, CFG_ZONE_INVIEW, true },
+		{ "{ in-view \"other\"; notify yes; }", NULL, CFG_ZONE_INVIEW,
+		  false },
+	};
+
+	for (size_t i = 0; i < ARRAY_SIZE(cases); i++) {
+		const char *texts[] = { cases[i].zone, cases[i].template };
+		cfg_obj_t *maps[] = { NULL, NULL };
+
+		for (size_t j = 0; j < ARRAY_SIZE(texts); j++) {
+			isc_buffer_t buffer;
+			if (texts[j] == NULL) {
+				continue;
+			}
+			isc_buffer_constinit(&buffer, texts[j],
+					     strlen(texts[j]));
+			isc_buffer_add(&buffer, strlen(texts[j]));
+			assert_int_equal(cfg_parse_buffer(&buffer,
+							  "zone-options", 0,
+							  &cfg_type_zoneopts, 0,
+							  &maps[j]),
+					 ISC_R_SUCCESS);
+		}
+
+		assert_int_equal(cfg_zoneopts_validforzone(maps[0], maps[1],
+							   cases[i].ztype),
+				 cases[i].valid);
+
+		for (size_t j = 0; j < ARRAY_SIZE(maps); j++) {
+			if (maps[j] != NULL) {
+				cfg_obj_detach(&maps[j]);
+			}
+		}
+	}
+}
+
 ISC_TEST_LIST_START

+ISC_TEST_ENTRY(zone_options_validity)
 ISC_TEST_ENTRY(query_source)
 ISC_TEST_ENTRY(query_source_v6)
 ISC_TEST_ENTRY(server_query_source)