time-t-casting-fixes-20060404
[openafs.git] / src / kauth / rebuild.c
1 /*
2  * Copyright 2000, International Business Machines Corporation and others.
3  * All Rights Reserved.
4  * 
5  * This software has been released under the terms of the IBM Public
6  * License.  For details, see the LICENSE file in the top-level source
7  * directory or online at http://www.openafs.org/dl/license10.html
8  */
9
10 #include <afsconfig.h>
11 #include <afs/param.h>
12
13 RCSID
14     ("$Header$");
15
16 #include <sys/types.h>
17 #include <sys/stat.h>
18 #ifdef AFS_NT40_ENV
19 #include <winsock2.h>
20 #include <fcntl.h>
21 #include <io.h>
22 #else
23 #include <sys/file.h>
24 #include <netinet/in.h>
25 #endif
26 #ifdef HAVE_STRING_H
27 #include <string.h>
28 #else
29 #ifdef HAVE_STRINGS_H
30 #include <strings.h>
31 #endif
32 #endif
33 #include <stdio.h>
34 #include <errno.h>
35 #include <time.h>
36 #include <ubik.h>
37 #include <afs/cmd.h>
38
39 #include <afs/com_err.h>
40
41 #include "kauth.h"
42 #include "kautils.h"
43 #include "kaserver.h"
44
45 #define UBIK_HEADERSIZE 64
46 #define UBIK_BUFFERSIZE 1024
47
48 char *whoami = "kadb_check";
49 int fd;
50 FILE *out;
51
52 void badEntry();
53
54 int listuheader, listkheader, listentries, verbose;
55
56 int
57 readUbikHeader()
58 {
59     int offset, r;
60     struct ubik_hdr uheader;
61
62     offset = lseek(fd, 0, 0);
63     if (offset != 0) {
64         printf("error: lseek to 0 failed: %d %d\n", offset, errno);
65         return (-1);
66     }
67
68     /* now read the info */
69     r = read(fd, &uheader, sizeof(uheader));
70     if (r != sizeof(uheader)) {
71         printf("error: read of %d bytes failed: %d %d\n", sizeof(uheader), r,
72                errno);
73         return (-1);
74     }
75
76     uheader.magic = ntohl(uheader.magic);
77     uheader.size = ntohl(uheader.size);
78     uheader.version.epoch = ntohl(uheader.version.epoch);
79     uheader.version.counter = ntohl(uheader.version.counter);
80
81     if (listuheader) {
82         printf("Ubik Header\n");
83         printf("   Magic           = 0x%x\n", uheader.magic);
84         printf("   Size            = %u\n", uheader.size);
85         printf("   Version.epoch   = %u\n", uheader.version.epoch);
86         printf("   Version.counter = %u\n", uheader.version.counter);
87     }
88
89     if (uheader.size != UBIK_HEADERSIZE)
90         printf("Ubik header size is %u (should be %u)\n", uheader.size,
91                UBIK_HEADERSIZE);
92     if (uheader.magic != UBIK_MAGIC)
93         printf("Ubik header magic is 0x%x (should be 0x%x)\n", uheader.magic,
94                UBIK_MAGIC);
95
96     return (0);
97 }
98
99 void
100 PrintHeader(header)
101      struct kaheader *header;
102 {
103     printf("Version          = %d\n", header->version);
104     printf("HeaderSize       = %d\n", header->headerSize);
105     printf("Free Ptr         = %u\n", header->freePtr);
106     printf("EOF  Ptr         = %u\n", header->eofPtr);
107     printf("Kvno Ptr         = %u\n", header->kvnoPtr);
108     printf("SpecialKeysVersion changed = %d\n", header->specialKeysVersion);
109     printf("# admin accounts = %d\n", header->admin_accounts);
110     printf("HashSize         = %d\n", header->hashsize);
111     printf("Check Version    = %d\n", header->checkVersion);
112     printf("stats.minorVersion     = %d\n", header->stats.minor_version);
113     printf("stats.AllocBlock calls = %d\n", header->stats.allocs);
114     printf("stats.FreeBlock  calls = %d\n", header->stats.frees);
115     printf("stats.cpw commands     = %d\n", header->stats.cpws);
116 }
117
118 void
119 PrintEntry(index, entry)
120      afs_int32 index;
121      struct kaentry *entry;
122 {
123     int i;
124     char Time[100];
125     struct tm *tm_p;
126     time_t tt;
127     time_t modification_time = entry->modification_time;
128     time_t change_password_time = entry->change_password_time;
129     time_t max_ticket_lifetime = entry->max_ticket_lifetime;
130
131     printf("\n");
132
133     i = (index - sizeof(struct kaheader)) / sizeof(struct kaentry);
134
135     printf("Entry %5d (%u):\n", i, index);
136
137     if (entry->flags & KAFNORMAL) {
138         printf("   Name = %s", entry->userID.name);
139         if (strlen(entry->userID.instance) > 0) {
140             printf(".%s", entry->userID.instance);
141         }
142         printf("\n");
143     }
144
145     printf("   flags = ");
146     if (entry->flags & KAFNORMAL)
147         printf("NORMAL ");
148     if (entry->flags & KAFADMIN)
149         printf("ADMIN ");
150     if (entry->flags & KAFNOTGS)
151         printf("NOTGS ");
152     if (entry->flags & KAFNOSEAL)
153         printf("NOSEAL ");
154     if (entry->flags & KAFNOCPW)
155         printf("NOCPW ");
156
157     if (entry->flags & KAFNEWASSOC)
158         printf("CR-ASSOC ");
159     if (entry->flags & KAFFREE)
160         printf("FREE ");
161     if (entry->flags & KAFOLDKEYS)
162         printf("OLDKEYS ");
163     if (entry->flags & KAFSPECIAL)
164         printf("SPECIAL ");
165     if (entry->flags & KAFASSOCROOT)
166         printf("ROOT-ASSOC ");
167     if (entry->flags & KAFASSOC)
168         printf("AN-ASSOC ");
169     printf("\n");
170
171     printf("   Next = %u\n", entry->next);
172
173     if (entry->flags & KAFFREE)
174         return;
175     if (entry->flags & KAFOLDKEYS)
176         return;
177
178     tt = entry->user_expiration;
179     tm_p = localtime(&tt);
180     if (tm_p)
181         strftime(Time, 100, "%m/%d/%Y %H:%M", tm_p);
182
183     printf("   User Expiration = %s\n",
184            (entry->user_expiration == 0xffffffff) ? "never" : Time);
185
186     printf("   Password Expiration = %u days %s\n",
187            entry->misc_auth_bytes[EXPIRES],
188            (entry->misc_auth_bytes[EXPIRES] ? "" : "(never)"));
189
190     printf("   Password Attempts before lock = ");
191     if (!entry->misc_auth_bytes[ATTEMPTS])
192         printf("unlimited\n");
193     else
194         printf("%d\n", entry->misc_auth_bytes[ATTEMPTS]);
195
196     printf("   Password lockout time = ");
197     if (!entry->misc_auth_bytes[LOCKTIME])
198         printf("unlimited\n");
199     else
200         printf("%.1f min\n", (entry->misc_auth_bytes[LOCKTIME] * 8.5));
201
202     printf("   Is entry locked = %s\n",
203            (entry->misc_auth_bytes[REUSEFLAGS] ==
204             KA_ISLOCKED) ? "yes" : "no");
205
206     printf("   Permit password reuse = %s\n",
207            (!entry->pwsums[0] && !entry->pwsums[1]) ? "yes" : "no");
208
209     printf("   Mod Time = %u: %s", entry->modification_time,
210            ctime(&modification_time));
211     printf("   Mod ID = %u\n", entry->modification_id);
212     printf("   Change Password Time = %u: %s", entry->change_password_time,
213            ctime(&change_password_time));
214     printf("   Ticket lifetime = %u: %s", entry->max_ticket_lifetime,
215            ctime(&max_ticket_lifetime));
216     printf("   Key Version = %d\n", entry->key_version);
217
218     printf("   Key = ");
219     ka_PrintBytes((char *)&entry->key, sizeof(entry->key));
220     printf("\n");
221
222     /* What about asServer structs and such and misc_ath_bytes */
223 }
224
225 /* ntohEntry - convert back to host-order */
226 ntohEntry(struct kaentry *entryp)
227 {
228     entryp->flags = ntohl(entryp->flags);
229     entryp->next = ntohl(entryp->next);
230     entryp->user_expiration = ntohl(entryp->user_expiration);
231     entryp->modification_time = ntohl(entryp->modification_time);
232     entryp->modification_id = ntohl(entryp->modification_id);
233     entryp->change_password_time = ntohl(entryp->change_password_time);
234     entryp->max_ticket_lifetime = ntohl(entryp->max_ticket_lifetime);
235     entryp->key_version = ntohl(entryp->key_version);
236     entryp->misc.asServer.nOldKeys = ntohl(entryp->misc.asServer.nOldKeys);
237     entryp->misc.asServer.oldKeys = ntohl(entryp->misc.asServer.oldKeys);
238 }
239
240 char principal[64];
241 char *
242 EntryName(entryp)
243      struct kaentry *entryp;
244 {
245     char name[32], inst[32];
246
247     ka_ConvertBytes(name, sizeof(name), entryp->userID.name,
248                     strlen(entryp->userID.name));
249     ka_ConvertBytes(inst, sizeof(inst), entryp->userID.instance,
250                     strlen(entryp->userID.instance));
251
252     if (strlen(entryp->userID.instance)) {
253         sprintf(principal, "%s.%s", name, inst);
254     } else {
255         strcpy(principal, name);
256     }
257
258     return (principal);
259 }
260
261 void
262 RebuildEntry(entryp)
263      struct kaentry *entryp;
264 {
265     char key[33];
266     char flags[128];
267     char Time[50];
268
269     /* Special entries are not rebuilt */
270     if (entryp->flags & KAFSPECIAL)
271         return;
272
273     fprintf(out, "create    -name %s", EntryName(entryp));
274
275     ka_ConvertBytes(key, sizeof(key), (char *)&entryp->key,
276                     sizeof(entryp->key));
277     fprintf(out, " -initial_password foo\n");
278
279     strcpy(flags, "");
280     if (entryp->flags & KAFADMIN)
281         strcat(flags, "+ADMIN");
282     if (entryp->flags & KAFNOTGS)
283         strcat(flags, "+NOTGS");
284     if (entryp->flags & KAFNOSEAL)
285         strcat(flags, "+NOSEAL");
286     if (entryp->flags & KAFNOCPW)
287         strcat(flags, "+NOCPW");
288
289     fprintf(out, "setfields -name %s", principal);
290     if (strcmp(flags, "") != 0)
291         fprintf(out, " -flags %s", &flags[1]);
292     if (entryp->user_expiration != 0xffffffff) {
293         time_t tt = entryp->user_expiration;
294         strftime(Time, 50, "%m/%d/%Y %H:%M",localtime(&tt));
295         fprintf(out, " -expiration '%s'", Time);
296     }
297     fprintf(out, " -lifetime %u", entryp->max_ticket_lifetime);
298     if (entryp->misc_auth_bytes[EXPIRES])
299         fprintf(out, " -pwexpires %u", entryp->misc_auth_bytes[EXPIRES]);
300     if (entryp->pwsums[0] || entryp->pwsums[1])
301         fprintf(out, " -reuse no");
302     if (entryp->misc_auth_bytes[ATTEMPTS])
303         fprintf(out, " -attempts %u", entryp->misc_auth_bytes[ATTEMPTS]);
304     if (entryp->misc_auth_bytes[LOCKTIME])
305         fprintf(out, " -locktime %d",
306                 (int)(entryp->misc_auth_bytes[LOCKTIME] * 8.5));
307     fprintf(out, "\n");
308
309     fprintf(out, "setkey    -name %s -new_key %s -kvno %d\n", principal, key,
310             ntohl(entryp->key_version));
311 }
312
313 CheckHeader(header)
314      struct kaheader *header;
315 {
316     afs_int32 i, code = 0;
317
318     header->version = ntohl(header->version);
319     header->headerSize = ntohl(header->headerSize);
320     header->freePtr = ntohl(header->freePtr);
321     header->eofPtr = ntohl(header->eofPtr);
322     header->kvnoPtr = ntohl(header->kvnoPtr);
323     header->stats.minor_version = ntohl(header->stats.minor_version);
324     header->stats.allocs = ntohl(header->stats.allocs);
325     header->stats.frees = ntohl(header->stats.frees);
326     header->stats.cpws = ntohl(header->stats.cpws);
327     header->admin_accounts = ntohl(header->admin_accounts);
328     header->specialKeysVersion = ntohl(header->specialKeysVersion);
329     header->hashsize = ntohl(header->hashsize);
330     for (i = 0; i < HASHSIZE; i++) {
331         header->nameHash[i] = ntohl(header->nameHash[i]);
332     }
333     header->checkVersion = ntohl(header->checkVersion);
334
335     if (header->version != header->checkVersion) {
336         code++;
337         fprintf(stderr, "HEADER VERSION MISMATCH: initial %d, final %d\n",
338                 header->version, header->checkVersion);
339     }
340     if (header->headerSize != sizeof(struct kaheader)) {
341         code++;
342         fprintf(stderr,
343                 "HEADER SIZE WRONG: file indicates %d, should be %d\n",
344                 header->headerSize, sizeof(struct kaheader));
345     }
346     if (header->hashsize != HASHSIZE) {
347         code++;
348         fprintf(stderr, "HASH SIZE WRONG: file indicates %d, should be %d\n",
349                 header->hashsize, HASHSIZE);
350     }
351     if ((header->kvnoPtr && ((header->kvnoPtr < header->headerSize)
352                              || (header->eofPtr < header->freePtr)))
353         || (header->freePtr && ((header->freePtr < header->headerSize)
354                                 || (header->eofPtr < header->kvnoPtr)))) {
355         code++;
356         fprintf(stderr,
357                 "DATABASE POINTERS BAD: header size = %d, freePtr = %d, kvnoPtr = %d, eofPtr = %d\n",
358                 header->headerSize, header->freePtr, header->kvnoPtr,
359                 header->eofPtr);
360     }
361
362 /*
363  *  fprintf(stderr, "DB Version %d, %d possible entries\n", header->version,
364  *          (header->eofPtr-header->headerSize) / sizeof(struct kaentry));
365  */
366     return code;
367 }
368
369 afs_int32
370 NameHash(entryp)
371      struct kaentry *entryp;
372 {
373     unsigned int hash;
374     int i;
375     char *aname = entryp->userID.name;
376     char *ainstance = entryp->userID.instance;
377
378     /* stolen directly from the HashString function in the vol package */
379     hash = 0;
380     for (i = strlen(aname), aname += i - 1; i--; aname--)
381         hash = (hash * 31) + (*((unsigned char *)aname) - 31);
382     for (i = strlen(ainstance), ainstance += i - 1; i--; ainstance--)
383         hash = (hash * 31) + (*((unsigned char *)ainstance) - 31);
384     return (hash % HASHSIZE);
385 }
386
387 readDB(offset, buffer, size)
388      afs_int32 offset;
389      char *buffer;
390      afs_int32 size;
391 {
392     afs_int32 code;
393
394     offset += UBIK_HEADERSIZE;
395     code = lseek(fd, offset, SEEK_SET);
396     if (code != offset) {
397         com_err(whoami, errno, "skipping Ubik header");
398         exit(2);
399     }
400     code = read(fd, buffer, size);
401     if (code != size) {
402         com_err(whoami, errno, "reading db got %d bytes", code);
403         exit(3);
404     }
405 }
406
407 #include "AFS_component_version_number.c"
408
409 WorkerBee(as, arock)
410      struct cmd_syndesc *as;
411      char *arock;
412 {
413     afs_int32 code;
414     char *dbFile;
415     char *outFile;
416     afs_int32 index;
417     struct stat info;
418     struct kaheader header;
419     int nentries, i, j, count;
420     int *entrys;
421     struct kaentry entry;
422
423     dbFile = as->parms[0].items->data;  /* -database */
424     listuheader = (as->parms[1].items ? 1 : 0); /* -uheader  */
425     listkheader = (as->parms[2].items ? 1 : 0); /* -kheader  */
426     listentries = (as->parms[3].items ? 1 : 0); /* -entries  */
427     verbose = (as->parms[4].items ? 1 : 0);     /* -verbose  */
428     outFile = (as->parms[5].items ? as->parms[5].items->data : NULL);   /* -rebuild  */
429
430     if (outFile) {
431         out = fopen(outFile, "w");
432         if (!out) {
433             com_err(whoami, errno, "opening output file %s", outFile);
434             exit(7);
435         }
436     } else
437         out = 0;
438
439     fd = open(dbFile, O_RDONLY, 0);
440     if (fd < 0) {
441         com_err(whoami, errno, "opening database file %s", dbFile);
442         exit(6);
443     }
444     code = fstat(fd, &info);
445     if (code) {
446         com_err(whoami, errno, "stat'ing file %s", dbFile);
447         exit(6);
448     }
449     if ((info.st_size - UBIK_HEADERSIZE) % UBIK_BUFFERSIZE)
450         fprintf(stderr,
451                 "DATABASE SIZE INCONSISTENT: was %d, should be (n*%d + %d), for integral n\n",
452                 info.st_size, UBIK_BUFFERSIZE, UBIK_HEADERSIZE);
453
454     readUbikHeader();
455
456     readDB(0, &header, sizeof(header));
457     code = CheckHeader(&header);
458     if (listkheader)
459         PrintHeader(&header);
460
461     nentries =
462         (info.st_size -
463          (UBIK_HEADERSIZE + header.headerSize)) / sizeof(struct kaentry);
464     entrys = (int *)malloc(nentries * sizeof(int));
465     memset(entrys, 0, nentries * sizeof(int));
466
467     for (i = 0, index = sizeof(header); i < nentries;
468          i++, index += sizeof(struct kaentry)) {
469         readDB(index, &entry, sizeof(entry));
470
471         if (index >= header.eofPtr) {
472             entrys[i] |= 0x8;
473         } else if (listentries) {
474             PrintEntry(index, &entry);
475         }
476
477         if (entry.flags & KAFNORMAL) {
478             entrys[i] |= 0x1;   /* user entry */
479
480             if (strlen(entry.userID.name) == 0) {
481                 if (verbose)
482                     printf("Entry %d has zero length name\n", i);
483                 continue;
484             }
485             if (!des_check_key_parity(&entry.key)
486                 || des_is_weak_key(&entry.key)) {
487                 fprintf(stderr, "Entry %d, %s, has bad key\n", i,
488                         EntryName(&entry));
489                 continue;
490             }
491
492             if (out) {
493                 RebuildEntry(&entry);
494             }
495
496         } else if (entry.flags & KAFFREE) {
497             entrys[i] |= 0x2;   /* free entry */
498
499         } else if (entry.flags & KAFOLDKEYS) {
500             entrys[i] |= 0x4;   /* old keys block */
501             /* Should check the structure of the oldkeys block? */
502
503         } else {
504             if (index < header.eofPtr) {
505                 fprintf(stderr, "Entry %d is unrecognizable\n", i);
506             }
507         }
508     }
509
510     /* Follow the hash chains */
511     for (j = 0; j < HASHSIZE; j++) {
512         for (index = header.nameHash[j]; index; index = entry.next) {
513             readDB(index, &entry, sizeof(entry));
514
515             /* check to see if the name is hashed correctly */
516             i = NameHash(&entry);
517             if (i != j) {
518                 fprintf(stderr,
519                         "Entry %d, %s, found in hash chain %d (should be %d)\n",
520                         ((index -
521                           sizeof(struct kaheader)) / sizeof(struct kaentry)),
522                         EntryName(&entry), j, i);
523             }
524
525             /* Is it on another hash chain or circular hash chain */
526             i = (index - header.headerSize) / sizeof(entry);
527             if (entrys[i] & 0x10) {
528                 fprintf(stderr,
529                         "Entry %d, %s, hash index %d, was found on another hash chain\n",
530                         i, EntryName(&entry), j);
531                 if (entry.next)
532                     fprintf(stderr, "Skipping rest of hash chain %d\n", j);
533                 else
534                     fprintf(stderr, "No next entry in hash chain %d\n", j);
535                 code++;
536                 break;
537             }
538             entrys[i] |= 0x10;  /* On hash chain */
539         }
540     }
541
542     /* Follow the free pointers */
543     count = 0;
544     for (index = header.freePtr; index; index = entry.next) {
545         readDB(index, &entry, sizeof(entry));
546
547         /* Is it on another chain or circular free chain */
548         i = (index - header.headerSize) / sizeof(entry);
549         if (entrys[i] & 0x20) {
550             fprintf(stderr, "Entry %d, %s, already found on free chain\n", i,
551                     EntryName(&entry));
552             fprintf(stderr, "Skipping rest of free chain\n");
553             code++;
554             break;
555         }
556         entrys[i] |= 0x20;      /* On free chain */
557
558         count++;
559     }
560     if (verbose)
561         printf("Found %d free entries\n", count);
562
563     /* Follow the oldkey blocks */
564     count = 0;
565     for (index = header.kvnoPtr; index; index = entry.next) {
566         readDB(index, &entry, sizeof(entry));
567
568         /* Is it on another chain or circular free chain */
569         i = (index - header.headerSize) / sizeof(entry);
570         if (entrys[i] & 0x40) {
571             fprintf(stderr, "Entry %d, %s, already found on olkeys chain\n",
572                     i, EntryName(&entry));
573             fprintf(stderr, "Skipping rest of oldkeys chain\n");
574             code++;
575             break;
576         }
577         entrys[i] |= 0x40;      /* On free chain */
578
579         count++;
580     }
581     if (verbose)
582         printf("Found %d oldkey blocks\n", count);
583
584     /* Now recheck all the blocks and see if they are allocated correctly
585      * 0x1 --> User Entry           0x10 --> On hash chain
586      * 0x2 --> Free Entry           0x20 --> On Free chain
587      * 0x4 --> OldKeys Entry        0x40 --> On Oldkeys chain
588      * 0x8 --> Past EOF
589      */
590     for (i = 0; i < nentries; i++) {
591         j = entrys[i];
592         if (j & 0x1) {          /* user entry */
593             if (!(j & 0x10))
594                 badEntry(j, i); /* on hash chain? */
595             else if (j & 0xee)
596                 badEntry(j, i); /* anything else? */
597         } else if (j & 0x2) {   /* free entry */
598             if (!(j & 0x20))
599                 badEntry(j, i); /* on free chain? */
600             else if (j & 0xdd)
601                 badEntry(j, i); /* anything else? */
602         } else if (j & 0x4) {   /* oldkeys entry */
603             if (!(j & 0x40))
604                 badEntry(j, i); /* on oldkeys chain? */
605             else if (j & 0xbb)
606                 badEntry(j, i); /* anything else? */
607         } else if (j & 0x8) {   /* past eof */
608             if (j & 0xf7)
609                 badEntry(j, i); /* anything else? */
610         } else
611             badEntry(j, i);     /* anything else? */
612     }
613
614     exit(code != 0);
615 }
616
617 void
618 badEntry(e, i)
619      afs_int32 e, i;
620 {
621     int offset;
622     struct kaentry entry;
623
624     offset = i * sizeof(struct kaentry) + sizeof(struct kaheader);
625     readDB(offset, &entry, sizeof(entry));
626
627     fprintf(stderr, "Entry %d, %s, hash index %d, is bad: [", i,
628             EntryName(&entry), NameHash(&entry));
629     if (e & 0x1)
630         fprintf(stderr, " UserEntry");
631     if (e & 0x2)
632         fprintf(stderr, " FreeEntry");
633     if (e & 0x4)
634         fprintf(stderr, " OldkeysEntry");
635     if (e & 0x8)
636         fprintf(stderr, " PastEOF");
637     if (!(e & 0xf))
638         fprintf(stderr, " <NULL>");
639     fprintf(stderr, " ] [");
640     if (e & 0x10)
641         fprintf(stderr, " UserChain");
642     if (e & 0x20)
643         fprintf(stderr, " FreeChain");
644     if (e & 0x40)
645         fprintf(stderr, " OldkeysChain");
646     if (!(e & 0xf0))
647         fprintf(stderr, " <NULL>");
648     fprintf(stderr, " ]\n");
649 }
650
651 main(argc, argv)
652      int argc;
653      char *argv[];
654 {
655     struct cmd_syndesc *ts;
656
657     setlinebuf(stdout);
658
659     ts = cmd_CreateSyntax(NULL, WorkerBee, NULL, "KADB check");
660     cmd_AddParm(ts, "-database", CMD_SINGLE, CMD_REQUIRED, "kadb_file");
661     cmd_AddParm(ts, "-uheader", CMD_FLAG, CMD_OPTIONAL,
662                 "Display UBIK header");
663     cmd_AddParm(ts, "-kheader", CMD_FLAG, CMD_OPTIONAL,
664                 "Display KADB header");
665     cmd_AddParm(ts, "-entries", CMD_FLAG, CMD_OPTIONAL, "Display entries");
666     cmd_AddParm(ts, "-verbose", CMD_FLAG, CMD_OPTIONAL, "verbose");
667     cmd_AddParm(ts, "-rebuild", CMD_SINGLE, CMD_OPTIONAL, "out_file");
668
669     return cmd_Dispatch(argc, argv);
670 }