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 keydata; 73 static tdump_princ_fn keyinfo; 74 static tdump_princ_fn princ_flags; 75 static tdump_princ_fn princ_lockout; 76 static tdump_princ_fn princ_meta; 77 static tdump_princ_fn princ_stringattrs; 78 static tdump_princ_fn princ_tktpolicy; 79 80 static char * const keydata_fields[] = { 81 "name", "keyindex", "kvno", "enctype", "key", "salttype", "salt", NULL 82 }; 83 static char * const keyinfo_fields[] = { 84 "name", "keyindex", "kvno", "enctype", "salttype", "salt", NULL 85 }; 86 static char * const princ_flags_fields[] = { 87 "name", "flag", "value", NULL 88 }; 89 static char * const princ_lockout_fields[] = { 90 "name", "last_success", "last_failed", "fail_count", NULL 91 }; 92 static char * const princ_meta_fields[] = { 93 "name", "modby", "modtime", "lastpwd", "policy", "mkvno", "hist_kvno", NULL 94 }; 95 static char * const princ_stringattrs_fields[] = { 96 "name", "key", "value", NULL 97 }; 98 static char * const princ_tktpolicy_fields[] = { 99 "name", "expiration", "pw_expiration", "max_life", "max_renew_life", NULL 100 }; 101 102 /* Lookup table for tabdump record types */ 103 static struct tdtype tdtypes[] = { 104 {"keydata", keydata_fields, keydata, NULL}, 105 {"keyinfo", keyinfo_fields, keyinfo, NULL}, 106 {"princ_flags", princ_flags_fields, princ_flags, NULL}, 107 {"princ_lockout", princ_lockout_fields, princ_lockout, NULL}, 108 {"princ_meta", princ_meta_fields, princ_meta, NULL}, 109 {"princ_stringattrs", princ_stringattrs_fields, princ_stringattrs, NULL}, 110 {"princ_tktpolicy", princ_tktpolicy_fields, princ_tktpolicy, NULL}, 111 }; 112 #define NTDTYPES (sizeof(tdtypes)/sizeof(tdtypes[0])) 113 114 /* State to pass to KDB iterator */ 115 struct rec_args { 116 FILE *f; 117 struct tdtype *tdtype; 118 struct rechandle *rh; 119 struct tdopts *opts; 120 }; 121 122 /* Decode the KADM_DATA from a DB entry.*/ 123 static int 124 get_adb(krb5_db_entry *dbe, osa_princ_ent_rec *adb) 125 { 126 XDR xdrs; 127 int success; 128 krb5_tl_data tl_data; 129 krb5_error_code ret; 130 131 memset(adb, 0, sizeof(*adb)); 132 tl_data.tl_data_type = KRB5_TL_KADM_DATA; 133 ret = krb5_dbe_lookup_tl_data(util_context, dbe, &tl_data); 134 if (ret != 0 || tl_data.tl_data_length == 0) 135 return 0; 136 xdrmem_create(&xdrs, (caddr_t)tl_data.tl_data_contents, 137 tl_data.tl_data_length, XDR_DECODE); 138 success = xdr_osa_princ_ent_rec(&xdrs, adb); 139 xdr_destroy(&xdrs); 140 return success; 141 } 142 143 /* Write a date field as an ISO 8601 UTC date/time representation. */ 144 static int 145 write_date_iso(struct rec_args *args, krb5_timestamp when) 146 { 147 char buf[64]; 148 time_t t; 149 struct tm *tm = NULL; 150 struct rechandle *h = args->rh; 151 152 t = ts2tt(when); 153 tm = gmtime(&t); 154 if (tm == NULL) { 155 errno = EINVAL; 156 return -1; 157 } 158 if (strftime(buf, sizeof(buf), "%Y-%m-%dT%H:%M:%SZ", tm) == 0) { 159 errno = EINVAL; 160 return -1; 161 } 162 if (writefield(h, "%s", buf) < 0) 163 return -1; 164 return 0; 165 } 166 167 /* Write a date field, optionally as a decimal POSIX timestamp. */ 168 static int 169 write_date(struct rec_args *args, krb5_timestamp when) 170 { 171 struct tdopts *opts = args->opts; 172 struct rechandle *h = args->rh; 173 174 if (opts->numeric) 175 return writefield(h, "%d", when); 176 177 return write_date_iso(args, when); 178 } 179 180 /* Write an enctype field, optionally as decimal. */ 181 static krb5_error_code 182 write_enctype(struct rec_args *args, krb5_int16 etype) 183 { 184 char buf[256]; 185 krb5_error_code ret; 186 struct rechandle *h = args->rh; 187 struct tdopts *opts = args->opts; 188 189 if (!opts->numeric) { 190 ret = krb5_enctype_to_name(etype, 0, buf, sizeof(buf)); 191 if (ret == 0) { 192 if (writefield(h, "%s", buf) < 0) 193 return errno; 194 return ret; 195 } 196 } 197 /* decimal if requested, or if conversion failed */ 198 if (writefield(h, "%d", etype) < 0) 199 return errno; 200 return 0; 201 } 202 203 /* Write a salttype field, optionally as decimal. */ 204 static krb5_error_code 205 write_salttype(struct rec_args *args, krb5_int16 salttype) 206 { 207 char buf[256]; 208 krb5_error_code ret; 209 struct rechandle *h = args->rh; 210 struct tdopts *opts = args->opts; 211 212 if (!opts->numeric) { 213 ret = krb5_salttype_to_string(salttype, buf, sizeof(buf)); 214 if (ret == 0) { 215 if (writefield(h, "%s", buf) < 0) 216 return errno; 217 return ret; 218 } 219 } 220 /* decimal if requested, or if conversion failed */ 221 if (writefield(h, "%d", salttype) < 0) 222 return errno; 223 return 0; 224 } 225 226 /* 227 * Write a field of bytes from krb5_data as a hexadecimal string. Write empty 228 * strings as "-1" unless requested. 229 */ 230 static int 231 write_data(struct rec_args *args, krb5_data *data) 232 { 233 int ret; 234 char *hex; 235 struct rechandle *h = args->rh; 236 struct tdopts *opts = args->opts; 237 238 if (data->length == 0 && !opts->emptyhex_empty) { 239 if (writefield(h, "-1") < 0) 240 return -1; 241 return 0; 242 } 243 244 ret = k5_hex_encode(data->data, data->length, FALSE, &hex); 245 if (ret) { 246 errno = ret; 247 return -1; 248 } 249 250 ret = writefield(h, "%s", hex); 251 free(hex); 252 return ret; 253 } 254 255 /* Write a single record of a keydata/keyinfo key set. */ 256 static krb5_error_code 257 keyinfo_rec(struct rec_args *args, const char *name, int i, krb5_key_data *kd, 258 int dumpkeys) 259 { 260 int ret; 261 krb5_data data; 262 struct rechandle *h = args->rh; 263 264 if (startrec(h) < 0) 265 return errno; 266 if (writefield(h, "%s", name) < 0) 267 return errno; 268 if (writefield(h, "%d", i) < 0) 269 return errno; 270 if (writefield(h, "%d", kd->key_data_kvno) < 0) 271 return errno; 272 if (write_enctype(args, kd->key_data_type[0]) < 0) 273 return errno; 274 if (dumpkeys) { 275 data.length = kd->key_data_length[0]; 276 data.data = (void *)kd->key_data_contents[0]; 277 if (write_data(args, &data) < 0) 278 return errno; 279 } 280 ret = write_salttype(args, kd->key_data_type[1]); 281 if (ret) 282 return ret; 283 data.length = kd->key_data_length[1]; 284 data.data = (void *)kd->key_data_contents[1]; 285 if (write_data(args, &data) < 0) 286 return errno; 287 if (endrec(h) < 0) 288 return errno; 289 return 0; 290 } 291 292 /* Write out a principal's key set, optionally including actual key data. */ 293 static krb5_error_code 294 keyinfo_common(struct rec_args *args, const char *name, krb5_db_entry *entry, 295 int dumpkeys) 296 { 297 krb5_error_code ret; 298 krb5_key_data kd; 299 int i; 300 301 for (i = 0; i < entry->n_key_data; i++) { 302 kd = entry->key_data[i]; 303 /* missing salt data -> normal salt */ 304 if (kd.key_data_ver == 1) { 305 kd.key_data_ver = 2; 306 kd.key_data_type[1] = KRB5_KDB_SALTTYPE_NORMAL; 307 kd.key_data_length[1] = 0; 308 kd.key_data_contents[1] = NULL; 309 } 310 ret = keyinfo_rec(args, name, i, &kd, dumpkeys); 311 if (ret) 312 return ret; 313 } 314 return 0; 315 } 316 317 /* Write a principal's key data. */ 318 static krb5_error_code 319 keydata(struct rec_args *args, const char *name, krb5_db_entry *dbe) 320 { 321 return keyinfo_common(args, name, dbe, 1); 322 } 323 324 /* Write a principal's key info (suppressing actual key data). */ 325 static krb5_error_code 326 keyinfo(struct rec_args *args, const char *name, krb5_db_entry *dbe) 327 { 328 return keyinfo_common(args, name, dbe, 0); 329 } 330 331 /* Write a record corresponding to a single principal flag setting. */ 332 static krb5_error_code 333 princflag_rec(struct rechandle *h, const char *name, const char *flagname, 334 int set) 335 { 336 if (startrec(h) < 0) 337 return errno; 338 if (writefield(h, "%s", name) < 0) 339 return errno; 340 if (writefield(h, "%s", flagname) < 0) 341 return errno; 342 if (writefield(h, "%d", set) < 0) 343 return errno; 344 if (endrec(h) < 0) 345 return errno; 346 return 0; 347 } 348 349 /* Write a principal's flag settings. */ 350 static krb5_error_code 351 princ_flags(struct rec_args *args, const char *name, krb5_db_entry *dbe) 352 { 353 int i; 354 char *s = NULL; 355 krb5_flags flags = dbe->attributes; 356 krb5_error_code ret; 357 struct tdopts *opts = args->opts; 358 struct rechandle *h = args->rh; 359 360 for (i = 0; i < 32; i++) { 361 if (opts->numeric) { 362 if (asprintf(&s, "0x%08lx", 1UL << i) == -1) 363 return ENOMEM; 364 } else { 365 ret = krb5_flagnum_to_string(i, &s); 366 if (ret) 367 return ret; 368 /* Don't print unknown flags if they're not set and numeric output 369 * isn't requested. */ 370 if (!(flags & (1UL << i)) && strncmp(s, "0x", 2) == 0) { 371 free(s); 372 continue; 373 } 374 } 375 ret = princflag_rec(h, name, s, ((flags & (1UL << i)) != 0)); 376 free(s); 377 if (ret) 378 return ret; 379 } 380 return 0; 381 } 382 383 /* Write a principal's lockout data. */ 384 static krb5_error_code 385 princ_lockout(struct rec_args *args, const char *name, krb5_db_entry *dbe) 386 { 387 struct rechandle *h = args->rh; 388 389 if (startrec(h) < 0) 390 return errno; 391 if (writefield(h, "%s", name) < 0) 392 return errno; 393 if (write_date(args, dbe->last_success) < 0) 394 return errno; 395 if (write_date(args, dbe->last_failed) < 0) 396 return errno; 397 if (writefield(h, "%d", dbe->fail_auth_count) < 0) 398 return errno; 399 if (endrec(h) < 0) 400 return errno; 401 return 0; 402 } 403 404 /* Write a principal's metadata. */ 405 static krb5_error_code 406 princ_meta(struct rec_args *args, const char *name, krb5_db_entry *dbe) 407 { 408 int got_adb = 0; 409 char *modby; 410 krb5_kvno mkvno; 411 const char *policy; 412 krb5_principal mod_princ = NULL; 413 krb5_timestamp mod_time, last_pwd; 414 krb5_error_code ret; 415 osa_princ_ent_rec adb; 416 struct rechandle *h = args->rh; 417 418 memset(&adb, 0, sizeof(adb)); 419 if (startrec(h) < 0) 420 return errno; 421 if (writefield(h, "%s", name) < 0) 422 return errno; 423 424 ret = krb5_dbe_lookup_last_pwd_change(util_context, dbe, &last_pwd); 425 if (ret) 426 return ret; 427 ret = krb5_dbe_get_mkvno(util_context, dbe, &mkvno); 428 if (ret) 429 return ret; 430 431 ret = krb5_dbe_lookup_mod_princ_data(util_context, dbe, &mod_time, 432 &mod_princ); 433 if (ret) 434 return ret; 435 ret = krb5_unparse_name(util_context, mod_princ, &modby); 436 krb5_free_principal(util_context, mod_princ); 437 if (ret) 438 return ret; 439 ret = writefield(h, "%s", modby); 440 krb5_free_unparsed_name(util_context, modby); 441 if (ret < 0) 442 return errno; 443 444 if (write_date(args, mod_time) < 0) 445 return errno; 446 if (write_date(args, last_pwd) < 0) 447 return errno; 448 449 got_adb = get_adb(dbe, &adb); 450 if (got_adb && adb.policy != NULL) 451 policy = adb.policy; 452 else 453 policy = ""; 454 ret = writefield(h, "%s", policy); 455 if (ret < 0) { 456 ret = errno; 457 goto cleanup; 458 } 459 if (writefield(h, "%d", mkvno) < 0) { 460 ret = errno; 461 goto cleanup; 462 } 463 if (writefield(h, "%d", adb.admin_history_kvno) < 0) { 464 ret = errno; 465 goto cleanup; 466 } 467 if (endrec(h) < 0) 468 ret = errno; 469 else 470 ret = 0; 471 472 cleanup: 473 kdb_free_entry(NULL, NULL, &adb); 474 return ret; 475 } 476 477 /* Write a principal's string attributes. */ 478 static krb5_error_code 479 princ_stringattrs(struct rec_args *args, const char *name, krb5_db_entry *dbe) 480 { 481 int i, nattrs; 482 krb5_error_code ret; 483 krb5_string_attr *attrs; 484 struct rechandle *h = args->rh; 485 486 ret = krb5_dbe_get_strings(util_context, dbe, &attrs, &nattrs); 487 if (ret) 488 return ret; 489 for (i = 0; i < nattrs; i++) { 490 if (startrec(h) < 0) { 491 ret = errno; 492 goto cleanup; 493 } 494 if (writefield(h, "%s", name) < 0) { 495 ret = errno; 496 goto cleanup; 497 } 498 if (writefield(h, "%s", attrs[i].key) < 0) { 499 ret = errno; 500 goto cleanup; 501 } 502 if (writefield(h, "%s", attrs[i].value) < 0) { 503 ret = errno; 504 goto cleanup; 505 } 506 if (endrec(h) < 0) { 507 ret = errno; 508 goto cleanup; 509 } 510 } 511 cleanup: 512 krb5_dbe_free_strings(util_context, attrs, nattrs); 513 return ret; 514 } 515 516 /* Write a principal's ticket policy. */ 517 static krb5_error_code 518 princ_tktpolicy(struct rec_args *args, const char *name, krb5_db_entry *dbe) 519 { 520 struct rechandle *h = args->rh; 521 522 if (startrec(h) < 0) 523 return errno; 524 if (writefield(h, "%s", name) < 0) 525 return errno; 526 if (write_date(args, dbe->expiration) < 0) 527 return errno; 528 if (write_date(args, dbe->pw_expiration) < 0) 529 return errno; 530 if (writefield(h, "%d", dbe->max_life) < 0) 531 return errno; 532 if (writefield(h, "%d", dbe->max_renewable_life) < 0) 533 return errno; 534 if (endrec(h) < 0) 535 return errno; 536 return 0; 537 } 538 539 /* Iterator function for krb5_db_iterate() */ 540 static krb5_error_code 541 tditer(void *ptr, krb5_db_entry *entry) 542 { 543 krb5_error_code ret; 544 struct rec_args *args = ptr; 545 char *name; 546 547 ret = krb5_unparse_name(util_context, entry->princ, &name); 548 if (ret) { 549 com_err(progname, ret, _("while unparsing principal name")); 550 return ret; 551 } 552 ret = args->tdtype->princ_fn(args, name, entry); 553 krb5_free_unparsed_name(util_context, name); 554 if (ret) 555 return ret; 556 return 0; 557 } 558 559 /* Set up state structure for the iterator. */ 560 static krb5_error_code 561 setup_args(struct rec_args *args, struct tdtype *tdtype, 562 struct tdopts *opts) 563 { 564 FILE *f = NULL; 565 const char *rectype = NULL; 566 struct rechandle *rh; 567 568 args->tdtype = tdtype; 569 args->opts = opts; 570 if (opts->fname != NULL && strcmp(opts->fname, "-") != 0) { 571 f = fopen(opts->fname, "w"); 572 if (f == NULL) { 573 com_err(progname, errno, _("opening %s for writing"), 574 opts->fname); 575 return errno; 576 } 577 args->f = f; 578 } else { 579 f = stdout; 580 args->f = NULL; 581 } 582 if (opts->writerectype) 583 rectype = tdtype->rectype; 584 if (opts->csv) 585 rh = rechandle_csv(f, rectype); 586 else 587 rh = rechandle_tabsep(f, rectype); 588 if (rh == NULL) 589 return ENOMEM; 590 args->rh = rh; 591 if (!opts->omitheader && writeheader(rh, tdtype->fieldnames) < 0) 592 return errno; 593 return 0; 594 } 595 596 /* Clean up the state structure. */ 597 static void 598 cleanup_args(struct rec_args *args) 599 { 600 rechandle_free(args->rh); 601 if (args->f != NULL) 602 fclose(args->f); 603 } 604 605 void 606 tabdump(int argc, char **argv) 607 { 608 int ch; 609 size_t i; 610 const char *rectype; 611 struct rec_args args; 612 struct tdopts opts; 613 krb5_error_code ret; 614 615 memset(&opts, 0, sizeof(opts)); 616 memset(&args, 0, sizeof(args)); 617 optind = 1; 618 while ((ch = getopt(argc, argv, "Hceno:")) != -1) { 619 switch (ch) { 620 case 'H': 621 opts.omitheader = 1; 622 break; 623 case 'c': 624 opts.csv = 1; 625 break; 626 case 'e': 627 opts.emptyhex_empty = 1; 628 break; 629 case 'n': 630 opts.numeric = 1; 631 break; 632 case 'o': 633 opts.fname = optarg; 634 break; 635 case '?': 636 default: 637 usage(); 638 break; 639 } 640 } 641 if (argc - optind < 1) 642 usage(); 643 rectype = argv[optind]; 644 for (i = 0; i < NTDTYPES; i++) { 645 if (strcmp(rectype, tdtypes[i].rectype) == 0) { 646 setup_args(&args, &tdtypes[i], &opts); 647 break; 648 } 649 } 650 if (i >= NTDTYPES) 651 usage(); 652 ret = krb5_db_iterate(util_context, NULL, tditer, &args, 0); 653 cleanup_args(&args); 654 if (ret) { 655 com_err(progname, ret, _("performing tabular dump")); 656 exit_status++; 657 } 658 } 659