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