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