check-tool.c revision 1.10
1/*	$NetBSD: check-tool.c,v 1.10 2024/02/21 22:50:59 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 <stdio.h>
21
22#include <isc/buffer.h>
23#include <isc/log.h>
24#include <isc/mem.h>
25#include <isc/net.h>
26#include <isc/netdb.h>
27#include <isc/print.h>
28#include <isc/region.h>
29#include <isc/result.h>
30#include <isc/stdio.h>
31#include <isc/string.h>
32#include <isc/symtab.h>
33#include <isc/types.h>
34#include <isc/util.h>
35
36#include <dns/db.h>
37#include <dns/dbiterator.h>
38#include <dns/fixedname.h>
39#include <dns/log.h>
40#include <dns/name.h>
41#include <dns/rdata.h>
42#include <dns/rdataclass.h>
43#include <dns/rdataset.h>
44#include <dns/rdatasetiter.h>
45#include <dns/rdatatype.h>
46#include <dns/types.h>
47#include <dns/zone.h>
48
49#include <isccfg/log.h>
50
51#include <ns/log.h>
52
53#include "check-tool.h"
54
55#ifndef CHECK_SIBLING
56#define CHECK_SIBLING 1
57#endif /* ifndef CHECK_SIBLING */
58
59#ifndef CHECK_LOCAL
60#define CHECK_LOCAL 1
61#endif /* ifndef CHECK_LOCAL */
62
63#define CHECK(r)                             \
64	do {                                 \
65		result = (r);                \
66		if (result != ISC_R_SUCCESS) \
67			goto cleanup;        \
68	} while (0)
69
70#define ERR_IS_CNAME	   1
71#define ERR_NO_ADDRESSES   2
72#define ERR_LOOKUP_FAILURE 3
73#define ERR_EXTRA_A	   4
74#define ERR_EXTRA_AAAA	   5
75#define ERR_MISSING_GLUE   5
76#define ERR_IS_MXCNAME	   6
77#define ERR_IS_SRVCNAME	   7
78
79static const char *dbtype[] = { "rbt" };
80
81int debug = 0;
82const char *journal = NULL;
83bool nomerge = true;
84#if CHECK_LOCAL
85bool docheckmx = true;
86bool dochecksrv = true;
87bool docheckns = true;
88#else  /* if CHECK_LOCAL */
89bool docheckmx = false;
90bool dochecksrv = false;
91bool docheckns = false;
92#endif /* if CHECK_LOCAL */
93dns_zoneopt_t zone_options = DNS_ZONEOPT_CHECKNS | DNS_ZONEOPT_CHECKMX |
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_CHECKWILDCARD |
100			     DNS_ZONEOPT_WARNMXCNAME | DNS_ZONEOPT_WARNSRVCNAME;
101
102/*
103 * This needs to match the list in bin/named/log.c.
104 */
105static isc_logcategory_t categories[] = { { "", 0 },
106					  { "unmatched", 0 },
107					  { NULL, 0 } };
108
109static isc_symtab_t *symtab = NULL;
110static isc_mem_t *sym_mctx;
111
112static void
113freekey(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
119static void
120add(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
146static bool
147logged(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
161static bool
162checkns(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
286checkaaaa:
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
320checkmissing:
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
379static bool
380checkmx(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
466static bool
467checksrv(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
553isc_result_t
554setup_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 */
582isc_result_t
583load_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	CHECK(dns_zone_create(&zone, mctx));
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	DE_CONST(classname, region.base);
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
649cleanup:
650	if (zone != NULL) {
651		dns_zone_detach(&zone);
652	}
653	return (result);
654}
655
656/*% dump the zone */
657isc_result_t
658dump_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