1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ 2 /* kdc/tabdump.c - reporting-friendly tabular KDB dumps */ 3 /* 4 * Copyright (C) 2015 by the Massachusetts Institute of Technology. 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 11 * * Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * 14 * * Redistributions in binary form must reproduce the above copyright 15 * notice, this list of conditions and the following disclaimer in 16 * the documentation and/or other materials provided with the 17 * distribution. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 22 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 23 * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 24 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 25 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 27 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 28 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 30 * OF THE POSSIBILITY OF SUCH DAMAGE. 31 */ 32 33 #include <k5-int.h> 34 #include "k5-platform.h" /* for asprintf */ 35 #include "k5-hex.h" 36 37 #include <limits.h> 38 #include <stdio.h> 39 #include <string.h> 40 #include <unistd.h> 41 42 #include <kadm5/admin.h> 43 #include <kadm5/server_internal.h> 44 45 #include "adm_proto.h" 46 #include "kdb5_util.h" 47 #include "tdumputil.h" 48 49 struct tdopts { 50 int csv; /* 1 for CSV, 0 for tab-separated */ 51 int emptyhex_empty; /* print empty hex strings as "" not "-1" */ 52 int numeric; /* numeric instead of symbolic output */ 53 int omitheader; /* omit field headers */ 54 int writerectype; /* write record type prefix */ 55 char *fname; /* output file name */ 56 }; 57 58 struct rec_args; 59 60 typedef int (tdump_princ_fn)(struct rec_args *, const char *, krb5_db_entry *); 61 typedef int (tdump_policy_fn)(struct rec_args *, const char *, 62 kadm5_policy_ent_t); 63 64 /* Descriptor for a tabdump record type */ 65 struct tdtype { 66 const char *rectype; 67 char * const *fieldnames; 68 tdump_princ_fn *princ_fn; 69 tdump_policy_fn *policy_fn; 70 }; 71 72 static tdump_princ_fn alias; 73 static tdump_princ_fn keydata; 74 static tdump_princ_fn keyinfo; 75 static tdump_princ_fn princ_flags; 76 static tdump_princ_fn princ_lockout; 77 static tdump_princ_fn princ_meta; 78 static tdump_princ_fn princ_stringattrs; 79 static tdump_princ_fn princ_tktpolicy; 80 81 static char * const keydata_fields[] = { 82 "name", "keyindex", "kvno", "enctype", "key", "salttype", "salt", NULL 83 }; 84 static char * const keyinfo_fields[] = { 85 "name", "keyindex", "kvno", "enctype", "salttype", "salt", NULL 86 }; 87 static char * const princ_flags_fields[] = { 88 "name", "flag", "value", NULL 89 }; 90 static char * const princ_lockout_fields[] = { 91 "name", "last_success", "last_failed", "fail_count", NULL 92 }; 93 static char * const princ_meta_fields[] = { 94 "name", "modby", "modtime", "lastpwd", "policy", "mkvno", "hist_kvno", NULL 95 }; 96 static char * const princ_stringattrs_fields[] = { 97 "name", "key", "value", NULL 98 }; 99 static char * const princ_tktpolicy_fields[] = { 100 "name", "expiration", "pw_expiration", "max_life", "max_renew_life", NULL 101 }; 102 static char * const alias_fields[] = { 103 "aliasname", "targetname", NULL 104 }; 105 106 /* Lookup table for tabdump record types */ 107 static struct tdtype tdtypes[] = { 108 {"alias", alias_fields, alias, NULL}, 109 {"keydata", keydata_fields, keydata, NULL}, 110 {"keyinfo", keyinfo_fields, keyinfo, NULL}, 111 {"princ_flags", princ_flags_fields, princ_flags, NULL}, 112 {"princ_lockout", princ_lockout_fields, princ_lockout, NULL}, 113 {"princ_meta", princ_meta_fields, princ_meta, NULL}, 114 {"princ_stringattrs", princ_stringattrs_fields, princ_stringattrs, NULL}, 115 {"princ_tktpolicy", princ_tktpolicy_fields, princ_tktpolicy, NULL}, 116 }; 117 #define NTDTYPES (sizeof(tdtypes)/sizeof(tdtypes[0])) 118 119 /* State to pass to KDB iterator */ 120 struct rec_args { 121 FILE *f; 122 struct tdtype *tdtype; 123 struct rechandle *rh; 124 struct tdopts *opts; 125 }; 126 127 /* Decode the KADM_DATA from a DB entry.*/ 128 static int 129 get_adb(krb5_db_entry *dbe, osa_princ_ent_rec *adb) 130 { 131 XDR xdrs; 132 int success; 133 krb5_tl_data tl_data; 134 krb5_error_code ret; 135 136 memset(adb, 0, sizeof(*adb)); 137 tl_data.tl_data_type = KRB5_TL_KADM_DATA; 138 ret = krb5_dbe_lookup_tl_data(util_context, dbe, &tl_data); 139 if (ret != 0 || tl_data.tl_data_length == 0) 140 return 0; 141 xdrmem_create(&xdrs, (caddr_t)tl_data.tl_data_contents, 142 tl_data.tl_data_length, XDR_DECODE); 143 success = xdr_osa_princ_ent_rec(&xdrs, adb); 144 xdr_destroy(&xdrs); 145 return success; 146 } 147 148 /* Write a date field as an ISO 8601 UTC date/time representation. */ 149 static int 150 write_date_iso(struct rec_args *args, krb5_timestamp when) 151 { 152 char buf[64]; 153 time_t t; 154 struct tm *tm = NULL; 155 struct rechandle *h = args->rh; 156 157 t = ts2tt(when); 158 tm = gmtime(&t); 159 if (tm == NULL) { 160 errno = EINVAL; 161 return -1; 162 } 163 if (strftime(buf, sizeof(buf), "%Y-%m-%dT%H:%M:%SZ", tm) == 0) { 164 errno = EINVAL; 165 return -1; 166 } 167 if (writefield(h, "%s", buf) < 0) 168 return -1; 169 return 0; 170 } 171 172 /* Write a date field, optionally as a decimal POSIX timestamp. */ 173 static int 174 write_date(struct rec_args *args, krb5_timestamp when) 175 { 176 struct tdopts *opts = args->opts; 177 struct rechandle *h = args->rh; 178 179 if (opts->numeric) 180 return writefield(h, "%d", when); 181 182 return write_date_iso(args, when); 183 } 184 185 /* Write an enctype field, optionally as decimal. */ 186 static krb5_error_code 187 write_enctype(struct rec_args *args, krb5_int16 etype) 188 { 189 char buf[256]; 190 krb5_error_code ret; 191 struct rechandle *h = args->rh; 192 struct tdopts *opts = args->opts; 193 194 if (!opts->numeric) { 195 ret = krb5_enctype_to_name(etype, 0, buf, sizeof(buf)); 196 if (ret == 0) { 197 if (writefield(h, "%s", buf) < 0) 198 return errno; 199 return ret; 200 } 201 } 202 /* decimal if requested, or if conversion failed */ 203 if (writefield(h, "%d", etype) < 0) 204 return errno; 205 return 0; 206 } 207 208 /* Write a salttype field, optionally as decimal. */ 209 static krb5_error_code 210 write_salttype(struct rec_args *args, krb5_int16 salttype) 211 { 212 char buf[256]; 213 krb5_error_code ret; 214 struct rechandle *h = args->rh; 215 struct tdopts *opts = args->opts; 216 217 if (!opts->numeric) { 218 ret = krb5_salttype_to_string(salttype, buf, sizeof(buf)); 219 if (ret == 0) { 220 if (writefield(h, "%s", buf) < 0) 221 return errno; 222 return ret; 223 } 224 } 225 /* decimal if requested, or if conversion failed */ 226 if (writefield(h, "%d", salttype) < 0) 227 return errno; 228 return 0; 229 } 230 231 /* 232 * Write a field of bytes from krb5_data as a hexadecimal string. Write empty 233 * strings as "-1" unless requested. 234 */ 235 static int 236 write_data(struct rec_args *args, krb5_data *data) 237 { 238 int ret; 239 char *hex; 240 struct rechandle *h = args->rh; 241 struct tdopts *opts = args->opts; 242 243 if (data->length == 0 && !opts->emptyhex_empty) { 244 if (writefield(h, "-1") < 0) 245 return -1; 246 return 0; 247 } 248 249 ret = k5_hex_encode(data->data, data->length, FALSE, &hex); 250 if (ret) { 251 errno = ret; 252 return -1; 253 } 254 255 ret = writefield(h, "%s", hex); 256 free(hex); 257 return ret; 258 } 259 260 static krb5_error_code 261 alias(struct rec_args *args, const char *name, krb5_db_entry *dbe) 262 { 263 krb5_error_code ret; 264 struct rechandle *h = args->rh; 265 krb5_principal target = NULL; 266 char *tname = NULL; 267 268 ret = krb5_dbe_read_alias(util_context, dbe, &target); 269 if (ret) 270 return ret; 271 if (target == NULL) 272 return 0; 273 274 ret = krb5_unparse_name(util_context, target, &tname); 275 if (ret) 276 goto cleanup; 277 278 if (startrec(h) < 0) 279 ret = errno; 280 if (!ret && writefield(h, "%s", name) < 0) 281 ret = errno; 282 if (!ret && writefield(h, "%s", tname) < 0) 283 ret = errno; 284 if (!ret && endrec(h) < 0) 285 ret = errno; 286 287 cleanup: 288 krb5_free_principal(util_context, target); 289 krb5_free_unparsed_name(util_context, tname); 290 return ret; 291 } 292 293 /* Write a single record of a keydata/keyinfo key set. */ 294 static krb5_error_code 295 keyinfo_rec(struct rec_args *args, const char *name, int i, krb5_key_data *kd, 296 int dumpkeys) 297 { 298 int ret; 299 krb5_data data; 300 struct rechandle *h = args->rh; 301 302 if (startrec(h) < 0) 303 return errno; 304 if (writefield(h, "%s", name) < 0) 305 return errno; 306 if (writefield(h, "%d", i) < 0) 307 return errno; 308 if (writefield(h, "%d", kd->key_data_kvno) < 0) 309 return errno; 310 if (write_enctype(args, kd->key_data_type[0]) < 0) 311 return errno; 312 if (dumpkeys) { 313 data.length = kd->key_data_length[0]; 314 data.data = (void *)kd->key_data_contents[0]; 315 if (write_data(args, &data) < 0) 316 return errno; 317 } 318 ret = write_salttype(args, kd->key_data_type[1]); 319 if (ret) 320 return ret; 321 data.length = kd->key_data_length[1]; 322 data.data = (void *)kd->key_data_contents[1]; 323 if (write_data(args, &data) < 0) 324 return errno; 325 if (endrec(h) < 0) 326 return errno; 327 return 0; 328 } 329 330 /* Write out a principal's key set, optionally including actual key data. */ 331 static krb5_error_code 332 keyinfo_common(struct rec_args *args, const char *name, krb5_db_entry *entry, 333 int dumpkeys) 334 { 335 krb5_error_code ret; 336 krb5_key_data kd; 337 int i; 338 339 for (i = 0; i < entry->n_key_data; i++) { 340 kd = entry->key_data[i]; 341 /* missing salt data -> normal salt */ 342 if (kd.key_data_ver == 1) { 343 kd.key_data_ver = 2; 344 kd.key_data_type[1] = KRB5_KDB_SALTTYPE_NORMAL; 345 kd.key_data_length[1] = 0; 346 kd.key_data_contents[1] = NULL; 347 } 348 ret = keyinfo_rec(args, name, i, &kd, dumpkeys); 349 if (ret) 350 return ret; 351 } 352 return 0; 353 } 354 355 /* Write a principal's key data. */ 356 static krb5_error_code 357 keydata(struct rec_args *args, const char *name, krb5_db_entry *dbe) 358 { 359 return keyinfo_common(args, name, dbe, 1); 360 } 361 362 /* Write a principal's key info (suppressing actual key data). */ 363 static krb5_error_code 364 keyinfo(struct rec_args *args, const char *name, krb5_db_entry *dbe) 365 { 366 return keyinfo_common(args, name, dbe, 0); 367 } 368 369 /* Write a record corresponding to a single principal flag setting. */ 370 static krb5_error_code 371 princflag_rec(struct rechandle *h, const char *name, const char *flagname, 372 int set) 373 { 374 if (startrec(h) < 0) 375 return errno; 376 if (writefield(h, "%s", name) < 0) 377 return errno; 378 if (writefield(h, "%s", flagname) < 0) 379 return errno; 380 if (writefield(h, "%d", set) < 0) 381 return errno; 382 if (endrec(h) < 0) 383 return errno; 384 return 0; 385 } 386 387 /* Write a principal's flag settings. */ 388 static krb5_error_code 389 princ_flags(struct rec_args *args, const char *name, krb5_db_entry *dbe) 390 { 391 int i; 392 char *s = NULL; 393 krb5_flags flags = dbe->attributes; 394 krb5_error_code ret; 395 struct tdopts *opts = args->opts; 396 struct rechandle *h = args->rh; 397 398 for (i = 0; i < 32; i++) { 399 if (opts->numeric) { 400 if (asprintf(&s, "0x%08lx", 1UL << i) == -1) 401 return ENOMEM; 402 } else { 403 ret = krb5_flagnum_to_string(i, &s); 404 if (ret) 405 return ret; 406 /* Don't print unknown flags if they're not set and numeric output 407 * isn't requested. */ 408 if (!(flags & (1UL << i)) && strncmp(s, "0x", 2) == 0) { 409 free(s); 410 continue; 411 } 412 } 413 ret = princflag_rec(h, name, s, ((flags & (1UL << i)) != 0)); 414 free(s); 415 if (ret) 416 return ret; 417 } 418 return 0; 419 } 420 421 /* Write a principal's lockout data. */ 422 static krb5_error_code 423 princ_lockout(struct rec_args *args, const char *name, krb5_db_entry *dbe) 424 { 425 struct rechandle *h = args->rh; 426 427 if (startrec(h) < 0) 428 return errno; 429 if (writefield(h, "%s", name) < 0) 430 return errno; 431 if (write_date(args, dbe->last_success) < 0) 432 return errno; 433 if (write_date(args, dbe->last_failed) < 0) 434 return errno; 435 if (writefield(h, "%d", dbe->fail_auth_count) < 0) 436 return errno; 437 if (endrec(h) < 0) 438 return errno; 439 return 0; 440 } 441 442 /* Write a principal's metadata. */ 443 static krb5_error_code 444 princ_meta(struct rec_args *args, const char *name, krb5_db_entry *dbe) 445 { 446 int got_adb = 0; 447 char *modby; 448 krb5_kvno mkvno; 449 const char *policy; 450 krb5_principal mod_princ = NULL; 451 krb5_timestamp mod_time, last_pwd; 452 krb5_error_code ret; 453 osa_princ_ent_rec adb; 454 struct rechandle *h = args->rh; 455 456 memset(&adb, 0, sizeof(adb)); 457 if (startrec(h) < 0) 458 return errno; 459 if (writefield(h, "%s", name) < 0) 460 return errno; 461 462 ret = krb5_dbe_lookup_last_pwd_change(util_context, dbe, &last_pwd); 463 if (ret) 464 return ret; 465 ret = krb5_dbe_get_mkvno(util_context, dbe, &mkvno); 466 if (ret) 467 return ret; 468 469 ret = krb5_dbe_lookup_mod_princ_data(util_context, dbe, &mod_time, 470 &mod_princ); 471 if (ret) 472 return ret; 473 ret = krb5_unparse_name(util_context, mod_princ, &modby); 474 krb5_free_principal(util_context, mod_princ); 475 if (ret) 476 return ret; 477 ret = writefield(h, "%s", modby); 478 krb5_free_unparsed_name(util_context, modby); 479 if (ret < 0) 480 return errno; 481 482 if (write_date(args, mod_time) < 0) 483 return errno; 484 if (write_date(args, last_pwd) < 0) 485 return errno; 486 487 got_adb = get_adb(dbe, &adb); 488 if (got_adb && adb.policy != NULL) 489 policy = adb.policy; 490 else 491 policy = ""; 492 ret = writefield(h, "%s", policy); 493 if (ret < 0) { 494 ret = errno; 495 goto cleanup; 496 } 497 if (writefield(h, "%d", mkvno) < 0) { 498 ret = errno; 499 goto cleanup; 500 } 501 if (writefield(h, "%d", adb.admin_history_kvno) < 0) { 502 ret = errno; 503 goto cleanup; 504 } 505 if (endrec(h) < 0) 506 ret = errno; 507 else 508 ret = 0; 509 510 cleanup: 511 kdb_free_entry(NULL, NULL, &adb); 512 return ret; 513 } 514 515 /* Write a principal's string attributes. */ 516 static krb5_error_code 517 princ_stringattrs(struct rec_args *args, const char *name, krb5_db_entry *dbe) 518 { 519 int i, nattrs; 520 krb5_error_code ret; 521 krb5_string_attr *attrs; 522 struct rechandle *h = args->rh; 523 524 ret = krb5_dbe_get_strings(util_context, dbe, &attrs, &nattrs); 525 if (ret) 526 return ret; 527 for (i = 0; i < nattrs; i++) { 528 if (startrec(h) < 0) { 529 ret = errno; 530 goto cleanup; 531 } 532 if (writefield(h, "%s", name) < 0) { 533 ret = errno; 534 goto cleanup; 535 } 536 if (writefield(h, "%s", attrs[i].key) < 0) { 537 ret = errno; 538 goto cleanup; 539 } 540 if (writefield(h, "%s", attrs[i].value) < 0) { 541 ret = errno; 542 goto cleanup; 543 } 544 if (endrec(h) < 0) { 545 ret = errno; 546 goto cleanup; 547 } 548 } 549 cleanup: 550 krb5_dbe_free_strings(util_context, attrs, nattrs); 551 return ret; 552 } 553 554 /* Write a principal's ticket policy. */ 555 static krb5_error_code 556 princ_tktpolicy(struct rec_args *args, const char *name, krb5_db_entry *dbe) 557 { 558 struct rechandle *h = args->rh; 559 560 if (startrec(h) < 0) 561 return errno; 562 if (writefield(h, "%s", name) < 0) 563 return errno; 564 if (write_date(args, dbe->expiration) < 0) 565 return errno; 566 if (write_date(args, dbe->pw_expiration) < 0) 567 return errno; 568 if (writefield(h, "%d", dbe->max_life) < 0) 569 return errno; 570 if (writefield(h, "%d", dbe->max_renewable_life) < 0) 571 return errno; 572 if (endrec(h) < 0) 573 return errno; 574 return 0; 575 } 576 577 /* Iterator function for krb5_db_iterate() */ 578 static krb5_error_code 579 tditer(void *ptr, krb5_db_entry *entry) 580 { 581 krb5_error_code ret; 582 struct rec_args *args = ptr; 583 char *name; 584 585 ret = krb5_unparse_name(util_context, entry->princ, &name); 586 if (ret) { 587 com_err(progname, ret, _("while unparsing principal name")); 588 return ret; 589 } 590 ret = args->tdtype->princ_fn(args, name, entry); 591 krb5_free_unparsed_name(util_context, name); 592 if (ret) 593 return ret; 594 return 0; 595 } 596 597 /* Set up state structure for the iterator. */ 598 static krb5_error_code 599 setup_args(struct rec_args *args, struct tdtype *tdtype, 600 struct tdopts *opts) 601 { 602 FILE *f = NULL; 603 const char *rectype = NULL; 604 struct rechandle *rh; 605 606 args->tdtype = tdtype; 607 args->opts = opts; 608 if (opts->fname != NULL && strcmp(opts->fname, "-") != 0) { 609 f = fopen(opts->fname, "w"); 610 if (f == NULL) { 611 com_err(progname, errno, _("opening %s for writing"), 612 opts->fname); 613 return errno; 614 } 615 args->f = f; 616 } else { 617 f = stdout; 618 args->f = NULL; 619 } 620 if (opts->writerectype) 621 rectype = tdtype->rectype; 622 if (opts->csv) 623 rh = rechandle_csv(f, rectype); 624 else 625 rh = rechandle_tabsep(f, rectype); 626 if (rh == NULL) 627 return ENOMEM; 628 args->rh = rh; 629 if (!opts->omitheader && writeheader(rh, tdtype->fieldnames) < 0) 630 return errno; 631 return 0; 632 } 633 634 /* Clean up the state structure. */ 635 static void 636 cleanup_args(struct rec_args *args) 637 { 638 rechandle_free(args->rh); 639 if (args->f != NULL) 640 fclose(args->f); 641 } 642 643 void 644 tabdump(int argc, char **argv) 645 { 646 int ch; 647 size_t i; 648 const char *rectype; 649 struct rec_args args; 650 struct tdopts opts; 651 krb5_error_code ret; 652 653 memset(&opts, 0, sizeof(opts)); 654 memset(&args, 0, sizeof(args)); 655 optind = 1; 656 while ((ch = getopt(argc, argv, "Hceno:")) != -1) { 657 switch (ch) { 658 case 'H': 659 opts.omitheader = 1; 660 break; 661 case 'c': 662 opts.csv = 1; 663 break; 664 case 'e': 665 opts.emptyhex_empty = 1; 666 break; 667 case 'n': 668 opts.numeric = 1; 669 break; 670 case 'o': 671 opts.fname = optarg; 672 break; 673 case '?': 674 default: 675 usage(); 676 break; 677 } 678 } 679 if (argc - optind < 1) 680 usage(); 681 rectype = argv[optind]; 682 for (i = 0; i < NTDTYPES; i++) { 683 if (strcmp(rectype, tdtypes[i].rectype) == 0) { 684 setup_args(&args, &tdtypes[i], &opts); 685 break; 686 } 687 } 688 if (i >= NTDTYPES) 689 usage(); 690 ret = krb5_db_iterate(util_context, NULL, tditer, &args, 0); 691 cleanup_args(&args); 692 if (ret) { 693 com_err(progname, ret, _("performing tabular dump")); 694 exit_status++; 695 } 696 } 697