xref: /freebsd/crypto/krb5/src/kadmin/dbutil/tabdump.c (revision f1c4c3daccbaf3820f0e2224de53df12fc952fcc)
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