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