1 /* $NetBSD: named-checkzone.c,v 1.12 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 <stdbool.h> 20 #include <stdlib.h> 21 22 #include <isc/attributes.h> 23 #include <isc/commandline.h> 24 #include <isc/dir.h> 25 #include <isc/file.h> 26 #include <isc/hash.h> 27 #include <isc/log.h> 28 #include <isc/mem.h> 29 #include <isc/result.h> 30 #include <isc/string.h> 31 #include <isc/timer.h> 32 #include <isc/util.h> 33 34 #include <dns/db.h> 35 #include <dns/fixedname.h> 36 #include <dns/log.h> 37 #include <dns/master.h> 38 #include <dns/masterdump.h> 39 #include <dns/name.h> 40 #include <dns/rdataclass.h> 41 #include <dns/rdataset.h> 42 #include <dns/types.h> 43 #include <dns/zone.h> 44 45 #include "check-tool.h" 46 47 static int quiet = 0; 48 static isc_mem_t *mctx = NULL; 49 dns_zone_t *zone = NULL; 50 dns_zonetype_t zonetype = dns_zone_primary; 51 static int dumpzone = 0; 52 static const char *output_filename; 53 static const char *prog_name = NULL; 54 static const dns_master_style_t *outputstyle = NULL; 55 static enum { progmode_check, progmode_compile } progmode; 56 57 #define ERRRET(result, function) \ 58 do { \ 59 if (result != ISC_R_SUCCESS) { \ 60 if (!quiet) \ 61 fprintf(stderr, "%s() returned %s\n", \ 62 function, isc_result_totext(result)); \ 63 return (result); \ 64 } \ 65 } while (0) 66 67 noreturn static void 68 usage(void); 69 70 static void 71 usage(void) { 72 fprintf(stderr, 73 "usage: %s [-djqvD] [-c class] " 74 "[-f inputformat] [-F outputformat] [-J filename] " 75 "[-s (full|relative)] [-t directory] [-w directory] " 76 "[-k (ignore|warn|fail)] [-m (ignore|warn|fail)] " 77 "[-n (ignore|warn|fail)] [-r (ignore|warn|fail)] " 78 "[-i (full|full-sibling|local|local-sibling|none)] " 79 "[-M (ignore|warn|fail)] [-S (ignore|warn|fail)] " 80 "[-W (ignore|warn)] " 81 "%s zonename [ (filename|-) ]\n", 82 prog_name, 83 progmode == progmode_check ? "[-o filename]" : "-o filename"); 84 exit(EXIT_FAILURE); 85 } 86 87 static void 88 destroy(void) { 89 if (zone != NULL) { 90 dns_zone_detach(&zone); 91 } 92 } 93 94 /*% main processing routine */ 95 int 96 main(int argc, char **argv) { 97 int c; 98 char *origin = NULL; 99 const char *filename = NULL; 100 isc_log_t *lctx = NULL; 101 isc_result_t result; 102 char classname_in[] = "IN"; 103 char *classname = classname_in; 104 const char *workdir = NULL; 105 const char *inputformatstr = NULL; 106 const char *outputformatstr = NULL; 107 dns_masterformat_t inputformat = dns_masterformat_text; 108 dns_masterformat_t outputformat = dns_masterformat_text; 109 dns_masterrawheader_t header; 110 uint32_t rawversion = 1, serialnum = 0; 111 dns_ttl_t maxttl = 0; 112 bool snset = false; 113 bool logdump = false; 114 FILE *errout = stdout; 115 char *endp; 116 117 /* 118 * Uncomment the following line if memory debugging is needed: 119 * isc_mem_debugging |= ISC_MEM_DEBUGRECORD; 120 */ 121 122 outputstyle = &dns_master_style_full; 123 124 prog_name = strrchr(argv[0], '/'); 125 if (prog_name == NULL) { 126 prog_name = strrchr(argv[0], '\\'); 127 } 128 if (prog_name != NULL) { 129 prog_name++; 130 } else { 131 prog_name = argv[0]; 132 } 133 /* 134 * Libtool doesn't preserve the program name prior to final 135 * installation. Remove the libtool prefix ("lt-"). 136 */ 137 if (strncmp(prog_name, "lt-", 3) == 0) { 138 prog_name += 3; 139 } 140 141 #define PROGCMP(X) \ 142 (strcasecmp(prog_name, X) == 0 || strcasecmp(prog_name, X ".exe") == 0) 143 144 if (PROGCMP("named-checkzone")) { 145 progmode = progmode_check; 146 } else if (PROGCMP("named-compilezone")) { 147 progmode = progmode_compile; 148 } else { 149 UNREACHABLE(); 150 } 151 152 /* When compiling, disable checks by default */ 153 if (progmode == progmode_compile) { 154 zone_options = 0; 155 docheckmx = false; 156 docheckns = false; 157 dochecksrv = false; 158 } 159 160 #define ARGCMP(X) (strcmp(isc_commandline_argument, X) == 0) 161 162 isc_commandline_errprint = false; 163 164 while ((c = isc_commandline_parse(argc, argv, 165 "c:df:hi:jJ:k:L:l:m:n:qr:s:t:o:vw:C:" 166 "DF:M:S:T:W:")) != EOF) 167 { 168 switch (c) { 169 case 'c': 170 classname = isc_commandline_argument; 171 break; 172 173 case 'd': 174 debug++; 175 break; 176 177 case 'i': 178 if (ARGCMP("full")) { 179 zone_options |= DNS_ZONEOPT_CHECKINTEGRITY | 180 DNS_ZONEOPT_CHECKSIBLING; 181 docheckmx = true; 182 docheckns = true; 183 dochecksrv = true; 184 } else if (ARGCMP("full-sibling")) { 185 zone_options |= DNS_ZONEOPT_CHECKINTEGRITY; 186 zone_options &= ~DNS_ZONEOPT_CHECKSIBLING; 187 docheckmx = true; 188 docheckns = true; 189 dochecksrv = true; 190 } else if (ARGCMP("local")) { 191 zone_options |= DNS_ZONEOPT_CHECKINTEGRITY; 192 zone_options |= DNS_ZONEOPT_CHECKSIBLING; 193 docheckmx = false; 194 docheckns = false; 195 dochecksrv = false; 196 } else if (ARGCMP("local-sibling")) { 197 zone_options |= DNS_ZONEOPT_CHECKINTEGRITY; 198 zone_options &= ~DNS_ZONEOPT_CHECKSIBLING; 199 docheckmx = false; 200 docheckns = false; 201 dochecksrv = false; 202 } else if (ARGCMP("none")) { 203 zone_options &= ~DNS_ZONEOPT_CHECKINTEGRITY; 204 zone_options &= ~DNS_ZONEOPT_CHECKSIBLING; 205 docheckmx = false; 206 docheckns = false; 207 dochecksrv = false; 208 } else { 209 fprintf(stderr, "invalid argument to -i: %s\n", 210 isc_commandline_argument); 211 exit(EXIT_FAILURE); 212 } 213 break; 214 215 case 'f': 216 inputformatstr = isc_commandline_argument; 217 break; 218 219 case 'F': 220 outputformatstr = isc_commandline_argument; 221 break; 222 223 case 'j': 224 nomerge = false; 225 break; 226 227 case 'J': 228 journal = isc_commandline_argument; 229 nomerge = false; 230 break; 231 232 case 'k': 233 if (ARGCMP("warn")) { 234 zone_options |= DNS_ZONEOPT_CHECKNAMES; 235 zone_options &= ~DNS_ZONEOPT_CHECKNAMESFAIL; 236 } else if (ARGCMP("fail")) { 237 zone_options |= DNS_ZONEOPT_CHECKNAMES | 238 DNS_ZONEOPT_CHECKNAMESFAIL; 239 } else if (ARGCMP("ignore")) { 240 zone_options &= ~(DNS_ZONEOPT_CHECKNAMES | 241 DNS_ZONEOPT_CHECKNAMESFAIL); 242 } else { 243 fprintf(stderr, "invalid argument to -k: %s\n", 244 isc_commandline_argument); 245 exit(EXIT_FAILURE); 246 } 247 break; 248 249 case 'L': 250 snset = true; 251 endp = NULL; 252 serialnum = strtol(isc_commandline_argument, &endp, 0); 253 if (*endp != '\0') { 254 fprintf(stderr, "source serial number " 255 "must be numeric"); 256 exit(EXIT_FAILURE); 257 } 258 break; 259 260 case 'l': 261 zone_options |= DNS_ZONEOPT_CHECKTTL; 262 endp = NULL; 263 maxttl = strtol(isc_commandline_argument, &endp, 0); 264 if (*endp != '\0') { 265 fprintf(stderr, "maximum TTL " 266 "must be numeric"); 267 exit(EXIT_FAILURE); 268 } 269 break; 270 271 case 'n': 272 if (ARGCMP("ignore")) { 273 zone_options &= ~(DNS_ZONEOPT_CHECKNS | 274 DNS_ZONEOPT_FATALNS); 275 } else if (ARGCMP("warn")) { 276 zone_options |= DNS_ZONEOPT_CHECKNS; 277 zone_options &= ~DNS_ZONEOPT_FATALNS; 278 } else if (ARGCMP("fail")) { 279 zone_options |= DNS_ZONEOPT_CHECKNS | 280 DNS_ZONEOPT_FATALNS; 281 } else { 282 fprintf(stderr, "invalid argument to -n: %s\n", 283 isc_commandline_argument); 284 exit(EXIT_FAILURE); 285 } 286 break; 287 288 case 'm': 289 if (ARGCMP("warn")) { 290 zone_options |= DNS_ZONEOPT_CHECKMX; 291 zone_options &= ~DNS_ZONEOPT_CHECKMXFAIL; 292 } else if (ARGCMP("fail")) { 293 zone_options |= DNS_ZONEOPT_CHECKMX | 294 DNS_ZONEOPT_CHECKMXFAIL; 295 } else if (ARGCMP("ignore")) { 296 zone_options &= ~(DNS_ZONEOPT_CHECKMX | 297 DNS_ZONEOPT_CHECKMXFAIL); 298 } else { 299 fprintf(stderr, "invalid argument to -m: %s\n", 300 isc_commandline_argument); 301 exit(EXIT_FAILURE); 302 } 303 break; 304 305 case 'o': 306 output_filename = isc_commandline_argument; 307 break; 308 309 case 'q': 310 quiet++; 311 break; 312 313 case 'r': 314 if (ARGCMP("warn")) { 315 zone_options |= DNS_ZONEOPT_CHECKDUPRR; 316 zone_options &= ~DNS_ZONEOPT_CHECKDUPRRFAIL; 317 } else if (ARGCMP("fail")) { 318 zone_options |= DNS_ZONEOPT_CHECKDUPRR | 319 DNS_ZONEOPT_CHECKDUPRRFAIL; 320 } else if (ARGCMP("ignore")) { 321 zone_options &= ~(DNS_ZONEOPT_CHECKDUPRR | 322 DNS_ZONEOPT_CHECKDUPRRFAIL); 323 } else { 324 fprintf(stderr, "invalid argument to -r: %s\n", 325 isc_commandline_argument); 326 exit(EXIT_FAILURE); 327 } 328 break; 329 330 case 's': 331 if (ARGCMP("full")) { 332 outputstyle = &dns_master_style_full; 333 } else if (ARGCMP("relative")) { 334 outputstyle = &dns_master_style_default; 335 } else { 336 fprintf(stderr, 337 "unknown or unsupported style: %s\n", 338 isc_commandline_argument); 339 exit(EXIT_FAILURE); 340 } 341 break; 342 343 case 't': 344 result = isc_dir_chroot(isc_commandline_argument); 345 if (result != ISC_R_SUCCESS) { 346 fprintf(stderr, "isc_dir_chroot: %s: %s\n", 347 isc_commandline_argument, 348 isc_result_totext(result)); 349 exit(EXIT_FAILURE); 350 } 351 break; 352 353 case 'v': 354 printf("%s\n", PACKAGE_VERSION); 355 exit(EXIT_SUCCESS); 356 357 case 'w': 358 workdir = isc_commandline_argument; 359 break; 360 361 case 'C': 362 if (ARGCMP("check-svcb:fail")) { 363 zone_options |= DNS_ZONEOPT_CHECKSVCB; 364 } else if (ARGCMP("check-svcb:ignore")) { 365 zone_options &= ~DNS_ZONEOPT_CHECKSVCB; 366 } else { 367 fprintf(stderr, "invalid argument to -C: %s\n", 368 isc_commandline_argument); 369 exit(EXIT_FAILURE); 370 } 371 break; 372 373 case 'D': 374 dumpzone++; 375 break; 376 377 case 'M': 378 if (ARGCMP("fail")) { 379 zone_options &= ~DNS_ZONEOPT_WARNMXCNAME; 380 zone_options &= ~DNS_ZONEOPT_IGNOREMXCNAME; 381 } else if (ARGCMP("warn")) { 382 zone_options |= DNS_ZONEOPT_WARNMXCNAME; 383 zone_options &= ~DNS_ZONEOPT_IGNOREMXCNAME; 384 } else if (ARGCMP("ignore")) { 385 zone_options |= DNS_ZONEOPT_WARNMXCNAME; 386 zone_options |= DNS_ZONEOPT_IGNOREMXCNAME; 387 } else { 388 fprintf(stderr, "invalid argument to -M: %s\n", 389 isc_commandline_argument); 390 exit(EXIT_FAILURE); 391 } 392 break; 393 394 case 'S': 395 if (ARGCMP("fail")) { 396 zone_options &= ~DNS_ZONEOPT_WARNSRVCNAME; 397 zone_options &= ~DNS_ZONEOPT_IGNORESRVCNAME; 398 } else if (ARGCMP("warn")) { 399 zone_options |= DNS_ZONEOPT_WARNSRVCNAME; 400 zone_options &= ~DNS_ZONEOPT_IGNORESRVCNAME; 401 } else if (ARGCMP("ignore")) { 402 zone_options |= DNS_ZONEOPT_WARNSRVCNAME; 403 zone_options |= DNS_ZONEOPT_IGNORESRVCNAME; 404 } else { 405 fprintf(stderr, "invalid argument to -S: %s\n", 406 isc_commandline_argument); 407 exit(EXIT_FAILURE); 408 } 409 break; 410 411 case 'T': 412 if (ARGCMP("warn")) { 413 zone_options |= DNS_ZONEOPT_CHECKSPF; 414 } else if (ARGCMP("ignore")) { 415 zone_options &= ~DNS_ZONEOPT_CHECKSPF; 416 } else { 417 fprintf(stderr, "invalid argument to -T: %s\n", 418 isc_commandline_argument); 419 exit(EXIT_FAILURE); 420 } 421 break; 422 423 case 'W': 424 if (ARGCMP("warn")) { 425 zone_options |= DNS_ZONEOPT_CHECKWILDCARD; 426 } else if (ARGCMP("ignore")) { 427 zone_options &= ~DNS_ZONEOPT_CHECKWILDCARD; 428 } 429 break; 430 431 case '?': 432 if (isc_commandline_option != '?') { 433 fprintf(stderr, "%s: invalid argument -%c\n", 434 prog_name, isc_commandline_option); 435 } 436 FALLTHROUGH; 437 case 'h': 438 usage(); 439 440 default: 441 fprintf(stderr, "%s: unhandled option -%c\n", prog_name, 442 isc_commandline_option); 443 exit(EXIT_FAILURE); 444 } 445 } 446 447 if (workdir != NULL) { 448 result = isc_dir_chdir(workdir); 449 if (result != ISC_R_SUCCESS) { 450 fprintf(stderr, "isc_dir_chdir: %s: %s\n", workdir, 451 isc_result_totext(result)); 452 exit(EXIT_FAILURE); 453 } 454 } 455 456 if (inputformatstr != NULL) { 457 if (strcasecmp(inputformatstr, "text") == 0) { 458 inputformat = dns_masterformat_text; 459 } else if (strcasecmp(inputformatstr, "raw") == 0) { 460 inputformat = dns_masterformat_raw; 461 } else if (strncasecmp(inputformatstr, "raw=", 4) == 0) { 462 inputformat = dns_masterformat_raw; 463 fprintf(stderr, "WARNING: input format raw, version " 464 "ignored\n"); 465 } else { 466 fprintf(stderr, "unknown file format: %s\n", 467 inputformatstr); 468 exit(EXIT_FAILURE); 469 } 470 } 471 472 if (outputformatstr != NULL) { 473 if (strcasecmp(outputformatstr, "text") == 0) { 474 outputformat = dns_masterformat_text; 475 } else if (strcasecmp(outputformatstr, "raw") == 0) { 476 outputformat = dns_masterformat_raw; 477 } else if (strncasecmp(outputformatstr, "raw=", 4) == 0) { 478 char *end; 479 480 outputformat = dns_masterformat_raw; 481 rawversion = strtol(outputformatstr + 4, &end, 10); 482 if (end == outputformatstr + 4 || *end != '\0' || 483 rawversion > 1U) 484 { 485 fprintf(stderr, "unknown raw format version\n"); 486 exit(EXIT_FAILURE); 487 } 488 } else { 489 fprintf(stderr, "unknown file format: %s\n", 490 outputformatstr); 491 exit(EXIT_FAILURE); 492 } 493 } 494 495 if (progmode == progmode_compile) { 496 dumpzone = 1; /* always dump */ 497 logdump = !quiet; 498 if (output_filename == NULL) { 499 fprintf(stderr, "output file required, but not " 500 "specified\n"); 501 usage(); 502 } 503 } 504 505 if (output_filename != NULL) { 506 dumpzone = 1; 507 } 508 509 /* 510 * If we are printing to stdout then send the informational 511 * output to stderr. 512 */ 513 if (dumpzone && 514 (output_filename == NULL || strcmp(output_filename, "-") == 0 || 515 strcmp(output_filename, "/dev/fd/1") == 0 || 516 strcmp(output_filename, "/dev/stdout") == 0)) 517 { 518 errout = stderr; 519 logdump = false; 520 } 521 522 if (argc - isc_commandline_index < 1 || 523 argc - isc_commandline_index > 2) 524 { 525 usage(); 526 } 527 528 isc_mem_create(&mctx); 529 if (!quiet) { 530 RUNTIME_CHECK(setup_logging(mctx, errout, &lctx) == 531 ISC_R_SUCCESS); 532 } 533 534 origin = argv[isc_commandline_index++]; 535 536 if (isc_commandline_index == argc) { 537 /* "-" will be interpreted as stdin */ 538 filename = "-"; 539 } else { 540 filename = argv[isc_commandline_index]; 541 } 542 543 isc_commandline_index++; 544 545 result = load_zone(mctx, origin, filename, inputformat, classname, 546 maxttl, &zone); 547 548 if (snset) { 549 dns_master_initrawheader(&header); 550 header.flags = DNS_MASTERRAW_SOURCESERIALSET; 551 header.sourceserial = serialnum; 552 dns_zone_setrawdata(zone, &header); 553 } 554 555 if (result == ISC_R_SUCCESS && dumpzone) { 556 if (logdump) { 557 fprintf(errout, "dump zone to %s...", output_filename); 558 fflush(errout); 559 } 560 result = dump_zone(origin, zone, output_filename, outputformat, 561 outputstyle, rawversion); 562 if (logdump) { 563 fprintf(errout, "done\n"); 564 } 565 } 566 567 if (!quiet && result == ISC_R_SUCCESS) { 568 fprintf(errout, "OK\n"); 569 } 570 destroy(); 571 if (lctx != NULL) { 572 isc_log_destroy(&lctx); 573 } 574 isc_mem_destroy(&mctx); 575 576 return (result == ISC_R_SUCCESS) ? 0 : 1; 577 } 578