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