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
get_adb(krb5_db_entry * dbe,osa_princ_ent_rec * adb)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
write_date_iso(struct rec_args * args,krb5_timestamp when)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
write_date(struct rec_args * args,krb5_timestamp when)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
write_enctype(struct rec_args * args,krb5_int16 etype)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
write_salttype(struct rec_args * args,krb5_int16 salttype)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
write_data(struct rec_args * args,krb5_data * data)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
alias(struct rec_args * args,const char * name,krb5_db_entry * dbe)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
keyinfo_rec(struct rec_args * args,const char * name,int i,krb5_key_data * kd,int dumpkeys)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
keyinfo_common(struct rec_args * args,const char * name,krb5_db_entry * entry,int dumpkeys)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
keydata(struct rec_args * args,const char * name,krb5_db_entry * dbe)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
keyinfo(struct rec_args * args,const char * name,krb5_db_entry * dbe)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
princflag_rec(struct rechandle * h,const char * name,const char * flagname,int set)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
princ_flags(struct rec_args * args,const char * name,krb5_db_entry * dbe)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
princ_lockout(struct rec_args * args,const char * name,krb5_db_entry * dbe)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
princ_meta(struct rec_args * args,const char * name,krb5_db_entry * dbe)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
princ_stringattrs(struct rec_args * args,const char * name,krb5_db_entry * dbe)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
princ_tktpolicy(struct rec_args * args,const char * name,krb5_db_entry * dbe)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
tditer(void * ptr,krb5_db_entry * entry)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
setup_args(struct rec_args * args,struct tdtype * tdtype,struct tdopts * opts)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
cleanup_args(struct rec_args * args)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
tabdump(int argc,char ** argv)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