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