xref: /netbsd-src/external/mpl/bind/dist/bin/check/check-tool.c (revision bcda20f65a8566e103791ec395f7f499ef322704)
1 /*	$NetBSD: check-tool.c,v 1.11 2025/01/26 16:24:31 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 <netdb.h>
20 #include <stdbool.h>
21 #include <stdio.h>
22 
23 #include <isc/buffer.h>
24 #include <isc/log.h>
25 #include <isc/mem.h>
26 #include <isc/net.h>
27 #include <isc/region.h>
28 #include <isc/result.h>
29 #include <isc/stdio.h>
30 #include <isc/string.h>
31 #include <isc/symtab.h>
32 #include <isc/types.h>
33 #include <isc/util.h>
34 
35 #include <dns/db.h>
36 #include <dns/dbiterator.h>
37 #include <dns/fixedname.h>
38 #include <dns/log.h>
39 #include <dns/name.h>
40 #include <dns/rdata.h>
41 #include <dns/rdataclass.h>
42 #include <dns/rdataset.h>
43 #include <dns/rdatasetiter.h>
44 #include <dns/rdatatype.h>
45 #include <dns/types.h>
46 #include <dns/zone.h>
47 
48 #include <isccfg/log.h>
49 
50 #include <ns/log.h>
51 
52 #include "check-tool.h"
53 
54 #ifndef CHECK_SIBLING
55 #define CHECK_SIBLING 1
56 #endif /* ifndef CHECK_SIBLING */
57 
58 #ifndef CHECK_LOCAL
59 #define CHECK_LOCAL 1
60 #endif /* ifndef CHECK_LOCAL */
61 
62 #define CHECK(r)                             \
63 	do {                                 \
64 		result = (r);                \
65 		if (result != ISC_R_SUCCESS) \
66 			goto cleanup;        \
67 	} while (0)
68 
69 #define ERR_IS_CNAME	   1
70 #define ERR_NO_ADDRESSES   2
71 #define ERR_LOOKUP_FAILURE 3
72 #define ERR_EXTRA_A	   4
73 #define ERR_EXTRA_AAAA	   5
74 #define ERR_MISSING_GLUE   5
75 #define ERR_IS_MXCNAME	   6
76 #define ERR_IS_SRVCNAME	   7
77 
78 static const char *dbtype[] = { ZONEDB_DEFAULT };
79 
80 int debug = 0;
81 const char *journal = NULL;
82 bool nomerge = true;
83 #if CHECK_LOCAL
84 bool docheckmx = true;
85 bool dochecksrv = true;
86 bool docheckns = true;
87 #else  /* if CHECK_LOCAL */
88 bool docheckmx = false;
89 bool dochecksrv = false;
90 bool docheckns = false;
91 #endif /* if CHECK_LOCAL */
92 dns_zoneopt_t zone_options = DNS_ZONEOPT_CHECKNS | DNS_ZONEOPT_CHECKMX |
93 			     DNS_ZONEOPT_CHECKDUPRR | DNS_ZONEOPT_CHECKSPF |
94 			     DNS_ZONEOPT_MANYERRORS | DNS_ZONEOPT_CHECKNAMES |
95 			     DNS_ZONEOPT_CHECKINTEGRITY |
96 #if CHECK_SIBLING
97 			     DNS_ZONEOPT_CHECKSIBLING |
98 #endif /* if CHECK_SIBLING */
99 			     DNS_ZONEOPT_CHECKSVCB | DNS_ZONEOPT_CHECKWILDCARD |
100 			     DNS_ZONEOPT_WARNMXCNAME | DNS_ZONEOPT_WARNSRVCNAME;
101 
102 /*
103  * This needs to match the list in bin/named/log.c.
104  */
105 static isc_logcategory_t categories[] = { { "", 0 },
106 					  { "unmatched", 0 },
107 					  { NULL, 0 } };
108 
109 static isc_symtab_t *symtab = NULL;
110 static isc_mem_t *sym_mctx;
111 
112 static void
113 freekey(char *key, unsigned int type, isc_symvalue_t value, void *userarg) {
114 	UNUSED(type);
115 	UNUSED(value);
116 	isc_mem_free(userarg, key);
117 }
118 
119 static void
120 add(char *key, int value) {
121 	isc_result_t result;
122 	isc_symvalue_t symvalue;
123 
124 	if (sym_mctx == NULL) {
125 		isc_mem_create(&sym_mctx);
126 	}
127 
128 	if (symtab == NULL) {
129 		result = isc_symtab_create(sym_mctx, 100, freekey, sym_mctx,
130 					   false, &symtab);
131 		if (result != ISC_R_SUCCESS) {
132 			return;
133 		}
134 	}
135 
136 	key = isc_mem_strdup(sym_mctx, key);
137 
138 	symvalue.as_pointer = NULL;
139 	result = isc_symtab_define(symtab, key, value, symvalue,
140 				   isc_symexists_reject);
141 	if (result != ISC_R_SUCCESS) {
142 		isc_mem_free(sym_mctx, key);
143 	}
144 }
145 
146 static bool
147 logged(char *key, int value) {
148 	isc_result_t result;
149 
150 	if (symtab == NULL) {
151 		return false;
152 	}
153 
154 	result = isc_symtab_lookup(symtab, key, value, NULL);
155 	if (result == ISC_R_SUCCESS) {
156 		return true;
157 	}
158 	return false;
159 }
160 
161 static bool
162 checkns(dns_zone_t *zone, const dns_name_t *name, const dns_name_t *owner,
163 	dns_rdataset_t *a, dns_rdataset_t *aaaa) {
164 	dns_rdataset_t *rdataset;
165 	dns_rdata_t rdata = DNS_RDATA_INIT;
166 	struct addrinfo hints, *ai, *cur;
167 	char namebuf[DNS_NAME_FORMATSIZE + 1];
168 	char ownerbuf[DNS_NAME_FORMATSIZE];
169 	char addrbuf[sizeof("xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:123.123.123.123")];
170 	bool answer = true;
171 	bool match;
172 	const char *type;
173 	void *ptr = NULL;
174 	int result;
175 
176 	REQUIRE(a == NULL || !dns_rdataset_isassociated(a) ||
177 		a->type == dns_rdatatype_a);
178 	REQUIRE(aaaa == NULL || !dns_rdataset_isassociated(aaaa) ||
179 		aaaa->type == dns_rdatatype_aaaa);
180 
181 	if (a == NULL || aaaa == NULL) {
182 		return answer;
183 	}
184 
185 	memset(&hints, 0, sizeof(hints));
186 	hints.ai_flags = AI_CANONNAME;
187 	hints.ai_family = PF_UNSPEC;
188 	hints.ai_socktype = SOCK_STREAM;
189 	hints.ai_protocol = IPPROTO_TCP;
190 
191 	dns_name_format(name, namebuf, sizeof(namebuf) - 1);
192 	/*
193 	 * Turn off search.
194 	 */
195 	if (dns_name_countlabels(name) > 1U) {
196 		strlcat(namebuf, ".", sizeof(namebuf));
197 	}
198 	dns_name_format(owner, ownerbuf, sizeof(ownerbuf));
199 
200 	result = getaddrinfo(namebuf, NULL, &hints, &ai);
201 	dns_name_format(name, namebuf, sizeof(namebuf) - 1);
202 	switch (result) {
203 	case 0:
204 		/*
205 		 * Work around broken getaddrinfo() implementations that
206 		 * fail to set ai_canonname on first entry.
207 		 */
208 		cur = ai;
209 		while (cur != NULL && cur->ai_canonname == NULL &&
210 		       cur->ai_next != NULL)
211 		{
212 			cur = cur->ai_next;
213 		}
214 		if (cur != NULL && cur->ai_canonname != NULL &&
215 		    strcasecmp(cur->ai_canonname, namebuf) != 0 &&
216 		    !logged(namebuf, ERR_IS_CNAME))
217 		{
218 			dns_zone_log(zone, ISC_LOG_ERROR,
219 				     "%s/NS '%s' (out of zone) "
220 				     "is a CNAME '%s' (illegal)",
221 				     ownerbuf, namebuf, cur->ai_canonname);
222 			/* XXX950 make fatal for 9.5.0 */
223 			/* answer = false; */
224 			add(namebuf, ERR_IS_CNAME);
225 		}
226 		break;
227 	case EAI_NONAME:
228 #if defined(EAI_NODATA) && (EAI_NODATA != EAI_NONAME)
229 	case EAI_NODATA:
230 #endif /* if defined(EAI_NODATA) && (EAI_NODATA != EAI_NONAME) */
231 		if (!logged(namebuf, ERR_NO_ADDRESSES)) {
232 			dns_zone_log(zone, ISC_LOG_ERROR,
233 				     "%s/NS '%s' (out of zone) "
234 				     "has no addresses records (A or AAAA)",
235 				     ownerbuf, namebuf);
236 			add(namebuf, ERR_NO_ADDRESSES);
237 		}
238 		/* XXX950 make fatal for 9.5.0 */
239 		return true;
240 
241 	default:
242 		if (!logged(namebuf, ERR_LOOKUP_FAILURE)) {
243 			dns_zone_log(zone, ISC_LOG_WARNING,
244 				     "getaddrinfo(%s) failed: %s", namebuf,
245 				     gai_strerror(result));
246 			add(namebuf, ERR_LOOKUP_FAILURE);
247 		}
248 		return true;
249 	}
250 
251 	/*
252 	 * Check that all glue records really exist.
253 	 */
254 	if (!dns_rdataset_isassociated(a)) {
255 		goto checkaaaa;
256 	}
257 	result = dns_rdataset_first(a);
258 	while (result == ISC_R_SUCCESS) {
259 		dns_rdataset_current(a, &rdata);
260 		match = false;
261 		for (cur = ai; cur != NULL; cur = cur->ai_next) {
262 			if (cur->ai_family != AF_INET) {
263 				continue;
264 			}
265 			ptr = &((struct sockaddr_in *)(cur->ai_addr))->sin_addr;
266 			if (memcmp(ptr, rdata.data, rdata.length) == 0) {
267 				match = true;
268 				break;
269 			}
270 		}
271 		if (!match && !logged(namebuf, ERR_EXTRA_A)) {
272 			dns_zone_log(zone, ISC_LOG_ERROR,
273 				     "%s/NS '%s' "
274 				     "extra GLUE A record (%s)",
275 				     ownerbuf, namebuf,
276 				     inet_ntop(AF_INET, rdata.data, addrbuf,
277 					       sizeof(addrbuf)));
278 			add(namebuf, ERR_EXTRA_A);
279 			/* XXX950 make fatal for 9.5.0 */
280 			/* answer = false; */
281 		}
282 		dns_rdata_reset(&rdata);
283 		result = dns_rdataset_next(a);
284 	}
285 
286 checkaaaa:
287 	if (!dns_rdataset_isassociated(aaaa)) {
288 		goto checkmissing;
289 	}
290 	result = dns_rdataset_first(aaaa);
291 	while (result == ISC_R_SUCCESS) {
292 		dns_rdataset_current(aaaa, &rdata);
293 		match = false;
294 		for (cur = ai; cur != NULL; cur = cur->ai_next) {
295 			if (cur->ai_family != AF_INET6) {
296 				continue;
297 			}
298 			ptr = &((struct sockaddr_in6 *)(cur->ai_addr))
299 				       ->sin6_addr;
300 			if (memcmp(ptr, rdata.data, rdata.length) == 0) {
301 				match = true;
302 				break;
303 			}
304 		}
305 		if (!match && !logged(namebuf, ERR_EXTRA_AAAA)) {
306 			dns_zone_log(zone, ISC_LOG_ERROR,
307 				     "%s/NS '%s' "
308 				     "extra GLUE AAAA record (%s)",
309 				     ownerbuf, namebuf,
310 				     inet_ntop(AF_INET6, rdata.data, addrbuf,
311 					       sizeof(addrbuf)));
312 			add(namebuf, ERR_EXTRA_AAAA);
313 			/* XXX950 make fatal for 9.5.0. */
314 			/* answer = false; */
315 		}
316 		dns_rdata_reset(&rdata);
317 		result = dns_rdataset_next(aaaa);
318 	}
319 
320 checkmissing:
321 	/*
322 	 * Check that all addresses appear in the glue.
323 	 */
324 	if (!logged(namebuf, ERR_MISSING_GLUE)) {
325 		bool missing_glue = false;
326 		for (cur = ai; cur != NULL; cur = cur->ai_next) {
327 			switch (cur->ai_family) {
328 			case AF_INET:
329 				rdataset = a;
330 				ptr = &((struct sockaddr_in *)(cur->ai_addr))
331 					       ->sin_addr;
332 				type = "A";
333 				break;
334 			case AF_INET6:
335 				rdataset = aaaa;
336 				ptr = &((struct sockaddr_in6 *)(cur->ai_addr))
337 					       ->sin6_addr;
338 				type = "AAAA";
339 				break;
340 			default:
341 				continue;
342 			}
343 			match = false;
344 			if (dns_rdataset_isassociated(rdataset)) {
345 				result = dns_rdataset_first(rdataset);
346 			} else {
347 				result = ISC_R_FAILURE;
348 			}
349 			while (result == ISC_R_SUCCESS && !match) {
350 				dns_rdataset_current(rdataset, &rdata);
351 				if (memcmp(ptr, rdata.data, rdata.length) == 0)
352 				{
353 					match = true;
354 				}
355 				dns_rdata_reset(&rdata);
356 				result = dns_rdataset_next(rdataset);
357 			}
358 			if (!match) {
359 				dns_zone_log(zone, ISC_LOG_ERROR,
360 					     "%s/NS '%s' "
361 					     "missing GLUE %s record (%s)",
362 					     ownerbuf, namebuf, type,
363 					     inet_ntop(cur->ai_family, ptr,
364 						       addrbuf,
365 						       sizeof(addrbuf)));
366 				/* XXX950 make fatal for 9.5.0. */
367 				/* answer = false; */
368 				missing_glue = true;
369 			}
370 		}
371 		if (missing_glue) {
372 			add(namebuf, ERR_MISSING_GLUE);
373 		}
374 	}
375 	freeaddrinfo(ai);
376 	return answer;
377 }
378 
379 static bool
380 checkmx(dns_zone_t *zone, const dns_name_t *name, const dns_name_t *owner) {
381 	struct addrinfo hints, *ai, *cur;
382 	char namebuf[DNS_NAME_FORMATSIZE + 1];
383 	char ownerbuf[DNS_NAME_FORMATSIZE];
384 	int result;
385 	int level = ISC_LOG_ERROR;
386 	bool answer = true;
387 
388 	memset(&hints, 0, sizeof(hints));
389 	hints.ai_flags = AI_CANONNAME;
390 	hints.ai_family = PF_UNSPEC;
391 	hints.ai_socktype = SOCK_STREAM;
392 	hints.ai_protocol = IPPROTO_TCP;
393 
394 	dns_name_format(name, namebuf, sizeof(namebuf) - 1);
395 	/*
396 	 * Turn off search.
397 	 */
398 	if (dns_name_countlabels(name) > 1U) {
399 		strlcat(namebuf, ".", sizeof(namebuf));
400 	}
401 	dns_name_format(owner, ownerbuf, sizeof(ownerbuf));
402 
403 	result = getaddrinfo(namebuf, NULL, &hints, &ai);
404 	dns_name_format(name, namebuf, sizeof(namebuf) - 1);
405 	switch (result) {
406 	case 0:
407 		/*
408 		 * Work around broken getaddrinfo() implementations that
409 		 * fail to set ai_canonname on first entry.
410 		 */
411 		cur = ai;
412 		while (cur != NULL && cur->ai_canonname == NULL &&
413 		       cur->ai_next != NULL)
414 		{
415 			cur = cur->ai_next;
416 		}
417 		if (cur != NULL && cur->ai_canonname != NULL &&
418 		    strcasecmp(cur->ai_canonname, namebuf) != 0)
419 		{
420 			if ((zone_options & DNS_ZONEOPT_WARNMXCNAME) != 0) {
421 				level = ISC_LOG_WARNING;
422 			}
423 			if ((zone_options & DNS_ZONEOPT_IGNOREMXCNAME) == 0) {
424 				if (!logged(namebuf, ERR_IS_MXCNAME)) {
425 					dns_zone_log(zone, level,
426 						     "%s/MX '%s' (out of zone)"
427 						     " is a CNAME '%s' "
428 						     "(illegal)",
429 						     ownerbuf, namebuf,
430 						     cur->ai_canonname);
431 					add(namebuf, ERR_IS_MXCNAME);
432 				}
433 				if (level == ISC_LOG_ERROR) {
434 					answer = false;
435 				}
436 			}
437 		}
438 		freeaddrinfo(ai);
439 		return answer;
440 
441 	case EAI_NONAME:
442 #if defined(EAI_NODATA) && (EAI_NODATA != EAI_NONAME)
443 	case EAI_NODATA:
444 #endif /* if defined(EAI_NODATA) && (EAI_NODATA != EAI_NONAME) */
445 		if (!logged(namebuf, ERR_NO_ADDRESSES)) {
446 			dns_zone_log(zone, ISC_LOG_ERROR,
447 				     "%s/MX '%s' (out of zone) "
448 				     "has no addresses records (A or AAAA)",
449 				     ownerbuf, namebuf);
450 			add(namebuf, ERR_NO_ADDRESSES);
451 		}
452 		/* XXX950 make fatal for 9.5.0. */
453 		return true;
454 
455 	default:
456 		if (!logged(namebuf, ERR_LOOKUP_FAILURE)) {
457 			dns_zone_log(zone, ISC_LOG_WARNING,
458 				     "getaddrinfo(%s) failed: %s", namebuf,
459 				     gai_strerror(result));
460 			add(namebuf, ERR_LOOKUP_FAILURE);
461 		}
462 		return true;
463 	}
464 }
465 
466 static bool
467 checksrv(dns_zone_t *zone, const dns_name_t *name, const dns_name_t *owner) {
468 	struct addrinfo hints, *ai, *cur;
469 	char namebuf[DNS_NAME_FORMATSIZE + 1];
470 	char ownerbuf[DNS_NAME_FORMATSIZE];
471 	int result;
472 	int level = ISC_LOG_ERROR;
473 	bool answer = true;
474 
475 	memset(&hints, 0, sizeof(hints));
476 	hints.ai_flags = AI_CANONNAME;
477 	hints.ai_family = PF_UNSPEC;
478 	hints.ai_socktype = SOCK_STREAM;
479 	hints.ai_protocol = IPPROTO_TCP;
480 
481 	dns_name_format(name, namebuf, sizeof(namebuf) - 1);
482 	/*
483 	 * Turn off search.
484 	 */
485 	if (dns_name_countlabels(name) > 1U) {
486 		strlcat(namebuf, ".", sizeof(namebuf));
487 	}
488 	dns_name_format(owner, ownerbuf, sizeof(ownerbuf));
489 
490 	result = getaddrinfo(namebuf, NULL, &hints, &ai);
491 	dns_name_format(name, namebuf, sizeof(namebuf) - 1);
492 	switch (result) {
493 	case 0:
494 		/*
495 		 * Work around broken getaddrinfo() implementations that
496 		 * fail to set ai_canonname on first entry.
497 		 */
498 		cur = ai;
499 		while (cur != NULL && cur->ai_canonname == NULL &&
500 		       cur->ai_next != NULL)
501 		{
502 			cur = cur->ai_next;
503 		}
504 		if (cur != NULL && cur->ai_canonname != NULL &&
505 		    strcasecmp(cur->ai_canonname, namebuf) != 0)
506 		{
507 			if ((zone_options & DNS_ZONEOPT_WARNSRVCNAME) != 0) {
508 				level = ISC_LOG_WARNING;
509 			}
510 			if ((zone_options & DNS_ZONEOPT_IGNORESRVCNAME) == 0) {
511 				if (!logged(namebuf, ERR_IS_SRVCNAME)) {
512 					dns_zone_log(zone, level,
513 						     "%s/SRV '%s'"
514 						     " (out of zone) is a "
515 						     "CNAME '%s' (illegal)",
516 						     ownerbuf, namebuf,
517 						     cur->ai_canonname);
518 					add(namebuf, ERR_IS_SRVCNAME);
519 				}
520 				if (level == ISC_LOG_ERROR) {
521 					answer = false;
522 				}
523 			}
524 		}
525 		freeaddrinfo(ai);
526 		return answer;
527 
528 	case EAI_NONAME:
529 #if defined(EAI_NODATA) && (EAI_NODATA != EAI_NONAME)
530 	case EAI_NODATA:
531 #endif /* if defined(EAI_NODATA) && (EAI_NODATA != EAI_NONAME) */
532 		if (!logged(namebuf, ERR_NO_ADDRESSES)) {
533 			dns_zone_log(zone, ISC_LOG_ERROR,
534 				     "%s/SRV '%s' (out of zone) "
535 				     "has no addresses records (A or AAAA)",
536 				     ownerbuf, namebuf);
537 			add(namebuf, ERR_NO_ADDRESSES);
538 		}
539 		/* XXX950 make fatal for 9.5.0. */
540 		return true;
541 
542 	default:
543 		if (!logged(namebuf, ERR_LOOKUP_FAILURE)) {
544 			dns_zone_log(zone, ISC_LOG_WARNING,
545 				     "getaddrinfo(%s) failed: %s", namebuf,
546 				     gai_strerror(result));
547 			add(namebuf, ERR_LOOKUP_FAILURE);
548 		}
549 		return true;
550 	}
551 }
552 
553 isc_result_t
554 setup_logging(isc_mem_t *mctx, FILE *errout, isc_log_t **logp) {
555 	isc_logdestination_t destination;
556 	isc_logconfig_t *logconfig = NULL;
557 	isc_log_t *log = NULL;
558 
559 	isc_log_create(mctx, &log, &logconfig);
560 	isc_log_registercategories(log, categories);
561 	isc_log_setcontext(log);
562 	dns_log_init(log);
563 	dns_log_setcontext(log);
564 	cfg_log_init(log);
565 	ns_log_init(log);
566 
567 	destination.file.stream = errout;
568 	destination.file.name = NULL;
569 	destination.file.versions = ISC_LOG_ROLLNEVER;
570 	destination.file.maximum_size = 0;
571 	isc_log_createchannel(logconfig, "stderr", ISC_LOG_TOFILEDESC,
572 			      ISC_LOG_DYNAMIC, &destination, 0);
573 
574 	RUNTIME_CHECK(isc_log_usechannel(logconfig, "stderr", NULL, NULL) ==
575 		      ISC_R_SUCCESS);
576 
577 	*logp = log;
578 	return ISC_R_SUCCESS;
579 }
580 
581 /*% load the zone */
582 isc_result_t
583 load_zone(isc_mem_t *mctx, const char *zonename, const char *filename,
584 	  dns_masterformat_t fileformat, const char *classname,
585 	  dns_ttl_t maxttl, dns_zone_t **zonep) {
586 	isc_result_t result;
587 	dns_rdataclass_t rdclass;
588 	isc_textregion_t region;
589 	isc_buffer_t buffer;
590 	dns_fixedname_t fixorigin;
591 	dns_name_t *origin;
592 	dns_zone_t *zone = NULL;
593 
594 	REQUIRE(zonep == NULL || *zonep == NULL);
595 
596 	if (debug) {
597 		fprintf(stderr, "loading \"%s\" from \"%s\" class \"%s\"\n",
598 			zonename, filename, classname);
599 	}
600 
601 	dns_zone_create(&zone, mctx, 0);
602 
603 	dns_zone_settype(zone, dns_zone_primary);
604 
605 	isc_buffer_constinit(&buffer, zonename, strlen(zonename));
606 	isc_buffer_add(&buffer, strlen(zonename));
607 	origin = dns_fixedname_initname(&fixorigin);
608 	CHECK(dns_name_fromtext(origin, &buffer, dns_rootname, 0, NULL));
609 	CHECK(dns_zone_setorigin(zone, origin));
610 	dns_zone_setdbtype(zone, 1, (const char *const *)dbtype);
611 	if (strcmp(filename, "-") == 0) {
612 		CHECK(dns_zone_setstream(zone, stdin, fileformat,
613 					 &dns_master_style_default));
614 	} else {
615 		CHECK(dns_zone_setfile(zone, filename, fileformat,
616 				       &dns_master_style_default));
617 	}
618 	if (journal != NULL) {
619 		CHECK(dns_zone_setjournal(zone, journal));
620 	}
621 
622 	region.base = UNCONST(classname);
623 	region.length = strlen(classname);
624 	CHECK(dns_rdataclass_fromtext(&rdclass, &region));
625 
626 	dns_zone_setclass(zone, rdclass);
627 	dns_zone_setoption(zone, zone_options, true);
628 	dns_zone_setoption(zone, DNS_ZONEOPT_NOMERGE, nomerge);
629 
630 	dns_zone_setmaxttl(zone, maxttl);
631 
632 	if (docheckmx) {
633 		dns_zone_setcheckmx(zone, checkmx);
634 	}
635 	if (docheckns) {
636 		dns_zone_setcheckns(zone, checkns);
637 	}
638 	if (dochecksrv) {
639 		dns_zone_setchecksrv(zone, checksrv);
640 	}
641 
642 	CHECK(dns_zone_load(zone, false));
643 
644 	if (zonep != NULL) {
645 		*zonep = zone;
646 		zone = NULL;
647 	}
648 
649 cleanup:
650 	if (zone != NULL) {
651 		dns_zone_detach(&zone);
652 	}
653 	return result;
654 }
655 
656 /*% dump the zone */
657 isc_result_t
658 dump_zone(const char *zonename, dns_zone_t *zone, const char *filename,
659 	  dns_masterformat_t fileformat, const dns_master_style_t *style,
660 	  const uint32_t rawversion) {
661 	isc_result_t result;
662 	FILE *output = stdout;
663 	const char *flags;
664 
665 	flags = (fileformat == dns_masterformat_text) ? "w" : "wb";
666 
667 	if (debug) {
668 		if (filename != NULL && strcmp(filename, "-") != 0) {
669 			fprintf(stderr, "dumping \"%s\" to \"%s\"\n", zonename,
670 				filename);
671 		} else {
672 			fprintf(stderr, "dumping \"%s\"\n", zonename);
673 		}
674 	}
675 
676 	if (filename != NULL && strcmp(filename, "-") != 0) {
677 		result = isc_stdio_open(filename, flags, &output);
678 
679 		if (result != ISC_R_SUCCESS) {
680 			fprintf(stderr,
681 				"could not open output "
682 				"file \"%s\" for writing\n",
683 				filename);
684 			return ISC_R_FAILURE;
685 		}
686 	}
687 
688 	result = dns_zone_dumptostream(zone, output, fileformat, style,
689 				       rawversion);
690 	if (output != stdout) {
691 		(void)isc_stdio_close(output);
692 	}
693 
694 	return result;
695 }
696