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