Home | History | Annotate | Line # | Download | only in dns
      1 /*	$NetBSD: catz.c,v 1.18 2026/06/19 20:09:59 christos Exp $	*/
      2 
      3 /*
      4  * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
      5  *
      6  * SPDX-License-Identifier: MPL-2.0
      7  *
      8  * This Source Code Form is subject to the terms of the Mozilla Public
      9  * License, v. 2.0. If a copy of the MPL was not distributed with this
     10  * file, you can obtain one at https://mozilla.org/MPL/2.0/.
     11  *
     12  * See the COPYRIGHT file distributed with this work for additional
     13  * information regarding copyright ownership.
     14  */
     15 
     16 /*! \file */
     17 
     18 #include <inttypes.h>
     19 #include <stdbool.h>
     20 #include <stdint.h>
     21 #include <stdlib.h>
     22 
     23 #include <isc/async.h>
     24 #include <isc/hex.h>
     25 #include <isc/loop.h>
     26 #include <isc/md.h>
     27 #include <isc/mem.h>
     28 #include <isc/parseint.h>
     29 #include <isc/result.h>
     30 #include <isc/util.h>
     31 #include <isc/work.h>
     32 
     33 #include <dns/catz.h>
     34 #include <dns/dbiterator.h>
     35 #include <dns/rdatasetiter.h>
     36 #include <dns/view.h>
     37 #include <dns/zone.h>
     38 
     39 #define DNS_CATZ_ZONE_MAGIC  ISC_MAGIC('c', 'a', 't', 'z')
     40 #define DNS_CATZ_ZONES_MAGIC ISC_MAGIC('c', 'a', 't', 's')
     41 #define DNS_CATZ_ENTRY_MAGIC ISC_MAGIC('c', 'a', 't', 'e')
     42 #define DNS_CATZ_COO_MAGIC   ISC_MAGIC('c', 'a', 't', 'c')
     43 
     44 #define DNS_CATZ_ZONE_VALID(catz)   ISC_MAGIC_VALID(catz, DNS_CATZ_ZONE_MAGIC)
     45 #define DNS_CATZ_ZONES_VALID(catzs) ISC_MAGIC_VALID(catzs, DNS_CATZ_ZONES_MAGIC)
     46 #define DNS_CATZ_ENTRY_VALID(entry) ISC_MAGIC_VALID(entry, DNS_CATZ_ENTRY_MAGIC)
     47 #define DNS_CATZ_COO_VALID(coo)	    ISC_MAGIC_VALID(coo, DNS_CATZ_COO_MAGIC)
     48 
     49 #define DNS_CATZ_VERSION_UNDEFINED ((uint32_t)(-1))
     50 
     51 /*%
     52  * Change of ownership permissions
     53  */
     54 struct dns_catz_coo {
     55 	unsigned int magic;
     56 	dns_name_t name;
     57 	isc_refcount_t references;
     58 };
     59 
     60 /*%
     61  * Single member zone in a catalog
     62  */
     63 struct dns_catz_entry {
     64 	unsigned int magic;
     65 	dns_name_t name;
     66 	dns_catz_options_t opts;
     67 	isc_refcount_t references;
     68 };
     69 
     70 /*%
     71  * Catalog zone
     72  */
     73 struct dns_catz_zone {
     74 	unsigned int magic;
     75 	isc_loop_t *loop;
     76 	dns_name_t name;
     77 	dns_catz_zones_t *catzs;
     78 	dns_rdata_t soa;
     79 	uint32_t version;
     80 	/* key in entries is 'mhash', not domain name! */
     81 	isc_ht_t *entries;
     82 	/* key in coos is domain name */
     83 	isc_ht_t *coos;
     84 
     85 	/*
     86 	 * defoptions are taken from named.conf
     87 	 * zoneoptions are global options from zone
     88 	 */
     89 	dns_catz_options_t defoptions;
     90 	dns_catz_options_t zoneoptions;
     91 	isc_time_t lastupdated;
     92 
     93 	bool updatepending;	      /* there is an update pending */
     94 	bool updaterunning;	      /* there is an update running */
     95 	isc_result_t updateresult;    /* result from the offloaded work */
     96 	dns_db_t *db;		      /* zones database */
     97 	dns_dbversion_t *dbversion;   /* version we will be updating to */
     98 	dns_db_t *updb;		      /* zones database we're working on */
     99 	dns_dbversion_t *updbversion; /* version we're working on */
    100 
    101 	isc_timer_t *updatetimer;
    102 
    103 	bool active;
    104 	bool broken;
    105 
    106 	isc_refcount_t references;
    107 	isc_mutex_t lock;
    108 };
    109 
    110 static void
    111 dns__catz_timer_cb(void *);
    112 static void
    113 dns__catz_timer_start(dns_catz_zone_t *catz);
    114 static void
    115 dns__catz_timer_stop(void *arg);
    116 
    117 static void
    118 dns__catz_update_cb(void *data);
    119 static void
    120 dns__catz_done_cb(void *data);
    121 
    122 static isc_result_t
    123 catz_process_zones_entry(dns_catz_zone_t *catz, dns_rdataset_t *value,
    124 			 dns_label_t *mhash);
    125 static isc_result_t
    126 catz_process_zones_suboption(dns_catz_zone_t *catz, dns_rdataset_t *value,
    127 			     dns_label_t *mhash, dns_name_t *name);
    128 static void
    129 catz_entry_add_or_mod(dns_catz_zone_t *catz, isc_ht_t *ht, unsigned char *key,
    130 		      size_t keysize, dns_catz_entry_t *nentry,
    131 		      dns_catz_entry_t *oentry, const char *msg,
    132 		      const char *zname, const char *czname);
    133 
    134 /*%
    135  * Collection of catalog zones for a view
    136  */
    137 struct dns_catz_zones {
    138 	unsigned int magic;
    139 	isc_ht_t *zones;
    140 	isc_mem_t *mctx;
    141 	isc_refcount_t references;
    142 	isc_mutex_t lock;
    143 	dns_catz_zonemodmethods_t *zmm;
    144 	isc_loopmgr_t *loopmgr;
    145 	dns_view_t *view;
    146 	atomic_bool shuttingdown;
    147 };
    148 
    149 void
    150 dns_catz_options_init(dns_catz_options_t *options) {
    151 	REQUIRE(options != NULL);
    152 
    153 	dns_ipkeylist_init(&options->masters);
    154 
    155 	options->allow_query = NULL;
    156 	options->allow_transfer = NULL;
    157 
    158 	options->allow_query = NULL;
    159 	options->allow_transfer = NULL;
    160 
    161 	options->in_memory = false;
    162 	options->min_update_interval = 5;
    163 	options->zonedir = NULL;
    164 }
    165 
    166 void
    167 dns_catz_options_free(dns_catz_options_t *options, isc_mem_t *mctx) {
    168 	REQUIRE(options != NULL);
    169 	REQUIRE(mctx != NULL);
    170 
    171 	if (options->masters.count != 0) {
    172 		dns_ipkeylist_clear(mctx, &options->masters);
    173 	}
    174 	if (options->zonedir != NULL) {
    175 		isc_mem_free(mctx, options->zonedir);
    176 		options->zonedir = NULL;
    177 	}
    178 	if (options->allow_query != NULL) {
    179 		isc_buffer_free(&options->allow_query);
    180 	}
    181 	if (options->allow_transfer != NULL) {
    182 		isc_buffer_free(&options->allow_transfer);
    183 	}
    184 }
    185 
    186 void
    187 dns_catz_options_copy(isc_mem_t *mctx, const dns_catz_options_t *src,
    188 		      dns_catz_options_t *dst) {
    189 	REQUIRE(mctx != NULL);
    190 	REQUIRE(src != NULL);
    191 	REQUIRE(dst != NULL);
    192 	REQUIRE(dst->masters.count == 0);
    193 	REQUIRE(dst->allow_query == NULL);
    194 	REQUIRE(dst->allow_transfer == NULL);
    195 
    196 	if (src->masters.count != 0) {
    197 		dns_ipkeylist_copy(mctx, &src->masters, &dst->masters);
    198 	}
    199 
    200 	if (dst->zonedir != NULL) {
    201 		isc_mem_free(mctx, dst->zonedir);
    202 		dst->zonedir = NULL;
    203 	}
    204 
    205 	if (src->zonedir != NULL) {
    206 		dst->zonedir = isc_mem_strdup(mctx, src->zonedir);
    207 	}
    208 
    209 	if (src->allow_query != NULL) {
    210 		isc_buffer_dup(mctx, &dst->allow_query, src->allow_query);
    211 	}
    212 
    213 	if (src->allow_transfer != NULL) {
    214 		isc_buffer_dup(mctx, &dst->allow_transfer, src->allow_transfer);
    215 	}
    216 }
    217 
    218 void
    219 dns_catz_options_setdefault(isc_mem_t *mctx, const dns_catz_options_t *defaults,
    220 			    dns_catz_options_t *opts) {
    221 	REQUIRE(mctx != NULL);
    222 	REQUIRE(defaults != NULL);
    223 	REQUIRE(opts != NULL);
    224 
    225 	if (opts->masters.count == 0 && defaults->masters.count != 0) {
    226 		dns_ipkeylist_copy(mctx, &defaults->masters, &opts->masters);
    227 	}
    228 
    229 	if (defaults->zonedir != NULL) {
    230 		if (opts->zonedir != NULL) {
    231 			isc_mem_free(mctx, opts->zonedir);
    232 		}
    233 		opts->zonedir = isc_mem_strdup(mctx, defaults->zonedir);
    234 	}
    235 
    236 	if (opts->allow_query == NULL && defaults->allow_query != NULL) {
    237 		isc_buffer_dup(mctx, &opts->allow_query, defaults->allow_query);
    238 	}
    239 	if (opts->allow_transfer == NULL && defaults->allow_transfer != NULL) {
    240 		isc_buffer_dup(mctx, &opts->allow_transfer,
    241 			       defaults->allow_transfer);
    242 	}
    243 
    244 	/* This option is always taken from config, so it's always 'default' */
    245 	opts->in_memory = defaults->in_memory;
    246 }
    247 
    248 static dns_catz_coo_t *
    249 catz_coo_new(isc_mem_t *mctx, const dns_name_t *domain) {
    250 	REQUIRE(mctx != NULL);
    251 	REQUIRE(domain != NULL);
    252 
    253 	dns_catz_coo_t *ncoo = isc_mem_get(mctx, sizeof(*ncoo));
    254 	*ncoo = (dns_catz_coo_t){
    255 		.magic = DNS_CATZ_COO_MAGIC,
    256 	};
    257 	dns_name_init(&ncoo->name, NULL);
    258 	dns_name_dup(domain, mctx, &ncoo->name);
    259 	isc_refcount_init(&ncoo->references, 1);
    260 
    261 	return ncoo;
    262 }
    263 
    264 static void
    265 catz_coo_detach(dns_catz_zone_t *catz, dns_catz_coo_t **coop) {
    266 	dns_catz_coo_t *coo;
    267 
    268 	REQUIRE(DNS_CATZ_ZONE_VALID(catz));
    269 	REQUIRE(coop != NULL && DNS_CATZ_COO_VALID(*coop));
    270 	coo = *coop;
    271 	*coop = NULL;
    272 
    273 	if (isc_refcount_decrement(&coo->references) == 1) {
    274 		isc_mem_t *mctx = catz->catzs->mctx;
    275 		coo->magic = 0;
    276 		isc_refcount_destroy(&coo->references);
    277 		if (dns_name_dynamic(&coo->name)) {
    278 			dns_name_free(&coo->name, mctx);
    279 		}
    280 		isc_mem_put(mctx, coo, sizeof(*coo));
    281 	}
    282 }
    283 
    284 static void
    285 catz_coo_add(dns_catz_zone_t *catz, dns_catz_entry_t *entry,
    286 	     const dns_name_t *domain) {
    287 	REQUIRE(DNS_CATZ_ZONE_VALID(catz));
    288 	REQUIRE(DNS_CATZ_ENTRY_VALID(entry));
    289 	REQUIRE(domain != NULL);
    290 
    291 	/* We are write locked, so the add must succeed if not found */
    292 	dns_catz_coo_t *coo = NULL;
    293 	isc_result_t result = isc_ht_find(catz->coos, entry->name.ndata,
    294 					  entry->name.length, (void **)&coo);
    295 	if (result != ISC_R_SUCCESS) {
    296 		coo = catz_coo_new(catz->catzs->mctx, domain);
    297 		result = isc_ht_add(catz->coos, entry->name.ndata,
    298 				    entry->name.length, coo);
    299 	}
    300 	INSIST(result == ISC_R_SUCCESS);
    301 }
    302 
    303 dns_catz_entry_t *
    304 dns_catz_entry_new(isc_mem_t *mctx, const dns_name_t *domain) {
    305 	REQUIRE(mctx != NULL);
    306 
    307 	dns_catz_entry_t *nentry = isc_mem_get(mctx, sizeof(*nentry));
    308 	*nentry = (dns_catz_entry_t){
    309 		.magic = DNS_CATZ_ENTRY_MAGIC,
    310 	};
    311 
    312 	dns_name_init(&nentry->name, NULL);
    313 	if (domain != NULL) {
    314 		dns_name_dup(domain, mctx, &nentry->name);
    315 	}
    316 
    317 	dns_catz_options_init(&nentry->opts);
    318 	isc_refcount_init(&nentry->references, 1);
    319 
    320 	return nentry;
    321 }
    322 
    323 dns_name_t *
    324 dns_catz_entry_getname(dns_catz_entry_t *entry) {
    325 	REQUIRE(DNS_CATZ_ENTRY_VALID(entry));
    326 	return &entry->name;
    327 }
    328 
    329 dns_catz_entry_t *
    330 dns_catz_entry_copy(dns_catz_zone_t *catz, const dns_catz_entry_t *entry) {
    331 	REQUIRE(DNS_CATZ_ZONE_VALID(catz));
    332 	REQUIRE(DNS_CATZ_ENTRY_VALID(entry));
    333 
    334 	dns_catz_entry_t *nentry = dns_catz_entry_new(catz->catzs->mctx,
    335 						      &entry->name);
    336 
    337 	dns_catz_options_copy(catz->catzs->mctx, &entry->opts, &nentry->opts);
    338 
    339 	return nentry;
    340 }
    341 
    342 void
    343 dns_catz_entry_attach(dns_catz_entry_t *entry, dns_catz_entry_t **entryp) {
    344 	REQUIRE(DNS_CATZ_ENTRY_VALID(entry));
    345 	REQUIRE(entryp != NULL && *entryp == NULL);
    346 
    347 	isc_refcount_increment(&entry->references);
    348 	*entryp = entry;
    349 }
    350 
    351 void
    352 dns_catz_entry_detach(dns_catz_zone_t *catz, dns_catz_entry_t **entryp) {
    353 	dns_catz_entry_t *entry;
    354 
    355 	REQUIRE(DNS_CATZ_ZONE_VALID(catz));
    356 	REQUIRE(entryp != NULL && DNS_CATZ_ENTRY_VALID(*entryp));
    357 	entry = *entryp;
    358 	*entryp = NULL;
    359 
    360 	if (isc_refcount_decrement(&entry->references) == 1) {
    361 		isc_mem_t *mctx = catz->catzs->mctx;
    362 		entry->magic = 0;
    363 		isc_refcount_destroy(&entry->references);
    364 		dns_catz_options_free(&entry->opts, mctx);
    365 		if (dns_name_dynamic(&entry->name)) {
    366 			dns_name_free(&entry->name, mctx);
    367 		}
    368 		isc_mem_put(mctx, entry, sizeof(*entry));
    369 	}
    370 }
    371 
    372 bool
    373 dns_catz_entry_validate(const dns_catz_entry_t *entry) {
    374 	REQUIRE(DNS_CATZ_ENTRY_VALID(entry));
    375 	UNUSED(entry);
    376 
    377 	return true;
    378 }
    379 
    380 bool
    381 dns_catz_entry_cmp(const dns_catz_entry_t *ea, const dns_catz_entry_t *eb) {
    382 	isc_region_t ra, rb;
    383 
    384 	REQUIRE(DNS_CATZ_ENTRY_VALID(ea));
    385 	REQUIRE(DNS_CATZ_ENTRY_VALID(eb));
    386 
    387 	if (ea == eb) {
    388 		return true;
    389 	}
    390 
    391 	if (ea->opts.masters.count != eb->opts.masters.count) {
    392 		return false;
    393 	}
    394 
    395 	if (memcmp(ea->opts.masters.addrs, eb->opts.masters.addrs,
    396 		   ea->opts.masters.count * sizeof(isc_sockaddr_t)))
    397 	{
    398 		return false;
    399 	}
    400 
    401 	for (size_t i = 0; i < eb->opts.masters.count; i++) {
    402 		if ((ea->opts.masters.keys[i] == NULL) !=
    403 		    (eb->opts.masters.keys[i] == NULL))
    404 		{
    405 			return false;
    406 		}
    407 		if (ea->opts.masters.keys[i] == NULL) {
    408 			continue;
    409 		}
    410 		if (!dns_name_equal(ea->opts.masters.keys[i],
    411 				    eb->opts.masters.keys[i]))
    412 		{
    413 			return false;
    414 		}
    415 	}
    416 
    417 	for (size_t i = 0; i < eb->opts.masters.count; i++) {
    418 		if ((ea->opts.masters.tlss[i] == NULL) !=
    419 		    (eb->opts.masters.tlss[i] == NULL))
    420 		{
    421 			return false;
    422 		}
    423 		if (ea->opts.masters.tlss[i] == NULL) {
    424 			continue;
    425 		}
    426 		if (!dns_name_equal(ea->opts.masters.tlss[i],
    427 				    eb->opts.masters.tlss[i]))
    428 		{
    429 			return false;
    430 		}
    431 	}
    432 
    433 	/* If one is NULL and the other isn't, the entries don't match */
    434 	if ((ea->opts.allow_query == NULL) != (eb->opts.allow_query == NULL)) {
    435 		return false;
    436 	}
    437 
    438 	/* If one is non-NULL, then they both are */
    439 	if (ea->opts.allow_query != NULL) {
    440 		isc_buffer_usedregion(ea->opts.allow_query, &ra);
    441 		isc_buffer_usedregion(eb->opts.allow_query, &rb);
    442 		if (isc_region_compare(&ra, &rb)) {
    443 			return false;
    444 		}
    445 	}
    446 
    447 	/* Repeat the above checks with allow_transfer */
    448 	if ((ea->opts.allow_transfer == NULL) !=
    449 	    (eb->opts.allow_transfer == NULL))
    450 	{
    451 		return false;
    452 	}
    453 
    454 	if (ea->opts.allow_transfer != NULL) {
    455 		isc_buffer_usedregion(ea->opts.allow_transfer, &ra);
    456 		isc_buffer_usedregion(eb->opts.allow_transfer, &rb);
    457 		if (isc_region_compare(&ra, &rb)) {
    458 			return false;
    459 		}
    460 	}
    461 
    462 	return true;
    463 }
    464 
    465 dns_name_t *
    466 dns_catz_zone_getname(dns_catz_zone_t *catz) {
    467 	REQUIRE(DNS_CATZ_ZONE_VALID(catz));
    468 
    469 	return &catz->name;
    470 }
    471 
    472 dns_catz_options_t *
    473 dns_catz_zone_getdefoptions(dns_catz_zone_t *catz) {
    474 	REQUIRE(DNS_CATZ_ZONE_VALID(catz));
    475 
    476 	return &catz->defoptions;
    477 }
    478 
    479 void
    480 dns_catz_zone_resetdefoptions(dns_catz_zone_t *catz) {
    481 	REQUIRE(DNS_CATZ_ZONE_VALID(catz));
    482 
    483 	dns_catz_options_free(&catz->defoptions, catz->catzs->mctx);
    484 	dns_catz_options_init(&catz->defoptions);
    485 }
    486 
    487 /*%<
    488  * Merge 'newcatz' into 'catz', calling addzone/delzone/modzone
    489  * (from catz->catzs->zmm) for appropriate member zones.
    490  *
    491  * Requires:
    492  * \li	'catz' is a valid dns_catz_zone_t.
    493  * \li	'newcatz' is a valid dns_catz_zone_t.
    494  *
    495  */
    496 static isc_result_t
    497 dns__catz_zones_merge(dns_catz_zone_t *catz, dns_catz_zone_t *newcatz) {
    498 	isc_result_t result;
    499 	isc_ht_iter_t *iter1 = NULL, *iter2 = NULL;
    500 	isc_ht_iter_t *iteradd = NULL, *itermod = NULL;
    501 	isc_ht_t *toadd = NULL, *tomod = NULL;
    502 	bool delcur = false;
    503 	char czname[DNS_NAME_FORMATSIZE];
    504 	char zname[DNS_NAME_FORMATSIZE];
    505 	dns_catz_zoneop_fn_t addzone, modzone, delzone;
    506 
    507 	REQUIRE(DNS_CATZ_ZONE_VALID(catz));
    508 	REQUIRE(DNS_CATZ_ZONE_VALID(newcatz));
    509 
    510 	LOCK(&catz->lock);
    511 
    512 	/* TODO verify the new zone first! */
    513 
    514 	addzone = catz->catzs->zmm->addzone;
    515 	modzone = catz->catzs->zmm->modzone;
    516 	delzone = catz->catzs->zmm->delzone;
    517 
    518 	/* Copy zoneoptions from newcatz into catz. */
    519 
    520 	dns_catz_options_free(&catz->zoneoptions, catz->catzs->mctx);
    521 	dns_catz_options_copy(catz->catzs->mctx, &newcatz->zoneoptions,
    522 			      &catz->zoneoptions);
    523 	dns_catz_options_setdefault(catz->catzs->mctx, &catz->defoptions,
    524 				    &catz->zoneoptions);
    525 
    526 	dns_name_format(&catz->name, czname, DNS_NAME_FORMATSIZE);
    527 
    528 	isc_ht_init(&toadd, catz->catzs->mctx, 1, ISC_HT_CASE_INSENSITIVE);
    529 	isc_ht_init(&tomod, catz->catzs->mctx, 1, ISC_HT_CASE_INSENSITIVE);
    530 	isc_ht_iter_create(newcatz->entries, &iter1);
    531 	isc_ht_iter_create(catz->entries, &iter2);
    532 
    533 	/*
    534 	 * We can create those iterators now, even though toadd and tomod are
    535 	 * empty
    536 	 */
    537 	isc_ht_iter_create(toadd, &iteradd);
    538 	isc_ht_iter_create(tomod, &itermod);
    539 
    540 	/*
    541 	 * First - walk the new zone and find all nodes that are not in the
    542 	 * old zone, or are in both zones and are modified.
    543 	 */
    544 	for (result = isc_ht_iter_first(iter1); result == ISC_R_SUCCESS;
    545 	     result = delcur ? isc_ht_iter_delcurrent_next(iter1)
    546 			     : isc_ht_iter_next(iter1))
    547 	{
    548 		isc_result_t find_result;
    549 		dns_catz_zone_t *parentcatz = NULL;
    550 		dns_catz_entry_t *nentry = NULL;
    551 		dns_catz_entry_t *oentry = NULL;
    552 		dns_zone_t *zone = NULL;
    553 		unsigned char *key = NULL;
    554 		size_t keysize;
    555 		delcur = false;
    556 
    557 		isc_ht_iter_current(iter1, (void **)&nentry);
    558 		isc_ht_iter_currentkey(iter1, &key, &keysize);
    559 
    560 		/*
    561 		 * Spurious record that came from suboption without main
    562 		 * record, removed.
    563 		 * xxxwpk: make it a separate verification phase?
    564 		 */
    565 		if (dns_name_countlabels(&nentry->name) == 0) {
    566 			dns_catz_entry_detach(newcatz, &nentry);
    567 			delcur = true;
    568 			continue;
    569 		}
    570 
    571 		dns_name_format(&nentry->name, zname, DNS_NAME_FORMATSIZE);
    572 
    573 		isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
    574 			      DNS_LOGMODULE_MASTER, ISC_LOG_DEBUG(3),
    575 			      "catz: iterating over '%s' from catalog '%s'",
    576 			      zname, czname);
    577 		dns_catz_options_setdefault(catz->catzs->mctx,
    578 					    &catz->zoneoptions, &nentry->opts);
    579 
    580 		/* Try to find the zone in the view */
    581 		find_result = dns_view_findzone(catz->catzs->view,
    582 						dns_catz_entry_getname(nentry),
    583 						DNS_ZTFIND_EXACT, &zone);
    584 		if (find_result == ISC_R_SUCCESS) {
    585 			dns_catz_coo_t *coo = NULL;
    586 			char pczname[DNS_NAME_FORMATSIZE];
    587 			bool parentcatz_locked = false;
    588 
    589 			/*
    590 			 * Change of ownership (coo) processing, if required
    591 			 */
    592 			parentcatz = dns_zone_get_parentcatz(zone);
    593 			if (parentcatz != NULL && parentcatz != catz) {
    594 				UNLOCK(&catz->lock);
    595 				LOCK(&parentcatz->lock);
    596 				parentcatz_locked = true;
    597 			}
    598 			if (parentcatz_locked &&
    599 			    isc_ht_find(parentcatz->coos, nentry->name.ndata,
    600 					nentry->name.length,
    601 					(void **)&coo) == ISC_R_SUCCESS &&
    602 			    dns_name_equal(&coo->name, &catz->name))
    603 			{
    604 				dns_name_format(&parentcatz->name, pczname,
    605 						DNS_NAME_FORMATSIZE);
    606 				isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
    607 					      DNS_LOGMODULE_MASTER,
    608 					      ISC_LOG_DEBUG(3),
    609 					      "catz: zone '%s' "
    610 					      "change of ownership from "
    611 					      "'%s' to '%s'",
    612 					      zname, pczname, czname);
    613 				result = delzone(nentry, parentcatz,
    614 						 parentcatz->catzs->view,
    615 						 parentcatz->catzs->zmm->udata);
    616 				isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
    617 					      DNS_LOGMODULE_MASTER,
    618 					      ISC_LOG_INFO,
    619 					      "catz: deleting zone '%s' "
    620 					      "from catalog '%s' - %s",
    621 					      zname, pczname,
    622 					      isc_result_totext(result));
    623 			}
    624 			if (parentcatz_locked) {
    625 				UNLOCK(&parentcatz->lock);
    626 				LOCK(&catz->lock);
    627 			}
    628 			dns_zone_detach(&zone);
    629 		}
    630 
    631 		/* Try to find the zone in the old catalog zone */
    632 		result = isc_ht_find(catz->entries, key, (uint32_t)keysize,
    633 				     (void **)&oentry);
    634 		if (result != ISC_R_SUCCESS) {
    635 			if (find_result == ISC_R_SUCCESS && parentcatz == catz)
    636 			{
    637 				/*
    638 				 * This means that the zone's unique label
    639 				 * has been changed, in that case we must
    640 				 * reset the zone's internal state by removing
    641 				 * and re-adding it.
    642 				 *
    643 				 * Scheduling the addition now, the removal will
    644 				 * be scheduled below, when walking the old
    645 				 * zone for remaining entries, and then we will
    646 				 * perform deletions earlier than additions and
    647 				 * modifications.
    648 				 */
    649 				isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
    650 					      DNS_LOGMODULE_MASTER,
    651 					      ISC_LOG_INFO,
    652 					      "catz: zone '%s' unique label "
    653 					      "has changed, reset state",
    654 					      zname);
    655 			}
    656 
    657 			catz_entry_add_or_mod(catz, toadd, key, keysize, nentry,
    658 					      NULL, "adding", zname, czname);
    659 			continue;
    660 		}
    661 
    662 		if (find_result != ISC_R_SUCCESS) {
    663 			isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
    664 				      DNS_LOGMODULE_MASTER, ISC_LOG_DEBUG(3),
    665 				      "catz: zone '%s' was expected to exist "
    666 				      "but can not be found, will be restored",
    667 				      zname);
    668 			catz_entry_add_or_mod(catz, toadd, key, keysize, nentry,
    669 					      oentry, "adding", zname, czname);
    670 			continue;
    671 		}
    672 
    673 		if (dns_catz_entry_cmp(oentry, nentry) != true) {
    674 			catz_entry_add_or_mod(catz, tomod, key, keysize, nentry,
    675 					      oentry, "modifying", zname,
    676 					      czname);
    677 			continue;
    678 		}
    679 
    680 		/*
    681 		 * Delete the old entry so that it won't accidentally be
    682 		 * removed as a non-existing entry below.
    683 		 */
    684 		dns_catz_entry_detach(catz, &oentry);
    685 		result = isc_ht_delete(catz->entries, key, (uint32_t)keysize);
    686 		RUNTIME_CHECK(result == ISC_R_SUCCESS);
    687 	}
    688 	RUNTIME_CHECK(result == ISC_R_NOMORE);
    689 	isc_ht_iter_destroy(&iter1);
    690 
    691 	/*
    692 	 * Then - walk the old zone; only deleted entries should remain.
    693 	 */
    694 	for (result = isc_ht_iter_first(iter2); result == ISC_R_SUCCESS;
    695 	     result = isc_ht_iter_delcurrent_next(iter2))
    696 	{
    697 		dns_catz_entry_t *entry = NULL;
    698 		isc_ht_iter_current(iter2, (void **)&entry);
    699 
    700 		dns_name_format(&entry->name, zname, DNS_NAME_FORMATSIZE);
    701 		result = delzone(entry, catz, catz->catzs->view,
    702 				 catz->catzs->zmm->udata);
    703 		isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
    704 			      DNS_LOGMODULE_MASTER, ISC_LOG_INFO,
    705 			      "catz: deleting zone '%s' from catalog '%s' - %s",
    706 			      zname, czname, isc_result_totext(result));
    707 		dns_catz_entry_detach(catz, &entry);
    708 	}
    709 	RUNTIME_CHECK(result == ISC_R_NOMORE);
    710 	isc_ht_iter_destroy(&iter2);
    711 	/* At this moment catz->entries has to be empty. */
    712 	INSIST(isc_ht_count(catz->entries) == 0);
    713 	isc_ht_destroy(&catz->entries);
    714 
    715 	for (result = isc_ht_iter_first(iteradd); result == ISC_R_SUCCESS;
    716 	     result = isc_ht_iter_delcurrent_next(iteradd))
    717 	{
    718 		dns_catz_entry_t *entry = NULL;
    719 		isc_ht_iter_current(iteradd, (void **)&entry);
    720 
    721 		dns_name_format(&entry->name, zname, DNS_NAME_FORMATSIZE);
    722 		result = addzone(entry, catz, catz->catzs->view,
    723 				 catz->catzs->zmm->udata);
    724 		isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
    725 			      DNS_LOGMODULE_MASTER, ISC_LOG_INFO,
    726 			      "catz: adding zone '%s' from catalog "
    727 			      "'%s' - %s",
    728 			      zname, czname, isc_result_totext(result));
    729 	}
    730 
    731 	for (result = isc_ht_iter_first(itermod); result == ISC_R_SUCCESS;
    732 	     result = isc_ht_iter_delcurrent_next(itermod))
    733 	{
    734 		dns_catz_entry_t *entry = NULL;
    735 		isc_ht_iter_current(itermod, (void **)&entry);
    736 
    737 		dns_name_format(&entry->name, zname, DNS_NAME_FORMATSIZE);
    738 		result = modzone(entry, catz, catz->catzs->view,
    739 				 catz->catzs->zmm->udata);
    740 		isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
    741 			      DNS_LOGMODULE_MASTER, ISC_LOG_INFO,
    742 			      "catz: modifying zone '%s' from catalog "
    743 			      "'%s' - %s",
    744 			      zname, czname, isc_result_totext(result));
    745 	}
    746 
    747 	catz->entries = newcatz->entries;
    748 	newcatz->entries = NULL;
    749 
    750 	/*
    751 	 * We do not need to merge old coo (change of ownership) permission
    752 	 * records with the new ones, just replace them.
    753 	 */
    754 	if (catz->coos != NULL && newcatz->coos != NULL) {
    755 		isc_ht_iter_t *iter = NULL;
    756 
    757 		isc_ht_iter_create(catz->coos, &iter);
    758 		for (result = isc_ht_iter_first(iter); result == ISC_R_SUCCESS;
    759 		     result = isc_ht_iter_delcurrent_next(iter))
    760 		{
    761 			dns_catz_coo_t *coo = NULL;
    762 
    763 			isc_ht_iter_current(iter, (void **)&coo);
    764 			catz_coo_detach(catz, &coo);
    765 		}
    766 		INSIST(result == ISC_R_NOMORE);
    767 		isc_ht_iter_destroy(&iter);
    768 
    769 		/* The hashtable has to be empty now. */
    770 		INSIST(isc_ht_count(catz->coos) == 0);
    771 		isc_ht_destroy(&catz->coos);
    772 
    773 		catz->coos = newcatz->coos;
    774 		newcatz->coos = NULL;
    775 	}
    776 
    777 	result = ISC_R_SUCCESS;
    778 
    779 	isc_ht_iter_destroy(&iteradd);
    780 	isc_ht_iter_destroy(&itermod);
    781 	isc_ht_destroy(&toadd);
    782 	isc_ht_destroy(&tomod);
    783 
    784 	UNLOCK(&catz->lock);
    785 
    786 	return result;
    787 }
    788 
    789 dns_catz_zones_t *
    790 dns_catz_zones_new(isc_mem_t *mctx, isc_loopmgr_t *loopmgr,
    791 		   dns_catz_zonemodmethods_t *zmm) {
    792 	REQUIRE(mctx != NULL);
    793 	REQUIRE(loopmgr != NULL);
    794 	REQUIRE(zmm != NULL);
    795 
    796 	dns_catz_zones_t *catzs = isc_mem_get(mctx, sizeof(*catzs));
    797 	*catzs = (dns_catz_zones_t){ .loopmgr = loopmgr,
    798 				     .zmm = zmm,
    799 				     .magic = DNS_CATZ_ZONES_MAGIC };
    800 
    801 	isc_mutex_init(&catzs->lock);
    802 	isc_refcount_init(&catzs->references, 1);
    803 	isc_ht_init(&catzs->zones, mctx, 4, ISC_HT_CASE_INSENSITIVE);
    804 	isc_mem_attach(mctx, &catzs->mctx);
    805 
    806 	return catzs;
    807 }
    808 
    809 void *
    810 dns_catz_zones_get_udata(dns_catz_zones_t *catzs) {
    811 	REQUIRE(DNS_CATZ_ZONES_VALID(catzs));
    812 
    813 	return catzs->zmm->udata;
    814 }
    815 
    816 void
    817 dns_catz_catzs_set_view(dns_catz_zones_t *catzs, dns_view_t *view) {
    818 	REQUIRE(DNS_CATZ_ZONES_VALID(catzs));
    819 	REQUIRE(DNS_VIEW_VALID(view));
    820 	/* Either it's a new one or it's being reconfigured. */
    821 	REQUIRE(catzs->view == NULL || !strcmp(catzs->view->name, view->name));
    822 
    823 	if (catzs->view == NULL) {
    824 		dns_view_weakattach(view, &catzs->view);
    825 	} else if (catzs->view != view) {
    826 		dns_view_weakdetach(&catzs->view);
    827 		dns_view_weakattach(view, &catzs->view);
    828 	}
    829 }
    830 
    831 dns_catz_zone_t *
    832 dns_catz_zone_new(dns_catz_zones_t *catzs, const dns_name_t *name) {
    833 	REQUIRE(DNS_CATZ_ZONES_VALID(catzs));
    834 	REQUIRE(ISC_MAGIC_VALID(name, DNS_NAME_MAGIC));
    835 
    836 	dns_catz_zone_t *catz = isc_mem_get(catzs->mctx, sizeof(*catz));
    837 	*catz = (dns_catz_zone_t){ .active = true,
    838 				   .version = DNS_CATZ_VERSION_UNDEFINED,
    839 				   .magic = DNS_CATZ_ZONE_MAGIC };
    840 
    841 	dns_catz_zones_attach(catzs, &catz->catzs);
    842 	isc_mutex_init(&catz->lock);
    843 	isc_refcount_init(&catz->references, 1);
    844 	isc_ht_init(&catz->entries, catzs->mctx, 4, ISC_HT_CASE_INSENSITIVE);
    845 	isc_ht_init(&catz->coos, catzs->mctx, 4, ISC_HT_CASE_INSENSITIVE);
    846 	isc_time_settoepoch(&catz->lastupdated);
    847 	dns_catz_options_init(&catz->defoptions);
    848 	dns_catz_options_init(&catz->zoneoptions);
    849 	dns_name_init(&catz->name, NULL);
    850 	dns_name_dup(name, catzs->mctx, &catz->name);
    851 
    852 	return catz;
    853 }
    854 
    855 static void
    856 dns__catz_timer_start(dns_catz_zone_t *catz) {
    857 	uint64_t tdiff;
    858 	isc_interval_t interval;
    859 	isc_time_t now;
    860 
    861 	REQUIRE(DNS_CATZ_ZONE_VALID(catz));
    862 
    863 	now = isc_time_now();
    864 	tdiff = isc_time_microdiff(&now, &catz->lastupdated) / 1000000;
    865 	if (tdiff < catz->defoptions.min_update_interval) {
    866 		uint64_t defer = catz->defoptions.min_update_interval - tdiff;
    867 		char dname[DNS_NAME_FORMATSIZE];
    868 
    869 		dns_name_format(&catz->name, dname, DNS_NAME_FORMATSIZE);
    870 		isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
    871 			      DNS_LOGMODULE_MASTER, ISC_LOG_INFO,
    872 			      "catz: %s: new zone version came "
    873 			      "too soon, deferring update for "
    874 			      "%" PRIu64 " seconds",
    875 			      dname, defer);
    876 		isc_interval_set(&interval, (unsigned int)defer, 0);
    877 	} else {
    878 		isc_interval_set(&interval, 0, 0);
    879 	}
    880 
    881 	catz->loop = isc_loop();
    882 
    883 	isc_timer_create(catz->loop, dns__catz_timer_cb, catz,
    884 			 &catz->updatetimer);
    885 	isc_timer_start(catz->updatetimer, isc_timertype_once, &interval);
    886 }
    887 
    888 static void
    889 dns__catz_timer_stop(void *arg) {
    890 	dns_catz_zone_t *catz = arg;
    891 	REQUIRE(DNS_CATZ_ZONE_VALID(catz));
    892 
    893 	isc_timer_stop(catz->updatetimer);
    894 	isc_timer_destroy(&catz->updatetimer);
    895 	catz->loop = NULL;
    896 
    897 	dns_catz_zone_detach(&catz);
    898 }
    899 
    900 isc_result_t
    901 dns_catz_zone_add(dns_catz_zones_t *catzs, const dns_name_t *name,
    902 		  dns_catz_zone_t **catzp) {
    903 	REQUIRE(DNS_CATZ_ZONES_VALID(catzs));
    904 	REQUIRE(ISC_MAGIC_VALID(name, DNS_NAME_MAGIC));
    905 	REQUIRE(catzp != NULL && *catzp == NULL);
    906 
    907 	dns_catz_zone_t *catz = NULL;
    908 	isc_result_t result;
    909 	char zname[DNS_NAME_FORMATSIZE];
    910 
    911 	dns_name_format(name, zname, DNS_NAME_FORMATSIZE);
    912 	isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, DNS_LOGMODULE_MASTER,
    913 		      ISC_LOG_DEBUG(3), "catz: dns_catz_zone_add %s", zname);
    914 
    915 	LOCK(&catzs->lock);
    916 
    917 	/*
    918 	 * This function is called only during a (re)configuration, while
    919 	 * 'catzs->zones' can become NULL only during shutdown.
    920 	 */
    921 	INSIST(catzs->zones != NULL);
    922 	INSIST(!atomic_load(&catzs->shuttingdown));
    923 
    924 	result = isc_ht_find(catzs->zones, name->ndata, name->length,
    925 			     (void **)&catz);
    926 	switch (result) {
    927 	case ISC_R_SUCCESS:
    928 		INSIST(!catz->active);
    929 		catz->active = true;
    930 		result = ISC_R_EXISTS;
    931 		break;
    932 	case ISC_R_NOTFOUND:
    933 		catz = dns_catz_zone_new(catzs, name);
    934 
    935 		result = isc_ht_add(catzs->zones, catz->name.ndata,
    936 				    catz->name.length, catz);
    937 		INSIST(result == ISC_R_SUCCESS);
    938 		break;
    939 	default:
    940 		UNREACHABLE();
    941 	}
    942 
    943 	UNLOCK(&catzs->lock);
    944 
    945 	*catzp = catz;
    946 
    947 	return result;
    948 }
    949 
    950 dns_catz_zone_t *
    951 dns_catz_zone_get(dns_catz_zones_t *catzs, const dns_name_t *name) {
    952 	isc_result_t result;
    953 	dns_catz_zone_t *found = NULL;
    954 
    955 	REQUIRE(DNS_CATZ_ZONES_VALID(catzs));
    956 	REQUIRE(ISC_MAGIC_VALID(name, DNS_NAME_MAGIC));
    957 
    958 	LOCK(&catzs->lock);
    959 	if (catzs->zones == NULL) {
    960 		UNLOCK(&catzs->lock);
    961 		return NULL;
    962 	}
    963 	result = isc_ht_find(catzs->zones, name->ndata, name->length,
    964 			     (void **)&found);
    965 	UNLOCK(&catzs->lock);
    966 	if (result != ISC_R_SUCCESS) {
    967 		return NULL;
    968 	}
    969 
    970 	return found;
    971 }
    972 
    973 static void
    974 dns__catz_zone_shutdown(dns_catz_zone_t *catz) {
    975 	/* lock must be locked */
    976 	if (catz->updatetimer != NULL) {
    977 		/* Don't wait for timer to trigger for shutdown */
    978 		INSIST(catz->loop != NULL);
    979 
    980 		isc_async_run(catz->loop, dns__catz_timer_stop, catz);
    981 	} else {
    982 		dns_catz_zone_detach(&catz);
    983 	}
    984 }
    985 
    986 static void
    987 dns__catz_zone_destroy(dns_catz_zone_t *catz) {
    988 	isc_mem_t *mctx = catz->catzs->mctx;
    989 
    990 	if (catz->entries != NULL) {
    991 		isc_ht_iter_t *iter = NULL;
    992 		isc_result_t result;
    993 		isc_ht_iter_create(catz->entries, &iter);
    994 		for (result = isc_ht_iter_first(iter); result == ISC_R_SUCCESS;
    995 		     result = isc_ht_iter_delcurrent_next(iter))
    996 		{
    997 			dns_catz_entry_t *entry = NULL;
    998 
    999 			isc_ht_iter_current(iter, (void **)&entry);
   1000 			dns_catz_entry_detach(catz, &entry);
   1001 		}
   1002 		INSIST(result == ISC_R_NOMORE);
   1003 		isc_ht_iter_destroy(&iter);
   1004 
   1005 		/* The hashtable has to be empty now. */
   1006 		INSIST(isc_ht_count(catz->entries) == 0);
   1007 		isc_ht_destroy(&catz->entries);
   1008 	}
   1009 	if (catz->coos != NULL) {
   1010 		isc_ht_iter_t *iter = NULL;
   1011 		isc_result_t result;
   1012 		isc_ht_iter_create(catz->coos, &iter);
   1013 		for (result = isc_ht_iter_first(iter); result == ISC_R_SUCCESS;
   1014 		     result = isc_ht_iter_delcurrent_next(iter))
   1015 		{
   1016 			dns_catz_coo_t *coo = NULL;
   1017 
   1018 			isc_ht_iter_current(iter, (void **)&coo);
   1019 			catz_coo_detach(catz, &coo);
   1020 		}
   1021 		INSIST(result == ISC_R_NOMORE);
   1022 		isc_ht_iter_destroy(&iter);
   1023 
   1024 		/* The hashtable has to be empty now. */
   1025 		INSIST(isc_ht_count(catz->coos) == 0);
   1026 		isc_ht_destroy(&catz->coos);
   1027 	}
   1028 	catz->magic = 0;
   1029 	isc_mutex_destroy(&catz->lock);
   1030 
   1031 	if (catz->updatetimer != NULL) {
   1032 		isc_timer_async_destroy(&catz->updatetimer);
   1033 	}
   1034 
   1035 	if (catz->db != NULL) {
   1036 		if (catz->dbversion != NULL) {
   1037 			dns_db_closeversion(catz->db, &catz->dbversion, false);
   1038 		}
   1039 		dns_db_updatenotify_unregister(
   1040 			catz->db, dns_catz_dbupdate_callback, catz->catzs);
   1041 		dns_db_detach(&catz->db);
   1042 	}
   1043 
   1044 	INSIST(!catz->updaterunning);
   1045 
   1046 	dns_name_free(&catz->name, mctx);
   1047 	dns_catz_options_free(&catz->defoptions, mctx);
   1048 	dns_catz_options_free(&catz->zoneoptions, mctx);
   1049 
   1050 	dns_catz_zones_detach(&catz->catzs);
   1051 
   1052 	isc_mem_put(mctx, catz, sizeof(*catz));
   1053 }
   1054 
   1055 static void
   1056 dns__catz_zones_destroy(dns_catz_zones_t *catzs) {
   1057 	REQUIRE(atomic_load(&catzs->shuttingdown));
   1058 	REQUIRE(catzs->zones == NULL);
   1059 
   1060 	catzs->magic = 0;
   1061 	isc_mutex_destroy(&catzs->lock);
   1062 	if (catzs->view != NULL) {
   1063 		dns_view_weakdetach(&catzs->view);
   1064 	}
   1065 	isc_mem_putanddetach(&catzs->mctx, catzs, sizeof(*catzs));
   1066 }
   1067 
   1068 void
   1069 dns_catz_zones_shutdown(dns_catz_zones_t *catzs) {
   1070 	REQUIRE(DNS_CATZ_ZONES_VALID(catzs));
   1071 
   1072 	if (!atomic_compare_exchange_strong(&catzs->shuttingdown,
   1073 					    &(bool){ false }, true))
   1074 	{
   1075 		return;
   1076 	}
   1077 
   1078 	LOCK(&catzs->lock);
   1079 	if (catzs->zones != NULL) {
   1080 		isc_ht_iter_t *iter = NULL;
   1081 		isc_result_t result;
   1082 		isc_ht_iter_create(catzs->zones, &iter);
   1083 		for (result = isc_ht_iter_first(iter); result == ISC_R_SUCCESS;)
   1084 		{
   1085 			dns_catz_zone_t *catz = NULL;
   1086 			isc_ht_iter_current(iter, (void **)&catz);
   1087 			result = isc_ht_iter_delcurrent_next(iter);
   1088 			dns__catz_zone_shutdown(catz);
   1089 		}
   1090 		INSIST(result == ISC_R_NOMORE);
   1091 		isc_ht_iter_destroy(&iter);
   1092 		INSIST(isc_ht_count(catzs->zones) == 0);
   1093 		isc_ht_destroy(&catzs->zones);
   1094 	}
   1095 	UNLOCK(&catzs->lock);
   1096 }
   1097 
   1098 #ifdef DNS_CATZ_TRACE
   1099 ISC_REFCOUNT_TRACE_IMPL(dns_catz_zone, dns__catz_zone_destroy);
   1100 ISC_REFCOUNT_TRACE_IMPL(dns_catz_zones, dns__catz_zones_destroy);
   1101 #else
   1102 ISC_REFCOUNT_IMPL(dns_catz_zone, dns__catz_zone_destroy);
   1103 ISC_REFCOUNT_IMPL(dns_catz_zones, dns__catz_zones_destroy);
   1104 #endif
   1105 
   1106 typedef enum {
   1107 	CATZ_OPT_NONE,
   1108 	CATZ_OPT_ZONES,
   1109 	CATZ_OPT_COO,
   1110 	CATZ_OPT_VERSION,
   1111 	CATZ_OPT_CUSTOM_START, /* CATZ custom properties must go below this */
   1112 	CATZ_OPT_EXT,
   1113 	CATZ_OPT_PRIMARIES,
   1114 	CATZ_OPT_ALLOW_QUERY,
   1115 	CATZ_OPT_ALLOW_TRANSFER,
   1116 } catz_opt_t;
   1117 
   1118 static bool
   1119 catz_opt_cmp(const dns_label_t *option, const char *opt) {
   1120 	size_t len = strlen(opt);
   1121 
   1122 	if (option->length - 1 == len &&
   1123 	    memcmp(opt, option->base + 1, len) == 0)
   1124 	{
   1125 		return true;
   1126 	} else {
   1127 		return false;
   1128 	}
   1129 }
   1130 
   1131 static catz_opt_t
   1132 catz_get_option(const dns_label_t *option) {
   1133 	if (catz_opt_cmp(option, "ext")) {
   1134 		return CATZ_OPT_EXT;
   1135 	} else if (catz_opt_cmp(option, "zones")) {
   1136 		return CATZ_OPT_ZONES;
   1137 	} else if (catz_opt_cmp(option, "masters") ||
   1138 		   catz_opt_cmp(option, "primaries"))
   1139 	{
   1140 		return CATZ_OPT_PRIMARIES;
   1141 	} else if (catz_opt_cmp(option, "allow-query")) {
   1142 		return CATZ_OPT_ALLOW_QUERY;
   1143 	} else if (catz_opt_cmp(option, "allow-transfer")) {
   1144 		return CATZ_OPT_ALLOW_TRANSFER;
   1145 	} else if (catz_opt_cmp(option, "coo")) {
   1146 		return CATZ_OPT_COO;
   1147 	} else if (catz_opt_cmp(option, "version")) {
   1148 		return CATZ_OPT_VERSION;
   1149 	} else {
   1150 		return CATZ_OPT_NONE;
   1151 	}
   1152 }
   1153 
   1154 static isc_result_t
   1155 catz_process_zones(dns_catz_zone_t *catz, dns_rdataset_t *value,
   1156 		   dns_name_t *name) {
   1157 	dns_label_t mhash;
   1158 	dns_name_t opt;
   1159 
   1160 	REQUIRE(DNS_CATZ_ZONE_VALID(catz));
   1161 	REQUIRE(DNS_RDATASET_VALID(value));
   1162 	REQUIRE(ISC_MAGIC_VALID(name, DNS_NAME_MAGIC));
   1163 
   1164 	if (name->labels == 0) {
   1165 		return ISC_R_FAILURE;
   1166 	}
   1167 
   1168 	dns_name_getlabel(name, name->labels - 1, &mhash);
   1169 
   1170 	if (name->labels == 1) {
   1171 		return catz_process_zones_entry(catz, value, &mhash);
   1172 	} else {
   1173 		dns_name_init(&opt, NULL);
   1174 		dns_name_split(name, 1, &opt, NULL);
   1175 		return catz_process_zones_suboption(catz, value, &mhash, &opt);
   1176 	}
   1177 }
   1178 
   1179 static isc_result_t
   1180 catz_process_coo(dns_catz_zone_t *catz, dns_label_t *mhash,
   1181 		 dns_rdataset_t *value) {
   1182 	isc_result_t result;
   1183 	dns_rdata_t rdata;
   1184 	dns_rdata_ptr_t ptr;
   1185 	dns_catz_entry_t *entry = NULL;
   1186 
   1187 	REQUIRE(DNS_CATZ_ZONE_VALID(catz));
   1188 	REQUIRE(mhash != NULL);
   1189 	REQUIRE(DNS_RDATASET_VALID(value));
   1190 
   1191 	/* Change of Ownership was introduced in version "2" of the schema. */
   1192 	if (catz->version < 2) {
   1193 		return ISC_R_FAILURE;
   1194 	}
   1195 
   1196 	if (value->type != dns_rdatatype_ptr) {
   1197 		return ISC_R_FAILURE;
   1198 	}
   1199 
   1200 	if (dns_rdataset_count(value) != 1) {
   1201 		isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
   1202 			      DNS_LOGMODULE_MASTER, ISC_LOG_WARNING,
   1203 			      "catz: 'coo' property PTR RRset contains "
   1204 			      "more than one record, which is invalid");
   1205 		catz->broken = true;
   1206 		return ISC_R_FAILURE;
   1207 	}
   1208 
   1209 	result = dns_rdataset_first(value);
   1210 	if (result != ISC_R_SUCCESS) {
   1211 		return result;
   1212 	}
   1213 
   1214 	dns_rdata_init(&rdata);
   1215 	dns_rdataset_current(value, &rdata);
   1216 
   1217 	result = dns_rdata_tostruct(&rdata, &ptr, NULL);
   1218 	if (result != ISC_R_SUCCESS) {
   1219 		return result;
   1220 	}
   1221 
   1222 	if (dns_name_countlabels(&ptr.ptr) == 0) {
   1223 		result = ISC_R_FAILURE;
   1224 		goto cleanup;
   1225 	}
   1226 
   1227 	result = isc_ht_find(catz->entries, mhash->base, mhash->length,
   1228 			     (void **)&entry);
   1229 	if (result != ISC_R_SUCCESS) {
   1230 		/* The entry was not found .*/
   1231 		goto cleanup;
   1232 	}
   1233 
   1234 	if (dns_name_countlabels(&entry->name) == 0) {
   1235 		result = ISC_R_FAILURE;
   1236 		goto cleanup;
   1237 	}
   1238 
   1239 	catz_coo_add(catz, entry, &ptr.ptr);
   1240 
   1241 cleanup:
   1242 	dns_rdata_freestruct(&ptr);
   1243 
   1244 	return result;
   1245 }
   1246 
   1247 static isc_result_t
   1248 catz_process_zones_entry(dns_catz_zone_t *catz, dns_rdataset_t *value,
   1249 			 dns_label_t *mhash) {
   1250 	isc_result_t result;
   1251 	dns_rdata_t rdata;
   1252 	dns_rdata_ptr_t ptr;
   1253 	dns_catz_entry_t *entry = NULL;
   1254 
   1255 	if (value->type != dns_rdatatype_ptr) {
   1256 		return ISC_R_FAILURE;
   1257 	}
   1258 
   1259 	if (dns_rdataset_count(value) != 1) {
   1260 		isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
   1261 			      DNS_LOGMODULE_MASTER, ISC_LOG_WARNING,
   1262 			      "catz: member zone PTR RRset contains "
   1263 			      "more than one record, which is invalid");
   1264 		catz->broken = true;
   1265 		return ISC_R_FAILURE;
   1266 	}
   1267 
   1268 	result = dns_rdataset_first(value);
   1269 	if (result != ISC_R_SUCCESS) {
   1270 		return result;
   1271 	}
   1272 
   1273 	dns_rdata_init(&rdata);
   1274 	dns_rdataset_current(value, &rdata);
   1275 
   1276 	result = dns_rdata_tostruct(&rdata, &ptr, NULL);
   1277 	if (result != ISC_R_SUCCESS) {
   1278 		return result;
   1279 	}
   1280 
   1281 	result = isc_ht_find(catz->entries, mhash->base, mhash->length,
   1282 			     (void **)&entry);
   1283 	if (result == ISC_R_SUCCESS) {
   1284 		if (dns_name_countlabels(&entry->name) != 0) {
   1285 			/* We have a duplicate. */
   1286 			dns_rdata_freestruct(&ptr);
   1287 			return ISC_R_FAILURE;
   1288 		} else {
   1289 			dns_name_dup(&ptr.ptr, catz->catzs->mctx, &entry->name);
   1290 		}
   1291 	} else {
   1292 		entry = dns_catz_entry_new(catz->catzs->mctx, &ptr.ptr);
   1293 
   1294 		result = isc_ht_add(catz->entries, mhash->base, mhash->length,
   1295 				    entry);
   1296 	}
   1297 	INSIST(result == ISC_R_SUCCESS);
   1298 
   1299 	dns_rdata_freestruct(&ptr);
   1300 
   1301 	return ISC_R_SUCCESS;
   1302 }
   1303 
   1304 static isc_result_t
   1305 catz_process_version(dns_catz_zone_t *catz, dns_rdataset_t *value) {
   1306 	isc_result_t result;
   1307 	dns_rdata_t rdata;
   1308 	dns_rdata_txt_t rdatatxt;
   1309 	dns_rdata_txt_string_t rdatastr;
   1310 	uint32_t tversion;
   1311 	char t[16];
   1312 
   1313 	REQUIRE(DNS_CATZ_ZONE_VALID(catz));
   1314 	REQUIRE(DNS_RDATASET_VALID(value));
   1315 
   1316 	if (value->type != dns_rdatatype_txt) {
   1317 		return ISC_R_FAILURE;
   1318 	}
   1319 
   1320 	if (dns_rdataset_count(value) != 1) {
   1321 		isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
   1322 			      DNS_LOGMODULE_MASTER, ISC_LOG_WARNING,
   1323 			      "catz: 'version' property TXT RRset contains "
   1324 			      "more than one record, which is invalid");
   1325 		catz->broken = true;
   1326 		return ISC_R_FAILURE;
   1327 	}
   1328 
   1329 	result = dns_rdataset_first(value);
   1330 	if (result != ISC_R_SUCCESS) {
   1331 		return result;
   1332 	}
   1333 
   1334 	dns_rdata_init(&rdata);
   1335 	dns_rdataset_current(value, &rdata);
   1336 
   1337 	result = dns_rdata_tostruct(&rdata, &rdatatxt, NULL);
   1338 	if (result != ISC_R_SUCCESS) {
   1339 		return result;
   1340 	}
   1341 
   1342 	result = dns_rdata_txt_first(&rdatatxt);
   1343 	if (result != ISC_R_SUCCESS) {
   1344 		goto cleanup;
   1345 	}
   1346 
   1347 	result = dns_rdata_txt_current(&rdatatxt, &rdatastr);
   1348 	if (result != ISC_R_SUCCESS) {
   1349 		goto cleanup;
   1350 	}
   1351 
   1352 	result = dns_rdata_txt_next(&rdatatxt);
   1353 	if (result != ISC_R_NOMORE) {
   1354 		result = ISC_R_FAILURE;
   1355 		goto cleanup;
   1356 	}
   1357 	if (rdatastr.length > 15) {
   1358 		result = ISC_R_BADNUMBER;
   1359 		goto cleanup;
   1360 	}
   1361 	memmove(t, rdatastr.data, rdatastr.length);
   1362 	t[rdatastr.length] = 0;
   1363 	result = isc_parse_uint32(&tversion, t, 10);
   1364 	if (result != ISC_R_SUCCESS) {
   1365 		goto cleanup;
   1366 	}
   1367 	catz->version = tversion;
   1368 	result = ISC_R_SUCCESS;
   1369 
   1370 cleanup:
   1371 	dns_rdata_freestruct(&rdatatxt);
   1372 	if (result != ISC_R_SUCCESS) {
   1373 		isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
   1374 			      DNS_LOGMODULE_MASTER, ISC_LOG_WARNING,
   1375 			      "catz: invalid record for the catalog "
   1376 			      "zone version property");
   1377 		catz->broken = true;
   1378 	}
   1379 	return result;
   1380 }
   1381 
   1382 static isc_result_t
   1383 catz_process_primaries(dns_catz_zone_t *catz, dns_ipkeylist_t *ipkl,
   1384 		       dns_rdataset_t *value, dns_name_t *name) {
   1385 	isc_result_t result;
   1386 	dns_rdata_t rdata;
   1387 	dns_rdata_in_a_t rdata_a;
   1388 	dns_rdata_in_aaaa_t rdata_aaaa;
   1389 	dns_rdata_txt_t rdata_txt;
   1390 	dns_rdata_txt_string_t rdatastr;
   1391 	dns_name_t *keyname = NULL;
   1392 	isc_mem_t *mctx;
   1393 	char keycbuf[DNS_NAME_FORMATSIZE];
   1394 	isc_buffer_t keybuf;
   1395 	unsigned int rcount;
   1396 
   1397 	REQUIRE(DNS_CATZ_ZONE_VALID(catz));
   1398 	REQUIRE(ipkl != NULL);
   1399 	REQUIRE(DNS_RDATASET_VALID(value));
   1400 	REQUIRE(dns_rdataset_isassociated(value));
   1401 	REQUIRE(ISC_MAGIC_VALID(name, DNS_NAME_MAGIC));
   1402 
   1403 	mctx = catz->catzs->mctx;
   1404 	memset(&rdata_a, 0, sizeof(rdata_a));
   1405 	memset(&rdata_aaaa, 0, sizeof(rdata_aaaa));
   1406 	memset(&rdata_txt, 0, sizeof(rdata_txt));
   1407 	isc_buffer_init(&keybuf, keycbuf, sizeof(keycbuf));
   1408 
   1409 	/*
   1410 	 * We have three possibilities here:
   1411 	 * - either empty name and IN A/IN AAAA record
   1412 	 * - label and IN A/IN AAAA
   1413 	 * - label and IN TXT - TSIG key name
   1414 	 */
   1415 	if (name->labels > 0) {
   1416 		isc_sockaddr_t sockaddr;
   1417 		size_t i;
   1418 
   1419 		/*
   1420 		 * We're pre-preparing the data once, we'll put it into
   1421 		 * the right spot in the primaries array once we find it.
   1422 		 */
   1423 		result = dns_rdataset_first(value);
   1424 		RUNTIME_CHECK(result == ISC_R_SUCCESS);
   1425 		dns_rdata_init(&rdata);
   1426 		dns_rdataset_current(value, &rdata);
   1427 		switch (value->type) {
   1428 		case dns_rdatatype_a:
   1429 			result = dns_rdata_tostruct(&rdata, &rdata_a, NULL);
   1430 			RUNTIME_CHECK(result == ISC_R_SUCCESS);
   1431 			isc_sockaddr_fromin(&sockaddr, &rdata_a.in_addr, 0);
   1432 			dns_rdata_freestruct(&rdata_a);
   1433 			break;
   1434 		case dns_rdatatype_aaaa:
   1435 			result = dns_rdata_tostruct(&rdata, &rdata_aaaa, NULL);
   1436 			RUNTIME_CHECK(result == ISC_R_SUCCESS);
   1437 			isc_sockaddr_fromin6(&sockaddr, &rdata_aaaa.in6_addr,
   1438 					     0);
   1439 			dns_rdata_freestruct(&rdata_aaaa);
   1440 			break;
   1441 		case dns_rdatatype_txt:
   1442 			result = dns_rdata_tostruct(&rdata, &rdata_txt, NULL);
   1443 			RUNTIME_CHECK(result == ISC_R_SUCCESS);
   1444 
   1445 			result = dns_rdata_txt_first(&rdata_txt);
   1446 			if (result != ISC_R_SUCCESS) {
   1447 				dns_rdata_freestruct(&rdata_txt);
   1448 				return result;
   1449 			}
   1450 
   1451 			result = dns_rdata_txt_current(&rdata_txt, &rdatastr);
   1452 			if (result != ISC_R_SUCCESS) {
   1453 				dns_rdata_freestruct(&rdata_txt);
   1454 				return result;
   1455 			}
   1456 
   1457 			result = dns_rdata_txt_next(&rdata_txt);
   1458 			if (result != ISC_R_NOMORE) {
   1459 				dns_rdata_freestruct(&rdata_txt);
   1460 				return ISC_R_FAILURE;
   1461 			}
   1462 
   1463 			/* rdatastr.length < DNS_NAME_MAXTEXT */
   1464 			keyname = isc_mem_get(mctx, sizeof(*keyname));
   1465 			dns_name_init(keyname, 0);
   1466 			memmove(keycbuf, rdatastr.data, rdatastr.length);
   1467 			keycbuf[rdatastr.length] = 0;
   1468 			dns_rdata_freestruct(&rdata_txt);
   1469 			result = dns_name_fromstring(keyname, keycbuf,
   1470 						     dns_rootname, 0, mctx);
   1471 			if (result != ISC_R_SUCCESS) {
   1472 				isc_mem_put(mctx, keyname, sizeof(*keyname));
   1473 				return result;
   1474 			}
   1475 			break;
   1476 		default:
   1477 			return ISC_R_FAILURE;
   1478 		}
   1479 
   1480 		/*
   1481 		 * We have to find the appropriate labeled record in
   1482 		 * primaries if it exists.  In the common case we'll
   1483 		 * have no more than 3-4 records here, so no optimization.
   1484 		 */
   1485 		for (i = 0; i < ipkl->count; i++) {
   1486 			if (ipkl->labels[i] != NULL &&
   1487 			    !dns_name_compare(name, ipkl->labels[i]))
   1488 			{
   1489 				break;
   1490 			}
   1491 		}
   1492 
   1493 		if (i < ipkl->count) { /* we have this record already */
   1494 			if (value->type == dns_rdatatype_txt) {
   1495 				if (ipkl->keys[i] != NULL) {
   1496 					if (dns_name_dynamic(ipkl->keys[i])) {
   1497 						dns_name_free(ipkl->keys[i],
   1498 							      mctx);
   1499 					}
   1500 					isc_mem_put(mctx, ipkl->keys[i],
   1501 						    sizeof(*ipkl->keys[i]));
   1502 				}
   1503 				ipkl->keys[i] = keyname;
   1504 			} else { /* A/AAAA */
   1505 				memmove(&ipkl->addrs[i], &sockaddr,
   1506 					sizeof(sockaddr));
   1507 			}
   1508 		} else {
   1509 			result = dns_ipkeylist_resize(mctx, ipkl, i + 1);
   1510 			if (result != ISC_R_SUCCESS) {
   1511 				return result;
   1512 			}
   1513 
   1514 			ipkl->labels[i] = isc_mem_get(mctx,
   1515 						      sizeof(*ipkl->labels[0]));
   1516 			dns_name_init(ipkl->labels[i], NULL);
   1517 			dns_name_dup(name, mctx, ipkl->labels[i]);
   1518 
   1519 			if (value->type == dns_rdatatype_txt) {
   1520 				ipkl->keys[i] = keyname;
   1521 			} else { /* A/AAAA */
   1522 				memmove(&ipkl->addrs[i], &sockaddr,
   1523 					sizeof(sockaddr));
   1524 			}
   1525 			ipkl->count++;
   1526 		}
   1527 		return ISC_R_SUCCESS;
   1528 	}
   1529 	/* else - 'simple' case - without labels */
   1530 
   1531 	if (value->type != dns_rdatatype_a && value->type != dns_rdatatype_aaaa)
   1532 	{
   1533 		return ISC_R_FAILURE;
   1534 	}
   1535 
   1536 	rcount = dns_rdataset_count(value) + ipkl->count;
   1537 
   1538 	result = dns_ipkeylist_resize(mctx, ipkl, rcount);
   1539 	if (result != ISC_R_SUCCESS) {
   1540 		return result;
   1541 	}
   1542 
   1543 	for (result = dns_rdataset_first(value); result == ISC_R_SUCCESS;
   1544 	     result = dns_rdataset_next(value))
   1545 	{
   1546 		dns_rdata_init(&rdata);
   1547 		dns_rdataset_current(value, &rdata);
   1548 		/*
   1549 		 * port 0 == take the default
   1550 		 */
   1551 		if (value->type == dns_rdatatype_a) {
   1552 			result = dns_rdata_tostruct(&rdata, &rdata_a, NULL);
   1553 			RUNTIME_CHECK(result == ISC_R_SUCCESS);
   1554 			isc_sockaddr_fromin(&ipkl->addrs[ipkl->count],
   1555 					    &rdata_a.in_addr, 0);
   1556 			dns_rdata_freestruct(&rdata_a);
   1557 		} else {
   1558 			result = dns_rdata_tostruct(&rdata, &rdata_aaaa, NULL);
   1559 			RUNTIME_CHECK(result == ISC_R_SUCCESS);
   1560 			isc_sockaddr_fromin6(&ipkl->addrs[ipkl->count],
   1561 					     &rdata_aaaa.in6_addr, 0);
   1562 			dns_rdata_freestruct(&rdata_aaaa);
   1563 		}
   1564 		ipkl->keys[ipkl->count] = NULL;
   1565 		ipkl->labels[ipkl->count] = NULL;
   1566 		ipkl->count++;
   1567 	}
   1568 	return ISC_R_SUCCESS;
   1569 }
   1570 
   1571 static isc_result_t
   1572 catz_process_apl(dns_catz_zone_t *catz, isc_buffer_t **aclbp,
   1573 		 dns_rdataset_t *value) {
   1574 	REQUIRE(DNS_RDATASET_VALID(value));
   1575 	REQUIRE(dns_rdataset_isassociated(value));
   1576 
   1577 	if (value->type != dns_rdatatype_apl) {
   1578 		return ISC_R_FAILURE;
   1579 	}
   1580 
   1581 	REQUIRE(DNS_CATZ_ZONE_VALID(catz));
   1582 	REQUIRE(aclbp != NULL);
   1583 	REQUIRE(*aclbp == NULL);
   1584 
   1585 	isc_result_t result = ISC_R_SUCCESS;
   1586 	dns_rdata_t rdata;
   1587 	dns_rdata_in_apl_t rdata_apl;
   1588 	dns_rdata_apl_ent_t apl_ent;
   1589 	isc_netaddr_t addr;
   1590 	isc_buffer_t *aclb = NULL;
   1591 	unsigned char buf[256]; /* larger than INET6_ADDRSTRLEN */
   1592 
   1593 	if (dns_rdataset_count(value) > 1) {
   1594 		isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
   1595 			      DNS_LOGMODULE_MASTER, ISC_LOG_WARNING,
   1596 			      "catz: more than one APL entry for member zone, "
   1597 			      "result is undefined");
   1598 	}
   1599 	result = dns_rdataset_first(value);
   1600 	RUNTIME_CHECK(result == ISC_R_SUCCESS);
   1601 	dns_rdata_init(&rdata);
   1602 	dns_rdataset_current(value, &rdata);
   1603 	result = dns_rdata_tostruct(&rdata, &rdata_apl, catz->catzs->mctx);
   1604 	if (result != ISC_R_SUCCESS) {
   1605 		return result;
   1606 	}
   1607 	isc_buffer_allocate(catz->catzs->mctx, &aclb, 16);
   1608 	for (result = dns_rdata_apl_first(&rdata_apl); result == ISC_R_SUCCESS;
   1609 	     result = dns_rdata_apl_next(&rdata_apl))
   1610 	{
   1611 		result = dns_rdata_apl_current(&rdata_apl, &apl_ent);
   1612 		RUNTIME_CHECK(result == ISC_R_SUCCESS);
   1613 		memset(buf, 0, sizeof(buf));
   1614 		if (apl_ent.data != NULL && apl_ent.length > 0) {
   1615 			memmove(buf, apl_ent.data, apl_ent.length);
   1616 		}
   1617 		if (apl_ent.family == 1) {
   1618 			isc_netaddr_fromin(&addr, (struct in_addr *)buf);
   1619 		} else if (apl_ent.family == 2) {
   1620 			isc_netaddr_fromin6(&addr, (struct in6_addr *)buf);
   1621 		} else {
   1622 			continue; /* xxxwpk log it or simply ignore? */
   1623 		}
   1624 		if (apl_ent.negative) {
   1625 			isc_buffer_putuint8(aclb, '!');
   1626 		}
   1627 		isc_buffer_reserve(aclb, INET6_ADDRSTRLEN);
   1628 		result = isc_netaddr_totext(&addr, aclb);
   1629 		RUNTIME_CHECK(result == ISC_R_SUCCESS);
   1630 		if ((apl_ent.family == 1 && apl_ent.prefix < 32) ||
   1631 		    (apl_ent.family == 2 && apl_ent.prefix < 128))
   1632 		{
   1633 			isc_buffer_putuint8(aclb, '/');
   1634 			isc_buffer_printf(aclb, "%" PRId8, apl_ent.prefix);
   1635 		}
   1636 		isc_buffer_putstr(aclb, "; ");
   1637 	}
   1638 	if (result == ISC_R_NOMORE) {
   1639 		result = ISC_R_SUCCESS;
   1640 	} else {
   1641 		goto cleanup;
   1642 	}
   1643 	*aclbp = aclb;
   1644 	aclb = NULL;
   1645 cleanup:
   1646 	if (aclb != NULL) {
   1647 		isc_buffer_free(&aclb);
   1648 	}
   1649 	dns_rdata_freestruct(&rdata_apl);
   1650 	return result;
   1651 }
   1652 
   1653 static isc_result_t
   1654 catz_process_zones_suboption(dns_catz_zone_t *catz, dns_rdataset_t *value,
   1655 			     dns_label_t *mhash, dns_name_t *name) {
   1656 	isc_result_t result;
   1657 	dns_catz_entry_t *entry = NULL;
   1658 	dns_label_t option;
   1659 	dns_name_t prefix;
   1660 	catz_opt_t opt;
   1661 	unsigned int suffix_labels = 1;
   1662 
   1663 	REQUIRE(DNS_CATZ_ZONE_VALID(catz));
   1664 	REQUIRE(mhash != NULL);
   1665 	REQUIRE(DNS_RDATASET_VALID(value));
   1666 	REQUIRE(ISC_MAGIC_VALID(name, DNS_NAME_MAGIC));
   1667 
   1668 	if (name->labels < 1) {
   1669 		return ISC_R_FAILURE;
   1670 	}
   1671 	dns_name_getlabel(name, name->labels - 1, &option);
   1672 	opt = catz_get_option(&option);
   1673 
   1674 	/*
   1675 	 * The custom properties in version 2 schema must be placed under the
   1676 	 * "ext" label.
   1677 	 */
   1678 	if (catz->version >= 2 && opt >= CATZ_OPT_CUSTOM_START) {
   1679 		if (opt != CATZ_OPT_EXT || name->labels < 2) {
   1680 			return ISC_R_FAILURE;
   1681 		}
   1682 		suffix_labels++;
   1683 		dns_name_getlabel(name, name->labels - 2, &option);
   1684 		opt = catz_get_option(&option);
   1685 	}
   1686 
   1687 	/*
   1688 	 * We're adding this entry now, in case the option is invalid we'll get
   1689 	 * rid of it in verification phase.
   1690 	 */
   1691 	result = isc_ht_find(catz->entries, mhash->base, mhash->length,
   1692 			     (void **)&entry);
   1693 	if (result != ISC_R_SUCCESS) {
   1694 		entry = dns_catz_entry_new(catz->catzs->mctx, NULL);
   1695 		result = isc_ht_add(catz->entries, mhash->base, mhash->length,
   1696 				    entry);
   1697 	}
   1698 	INSIST(result == ISC_R_SUCCESS);
   1699 
   1700 	dns_name_init(&prefix, NULL);
   1701 	dns_name_split(name, suffix_labels, &prefix, NULL);
   1702 	switch (opt) {
   1703 	case CATZ_OPT_COO:
   1704 		return catz_process_coo(catz, mhash, value);
   1705 	case CATZ_OPT_PRIMARIES:
   1706 		return catz_process_primaries(catz, &entry->opts.masters, value,
   1707 					      &prefix);
   1708 	case CATZ_OPT_ALLOW_QUERY:
   1709 		if (prefix.labels != 0) {
   1710 			return ISC_R_FAILURE;
   1711 		}
   1712 		return catz_process_apl(catz, &entry->opts.allow_query, value);
   1713 	case CATZ_OPT_ALLOW_TRANSFER:
   1714 		if (prefix.labels != 0) {
   1715 			return ISC_R_FAILURE;
   1716 		}
   1717 		return catz_process_apl(catz, &entry->opts.allow_transfer,
   1718 					value);
   1719 	default:
   1720 		return ISC_R_FAILURE;
   1721 	}
   1722 
   1723 	return ISC_R_FAILURE;
   1724 }
   1725 
   1726 static void
   1727 catz_entry_add_or_mod(dns_catz_zone_t *catz, isc_ht_t *ht, unsigned char *key,
   1728 		      size_t keysize, dns_catz_entry_t *nentry,
   1729 		      dns_catz_entry_t *oentry, const char *msg,
   1730 		      const char *zname, const char *czname) {
   1731 	isc_result_t result = isc_ht_add(ht, key, (uint32_t)keysize, nentry);
   1732 
   1733 	if (result != ISC_R_SUCCESS) {
   1734 		isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
   1735 			      DNS_LOGMODULE_MASTER, ISC_LOG_ERROR,
   1736 			      "catz: error %s zone '%s' from catalog '%s' - %s",
   1737 			      msg, zname, czname, isc_result_totext(result));
   1738 	}
   1739 	if (oentry != NULL) {
   1740 		dns_catz_entry_detach(catz, &oentry);
   1741 		result = isc_ht_delete(catz->entries, key, (uint32_t)keysize);
   1742 		RUNTIME_CHECK(result == ISC_R_SUCCESS);
   1743 	}
   1744 }
   1745 
   1746 static isc_result_t
   1747 catz_process_value(dns_catz_zone_t *catz, dns_name_t *name,
   1748 		   dns_rdataset_t *rdataset) {
   1749 	dns_label_t option;
   1750 	dns_name_t prefix;
   1751 	catz_opt_t opt;
   1752 	unsigned int suffix_labels = 1;
   1753 
   1754 	REQUIRE(DNS_CATZ_ZONE_VALID(catz));
   1755 	REQUIRE(ISC_MAGIC_VALID(name, DNS_NAME_MAGIC));
   1756 	REQUIRE(DNS_RDATASET_VALID(rdataset));
   1757 
   1758 	if (name->labels < 1) {
   1759 		return ISC_R_FAILURE;
   1760 	}
   1761 	dns_name_getlabel(name, name->labels - 1, &option);
   1762 	opt = catz_get_option(&option);
   1763 
   1764 	/*
   1765 	 * The custom properties in version 2 schema must be placed under the
   1766 	 * "ext" label.
   1767 	 */
   1768 	if (catz->version >= 2 && opt >= CATZ_OPT_CUSTOM_START) {
   1769 		if (opt != CATZ_OPT_EXT || name->labels < 2) {
   1770 			return ISC_R_FAILURE;
   1771 		}
   1772 		suffix_labels++;
   1773 		dns_name_getlabel(name, name->labels - 2, &option);
   1774 		opt = catz_get_option(&option);
   1775 	}
   1776 
   1777 	dns_name_init(&prefix, NULL);
   1778 	dns_name_split(name, suffix_labels, &prefix, NULL);
   1779 
   1780 	switch (opt) {
   1781 	case CATZ_OPT_ZONES:
   1782 		return catz_process_zones(catz, rdataset, &prefix);
   1783 	case CATZ_OPT_PRIMARIES:
   1784 		return catz_process_primaries(catz, &catz->zoneoptions.masters,
   1785 					      rdataset, &prefix);
   1786 	case CATZ_OPT_ALLOW_QUERY:
   1787 		if (prefix.labels != 0) {
   1788 			return ISC_R_FAILURE;
   1789 		}
   1790 		return catz_process_apl(catz, &catz->zoneoptions.allow_query,
   1791 					rdataset);
   1792 	case CATZ_OPT_ALLOW_TRANSFER:
   1793 		if (prefix.labels != 0) {
   1794 			return ISC_R_FAILURE;
   1795 		}
   1796 		return catz_process_apl(catz, &catz->zoneoptions.allow_transfer,
   1797 					rdataset);
   1798 	case CATZ_OPT_VERSION:
   1799 		if (prefix.labels != 0) {
   1800 			return ISC_R_FAILURE;
   1801 		}
   1802 		return catz_process_version(catz, rdataset);
   1803 	default:
   1804 		return ISC_R_FAILURE;
   1805 	}
   1806 }
   1807 
   1808 /*%<
   1809  * Process a single rdataset from a catalog zone 'catz' update, src_name is the
   1810  * record name.
   1811  *
   1812  * Requires:
   1813  * \li	'catz' is a valid dns_catz_zone_t.
   1814  * \li	'src_name' is a valid dns_name_t.
   1815  * \li	'rdataset' is valid rdataset.
   1816  */
   1817 static isc_result_t
   1818 dns__catz_update_process(dns_catz_zone_t *catz, const dns_name_t *src_name,
   1819 			 dns_rdataset_t *rdataset) {
   1820 	isc_result_t result;
   1821 	int order;
   1822 	unsigned int nlabels;
   1823 	dns_namereln_t nrres;
   1824 	dns_rdata_t rdata = DNS_RDATA_INIT;
   1825 	dns_rdata_soa_t soa;
   1826 	dns_name_t prefix;
   1827 
   1828 	REQUIRE(DNS_CATZ_ZONE_VALID(catz));
   1829 	REQUIRE(ISC_MAGIC_VALID(src_name, DNS_NAME_MAGIC));
   1830 
   1831 	if (rdataset->rdclass != dns_rdataclass_in) {
   1832 		isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
   1833 			      DNS_LOGMODULE_MASTER, ISC_LOG_ERROR,
   1834 			      "catz: RR found which has a non-IN class");
   1835 		catz->broken = true;
   1836 		return ISC_R_FAILURE;
   1837 	}
   1838 
   1839 	nrres = dns_name_fullcompare(src_name, &catz->name, &order, &nlabels);
   1840 	if (nrres == dns_namereln_equal) {
   1841 		if (rdataset->type == dns_rdatatype_soa) {
   1842 			result = dns_rdataset_first(rdataset);
   1843 			if (result != ISC_R_SUCCESS) {
   1844 				return result;
   1845 			}
   1846 
   1847 			dns_rdataset_current(rdataset, &rdata);
   1848 			result = dns_rdata_tostruct(&rdata, &soa, NULL);
   1849 			RUNTIME_CHECK(result == ISC_R_SUCCESS);
   1850 
   1851 			/*
   1852 			 * xxxwpk TODO do we want to save something from SOA?
   1853 			 */
   1854 			dns_rdata_freestruct(&soa);
   1855 			return result;
   1856 		} else if (rdataset->type == dns_rdatatype_ns) {
   1857 			return ISC_R_SUCCESS;
   1858 		} else {
   1859 			return ISC_R_UNEXPECTED;
   1860 		}
   1861 	} else if (nrres != dns_namereln_subdomain) {
   1862 		return ISC_R_UNEXPECTED;
   1863 	}
   1864 
   1865 	dns_name_init(&prefix, NULL);
   1866 	dns_name_split(src_name, catz->name.labels, &prefix, NULL);
   1867 	result = catz_process_value(catz, &prefix, rdataset);
   1868 
   1869 	return result;
   1870 }
   1871 
   1872 static isc_result_t
   1873 digest2hex(unsigned char *digest, unsigned int digestlen, char *hash,
   1874 	   size_t hashlen) {
   1875 	unsigned int i;
   1876 	for (i = 0; i < digestlen; i++) {
   1877 		size_t left = hashlen - i * 2;
   1878 		int ret = snprintf(hash + i * 2, left, "%02x", digest[i]);
   1879 		if (ret < 0 || (size_t)ret >= left) {
   1880 			return ISC_R_NOSPACE;
   1881 		}
   1882 	}
   1883 	return ISC_R_SUCCESS;
   1884 }
   1885 
   1886 isc_result_t
   1887 dns_catz_generate_masterfilename(dns_catz_zone_t *catz, dns_catz_entry_t *entry,
   1888 				 isc_buffer_t **buffer) {
   1889 	isc_buffer_t *tbuf = NULL;
   1890 	isc_region_t r;
   1891 	isc_result_t result;
   1892 	size_t rlen;
   1893 	bool special = false;
   1894 
   1895 	REQUIRE(DNS_CATZ_ZONE_VALID(catz));
   1896 	REQUIRE(DNS_CATZ_ENTRY_VALID(entry));
   1897 	REQUIRE(buffer != NULL && *buffer != NULL);
   1898 
   1899 	isc_buffer_allocate(catz->catzs->mctx, &tbuf,
   1900 			    strlen(catz->catzs->view->name) +
   1901 				    2 * DNS_NAME_FORMATSIZE + 2);
   1902 
   1903 	isc_buffer_putstr(tbuf, catz->catzs->view->name);
   1904 	isc_buffer_putstr(tbuf, "_");
   1905 	result = dns_name_totext(&catz->name, DNS_NAME_OMITFINALDOT, tbuf);
   1906 	if (result != ISC_R_SUCCESS) {
   1907 		goto cleanup;
   1908 	}
   1909 
   1910 	isc_buffer_putstr(tbuf, "_");
   1911 	result = dns_name_totext(&entry->name, DNS_NAME_OMITFINALDOT, tbuf);
   1912 	if (result != ISC_R_SUCCESS) {
   1913 		goto cleanup;
   1914 	}
   1915 
   1916 	/*
   1917 	 * Search for slash and other special characters in the view and
   1918 	 * zone names.  Add a null terminator so we can use strpbrk(), then
   1919 	 * remove it.
   1920 	 */
   1921 	isc_buffer_putuint8(tbuf, 0);
   1922 	if (strpbrk(isc_buffer_base(tbuf), "\\/:") != NULL) {
   1923 		special = true;
   1924 	}
   1925 	isc_buffer_subtract(tbuf, 1);
   1926 
   1927 	/* __catz__<digest>.db */
   1928 	rlen = (isc_md_type_get_size(ISC_MD_SHA256) * 2 + 1) + 12;
   1929 
   1930 	/* optionally prepend with <zonedir>/ */
   1931 	if (entry->opts.zonedir != NULL) {
   1932 		rlen += strlen(entry->opts.zonedir) + 1;
   1933 	}
   1934 
   1935 	result = isc_buffer_reserve(*buffer, (unsigned int)rlen);
   1936 	if (result != ISC_R_SUCCESS) {
   1937 		goto cleanup;
   1938 	}
   1939 
   1940 	if (entry->opts.zonedir != NULL) {
   1941 		isc_buffer_putstr(*buffer, entry->opts.zonedir);
   1942 		isc_buffer_putstr(*buffer, "/");
   1943 	}
   1944 
   1945 	isc_buffer_usedregion(tbuf, &r);
   1946 	isc_buffer_putstr(*buffer, "__catz__");
   1947 	if (special || tbuf->used > ISC_SHA256_DIGESTLENGTH * 2 + 1) {
   1948 		unsigned char digest[ISC_MAX_MD_SIZE];
   1949 		unsigned int digestlen;
   1950 
   1951 		/* we can do that because digest string < 2 * DNS_NAME */
   1952 		result = isc_md(ISC_MD_SHA256, r.base, r.length, digest,
   1953 				&digestlen);
   1954 		if (result != ISC_R_SUCCESS) {
   1955 			goto cleanup;
   1956 		}
   1957 		result = digest2hex(digest, digestlen, (char *)r.base,
   1958 				    ISC_SHA256_DIGESTLENGTH * 2 + 1);
   1959 		if (result != ISC_R_SUCCESS) {
   1960 			goto cleanup;
   1961 		}
   1962 		isc_buffer_putstr(*buffer, (char *)r.base);
   1963 	} else {
   1964 		isc_buffer_copyregion(*buffer, &r);
   1965 	}
   1966 
   1967 	isc_buffer_putstr(*buffer, ".db");
   1968 	result = ISC_R_SUCCESS;
   1969 
   1970 cleanup:
   1971 	isc_buffer_free(&tbuf);
   1972 	return result;
   1973 }
   1974 
   1975 /*
   1976  * We have to generate a text buffer with regular zone config:
   1977  * zone "foo.bar" {
   1978  * 	type secondary;
   1979  * 	primaries { ip1 port port1; ip2 port port2; };
   1980  * }
   1981  */
   1982 isc_result_t
   1983 dns_catz_generate_zonecfg(dns_catz_zone_t *catz, dns_catz_entry_t *entry,
   1984 			  isc_buffer_t **buf) {
   1985 	isc_buffer_t *buffer = NULL;
   1986 	isc_region_t region;
   1987 	isc_result_t result;
   1988 	uint32_t i;
   1989 	isc_netaddr_t netaddr;
   1990 	char pbuf[sizeof("65535")]; /* used for port number */
   1991 	char namebuf[DNS_NAME_FORMATSIZE];
   1992 
   1993 	REQUIRE(DNS_CATZ_ZONE_VALID(catz));
   1994 	REQUIRE(DNS_CATZ_ENTRY_VALID(entry));
   1995 	REQUIRE(buf != NULL && *buf == NULL);
   1996 
   1997 	/*
   1998 	 * The buffer will be reallocated if something won't fit,
   1999 	 * ISC_BUFFER_INCR seems like a good start.
   2000 	 */
   2001 	isc_buffer_allocate(catz->catzs->mctx, &buffer, ISC_BUFFER_INCR);
   2002 
   2003 	isc_buffer_putstr(buffer, "zone \"");
   2004 	dns_name_format(&entry->name, namebuf, sizeof(namebuf));
   2005 	isc_buffer_putstr(buffer, namebuf);
   2006 	isc_buffer_putstr(buffer, "\" { type secondary; primaries");
   2007 
   2008 	isc_buffer_putstr(buffer, " { ");
   2009 	for (i = 0; i < entry->opts.masters.count; i++) {
   2010 		/*
   2011 		 * Every primary must have an IP address assigned.
   2012 		 */
   2013 		switch (entry->opts.masters.addrs[i].type.sa.sa_family) {
   2014 		case AF_INET:
   2015 		case AF_INET6:
   2016 			break;
   2017 		default:
   2018 			dns_name_format(&entry->name, namebuf, sizeof(namebuf));
   2019 			isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
   2020 				      DNS_LOGMODULE_MASTER, ISC_LOG_ERROR,
   2021 				      "catz: zone '%s' uses an invalid primary "
   2022 				      "(no IP address assigned)",
   2023 				      namebuf);
   2024 			result = ISC_R_FAILURE;
   2025 			goto cleanup;
   2026 		}
   2027 		isc_netaddr_fromsockaddr(&netaddr,
   2028 					 &entry->opts.masters.addrs[i]);
   2029 		isc_buffer_reserve(buffer, INET6_ADDRSTRLEN);
   2030 		result = isc_netaddr_totext(&netaddr, buffer);
   2031 		RUNTIME_CHECK(result == ISC_R_SUCCESS);
   2032 
   2033 		isc_buffer_putstr(buffer, " port ");
   2034 		snprintf(pbuf, sizeof(pbuf), "%u",
   2035 			 isc_sockaddr_getport(&entry->opts.masters.addrs[i]));
   2036 		isc_buffer_putstr(buffer, pbuf);
   2037 
   2038 		if (entry->opts.masters.keys[i] != NULL) {
   2039 			isc_buffer_putstr(buffer, " key ");
   2040 			dns_name_format(entry->opts.masters.keys[i], namebuf,
   2041 					sizeof(namebuf));
   2042 			isc_buffer_putstr(buffer, namebuf);
   2043 		}
   2044 
   2045 		if (entry->opts.masters.tlss[i] != NULL) {
   2046 			isc_buffer_putstr(buffer, " tls ");
   2047 			dns_name_format(entry->opts.masters.tlss[i], namebuf,
   2048 					sizeof(namebuf));
   2049 			isc_buffer_putstr(buffer, namebuf);
   2050 		}
   2051 		isc_buffer_putstr(buffer, "; ");
   2052 	}
   2053 	isc_buffer_putstr(buffer, "}; ");
   2054 	if (!entry->opts.in_memory) {
   2055 		isc_buffer_putstr(buffer, "file \"");
   2056 		result = dns_catz_generate_masterfilename(catz, entry, &buffer);
   2057 		if (result != ISC_R_SUCCESS) {
   2058 			goto cleanup;
   2059 		}
   2060 		isc_buffer_putstr(buffer, "\"; ");
   2061 	}
   2062 	if (entry->opts.allow_query != NULL) {
   2063 		isc_buffer_putstr(buffer, "allow-query { ");
   2064 		isc_buffer_usedregion(entry->opts.allow_query, &region);
   2065 		isc_buffer_copyregion(buffer, &region);
   2066 		isc_buffer_putstr(buffer, "}; ");
   2067 	}
   2068 	if (entry->opts.allow_transfer != NULL) {
   2069 		isc_buffer_putstr(buffer, "allow-transfer { ");
   2070 		isc_buffer_usedregion(entry->opts.allow_transfer, &region);
   2071 		isc_buffer_copyregion(buffer, &region);
   2072 		isc_buffer_putstr(buffer, "}; ");
   2073 	}
   2074 
   2075 	isc_buffer_putstr(buffer, "};");
   2076 	*buf = buffer;
   2077 
   2078 	return ISC_R_SUCCESS;
   2079 
   2080 cleanup:
   2081 	isc_buffer_free(&buffer);
   2082 	return result;
   2083 }
   2084 
   2085 static void
   2086 dns__catz_timer_cb(void *arg) {
   2087 	char domain[DNS_NAME_FORMATSIZE];
   2088 	dns_catz_zone_t *catz = (dns_catz_zone_t *)arg;
   2089 
   2090 	REQUIRE(DNS_CATZ_ZONE_VALID(catz));
   2091 
   2092 	if (atomic_load(&catz->catzs->shuttingdown)) {
   2093 		return;
   2094 	}
   2095 
   2096 	LOCK(&catz->catzs->lock);
   2097 
   2098 	INSIST(DNS_DB_VALID(catz->db));
   2099 	INSIST(catz->dbversion != NULL);
   2100 	INSIST(catz->updb == NULL);
   2101 	INSIST(catz->updbversion == NULL);
   2102 
   2103 	catz->updatepending = false;
   2104 	catz->updaterunning = true;
   2105 	catz->updateresult = ISC_R_UNSET;
   2106 
   2107 	dns_name_format(&catz->name, domain, DNS_NAME_FORMATSIZE);
   2108 
   2109 	if (!catz->active) {
   2110 		isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
   2111 			      DNS_LOGMODULE_MASTER, ISC_LOG_INFO,
   2112 			      "catz: %s: no longer active, reload is canceled",
   2113 			      domain);
   2114 		catz->updaterunning = false;
   2115 		catz->updateresult = ISC_R_CANCELED;
   2116 		goto exit;
   2117 	}
   2118 
   2119 	dns_db_attach(catz->db, &catz->updb);
   2120 	catz->updbversion = catz->dbversion;
   2121 	catz->dbversion = NULL;
   2122 
   2123 	isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, DNS_LOGMODULE_MASTER,
   2124 		      ISC_LOG_INFO, "catz: %s: reload start", domain);
   2125 
   2126 	dns_catz_zone_ref(catz);
   2127 	isc_work_enqueue(catz->loop, dns__catz_update_cb, dns__catz_done_cb,
   2128 			 catz);
   2129 
   2130 exit:
   2131 	isc_timer_destroy(&catz->updatetimer);
   2132 	catz->loop = NULL;
   2133 
   2134 	catz->lastupdated = isc_time_now();
   2135 
   2136 	UNLOCK(&catz->catzs->lock);
   2137 }
   2138 
   2139 isc_result_t
   2140 dns_catz_dbupdate_callback(dns_db_t *db, void *fn_arg) {
   2141 	dns_catz_zones_t *catzs = NULL;
   2142 	dns_catz_zone_t *catz = NULL;
   2143 	isc_result_t result = ISC_R_SUCCESS;
   2144 	isc_region_t r;
   2145 
   2146 	REQUIRE(DNS_DB_VALID(db));
   2147 	REQUIRE(DNS_CATZ_ZONES_VALID(fn_arg));
   2148 	catzs = (dns_catz_zones_t *)fn_arg;
   2149 
   2150 	if (atomic_load(&catzs->shuttingdown)) {
   2151 		return ISC_R_SHUTTINGDOWN;
   2152 	}
   2153 
   2154 	dns_name_toregion(&db->origin, &r);
   2155 
   2156 	LOCK(&catzs->lock);
   2157 	if (catzs->zones == NULL) {
   2158 		result = ISC_R_SHUTTINGDOWN;
   2159 		goto cleanup;
   2160 	}
   2161 	result = isc_ht_find(catzs->zones, r.base, r.length, (void **)&catz);
   2162 	if (result != ISC_R_SUCCESS) {
   2163 		goto cleanup;
   2164 	}
   2165 
   2166 	/* New zone came as AXFR */
   2167 	if (catz->db != NULL && catz->db != db) {
   2168 		/* Old db cleanup. */
   2169 		if (catz->dbversion != NULL) {
   2170 			dns_db_closeversion(catz->db, &catz->dbversion, false);
   2171 		}
   2172 		dns_db_updatenotify_unregister(
   2173 			catz->db, dns_catz_dbupdate_callback, catz->catzs);
   2174 		dns_db_detach(&catz->db);
   2175 	}
   2176 	if (catz->db == NULL) {
   2177 		/* New db registration. */
   2178 		dns_db_attach(db, &catz->db);
   2179 		dns_db_updatenotify_register(db, dns_catz_dbupdate_callback,
   2180 					     catz->catzs);
   2181 	}
   2182 
   2183 	if (!catz->updatepending && !catz->updaterunning) {
   2184 		catz->updatepending = true;
   2185 		dns_db_currentversion(db, &catz->dbversion);
   2186 		dns__catz_timer_start(catz);
   2187 	} else {
   2188 		char dname[DNS_NAME_FORMATSIZE];
   2189 
   2190 		catz->updatepending = true;
   2191 		dns_name_format(&catz->name, dname, DNS_NAME_FORMATSIZE);
   2192 		isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
   2193 			      DNS_LOGMODULE_MASTER, ISC_LOG_DEBUG(3),
   2194 			      "catz: %s: update already queued or running",
   2195 			      dname);
   2196 		if (catz->dbversion != NULL) {
   2197 			dns_db_closeversion(catz->db, &catz->dbversion, false);
   2198 		}
   2199 		dns_db_currentversion(catz->db, &catz->dbversion);
   2200 	}
   2201 
   2202 cleanup:
   2203 	UNLOCK(&catzs->lock);
   2204 
   2205 	return result;
   2206 }
   2207 
   2208 void
   2209 dns_catz_dbupdate_unregister(dns_db_t *db, dns_catz_zones_t *catzs) {
   2210 	REQUIRE(DNS_DB_VALID(db));
   2211 	REQUIRE(DNS_CATZ_ZONES_VALID(catzs));
   2212 
   2213 	dns_db_updatenotify_unregister(db, dns_catz_dbupdate_callback, catzs);
   2214 }
   2215 
   2216 void
   2217 dns_catz_dbupdate_register(dns_db_t *db, dns_catz_zones_t *catzs) {
   2218 	REQUIRE(DNS_DB_VALID(db));
   2219 	REQUIRE(DNS_CATZ_ZONES_VALID(catzs));
   2220 
   2221 	dns_db_updatenotify_register(db, dns_catz_dbupdate_callback, catzs);
   2222 }
   2223 
   2224 static bool
   2225 catz_rdatatype_is_processable(const dns_rdatatype_t type) {
   2226 	return !dns_rdatatype_isdnssec(type) && type != dns_rdatatype_cds &&
   2227 	       type != dns_rdatatype_cdnskey && type != dns_rdatatype_zonemd;
   2228 }
   2229 
   2230 /*
   2231  * Process an updated database for a catalog zone.
   2232  * It creates a new catz, iterates over database to fill it with content, and
   2233  * then merges new catz into old catz.
   2234  */
   2235 static void
   2236 dns__catz_update_cb(void *data) {
   2237 	dns_catz_zone_t *catz = (dns_catz_zone_t *)data;
   2238 	dns_db_t *updb = NULL;
   2239 	dns_catz_zones_t *catzs = NULL;
   2240 	dns_catz_zone_t *oldcatz = NULL, *newcatz = NULL;
   2241 	isc_result_t result;
   2242 	isc_region_t r;
   2243 	dns_dbnode_t *node = NULL;
   2244 	const dns_dbnode_t *vers_node = NULL;
   2245 	dns_dbiterator_t *updbit = NULL;
   2246 	dns_fixedname_t fixname;
   2247 	dns_name_t *name = NULL;
   2248 	dns_rdatasetiter_t *rdsiter = NULL;
   2249 	dns_rdataset_t rdataset;
   2250 	char bname[DNS_NAME_FORMATSIZE];
   2251 	char cname[DNS_NAME_FORMATSIZE];
   2252 	bool is_vers_processed = false;
   2253 	bool is_active;
   2254 	uint32_t vers;
   2255 	uint32_t catz_vers;
   2256 
   2257 	REQUIRE(DNS_CATZ_ZONE_VALID(catz));
   2258 	REQUIRE(DNS_DB_VALID(catz->updb));
   2259 	REQUIRE(DNS_CATZ_ZONES_VALID(catz->catzs));
   2260 
   2261 	updb = catz->updb;
   2262 	catzs = catz->catzs;
   2263 
   2264 	if (atomic_load(&catzs->shuttingdown)) {
   2265 		result = ISC_R_SHUTTINGDOWN;
   2266 		goto exit;
   2267 	}
   2268 
   2269 	dns_name_format(&updb->origin, bname, DNS_NAME_FORMATSIZE);
   2270 
   2271 	/*
   2272 	 * Create a new catz in the same context as current catz.
   2273 	 */
   2274 	dns_name_toregion(&updb->origin, &r);
   2275 	LOCK(&catzs->lock);
   2276 	if (catzs->zones == NULL) {
   2277 		UNLOCK(&catzs->lock);
   2278 		result = ISC_R_SHUTTINGDOWN;
   2279 		goto exit;
   2280 	}
   2281 	result = isc_ht_find(catzs->zones, r.base, r.length, (void **)&oldcatz);
   2282 	is_active = (result == ISC_R_SUCCESS && oldcatz->active);
   2283 	UNLOCK(&catzs->lock);
   2284 	if (result != ISC_R_SUCCESS) {
   2285 		/* This can happen if we remove the zone in the meantime. */
   2286 		isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
   2287 			      DNS_LOGMODULE_MASTER, ISC_LOG_ERROR,
   2288 			      "catz: zone '%s' not in config", bname);
   2289 		goto exit;
   2290 	}
   2291 
   2292 	if (!is_active) {
   2293 		/* This can happen during a reconfiguration. */
   2294 		isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
   2295 			      DNS_LOGMODULE_MASTER, ISC_LOG_INFO,
   2296 			      "catz: zone '%s' is no longer active", bname);
   2297 		result = ISC_R_CANCELED;
   2298 		goto exit;
   2299 	}
   2300 
   2301 	result = dns_db_getsoaserial(updb, oldcatz->updbversion, &vers);
   2302 	if (result != ISC_R_SUCCESS) {
   2303 		/* A zone without SOA record?!? */
   2304 		isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
   2305 			      DNS_LOGMODULE_MASTER, ISC_LOG_ERROR,
   2306 			      "catz: zone '%s' has no SOA record (%s)", bname,
   2307 			      isc_result_totext(result));
   2308 		goto exit;
   2309 	}
   2310 
   2311 	isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, DNS_LOGMODULE_MASTER,
   2312 		      ISC_LOG_INFO,
   2313 		      "catz: updating catalog zone '%s' with serial %" PRIu32,
   2314 		      bname, vers);
   2315 
   2316 	result = dns_db_createiterator(updb, DNS_DB_NONSEC3, &updbit);
   2317 	if (result != ISC_R_SUCCESS) {
   2318 		isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
   2319 			      DNS_LOGMODULE_MASTER, ISC_LOG_ERROR,
   2320 			      "catz: failed to create DB iterator - %s",
   2321 			      isc_result_totext(result));
   2322 		goto exit;
   2323 	}
   2324 
   2325 	name = dns_fixedname_initname(&fixname);
   2326 
   2327 	/*
   2328 	 * Take the version record to process first, because the other
   2329 	 * records might be processed differently depending on the version of
   2330 	 * the catalog zone's schema.
   2331 	 */
   2332 	result = dns_name_fromstring(name, "version", &updb->origin, 0, NULL);
   2333 	if (result != ISC_R_SUCCESS) {
   2334 		dns_dbiterator_destroy(&updbit);
   2335 		isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
   2336 			      DNS_LOGMODULE_MASTER, ISC_LOG_ERROR,
   2337 			      "catz: failed to create name from string - %s",
   2338 			      isc_result_totext(result));
   2339 		goto exit;
   2340 	}
   2341 
   2342 	result = dns_dbiterator_seek(updbit, name);
   2343 	if (result != ISC_R_SUCCESS) {
   2344 		dns_dbiterator_destroy(&updbit);
   2345 		isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
   2346 			      DNS_LOGMODULE_MASTER, ISC_LOG_ERROR,
   2347 			      "catz: zone '%s' has no 'version' record (%s) "
   2348 			      "and will not be processed",
   2349 			      bname, isc_result_totext(result));
   2350 		goto exit;
   2351 	}
   2352 
   2353 	newcatz = dns_catz_zone_new(catzs, &updb->origin);
   2354 	name = dns_fixedname_initname(&fixname);
   2355 
   2356 	/*
   2357 	 * Iterate over database to fill the new zone.
   2358 	 */
   2359 	while (result == ISC_R_SUCCESS) {
   2360 		if (atomic_load(&catzs->shuttingdown)) {
   2361 			result = ISC_R_SHUTTINGDOWN;
   2362 			break;
   2363 		}
   2364 
   2365 		result = dns_dbiterator_current(updbit, &node, name);
   2366 		if (result != ISC_R_SUCCESS) {
   2367 			isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
   2368 				      DNS_LOGMODULE_MASTER, ISC_LOG_ERROR,
   2369 				      "catz: failed to get db iterator - %s",
   2370 				      isc_result_totext(result));
   2371 			break;
   2372 		}
   2373 
   2374 		result = dns_dbiterator_pause(updbit);
   2375 		RUNTIME_CHECK(result == ISC_R_SUCCESS);
   2376 
   2377 		if (!is_vers_processed) {
   2378 			/* Keep the version node to skip it later in the loop */
   2379 			vers_node = node;
   2380 		} else if (node == vers_node) {
   2381 			/* Skip the already processed version node */
   2382 			dns_db_detachnode(updb, &node);
   2383 			result = dns_dbiterator_next(updbit);
   2384 			continue;
   2385 		}
   2386 
   2387 		result = dns_db_allrdatasets(updb, node, oldcatz->updbversion,
   2388 					     0, 0, &rdsiter);
   2389 		if (result != ISC_R_SUCCESS) {
   2390 			isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
   2391 				      DNS_LOGMODULE_MASTER, ISC_LOG_ERROR,
   2392 				      "catz: failed to fetch rrdatasets - %s",
   2393 				      isc_result_totext(result));
   2394 			dns_db_detachnode(updb, &node);
   2395 			break;
   2396 		}
   2397 
   2398 		dns_rdataset_init(&rdataset);
   2399 		result = dns_rdatasetiter_first(rdsiter);
   2400 		while (result == ISC_R_SUCCESS) {
   2401 			dns_rdatasetiter_current(rdsiter, &rdataset);
   2402 
   2403 			/*
   2404 			 * Skip processing DNSSEC-related and ZONEMD types,
   2405 			 * because we are not interested in them in the context
   2406 			 * of a catalog zone, and processing them will fail
   2407 			 * and produce an unnecessary warning message.
   2408 			 */
   2409 			if (!catz_rdatatype_is_processable(rdataset.type)) {
   2410 				goto next;
   2411 			}
   2412 
   2413 			/*
   2414 			 * Although newcatz->coos is accessed in
   2415 			 * catz_process_coo() in the call-chain below, we don't
   2416 			 * need to hold the newcatz->lock, because the newcatz
   2417 			 * is still local to this thread and function and
   2418 			 * newcatz->coos can't be accessed from the outside
   2419 			 * until dns__catz_zones_merge() has been called.
   2420 			 */
   2421 			result = dns__catz_update_process(newcatz, name,
   2422 							  &rdataset);
   2423 			if (result != ISC_R_SUCCESS) {
   2424 				char typebuf[DNS_RDATATYPE_FORMATSIZE];
   2425 				char classbuf[DNS_RDATACLASS_FORMATSIZE];
   2426 
   2427 				dns_name_format(name, cname,
   2428 						DNS_NAME_FORMATSIZE);
   2429 				dns_rdataclass_format(rdataset.rdclass,
   2430 						      classbuf,
   2431 						      sizeof(classbuf));
   2432 				dns_rdatatype_format(rdataset.type, typebuf,
   2433 						     sizeof(typebuf));
   2434 				isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
   2435 					      DNS_LOGMODULE_MASTER,
   2436 					      ISC_LOG_WARNING,
   2437 					      "catz: invalid record in catalog "
   2438 					      "zone - %s %s %s (%s) - ignoring",
   2439 					      cname, classbuf, typebuf,
   2440 					      isc_result_totext(result));
   2441 			}
   2442 		next:
   2443 			dns_rdataset_disassociate(&rdataset);
   2444 			result = dns_rdatasetiter_next(rdsiter);
   2445 		}
   2446 
   2447 		dns_rdatasetiter_destroy(&rdsiter);
   2448 
   2449 		dns_db_detachnode(updb, &node);
   2450 
   2451 		if (!is_vers_processed) {
   2452 			is_vers_processed = true;
   2453 			result = dns_dbiterator_first(updbit);
   2454 		} else {
   2455 			result = dns_dbiterator_next(updbit);
   2456 		}
   2457 	}
   2458 
   2459 	dns_dbiterator_destroy(&updbit);
   2460 	isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, DNS_LOGMODULE_MASTER,
   2461 		      ISC_LOG_DEBUG(3),
   2462 		      "catz: update_from_db: iteration finished: %s",
   2463 		      isc_result_totext(result));
   2464 
   2465 	/*
   2466 	 * Check catalog zone version compatibilites.
   2467 	 */
   2468 	catz_vers = (newcatz->version == DNS_CATZ_VERSION_UNDEFINED)
   2469 			    ? oldcatz->version
   2470 			    : newcatz->version;
   2471 	if (catz_vers == DNS_CATZ_VERSION_UNDEFINED) {
   2472 		isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
   2473 			      DNS_LOGMODULE_MASTER, ISC_LOG_WARNING,
   2474 			      "catz: zone '%s' version is not set", bname);
   2475 		newcatz->broken = true;
   2476 	} else if (catz_vers != 1 && catz_vers != 2) {
   2477 		isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
   2478 			      DNS_LOGMODULE_MASTER, ISC_LOG_WARNING,
   2479 			      "catz: zone '%s' unsupported version "
   2480 			      "'%" PRIu32 "'",
   2481 			      bname, catz_vers);
   2482 		newcatz->broken = true;
   2483 	} else {
   2484 		oldcatz->version = catz_vers;
   2485 	}
   2486 
   2487 	if (newcatz->broken) {
   2488 		dns_name_format(name, cname, DNS_NAME_FORMATSIZE);
   2489 		isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
   2490 			      DNS_LOGMODULE_MASTER, ISC_LOG_ERROR,
   2491 			      "catz: new catalog zone '%s' is broken and "
   2492 			      "will not be processed",
   2493 			      bname);
   2494 		dns_catz_zone_detach(&newcatz);
   2495 		result = ISC_R_FAILURE;
   2496 		goto exit;
   2497 	}
   2498 
   2499 	/*
   2500 	 * Finally merge new zone into old zone.
   2501 	 */
   2502 	result = dns__catz_zones_merge(oldcatz, newcatz);
   2503 	dns_catz_zone_detach(&newcatz);
   2504 	if (result != ISC_R_SUCCESS) {
   2505 		isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
   2506 			      DNS_LOGMODULE_MASTER, ISC_LOG_ERROR,
   2507 			      "catz: failed merging zones: %s",
   2508 			      isc_result_totext(result));
   2509 
   2510 		goto exit;
   2511 	}
   2512 
   2513 	isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, DNS_LOGMODULE_MASTER,
   2514 		      ISC_LOG_DEBUG(3),
   2515 		      "catz: update_from_db: new zone merged");
   2516 
   2517 exit:
   2518 	catz->updateresult = result;
   2519 }
   2520 
   2521 static void
   2522 dns__catz_done_cb(void *data) {
   2523 	dns_catz_zone_t *catz = (dns_catz_zone_t *)data;
   2524 	char dname[DNS_NAME_FORMATSIZE];
   2525 
   2526 	REQUIRE(DNS_CATZ_ZONE_VALID(catz));
   2527 
   2528 	LOCK(&catz->catzs->lock);
   2529 	catz->updaterunning = false;
   2530 
   2531 	dns_name_format(&catz->name, dname, DNS_NAME_FORMATSIZE);
   2532 
   2533 	if (catz->updatepending && !atomic_load(&catz->catzs->shuttingdown)) {
   2534 		/* Restart the timer */
   2535 		dns__catz_timer_start(catz);
   2536 	}
   2537 
   2538 	dns_db_closeversion(catz->updb, &catz->updbversion, false);
   2539 	dns_db_detach(&catz->updb);
   2540 
   2541 	UNLOCK(&catz->catzs->lock);
   2542 
   2543 	isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, DNS_LOGMODULE_MASTER,
   2544 		      ISC_LOG_INFO, "catz: %s: reload done: %s", dname,
   2545 		      isc_result_totext(catz->updateresult));
   2546 
   2547 	dns_catz_zone_unref(catz);
   2548 }
   2549 
   2550 void
   2551 dns_catz_prereconfig(dns_catz_zones_t *catzs) {
   2552 	isc_result_t result;
   2553 	isc_ht_iter_t *iter = NULL;
   2554 
   2555 	REQUIRE(DNS_CATZ_ZONES_VALID(catzs));
   2556 
   2557 	LOCK(&catzs->lock);
   2558 	isc_ht_iter_create(catzs->zones, &iter);
   2559 	for (result = isc_ht_iter_first(iter); result == ISC_R_SUCCESS;
   2560 	     result = isc_ht_iter_next(iter))
   2561 	{
   2562 		dns_catz_zone_t *catz = NULL;
   2563 		isc_ht_iter_current(iter, (void **)&catz);
   2564 		catz->active = false;
   2565 	}
   2566 	UNLOCK(&catzs->lock);
   2567 	INSIST(result == ISC_R_NOMORE);
   2568 	isc_ht_iter_destroy(&iter);
   2569 }
   2570 
   2571 void
   2572 dns_catz_postreconfig(dns_catz_zones_t *catzs) {
   2573 	isc_result_t result;
   2574 	dns_catz_zone_t *newcatz = NULL;
   2575 	isc_ht_iter_t *iter = NULL;
   2576 
   2577 	REQUIRE(DNS_CATZ_ZONES_VALID(catzs));
   2578 
   2579 	LOCK(&catzs->lock);
   2580 	isc_ht_iter_create(catzs->zones, &iter);
   2581 	for (result = isc_ht_iter_first(iter); result == ISC_R_SUCCESS;) {
   2582 		dns_catz_zone_t *catz = NULL;
   2583 
   2584 		isc_ht_iter_current(iter, (void **)&catz);
   2585 		if (!catz->active) {
   2586 			char cname[DNS_NAME_FORMATSIZE];
   2587 			dns_name_format(&catz->name, cname,
   2588 					DNS_NAME_FORMATSIZE);
   2589 			isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
   2590 				      DNS_LOGMODULE_MASTER, ISC_LOG_WARNING,
   2591 				      "catz: removing catalog zone %s", cname);
   2592 
   2593 			/*
   2594 			 * Merge the old zone with an empty one to remove
   2595 			 * all members.
   2596 			 */
   2597 			newcatz = dns_catz_zone_new(catzs, &catz->name);
   2598 			dns__catz_zones_merge(catz, newcatz);
   2599 			dns_catz_zone_detach(&newcatz);
   2600 
   2601 			/* Make sure that we have an empty catalog zone. */
   2602 			INSIST(isc_ht_count(catz->entries) == 0);
   2603 			result = isc_ht_iter_delcurrent_next(iter);
   2604 			dns_catz_zone_detach(&catz);
   2605 		} else {
   2606 			result = isc_ht_iter_next(iter);
   2607 		}
   2608 	}
   2609 	UNLOCK(&catzs->lock);
   2610 	RUNTIME_CHECK(result == ISC_R_NOMORE);
   2611 	isc_ht_iter_destroy(&iter);
   2612 }
   2613 
   2614 void
   2615 dns_catz_zone_prereconfig(dns_catz_zone_t *catz) {
   2616 	LOCK(&catz->lock);
   2617 }
   2618 
   2619 void
   2620 dns_catz_zone_postreconfig(dns_catz_zone_t *catz) {
   2621 	UNLOCK(&catz->lock);
   2622 }
   2623 
   2624 void
   2625 dns_catz_zone_for_each_entry2(dns_catz_zone_t *catz, dns_catz_entry_cb2 cb,
   2626 			      void *arg1, void *arg2) {
   2627 	REQUIRE(DNS_CATZ_ZONE_VALID(catz));
   2628 
   2629 	isc_ht_iter_t *iter = NULL;
   2630 	isc_result_t result;
   2631 
   2632 	LOCK(&catz->catzs->lock);
   2633 	isc_ht_iter_create(catz->entries, &iter);
   2634 	for (result = isc_ht_iter_first(iter); result == ISC_R_SUCCESS;
   2635 	     result = isc_ht_iter_next(iter))
   2636 	{
   2637 		dns_catz_entry_t *entry = NULL;
   2638 
   2639 		isc_ht_iter_current(iter, (void **)&entry);
   2640 		cb(entry, arg1, arg2);
   2641 	}
   2642 	isc_ht_iter_destroy(&iter);
   2643 	UNLOCK(&catz->catzs->lock);
   2644 }
   2645