xref: /netbsd-src/external/mpl/bind/dist/lib/dns/nta.c (revision ae082add65442546470c0ba499a860ee89eed305)
1 /*	$NetBSD: nta.c,v 1.8 2022/09/23 12:15:30 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 
21 #include <isc/buffer.h>
22 #include <isc/log.h>
23 #include <isc/mem.h>
24 #include <isc/print.h>
25 #include <isc/rwlock.h>
26 #include <isc/string.h>
27 #include <isc/task.h>
28 #include <isc/time.h>
29 #include <isc/timer.h>
30 #include <isc/util.h>
31 
32 #include <dns/db.h>
33 #include <dns/fixedname.h>
34 #include <dns/log.h>
35 #include <dns/name.h>
36 #include <dns/nta.h>
37 #include <dns/rbt.h>
38 #include <dns/rdataset.h>
39 #include <dns/resolver.h>
40 #include <dns/result.h>
41 #include <dns/time.h>
42 
43 struct dns_nta {
44 	unsigned int magic;
45 	isc_refcount_t refcount;
46 	dns_ntatable_t *ntatable;
47 	bool forced;
48 	isc_timer_t *timer;
49 	dns_fetch_t *fetch;
50 	dns_rdataset_t rdataset;
51 	dns_rdataset_t sigrdataset;
52 	dns_fixedname_t fn;
53 	dns_name_t *name;
54 	isc_stdtime_t expiry;
55 };
56 
57 #define NTA_MAGIC     ISC_MAGIC('N', 'T', 'A', 'n')
58 #define VALID_NTA(nn) ISC_MAGIC_VALID(nn, NTA_MAGIC)
59 
60 /*
61  * Obtain a reference to the nta object.  Released by
62  * nta_detach.
63  */
64 static void
65 nta_ref(dns_nta_t *nta) {
66 	isc_refcount_increment(&nta->refcount);
67 }
68 
69 static void
70 nta_detach(isc_mem_t *mctx, dns_nta_t **ntap) {
71 	REQUIRE(ntap != NULL && VALID_NTA(*ntap));
72 	dns_nta_t *nta = *ntap;
73 	*ntap = NULL;
74 
75 	if (isc_refcount_decrement(&nta->refcount) == 1) {
76 		isc_refcount_destroy(&nta->refcount);
77 		nta->magic = 0;
78 		if (nta->timer != NULL) {
79 			(void)isc_timer_reset(nta->timer,
80 					      isc_timertype_inactive, NULL,
81 					      NULL, true);
82 			isc_timer_detach(&nta->timer);
83 		}
84 		if (dns_rdataset_isassociated(&nta->rdataset)) {
85 			dns_rdataset_disassociate(&nta->rdataset);
86 		}
87 		if (dns_rdataset_isassociated(&nta->sigrdataset)) {
88 			dns_rdataset_disassociate(&nta->sigrdataset);
89 		}
90 		if (nta->fetch != NULL) {
91 			dns_resolver_cancelfetch(nta->fetch);
92 			dns_resolver_destroyfetch(&nta->fetch);
93 		}
94 		isc_mem_put(mctx, nta, sizeof(dns_nta_t));
95 	}
96 }
97 
98 static void
99 free_nta(void *data, void *arg) {
100 	dns_nta_t *nta = (dns_nta_t *)data;
101 	isc_mem_t *mctx = (isc_mem_t *)arg;
102 
103 	nta_detach(mctx, &nta);
104 }
105 
106 isc_result_t
107 dns_ntatable_create(dns_view_t *view, isc_taskmgr_t *taskmgr,
108 		    isc_timermgr_t *timermgr, dns_ntatable_t **ntatablep) {
109 	dns_ntatable_t *ntatable;
110 	isc_result_t result;
111 
112 	REQUIRE(ntatablep != NULL && *ntatablep == NULL);
113 
114 	ntatable = isc_mem_get(view->mctx, sizeof(*ntatable));
115 
116 	ntatable->task = NULL;
117 	result = isc_task_create(taskmgr, 0, &ntatable->task);
118 	if (result != ISC_R_SUCCESS) {
119 		goto cleanup_ntatable;
120 	}
121 	isc_task_setname(ntatable->task, "ntatable", ntatable);
122 
123 	ntatable->table = NULL;
124 	result = dns_rbt_create(view->mctx, free_nta, view->mctx,
125 				&ntatable->table);
126 	if (result != ISC_R_SUCCESS) {
127 		goto cleanup_task;
128 	}
129 
130 	isc_rwlock_init(&ntatable->rwlock, 0, 0);
131 
132 	ntatable->shuttingdown = false;
133 	ntatable->timermgr = timermgr;
134 	ntatable->taskmgr = taskmgr;
135 
136 	ntatable->view = view;
137 	isc_refcount_init(&ntatable->references, 1);
138 
139 	ntatable->magic = NTATABLE_MAGIC;
140 	*ntatablep = ntatable;
141 
142 	return (ISC_R_SUCCESS);
143 
144 cleanup_task:
145 	isc_task_detach(&ntatable->task);
146 
147 cleanup_ntatable:
148 	isc_mem_put(view->mctx, ntatable, sizeof(*ntatable));
149 
150 	return (result);
151 }
152 
153 void
154 dns_ntatable_attach(dns_ntatable_t *source, dns_ntatable_t **targetp) {
155 	REQUIRE(VALID_NTATABLE(source));
156 	REQUIRE(targetp != NULL && *targetp == NULL);
157 
158 	isc_refcount_increment(&source->references);
159 
160 	*targetp = source;
161 }
162 
163 void
164 dns_ntatable_detach(dns_ntatable_t **ntatablep) {
165 	dns_ntatable_t *ntatable;
166 
167 	REQUIRE(ntatablep != NULL && VALID_NTATABLE(*ntatablep));
168 
169 	ntatable = *ntatablep;
170 	*ntatablep = NULL;
171 
172 	if (isc_refcount_decrement(&ntatable->references) == 1) {
173 		dns_rbt_destroy(&ntatable->table);
174 		isc_rwlock_destroy(&ntatable->rwlock);
175 		isc_refcount_destroy(&ntatable->references);
176 		if (ntatable->task != NULL) {
177 			isc_task_detach(&ntatable->task);
178 		}
179 		ntatable->timermgr = NULL;
180 		ntatable->taskmgr = NULL;
181 		ntatable->magic = 0;
182 		isc_mem_put(ntatable->view->mctx, ntatable, sizeof(*ntatable));
183 	}
184 }
185 
186 static void
187 fetch_done(isc_task_t *task, isc_event_t *event) {
188 	dns_fetchevent_t *devent = (dns_fetchevent_t *)event;
189 	dns_nta_t *nta = devent->ev_arg;
190 	isc_result_t eresult = devent->result;
191 	dns_ntatable_t *ntatable = nta->ntatable;
192 	dns_view_t *view = ntatable->view;
193 	isc_stdtime_t now;
194 
195 	UNUSED(task);
196 
197 	if (dns_rdataset_isassociated(&nta->rdataset)) {
198 		dns_rdataset_disassociate(&nta->rdataset);
199 	}
200 	if (dns_rdataset_isassociated(&nta->sigrdataset)) {
201 		dns_rdataset_disassociate(&nta->sigrdataset);
202 	}
203 	if (nta->fetch == devent->fetch) {
204 		nta->fetch = NULL;
205 	}
206 	dns_resolver_destroyfetch(&devent->fetch);
207 
208 	if (devent->node != NULL) {
209 		dns_db_detachnode(devent->db, &devent->node);
210 	}
211 	if (devent->db != NULL) {
212 		dns_db_detach(&devent->db);
213 	}
214 
215 	isc_event_free(&event);
216 	isc_stdtime_get(&now);
217 
218 	switch (eresult) {
219 	case ISC_R_SUCCESS:
220 	case DNS_R_NCACHENXDOMAIN:
221 	case DNS_R_NXDOMAIN:
222 	case DNS_R_NCACHENXRRSET:
223 	case DNS_R_NXRRSET:
224 		if (nta->expiry > now) {
225 			nta->expiry = now;
226 		}
227 		break;
228 	default:
229 		break;
230 	}
231 
232 	/*
233 	 * If we're expiring before the next recheck, we might
234 	 * as well stop the timer now.
235 	 */
236 	if (nta->timer != NULL && nta->expiry - now < view->nta_recheck) {
237 		(void)isc_timer_reset(nta->timer, isc_timertype_inactive, NULL,
238 				      NULL, true);
239 	}
240 	nta_detach(view->mctx, &nta);
241 	dns_view_weakdetach(&view);
242 }
243 
244 static void
245 checkbogus(isc_task_t *task, isc_event_t *event) {
246 	dns_nta_t *nta = event->ev_arg;
247 	dns_ntatable_t *ntatable = nta->ntatable;
248 	dns_view_t *view = NULL;
249 	isc_result_t result;
250 
251 	if (nta->fetch != NULL) {
252 		dns_resolver_cancelfetch(nta->fetch);
253 		nta->fetch = NULL;
254 	}
255 	if (dns_rdataset_isassociated(&nta->rdataset)) {
256 		dns_rdataset_disassociate(&nta->rdataset);
257 	}
258 	if (dns_rdataset_isassociated(&nta->sigrdataset)) {
259 		dns_rdataset_disassociate(&nta->sigrdataset);
260 	}
261 
262 	isc_event_free(&event);
263 
264 	nta_ref(nta);
265 	dns_view_weakattach(ntatable->view, &view);
266 	result = dns_resolver_createfetch(
267 		view->resolver, nta->name, dns_rdatatype_nsec, NULL, NULL, NULL,
268 		NULL, 0, DNS_FETCHOPT_NONTA, 0, NULL, task, fetch_done, nta,
269 		&nta->rdataset, &nta->sigrdataset, &nta->fetch);
270 	if (result != ISC_R_SUCCESS) {
271 		nta_detach(view->mctx, &nta);
272 		dns_view_weakdetach(&view);
273 	}
274 }
275 
276 static isc_result_t
277 settimer(dns_ntatable_t *ntatable, dns_nta_t *nta, uint32_t lifetime) {
278 	isc_result_t result;
279 	isc_interval_t interval;
280 	dns_view_t *view;
281 
282 	REQUIRE(VALID_NTATABLE(ntatable));
283 	REQUIRE(VALID_NTA(nta));
284 
285 	if (ntatable->timermgr == NULL) {
286 		return (ISC_R_SUCCESS);
287 	}
288 
289 	view = ntatable->view;
290 	if (view->nta_recheck == 0 || lifetime <= view->nta_recheck) {
291 		return (ISC_R_SUCCESS);
292 	}
293 
294 	isc_interval_set(&interval, view->nta_recheck, 0);
295 	result = isc_timer_create(ntatable->timermgr, isc_timertype_ticker,
296 				  NULL, &interval, ntatable->task, checkbogus,
297 				  nta, &nta->timer);
298 	return (result);
299 }
300 
301 static isc_result_t
302 nta_create(dns_ntatable_t *ntatable, const dns_name_t *name,
303 	   dns_nta_t **target) {
304 	dns_nta_t *nta = NULL;
305 	dns_view_t *view;
306 
307 	REQUIRE(VALID_NTATABLE(ntatable));
308 	REQUIRE(target != NULL && *target == NULL);
309 
310 	view = ntatable->view;
311 
312 	nta = isc_mem_get(view->mctx, sizeof(dns_nta_t));
313 
314 	nta->ntatable = ntatable;
315 	nta->expiry = 0;
316 	nta->timer = NULL;
317 	nta->fetch = NULL;
318 	dns_rdataset_init(&nta->rdataset);
319 	dns_rdataset_init(&nta->sigrdataset);
320 
321 	isc_refcount_init(&nta->refcount, 1);
322 
323 	nta->name = dns_fixedname_initname(&nta->fn);
324 	dns_name_copynf(name, nta->name);
325 
326 	nta->magic = NTA_MAGIC;
327 
328 	*target = nta;
329 	return (ISC_R_SUCCESS);
330 }
331 
332 isc_result_t
333 dns_ntatable_add(dns_ntatable_t *ntatable, const dns_name_t *name, bool force,
334 		 isc_stdtime_t now, uint32_t lifetime) {
335 	isc_result_t result = ISC_R_SUCCESS;
336 	dns_nta_t *nta = NULL;
337 	dns_rbtnode_t *node;
338 	dns_view_t *view;
339 
340 	REQUIRE(VALID_NTATABLE(ntatable));
341 
342 	view = ntatable->view;
343 
344 	RWLOCK(&ntatable->rwlock, isc_rwlocktype_write);
345 
346 	if (ntatable->shuttingdown) {
347 		goto unlock;
348 	}
349 
350 	result = nta_create(ntatable, name, &nta);
351 	if (result != ISC_R_SUCCESS) {
352 		goto unlock;
353 	}
354 
355 	nta->expiry = now + lifetime;
356 	nta->forced = force;
357 
358 	node = NULL;
359 	result = dns_rbt_addnode(ntatable->table, name, &node);
360 	if (result == ISC_R_SUCCESS) {
361 		if (!force) {
362 			(void)settimer(ntatable, nta, lifetime);
363 		}
364 		node->data = nta;
365 		nta = NULL;
366 	} else if (result == ISC_R_EXISTS) {
367 		dns_nta_t *n = node->data;
368 		if (n == NULL) {
369 			if (!force) {
370 				(void)settimer(ntatable, nta, lifetime);
371 			}
372 			node->data = nta;
373 			nta = NULL;
374 		} else {
375 			n->expiry = nta->expiry;
376 			nta_detach(view->mctx, &nta);
377 		}
378 		result = ISC_R_SUCCESS;
379 	}
380 
381 unlock:
382 	RWUNLOCK(&ntatable->rwlock, isc_rwlocktype_write);
383 
384 	if (nta != NULL) {
385 		nta_detach(view->mctx, &nta);
386 	}
387 
388 	return (result);
389 }
390 
391 /*
392  * Caller must hold a write lock on rwlock.
393  */
394 static isc_result_t
395 deletenode(dns_ntatable_t *ntatable, const dns_name_t *name) {
396 	isc_result_t result;
397 	dns_rbtnode_t *node = NULL;
398 
399 	REQUIRE(VALID_NTATABLE(ntatable));
400 	REQUIRE(name != NULL);
401 
402 	result = dns_rbt_findnode(ntatable->table, name, NULL, &node, NULL,
403 				  DNS_RBTFIND_NOOPTIONS, NULL, NULL);
404 	if (result == ISC_R_SUCCESS) {
405 		if (node->data != NULL) {
406 			result = dns_rbt_deletenode(ntatable->table, node,
407 						    false);
408 		} else {
409 			result = ISC_R_NOTFOUND;
410 		}
411 	} else if (result == DNS_R_PARTIALMATCH) {
412 		result = ISC_R_NOTFOUND;
413 	}
414 
415 	return (result);
416 }
417 
418 isc_result_t
419 dns_ntatable_delete(dns_ntatable_t *ntatable, const dns_name_t *name) {
420 	isc_result_t result;
421 
422 	RWLOCK(&ntatable->rwlock, isc_rwlocktype_write);
423 	result = deletenode(ntatable, name);
424 	RWUNLOCK(&ntatable->rwlock, isc_rwlocktype_write);
425 
426 	return (result);
427 }
428 
429 bool
430 dns_ntatable_covered(dns_ntatable_t *ntatable, isc_stdtime_t now,
431 		     const dns_name_t *name, const dns_name_t *anchor) {
432 	isc_result_t result;
433 	dns_fixedname_t fn;
434 	dns_rbtnode_t *node;
435 	dns_name_t *foundname;
436 	dns_nta_t *nta = NULL;
437 	bool answer = false;
438 	isc_rwlocktype_t locktype = isc_rwlocktype_read;
439 
440 	REQUIRE(ntatable == NULL || VALID_NTATABLE(ntatable));
441 	REQUIRE(dns_name_isabsolute(name));
442 
443 	if (ntatable == NULL) {
444 		return (false);
445 	}
446 
447 	foundname = dns_fixedname_initname(&fn);
448 
449 relock:
450 	RWLOCK(&ntatable->rwlock, locktype);
451 again:
452 	node = NULL;
453 	result = dns_rbt_findnode(ntatable->table, name, foundname, &node, NULL,
454 				  DNS_RBTFIND_NOOPTIONS, NULL, NULL);
455 	if (result == DNS_R_PARTIALMATCH) {
456 		if (dns_name_issubdomain(foundname, anchor)) {
457 			result = ISC_R_SUCCESS;
458 		}
459 	}
460 	if (result == ISC_R_SUCCESS) {
461 		nta = (dns_nta_t *)node->data;
462 		answer = (nta->expiry > now);
463 	}
464 
465 	/* Deal with expired NTA */
466 	if (result == ISC_R_SUCCESS && !answer) {
467 		char nb[DNS_NAME_FORMATSIZE];
468 
469 		if (locktype == isc_rwlocktype_read) {
470 			RWUNLOCK(&ntatable->rwlock, locktype);
471 			locktype = isc_rwlocktype_write;
472 			goto relock;
473 		}
474 
475 		dns_name_format(foundname, nb, sizeof(nb));
476 		isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC,
477 			      DNS_LOGMODULE_NTA, ISC_LOG_INFO,
478 			      "deleting expired NTA at %s", nb);
479 
480 		if (nta->timer != NULL) {
481 			(void)isc_timer_reset(nta->timer,
482 					      isc_timertype_inactive, NULL,
483 					      NULL, true);
484 			isc_timer_detach(&nta->timer);
485 		}
486 
487 		result = deletenode(ntatable, foundname);
488 		if (result != ISC_R_SUCCESS) {
489 			isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC,
490 				      DNS_LOGMODULE_NTA, ISC_LOG_INFO,
491 				      "deleting NTA failed: %s",
492 				      isc_result_totext(result));
493 		}
494 		goto again;
495 	}
496 	RWUNLOCK(&ntatable->rwlock, locktype);
497 
498 	return (answer);
499 }
500 
501 static isc_result_t
502 putstr(isc_buffer_t **b, const char *str) {
503 	isc_result_t result;
504 
505 	result = isc_buffer_reserve(b, strlen(str));
506 	if (result != ISC_R_SUCCESS) {
507 		return (result);
508 	}
509 
510 	isc_buffer_putstr(*b, str);
511 	return (ISC_R_SUCCESS);
512 }
513 
514 isc_result_t
515 dns_ntatable_totext(dns_ntatable_t *ntatable, const char *view,
516 		    isc_buffer_t **buf) {
517 	isc_result_t result;
518 	dns_rbtnode_t *node;
519 	dns_rbtnodechain_t chain;
520 	bool first = true;
521 	isc_stdtime_t now;
522 
523 	REQUIRE(VALID_NTATABLE(ntatable));
524 
525 	isc_stdtime_get(&now);
526 
527 	RWLOCK(&ntatable->rwlock, isc_rwlocktype_read);
528 	dns_rbtnodechain_init(&chain);
529 	result = dns_rbtnodechain_first(&chain, ntatable->table, NULL, NULL);
530 	if (result != ISC_R_SUCCESS && result != DNS_R_NEWORIGIN) {
531 		if (result == ISC_R_NOTFOUND) {
532 			result = ISC_R_SUCCESS;
533 		}
534 		goto cleanup;
535 	}
536 	for (;;) {
537 		dns_rbtnodechain_current(&chain, NULL, NULL, &node);
538 		if (node->data != NULL) {
539 			dns_nta_t *n = (dns_nta_t *)node->data;
540 			char nbuf[DNS_NAME_FORMATSIZE];
541 			char tbuf[ISC_FORMATHTTPTIMESTAMP_SIZE];
542 			char obuf[DNS_NAME_FORMATSIZE +
543 				  ISC_FORMATHTTPTIMESTAMP_SIZE +
544 				  sizeof("expired:  \n")];
545 			dns_fixedname_t fn;
546 			dns_name_t *name;
547 			isc_time_t t;
548 
549 			/*
550 			 * Skip "validate-except" entries.
551 			 */
552 			if (n->expiry != 0xffffffffU) {
553 				name = dns_fixedname_initname(&fn);
554 				dns_rbt_fullnamefromnode(node, name);
555 				dns_name_format(name, nbuf, sizeof(nbuf));
556 				isc_time_set(&t, n->expiry, 0);
557 				isc_time_formattimestamp(&t, tbuf,
558 							 sizeof(tbuf));
559 
560 				snprintf(obuf, sizeof(obuf), "%s%s%s%s: %s %s",
561 					 first ? "" : "\n", nbuf,
562 					 view != NULL ? "/" : "",
563 					 view != NULL ? view : "",
564 					 n->expiry <= now ? "expired"
565 							  : "expiry",
566 					 tbuf);
567 				first = false;
568 				result = putstr(buf, obuf);
569 				if (result != ISC_R_SUCCESS) {
570 					goto cleanup;
571 				}
572 			}
573 		}
574 		result = dns_rbtnodechain_next(&chain, NULL, NULL);
575 		if (result != ISC_R_SUCCESS && result != DNS_R_NEWORIGIN) {
576 			if (result == ISC_R_NOMORE) {
577 				result = ISC_R_SUCCESS;
578 			}
579 			break;
580 		}
581 	}
582 
583 cleanup:
584 	dns_rbtnodechain_invalidate(&chain);
585 	RWUNLOCK(&ntatable->rwlock, isc_rwlocktype_read);
586 	return (result);
587 }
588 
589 isc_result_t
590 dns_ntatable_dump(dns_ntatable_t *ntatable, FILE *fp) {
591 	isc_result_t result;
592 	isc_buffer_t *text = NULL;
593 	int len = 4096;
594 
595 	isc_buffer_allocate(ntatable->view->mctx, &text, len);
596 
597 	result = dns_ntatable_totext(ntatable, NULL, &text);
598 
599 	if (isc_buffer_usedlength(text) != 0) {
600 		(void)putstr(&text, "\n");
601 	} else if (result == ISC_R_SUCCESS) {
602 		(void)putstr(&text, "none");
603 	} else {
604 		(void)putstr(&text, "could not dump NTA table: ");
605 		(void)putstr(&text, isc_result_totext(result));
606 	}
607 
608 	fprintf(fp, "%.*s", (int)isc_buffer_usedlength(text),
609 		(char *)isc_buffer_base(text));
610 	isc_buffer_free(&text);
611 	return (result);
612 }
613 
614 isc_result_t
615 dns_ntatable_save(dns_ntatable_t *ntatable, FILE *fp) {
616 	isc_result_t result;
617 	dns_rbtnode_t *node;
618 	dns_rbtnodechain_t chain;
619 	isc_stdtime_t now;
620 	bool written = false;
621 
622 	REQUIRE(VALID_NTATABLE(ntatable));
623 
624 	isc_stdtime_get(&now);
625 
626 	RWLOCK(&ntatable->rwlock, isc_rwlocktype_read);
627 	dns_rbtnodechain_init(&chain);
628 	result = dns_rbtnodechain_first(&chain, ntatable->table, NULL, NULL);
629 	if (result != ISC_R_SUCCESS && result != DNS_R_NEWORIGIN) {
630 		goto cleanup;
631 	}
632 
633 	for (;;) {
634 		dns_rbtnodechain_current(&chain, NULL, NULL, &node);
635 		if (node->data != NULL) {
636 			isc_buffer_t b;
637 			char nbuf[DNS_NAME_FORMATSIZE + 1], tbuf[80];
638 			dns_fixedname_t fn;
639 			dns_name_t *name;
640 			dns_nta_t *n = (dns_nta_t *)node->data;
641 
642 			/*
643 			 * Skip this node if the expiry is already in the
644 			 * past, or if this is a "validate-except" entry.
645 			 */
646 			if (n->expiry <= now || n->expiry == 0xffffffffU) {
647 				goto skip;
648 			}
649 
650 			name = dns_fixedname_initname(&fn);
651 			dns_rbt_fullnamefromnode(node, name);
652 
653 			isc_buffer_init(&b, nbuf, sizeof(nbuf));
654 			result = dns_name_totext(name, false, &b);
655 			if (result != ISC_R_SUCCESS) {
656 				goto skip;
657 			}
658 
659 			/* Zero terminate. */
660 			isc_buffer_putuint8(&b, 0);
661 
662 			isc_buffer_init(&b, tbuf, sizeof(tbuf));
663 			dns_time32_totext(n->expiry, &b);
664 
665 			/* Zero terminate. */
666 			isc_buffer_putuint8(&b, 0);
667 
668 			fprintf(fp, "%s %s %s\n", nbuf,
669 				n->forced ? "forced" : "regular", tbuf);
670 			written = true;
671 		}
672 	skip:
673 		result = dns_rbtnodechain_next(&chain, NULL, NULL);
674 		if (result != ISC_R_SUCCESS && result != DNS_R_NEWORIGIN) {
675 			if (result == ISC_R_NOMORE) {
676 				result = ISC_R_SUCCESS;
677 			}
678 			break;
679 		}
680 	}
681 
682 cleanup:
683 	dns_rbtnodechain_invalidate(&chain);
684 	RWUNLOCK(&ntatable->rwlock, isc_rwlocktype_read);
685 
686 	if (result != ISC_R_SUCCESS) {
687 		return (result);
688 	} else {
689 		return (written ? ISC_R_SUCCESS : ISC_R_NOTFOUND);
690 	}
691 }
692 
693 void
694 dns_ntatable_shutdown(dns_ntatable_t *ntatable) {
695 	isc_result_t result;
696 	dns_rbtnode_t *node;
697 	dns_rbtnodechain_t chain;
698 
699 	REQUIRE(VALID_NTATABLE(ntatable));
700 
701 	RWLOCK(&ntatable->rwlock, isc_rwlocktype_write);
702 	ntatable->shuttingdown = true;
703 
704 	dns_rbtnodechain_init(&chain);
705 	result = dns_rbtnodechain_first(&chain, ntatable->table, NULL, NULL);
706 	while (result == ISC_R_SUCCESS || result == DNS_R_NEWORIGIN) {
707 		dns_rbtnodechain_current(&chain, NULL, NULL, &node);
708 		if (node->data != NULL) {
709 			dns_nta_t *nta = (dns_nta_t *)node->data;
710 			if (nta->timer != NULL) {
711 				(void)isc_timer_reset(nta->timer,
712 						      isc_timertype_inactive,
713 						      NULL, NULL, true);
714 			}
715 		}
716 		result = dns_rbtnodechain_next(&chain, NULL, NULL);
717 	}
718 
719 	dns_rbtnodechain_invalidate(&chain);
720 	RWUNLOCK(&ntatable->rwlock, isc_rwlocktype_write);
721 }
722