From: Tom Yu Date: Wed, 9 Sep 2015 18:06:06 +0000 (-0400) Subject: Add tabular dump capability to kdb5_util X-Git-Tag: krb5-1.14-alpha1~14 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=899fe672b88d59902986baec42c4e42fc5af4d03;p=thirdparty%2Fkrb5.git Add tabular dump capability to kdb5_util This new kdb5_util tabdump command provides a reporting-friendly tabular dump format for the KDC database. This format is also suitable for importing into relational databases for queries. Output is in tab-separated or CSV format. The user can select an output table with a fixed number of columns. Currently, this only provides tables for a subset of the available principal data. This includes making visible some data that is hidden in hexadecimal strings in the tl_data of the ordinary dump format. ticket: 8243 --- diff --git a/src/kadmin/dbutil/Makefile.in b/src/kadmin/dbutil/Makefile.in index 0b2b405804..ceaf7931a0 100644 --- a/src/kadmin/dbutil/Makefile.in +++ b/src/kadmin/dbutil/Makefile.in @@ -6,10 +6,12 @@ KDB_DEP_LIB=$(DL_LIB) $(THREAD_LINKOPTS) PROG = kdb5_util SRCS = kdb5_util.c kdb5_create.c kadm5_create.c kdb5_destroy.c \ - kdb5_stash.c import_err.c strtok.c dump.c ovload.c kdb5_mkey.c + kdb5_stash.c import_err.c strtok.c dump.c ovload.c kdb5_mkey.c \ + tabdump.c tdumputil.c OBJS = kdb5_util.o kdb5_create.o kadm5_create.o kdb5_destroy.o \ - kdb5_stash.o import_err.o strtok.o dump.o ovload.o kdb5_mkey.o + kdb5_stash.o import_err.o strtok.o dump.o ovload.o kdb5_mkey.o \ + tabdump.o tdumputil.o GETDATE = ../cli/getdate.o diff --git a/src/kadmin/dbutil/kdb5_util.c b/src/kadmin/dbutil/kdb5_util.c index e2bb36c895..97a349a824 100644 --- a/src/kadmin/dbutil/kdb5_util.c +++ b/src/kadmin/dbutil/kdb5_util.c @@ -97,6 +97,7 @@ void usage() fprintf(stderr, _("\tupdate_princ_encryption [-f] [-n] [-v] [princ-pattern]\n" "\tpurge_mkeys [-f] [-n] [-v]\n" + "\ttabdump [-H] [-c] [-e] [-n] [-o outfile] dumptype\n" "\nwhere,\n\t[-x db_args]* - any number of database specific " "arguments.\n" "\t\t\tLook at each database documentation for supported " @@ -136,6 +137,7 @@ struct _cmd_table { {"list_mkeys", kdb5_list_mkeys, 1}, {"update_princ_encryption", kdb5_update_princ_encryption, 1}, {"purge_mkeys", kdb5_purge_mkeys, 1}, + {"tabdump", tabdump, 1}, {NULL, NULL, 0}, }; diff --git a/src/kadmin/dbutil/kdb5_util.h b/src/kadmin/dbutil/kdb5_util.h index eb520aff3b..69e186d4b2 100644 --- a/src/kadmin/dbutil/kdb5_util.h +++ b/src/kadmin/dbutil/kdb5_util.h @@ -83,6 +83,8 @@ extern void kdb5_add_mkey (int argc, char **argv); extern void kdb5_use_mkey (int argc, char **argv); extern void kdb5_list_mkeys (int argc, char **argv); extern void kdb5_update_princ_encryption (int argc, char **argv); +extern void tabdump (int argc, char **argv); + extern krb5_error_code master_key_convert(krb5_context context, krb5_db_entry *db_entry); extern void kdb5_purge_mkeys (int argc, char **argv); diff --git a/src/kadmin/dbutil/tabdump.c b/src/kadmin/dbutil/tabdump.c new file mode 100644 index 0000000000..4f9eb9d83b --- /dev/null +++ b/src/kadmin/dbutil/tabdump.c @@ -0,0 +1,663 @@ +/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* kdc/tabdump.c - reporting-friendly tabular KDB dumps */ +/* + * Copyright (C) 2015 by the Massachusetts Institute of Technology. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include "k5-platform.h" /* for asprintf */ + +#include +#include +#include +#include + +#include +#include + +#include "adm_proto.h" +#include "kdb5_util.h" +#include "tdumputil.h" + +struct tdopts { + int csv; /* 1 for CSV, 0 for tab-separated */ + int emptyhex_empty; /* print empty hex strings as "" not "-1" */ + int numeric; /* numeric instead of symbolic output */ + int omitheader; /* omit field headers */ + int writerectype; /* write record type prefix */ + char *fname; /* output file name */ +}; + +struct rec_args; + +typedef int (tdump_princ_fn)(struct rec_args *, const char *, krb5_db_entry *); +typedef int (tdump_policy_fn)(struct rec_args *, const char *, + kadm5_policy_ent_t); + +/* Descriptor for a tabdump record type */ +struct tdtype { + const char *rectype; + char * const *fieldnames; + tdump_princ_fn *princ_fn; + tdump_policy_fn *policy_fn; +}; + +static tdump_princ_fn keydata; +static tdump_princ_fn keyinfo; +static tdump_princ_fn princ_flags; +static tdump_princ_fn princ_lockout; +static tdump_princ_fn princ_meta; +static tdump_princ_fn princ_stringattrs; +static tdump_princ_fn princ_tktpolicy; + +static char * const keydata_fields[] = { + "name", "keyindex", "kvno", "enctype", "key", "salttype", "salt", NULL +}; +static char * const keyinfo_fields[] = { + "name", "keyindex", "kvno", "enctype", "salttype", "salt", NULL +}; +static char * const princ_flags_fields[] = { + "name", "flag", "value", NULL +}; +static char * const princ_lockout_fields[] = { + "name", "last_success", "last_failed", "fail_count", NULL +}; +static char * const princ_meta_fields[] = { + "name", "modby", "modtime", "lastpwd", "policy", "mkvno", "hist_kvno", NULL +}; +static char * const princ_stringattrs_fields[] = { + "name", "key", "value", NULL +}; +static char * const princ_tktpolicy_fields[] = { + "name", "expiration", "pw_expiration", "max_life", "max_renew_life", NULL +}; + +/* Lookup table for tabdump record types */ +static struct tdtype tdtypes[] = { + {"keydata", keydata_fields, keydata, NULL}, + {"keyinfo", keyinfo_fields, keyinfo, NULL}, + {"princ_flags", princ_flags_fields, princ_flags, NULL}, + {"princ_lockout", princ_lockout_fields, princ_lockout, NULL}, + {"princ_meta", princ_meta_fields, princ_meta, NULL}, + {"princ_stringattrs", princ_stringattrs_fields, princ_stringattrs, NULL}, + {"princ_tktpolicy", princ_tktpolicy_fields, princ_tktpolicy, NULL}, +}; +#define NTDTYPES (sizeof(tdtypes)/sizeof(tdtypes[0])) + +/* State to pass to KDB iterator */ +struct rec_args { + FILE *f; + struct tdtype *tdtype; + struct rechandle *rh; + struct tdopts *opts; +}; + +/* Decode the KADM_DATA from a DB entry.*/ +static int +get_adb(krb5_db_entry *dbe, osa_princ_ent_rec *adb) +{ + XDR xdrs; + int success; + krb5_tl_data tl_data; + krb5_error_code ret; + + memset(adb, 0, sizeof(*adb)); + tl_data.tl_data_type = KRB5_TL_KADM_DATA; + ret = krb5_dbe_lookup_tl_data(util_context, dbe, &tl_data); + if (ret != 0 || tl_data.tl_data_length == 0) + return 0; + xdrmem_create(&xdrs, (caddr_t)tl_data.tl_data_contents, + tl_data.tl_data_length, XDR_DECODE); + success = xdr_osa_princ_ent_rec(&xdrs, adb); + xdr_destroy(&xdrs); + return success; +} + +/* Write a date field as an ISO 8601 UTC date/time representation. */ +static int +write_date_iso(struct rec_args *args, krb5_timestamp when) +{ + char buf[64]; + time_t t; + struct tm *tm = NULL; + struct rechandle *h = args->rh; + + t = when; + tm = gmtime(&t); + if (tm == NULL) { + errno = EINVAL; + return -1; + } + if (strftime(buf, sizeof(buf), "%Y-%m-%dT%H:%M:%SZ", tm) == 0) { + errno = EINVAL; + return -1; + } + if (writefield(h, "%s", buf) < 0) + return -1; + return 0; +} + +/* Write a date field, optionally as a decimal POSIX timestamp. */ +static int +write_date(struct rec_args *args, krb5_timestamp when) +{ + struct tdopts *opts = args->opts; + struct rechandle *h = args->rh; + + if (opts->numeric) + return writefield(h, "%d", when); + + return write_date_iso(args, when); +} + +/* Write an enctype field, optionally as decimal. */ +static krb5_error_code +write_enctype(struct rec_args *args, krb5_int16 etype) +{ + char buf[256]; + krb5_error_code ret; + struct rechandle *h = args->rh; + struct tdopts *opts = args->opts; + + if (!opts->numeric) { + ret = krb5_enctype_to_name(etype, 0, buf, sizeof(buf)); + if (ret == 0) { + if (writefield(h, "%s", buf) < 0) + return errno; + return ret; + } + } + /* decimal if requested, or if conversion failed */ + if (writefield(h, "%d", etype) < 0) + return errno; + return 0; +} + +/* Write a salttype field, optionally as decimal. */ +static krb5_error_code +write_salttype(struct rec_args *args, krb5_int16 salttype) +{ + char buf[256]; + krb5_error_code ret; + struct rechandle *h = args->rh; + struct tdopts *opts = args->opts; + + if (!opts->numeric) { + ret = krb5_salttype_to_string(salttype, buf, sizeof(buf)); + if (ret == 0) { + if (writefield(h, "%s", buf) < 0) + return errno; + return ret; + } + } + /* decimal if requested, or if conversion failed */ + if (writefield(h, "%d", salttype) < 0) + return errno; + return 0; +} + +/* + * Write a field of bytes from krb5_data as a hexadecimal string. Write empty + * strings as "-1" unless requested. + */ +static int +write_data(struct rec_args *args, krb5_data *data) +{ + int ret; + char *p; + size_t i; + struct k5buf buf; + struct rechandle *h = args->rh; + struct tdopts *opts = args->opts; + + if (data->length == 0 && !opts->emptyhex_empty) { + if (writefield(h, "-1") < 0) + return -1; + return 0; + } + k5_buf_init_dynamic(&buf); + p = data->data; + for (i = 0; i < data->length; i++) + k5_buf_add_fmt(&buf, "%02x", (unsigned char)p[i]); + + if (buf.data == NULL) { + errno = ENOMEM; + return -1; + } + ret = writefield(h, "%s", (char *)buf.data); + k5_buf_free(&buf); + return ret; +} + +/* Write a single record of a keydata/keyinfo key set. */ +static krb5_error_code +keyinfo_rec(struct rec_args *args, const char *name, int i, krb5_key_data *kd, + int dumpkeys) +{ + int ret; + krb5_data data; + struct rechandle *h = args->rh; + + if (startrec(h) < 0) + return errno; + if (writefield(h, "%s", name) < 0) + return errno; + if (writefield(h, "%d", i) < 0) + return errno; + if (writefield(h, "%d", kd->key_data_kvno) < 0) + return errno; + if (write_enctype(args, kd->key_data_type[0]) < 0) + return errno; + if (dumpkeys) { + data.length = kd->key_data_length[0]; + data.data = (void *)kd->key_data_contents[0]; + if (write_data(args, &data) < 0) + return errno; + } + ret = write_salttype(args, kd->key_data_type[1]); + if (ret) + return ret; + data.length = kd->key_data_length[1]; + data.data = (void *)kd->key_data_contents[1]; + if (write_data(args, &data) < 0) + return errno; + if (endrec(h) < 0) + return errno; + return 0; +} + +/* Write out a principal's key set, optionally including actual key data. */ +static krb5_error_code +keyinfo_common(struct rec_args *args, const char *name, krb5_db_entry *entry, + int dumpkeys) +{ + krb5_error_code ret; + krb5_key_data kd; + int i; + + for (i = 0; i < entry->n_key_data; i++) { + kd = entry->key_data[i]; + /* missing salt data -> normal salt */ + if (kd.key_data_ver == 1) { + kd.key_data_ver = 2; + kd.key_data_type[1] = KRB5_KDB_SALTTYPE_NORMAL; + kd.key_data_length[1] = 0; + kd.key_data_contents[1] = NULL; + } + ret = keyinfo_rec(args, name, i, &kd, dumpkeys); + if (ret) + return ret; + } + return 0; +} + +/* Write a principal's key data. */ +static krb5_error_code +keydata(struct rec_args *args, const char *name, krb5_db_entry *dbe) +{ + return keyinfo_common(args, name, dbe, 1); +} + +/* Write a principal's key info (suppressing actual key data). */ +static krb5_error_code +keyinfo(struct rec_args *args, const char *name, krb5_db_entry *dbe) +{ + return keyinfo_common(args, name, dbe, 0); +} + +/* Write a record corresponding to a single principal flag setting. */ +static krb5_error_code +princflag_rec(struct rechandle *h, const char *name, const char *flagname, + int set) +{ + if (startrec(h) < 0) + return errno; + if (writefield(h, "%s", name) < 0) + return errno; + if (writefield(h, "%s", flagname) < 0) + return errno; + if (writefield(h, "%d", set) < 0) + return errno; + if (endrec(h) < 0) + return errno; + return 0; +} + +/* Write a principal's flag settings. */ +static krb5_error_code +princ_flags(struct rec_args *args, const char *name, krb5_db_entry *dbe) +{ + int i; + char *s = NULL; + krb5_flags flags = dbe->attributes; + krb5_error_code ret; + struct tdopts *opts = args->opts; + struct rechandle *h = args->rh; + + for (i = 0; i < 32; i++) { + if (opts->numeric) { + if (asprintf(&s, "0x%08lx", 1UL << i) == -1) + return ENOMEM; + } else { + ret = krb5_flagnum_to_string(i, &s); + if (ret) + return ret; + /* Don't print unknown flags if they're not set and numeric output + * isn't requested. */ + if (!(flags & (1UL << i)) && strncmp(s, "0x", 2) == 0) + continue; + } + ret = princflag_rec(h, name, s, ((flags & (1UL << i)) != 0)); + free(s); + if (ret) + return ret; + } + return 0; +} + +/* Write a principal's lockout data. */ +static krb5_error_code +princ_lockout(struct rec_args *args, const char *name, krb5_db_entry *dbe) +{ + struct rechandle *h = args->rh; + + if (startrec(h) < 0) + return errno; + if (writefield(h, "%s", name) < 0) + return errno; + if (write_date(args, dbe->last_success) < 0) + return errno; + if (write_date(args, dbe->last_failed) < 0) + return errno; + if (writefield(h, "%d", dbe->fail_auth_count) < 0) + return errno; + if (endrec(h) < 0) + return errno; + return 0; +} + +/* Write a principal's metadata. */ +static krb5_error_code +princ_meta(struct rec_args *args, const char *name, krb5_db_entry *dbe) +{ + int got_adb = 0; + char *modby; + krb5_kvno mkvno; + const char *policy; + krb5_principal mod_princ = NULL; + krb5_timestamp mod_time, last_pwd; + krb5_error_code ret; + osa_princ_ent_rec adb; + struct rechandle *h = args->rh; + + memset(&adb, 0, sizeof(adb)); + if (startrec(h) < 0) + return errno; + if (writefield(h, "%s", name) < 0) + return errno; + + ret = krb5_dbe_lookup_last_pwd_change(util_context, dbe, &last_pwd); + if (ret) + return ret; + ret = krb5_dbe_get_mkvno(util_context, dbe, &mkvno); + if (ret) + return ret; + + ret = krb5_dbe_lookup_mod_princ_data(util_context, dbe, &mod_time, + &mod_princ); + if (ret) + return ret; + ret = krb5_unparse_name(util_context, mod_princ, &modby); + krb5_free_principal(util_context, mod_princ); + if (ret) + return ret; + ret = writefield(h, "%s", modby); + krb5_free_unparsed_name(util_context, modby); + if (ret < 0) + return errno; + + if (write_date(args, mod_time) < 0) + return errno; + if (write_date(args, last_pwd) < 0) + return errno; + + got_adb = get_adb(dbe, &adb); + if (got_adb && adb.policy != NULL) + policy = adb.policy; + else + policy = ""; + ret = writefield(h, "%s", policy); + if (ret < 0) { + ret = errno; + goto cleanup; + } + if (writefield(h, "%d", mkvno) < 0) { + ret = errno; + goto cleanup; + } + if (writefield(h, "%d", adb.admin_history_kvno) < 0) { + ret = errno; + goto cleanup; + } + if (endrec(h) < 0) + ret = errno; + else + ret = 0; + +cleanup: + kdb_free_entry(NULL, NULL, &adb); + return ret; +} + +/* Write a principal's string attributes. */ +static krb5_error_code +princ_stringattrs(struct rec_args *args, const char *name, krb5_db_entry *dbe) +{ + int i, nattrs; + krb5_error_code ret; + krb5_string_attr *attrs; + struct rechandle *h = args->rh; + + ret = krb5_dbe_get_strings(util_context, dbe, &attrs, &nattrs); + if (ret) + return ret; + for (i = 0; i < nattrs; i++) { + if (startrec(h) < 0) { + ret = errno; + goto cleanup; + } + if (writefield(h, "%s", name) < 0) { + ret = errno; + goto cleanup; + } + if (writefield(h, "%s", attrs[i].key) < 0) { + ret = errno; + goto cleanup; + } + if (writefield(h, "%s", attrs[i].value) < 0) { + ret = errno; + goto cleanup; + } + if (endrec(h) < 0) { + ret = errno; + goto cleanup; + } + } +cleanup: + krb5_dbe_free_strings(util_context, attrs, nattrs); + return ret; +} + +/* Write a principal's ticket policy. */ +static krb5_error_code +princ_tktpolicy(struct rec_args *args, const char *name, krb5_db_entry *dbe) +{ + struct rechandle *h = args->rh; + + if (startrec(h) < 0) + return errno; + if (writefield(h, "%s", name) < 0) + return errno; + if (write_date(args, dbe->expiration) < 0) + return errno; + if (write_date(args, dbe->pw_expiration) < 0) + return errno; + if (writefield(h, "%d", dbe->max_life) < 0) + return errno; + if (writefield(h, "%d", dbe->max_renewable_life) < 0) + return errno; + if (endrec(h) < 0) + return errno; + return 0; +} + +/* Iterator function for krb5_db_iterate() */ +static krb5_error_code +tditer(void *ptr, krb5_db_entry *entry) +{ + krb5_error_code ret; + struct rec_args *args = ptr; + char *name; + + ret = krb5_unparse_name(util_context, entry->princ, &name); + if (ret) { + com_err(progname, ret, _("while unparsing principal name")); + return ret; + } + ret = args->tdtype->princ_fn(args, name, entry); + krb5_free_unparsed_name(util_context, name); + if (ret) + return ret; + return 0; +} + +/* Set up state structure for the iterator. */ +static krb5_error_code +setup_args(struct rec_args *args, struct tdtype *tdtype, + struct tdopts *opts) +{ + FILE *f = NULL; + const char *rectype = NULL; + struct rechandle *rh; + + args->tdtype = tdtype; + args->opts = opts; + if (opts->fname != NULL && strcmp(opts->fname, "-") != 0) { + f = fopen(opts->fname, "w"); + if (f == NULL) { + com_err(progname, errno, _("opening %s for writing"), + opts->fname); + return errno; + } + args->f = f; + } else { + f = stdout; + args->f = NULL; + } + if (opts->writerectype) + rectype = tdtype->rectype; + if (opts->csv) + rh = rechandle_csv(f, rectype); + else + rh = rechandle_tabsep(f, rectype); + if (rh == NULL) + return ENOMEM; + args->rh = rh; + if (!opts->omitheader && writeheader(rh, tdtype->fieldnames) < 0) + return errno; + return 0; +} + +/* Clean up the state structure. */ +static void +cleanup_args(struct rec_args *args) +{ + rechandle_free(args->rh); + if (args->f != NULL) + fclose(args->f); +} + +/* + * Usaage is: + * tabdump [-H] [-c] [-e] [-n] [-o outfile] dumptype + */ +void +tabdump(int argc, char **argv) +{ + int ch; + size_t i; + const char *rectype; + struct rec_args args; + struct tdopts opts; + krb5_error_code ret; + + memset(&opts, 0, sizeof(opts)); + memset(&args, 0, sizeof(args)); + optind = 1; + while ((ch = getopt(argc, argv, "Hceno:")) != -1) { + switch (ch) { + case 'H': + opts.omitheader = 1; + break; + case 'c': + opts.csv = 1; + break; + case 'e': + opts.emptyhex_empty = 1; + break; + case 'n': + opts.numeric = 1; + break; + case 'o': + opts.fname = optarg; + break; + case '?': + default: + usage(); + break; + } + } + if (argc - optind < 1) + usage(); + rectype = argv[optind]; + for (i = 0; i < NTDTYPES; i++) { + if (strcmp(rectype, tdtypes[i].rectype) == 0) { + setup_args(&args, &tdtypes[i], &opts); + break; + } + } + if (i >= NTDTYPES) + usage(); + ret = krb5_db_iterate(util_context, NULL, tditer, &args, 0); + cleanup_args(&args); + if (ret) { + com_err(progname, ret, _("performing tabular dump")); + exit_status++; + } +}