1 /* $NetBSD: dnssectool.c,v 1.9 2024/02/21 22:51:03 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 /*% 19 * DNSSEC Support Routines. 20 */ 21 22 #include <inttypes.h> 23 #include <stdbool.h> 24 #include <stdlib.h> 25 26 #include <isc/base32.h> 27 #include <isc/buffer.h> 28 #include <isc/commandline.h> 29 #include <isc/dir.h> 30 #include <isc/file.h> 31 #include <isc/heap.h> 32 #include <isc/list.h> 33 #include <isc/mem.h> 34 #include <isc/print.h> 35 #include <isc/result.h> 36 #include <isc/string.h> 37 #include <isc/time.h> 38 #include <isc/tm.h> 39 #include <isc/util.h> 40 41 #include <dns/db.h> 42 #include <dns/dbiterator.h> 43 #include <dns/dnssec.h> 44 #include <dns/fixedname.h> 45 #include <dns/keyvalues.h> 46 #include <dns/log.h> 47 #include <dns/name.h> 48 #include <dns/nsec.h> 49 #include <dns/nsec3.h> 50 #include <dns/rdataclass.h> 51 #include <dns/rdataset.h> 52 #include <dns/rdatasetiter.h> 53 #include <dns/rdatastruct.h> 54 #include <dns/rdatatype.h> 55 #include <dns/secalg.h> 56 #include <dns/time.h> 57 58 #include "dnssectool.h" 59 60 #define KEYSTATES_NVALUES 4 61 static const char *keystates[KEYSTATES_NVALUES] = { 62 "hidden", 63 "rumoured", 64 "omnipresent", 65 "unretentive", 66 }; 67 68 int verbose = 0; 69 bool quiet = false; 70 dns_dsdigest_t dtype[8]; 71 72 static fatalcallback_t *fatalcallback = NULL; 73 74 void 75 fatal(const char *format, ...) { 76 va_list args; 77 78 fprintf(stderr, "%s: fatal: ", program); 79 va_start(args, format); 80 vfprintf(stderr, format, args); 81 va_end(args); 82 fprintf(stderr, "\n"); 83 if (fatalcallback != NULL) { 84 (*fatalcallback)(); 85 } 86 exit(1); 87 } 88 89 void 90 setfatalcallback(fatalcallback_t *callback) { 91 fatalcallback = callback; 92 } 93 94 void 95 check_result(isc_result_t result, const char *message) { 96 if (result != ISC_R_SUCCESS) { 97 fatal("%s: %s", message, isc_result_totext(result)); 98 } 99 } 100 101 void 102 vbprintf(int level, const char *fmt, ...) { 103 va_list ap; 104 if (level > verbose) { 105 return; 106 } 107 va_start(ap, fmt); 108 fprintf(stderr, "%s: ", program); 109 vfprintf(stderr, fmt, ap); 110 va_end(ap); 111 } 112 113 void 114 version(const char *name) { 115 fprintf(stderr, "%s %s\n", name, PACKAGE_VERSION); 116 exit(0); 117 } 118 119 void 120 sig_format(dns_rdata_rrsig_t *sig, char *cp, unsigned int size) { 121 char namestr[DNS_NAME_FORMATSIZE]; 122 char algstr[DNS_NAME_FORMATSIZE]; 123 124 dns_name_format(&sig->signer, namestr, sizeof(namestr)); 125 dns_secalg_format(sig->algorithm, algstr, sizeof(algstr)); 126 snprintf(cp, size, "%s/%s/%d", namestr, algstr, sig->keyid); 127 } 128 129 void 130 setup_logging(isc_mem_t *mctx, isc_log_t **logp) { 131 isc_logdestination_t destination; 132 isc_logconfig_t *logconfig = NULL; 133 isc_log_t *log = NULL; 134 int level; 135 136 if (verbose < 0) { 137 verbose = 0; 138 } 139 switch (verbose) { 140 case 0: 141 /* 142 * We want to see warnings about things like out-of-zone 143 * data in the master file even when not verbose. 144 */ 145 level = ISC_LOG_WARNING; 146 break; 147 case 1: 148 level = ISC_LOG_INFO; 149 break; 150 default: 151 level = ISC_LOG_DEBUG(verbose - 2 + 1); 152 break; 153 } 154 155 isc_log_create(mctx, &log, &logconfig); 156 isc_log_setcontext(log); 157 dns_log_init(log); 158 dns_log_setcontext(log); 159 isc_log_settag(logconfig, program); 160 161 /* 162 * Set up a channel similar to default_stderr except: 163 * - the logging level is passed in 164 * - the program name and logging level are printed 165 * - no time stamp is printed 166 */ 167 destination.file.stream = stderr; 168 destination.file.name = NULL; 169 destination.file.versions = ISC_LOG_ROLLNEVER; 170 destination.file.maximum_size = 0; 171 isc_log_createchannel(logconfig, "stderr", ISC_LOG_TOFILEDESC, level, 172 &destination, 173 ISC_LOG_PRINTTAG | ISC_LOG_PRINTLEVEL); 174 175 RUNTIME_CHECK(isc_log_usechannel(logconfig, "stderr", NULL, NULL) == 176 ISC_R_SUCCESS); 177 178 *logp = log; 179 } 180 181 void 182 cleanup_logging(isc_log_t **logp) { 183 isc_log_t *log; 184 185 REQUIRE(logp != NULL); 186 187 log = *logp; 188 *logp = NULL; 189 190 if (log == NULL) { 191 return; 192 } 193 194 isc_log_destroy(&log); 195 isc_log_setcontext(NULL); 196 dns_log_setcontext(NULL); 197 } 198 199 static isc_stdtime_t 200 time_units(isc_stdtime_t offset, char *suffix, const char *str) { 201 switch (suffix[0]) { 202 case 'Y': 203 case 'y': 204 return (offset * (365 * 24 * 3600)); 205 case 'M': 206 case 'm': 207 switch (suffix[1]) { 208 case 'O': 209 case 'o': 210 return (offset * (30 * 24 * 3600)); 211 case 'I': 212 case 'i': 213 return (offset * 60); 214 case '\0': 215 fatal("'%s' ambiguous: use 'mi' for minutes " 216 "or 'mo' for months", 217 str); 218 default: 219 fatal("time value %s is invalid", str); 220 } 221 UNREACHABLE(); 222 break; 223 case 'W': 224 case 'w': 225 return (offset * (7 * 24 * 3600)); 226 case 'D': 227 case 'd': 228 return (offset * (24 * 3600)); 229 case 'H': 230 case 'h': 231 return (offset * 3600); 232 case 'S': 233 case 's': 234 case '\0': 235 return (offset); 236 default: 237 fatal("time value %s is invalid", str); 238 } 239 UNREACHABLE(); 240 return (0); /* silence compiler warning */ 241 } 242 243 static bool 244 isnone(const char *str) { 245 return ((strcasecmp(str, "none") == 0) || 246 (strcasecmp(str, "never") == 0) || 247 (strcasecmp(str, "unset") == 0)); 248 } 249 250 dns_ttl_t 251 strtottl(const char *str) { 252 const char *orig = str; 253 dns_ttl_t ttl; 254 char *endp; 255 256 if (isnone(str)) { 257 return ((dns_ttl_t)0); 258 } 259 260 ttl = strtol(str, &endp, 0); 261 if (ttl == 0 && endp == str) { 262 fatal("TTL must be numeric"); 263 } 264 ttl = time_units(ttl, endp, orig); 265 return (ttl); 266 } 267 268 dst_key_state_t 269 strtokeystate(const char *str) { 270 if (isnone(str)) { 271 return (DST_KEY_STATE_NA); 272 } 273 274 for (int i = 0; i < KEYSTATES_NVALUES; i++) { 275 if (keystates[i] != NULL && strcasecmp(str, keystates[i]) == 0) 276 { 277 return ((dst_key_state_t)i); 278 } 279 } 280 fatal("unknown key state %s", str); 281 } 282 283 isc_stdtime_t 284 strtotime(const char *str, int64_t now, int64_t base, bool *setp) { 285 int64_t val, offset; 286 isc_result_t result; 287 const char *orig = str; 288 char *endp; 289 size_t n; 290 struct tm tm; 291 292 if (isnone(str)) { 293 if (setp != NULL) { 294 *setp = false; 295 } 296 return ((isc_stdtime_t)0); 297 } 298 299 if (setp != NULL) { 300 *setp = true; 301 } 302 303 if ((str[0] == '0' || str[0] == '-') && str[1] == '\0') { 304 return ((isc_stdtime_t)0); 305 } 306 307 /* 308 * We accept times in the following formats: 309 * now([+-]offset) 310 * YYYYMMDD([+-]offset) 311 * YYYYMMDDhhmmss([+-]offset) 312 * Day Mon DD HH:MM:SS YYYY([+-]offset) 313 * 1234567890([+-]offset) 314 * [+-]offset 315 */ 316 n = strspn(str, "0123456789"); 317 if ((n == 8u || n == 14u) && 318 (str[n] == '\0' || str[n] == '-' || str[n] == '+')) 319 { 320 char timestr[15]; 321 322 strlcpy(timestr, str, sizeof(timestr)); 323 timestr[n] = 0; 324 if (n == 8u) { 325 strlcat(timestr, "000000", sizeof(timestr)); 326 } 327 result = dns_time64_fromtext(timestr, &val); 328 if (result != ISC_R_SUCCESS) { 329 fatal("time value %s is invalid: %s", orig, 330 isc_result_totext(result)); 331 } 332 base = val; 333 str += n; 334 } else if (n == 10u && 335 (str[n] == '\0' || str[n] == '-' || str[n] == '+')) 336 { 337 base = strtoll(str, &endp, 0); 338 str += 10; 339 } else if (strncmp(str, "now", 3) == 0) { 340 base = now; 341 str += 3; 342 } else if (str[0] >= 'A' && str[0] <= 'Z') { 343 /* parse ctime() format as written by `dnssec-settime -p` */ 344 endp = isc_tm_strptime(str, "%a %b %d %H:%M:%S %Y", &tm); 345 if (endp != str + 24) { 346 fatal("time value %s is invalid", orig); 347 } 348 base = mktime(&tm); 349 str += 24; 350 } 351 352 if (str[0] == '\0') { 353 return ((isc_stdtime_t)base); 354 } else if (str[0] == '+') { 355 offset = strtol(str + 1, &endp, 0); 356 offset = time_units((isc_stdtime_t)offset, endp, orig); 357 val = base + offset; 358 } else if (str[0] == '-') { 359 offset = strtol(str + 1, &endp, 0); 360 offset = time_units((isc_stdtime_t)offset, endp, orig); 361 val = base - offset; 362 } else { 363 fatal("time value %s is invalid", orig); 364 } 365 366 return ((isc_stdtime_t)val); 367 } 368 369 dns_rdataclass_t 370 strtoclass(const char *str) { 371 isc_textregion_t r; 372 dns_rdataclass_t rdclass; 373 isc_result_t result; 374 375 if (str == NULL) { 376 return (dns_rdataclass_in); 377 } 378 DE_CONST(str, r.base); 379 r.length = strlen(str); 380 result = dns_rdataclass_fromtext(&rdclass, &r); 381 if (result != ISC_R_SUCCESS) { 382 fatal("unknown class %s", str); 383 } 384 return (rdclass); 385 } 386 387 unsigned int 388 strtodsdigest(const char *str) { 389 isc_textregion_t r; 390 dns_dsdigest_t alg; 391 isc_result_t result; 392 393 DE_CONST(str, r.base); 394 r.length = strlen(str); 395 result = dns_dsdigest_fromtext(&alg, &r); 396 if (result != ISC_R_SUCCESS) { 397 fatal("unknown DS algorithm %s", str); 398 } 399 return (alg); 400 } 401 402 static int 403 cmp_dtype(const void *ap, const void *bp) { 404 int a = *(const uint8_t *)ap; 405 int b = *(const uint8_t *)bp; 406 return (a - b); 407 } 408 409 void 410 add_dtype(unsigned int dt) { 411 unsigned i, n; 412 413 /* ensure there is space for a zero terminator */ 414 n = sizeof(dtype) / sizeof(dtype[0]) - 1; 415 for (i = 0; i < n; i++) { 416 if (dtype[i] == dt) { 417 return; 418 } 419 if (dtype[i] == 0) { 420 dtype[i] = dt; 421 qsort(dtype, i + 1, 1, cmp_dtype); 422 return; 423 } 424 } 425 fatal("too many -a digest type arguments"); 426 } 427 428 isc_result_t 429 try_dir(const char *dirname) { 430 isc_result_t result; 431 isc_dir_t d; 432 433 isc_dir_init(&d); 434 result = isc_dir_open(&d, dirname); 435 if (result == ISC_R_SUCCESS) { 436 isc_dir_close(&d); 437 } 438 return (result); 439 } 440 441 /* 442 * Check private key version compatibility. 443 */ 444 void 445 check_keyversion(dst_key_t *key, char *keystr) { 446 int major, minor; 447 dst_key_getprivateformat(key, &major, &minor); 448 INSIST(major <= DST_MAJOR_VERSION); /* invalid private key */ 449 450 if (major < DST_MAJOR_VERSION || minor < DST_MINOR_VERSION) { 451 fatal("Key %s has incompatible format version %d.%d, " 452 "use -f to force upgrade to new version.", 453 keystr, major, minor); 454 } 455 if (minor > DST_MINOR_VERSION) { 456 fatal("Key %s has incompatible format version %d.%d, " 457 "use -f to force downgrade to current version.", 458 keystr, major, minor); 459 } 460 } 461 462 void 463 set_keyversion(dst_key_t *key) { 464 int major, minor; 465 dst_key_getprivateformat(key, &major, &minor); 466 INSIST(major <= DST_MAJOR_VERSION); 467 468 if (major != DST_MAJOR_VERSION || minor != DST_MINOR_VERSION) { 469 dst_key_setprivateformat(key, DST_MAJOR_VERSION, 470 DST_MINOR_VERSION); 471 } 472 473 /* 474 * If the key is from a version older than 1.3, set 475 * set the creation date 476 */ 477 if (major < 1 || (major == 1 && minor <= 2)) { 478 isc_stdtime_t now; 479 isc_stdtime_get(&now); 480 dst_key_settime(key, DST_TIME_CREATED, now); 481 } 482 } 483 484 bool 485 key_collision(dst_key_t *dstkey, dns_name_t *name, const char *dir, 486 isc_mem_t *mctx, bool *exact) { 487 isc_result_t result; 488 bool conflict = false; 489 dns_dnsseckeylist_t matchkeys; 490 dns_dnsseckey_t *key = NULL; 491 uint16_t id, oldid; 492 uint32_t rid, roldid; 493 dns_secalg_t alg; 494 char filename[NAME_MAX]; 495 isc_buffer_t fileb; 496 isc_stdtime_t now; 497 498 if (exact != NULL) { 499 *exact = false; 500 } 501 502 id = dst_key_id(dstkey); 503 rid = dst_key_rid(dstkey); 504 alg = dst_key_alg(dstkey); 505 506 /* 507 * For Diffie Hellman just check if there is a direct collision as 508 * they can't be revoked. Additionally dns_dnssec_findmatchingkeys 509 * only handles DNSKEY which is not used for HMAC. 510 */ 511 if (alg == DST_ALG_DH) { 512 isc_buffer_init(&fileb, filename, sizeof(filename)); 513 result = dst_key_buildfilename(dstkey, DST_TYPE_PRIVATE, dir, 514 &fileb); 515 if (result != ISC_R_SUCCESS) { 516 return (true); 517 } 518 return (isc_file_exists(filename)); 519 } 520 521 ISC_LIST_INIT(matchkeys); 522 isc_stdtime_get(&now); 523 result = dns_dnssec_findmatchingkeys(name, dir, now, mctx, &matchkeys); 524 if (result == ISC_R_NOTFOUND) { 525 return (false); 526 } 527 528 while (!ISC_LIST_EMPTY(matchkeys) && !conflict) { 529 key = ISC_LIST_HEAD(matchkeys); 530 if (dst_key_alg(key->key) != alg) { 531 goto next; 532 } 533 534 oldid = dst_key_id(key->key); 535 roldid = dst_key_rid(key->key); 536 537 if (oldid == rid || roldid == id || id == oldid) { 538 conflict = true; 539 if (id != oldid) { 540 if (verbose > 1) { 541 fprintf(stderr, 542 "Key ID %d could " 543 "collide with %d\n", 544 id, oldid); 545 } 546 } else { 547 if (exact != NULL) { 548 *exact = true; 549 } 550 if (verbose > 1) { 551 fprintf(stderr, "Key ID %d exists\n", 552 id); 553 } 554 } 555 } 556 557 next: 558 ISC_LIST_UNLINK(matchkeys, key, link); 559 dns_dnsseckey_destroy(mctx, &key); 560 } 561 562 /* Finish freeing the list */ 563 while (!ISC_LIST_EMPTY(matchkeys)) { 564 key = ISC_LIST_HEAD(matchkeys); 565 ISC_LIST_UNLINK(matchkeys, key, link); 566 dns_dnsseckey_destroy(mctx, &key); 567 } 568 569 return (conflict); 570 } 571 572 bool 573 isoptarg(const char *arg, char **argv, void (*usage)(void)) { 574 if (!strcasecmp(isc_commandline_argument, arg)) { 575 if (argv[isc_commandline_index] == NULL) { 576 fprintf(stderr, "%s: missing argument -%c %s\n", 577 program, isc_commandline_option, 578 isc_commandline_argument); 579 usage(); 580 } 581 isc_commandline_argument = argv[isc_commandline_index]; 582 /* skip to next argument */ 583 isc_commandline_index++; 584 return (true); 585 } 586 return (false); 587 } 588