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