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