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