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, ®ion)); 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