chiark / gitweb /
Fix usage message.
[catacomb] / catcrypt.c
1 /* -*-c-*-
2  *
3  * $Id: catcrypt.c,v 1.2 2004/05/09 13:03:46 mdw Exp $
4  *
5  * Command-line encryption tool
6  *
7  * (c) 2004 Straylight/Edgeware
8  */
9
10 /*----- Licensing notice --------------------------------------------------* 
11  *
12  * This file is part of Catacomb.
13  *
14  * Catacomb is free software; you can redistribute it and/or modify
15  * it under the terms of the GNU Library General Public License as
16  * published by the Free Software Foundation; either version 2 of the
17  * License, or (at your option) any later version.
18  * 
19  * Catacomb is distributed in the hope that it will be useful,
20  * but WITHOUT ANY WARRANTY; without even the implied warranty of
21  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22  * GNU Library General Public License for more details.
23  * 
24  * You should have received a copy of the GNU Library General Public
25  * License along with Catacomb; if not, write to the Free
26  * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
27  * MA 02111-1307, USA.
28  */
29
30 /*----- Header files ------------------------------------------------------*/
31
32 #include "config.h"
33
34 #include <errno.h>
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <string.h>
38
39 #include <mLib/base64.h>
40 #include <mLib/dstr.h>
41 #include <mLib/mdwopt.h>
42 #include <mLib/quis.h>
43 #include <mLib/report.h>
44 #include <mLib/sub.h>
45
46 #include "buf.h"
47 #include "rand.h"
48 #include "noise.h"
49 #include "mprand.h"
50 #include "key.h"
51 #include "cc.h"
52
53 /*----- Utilities ---------------------------------------------------------*/
54
55 /* --- @keyreport@ --- *
56  *
57  * Arguments:   @const char *file@ = filename containing the error
58  *              @int line@ = line number in file
59  *              @const char *err@ = error text message
60  *              @void *p@ = unimportant pointer
61  *
62  * Returns:     ---
63  *
64  * Use:         Reports errors during the opening of a key file.
65  */
66
67 static void keyreport(const char *file, int line, const char *err, void *p)
68 {
69   moan("error in keyring `%s' at line `%s': %s", file, line, err);
70 }
71
72 /*----- Static variables --------------------------------------------------*/
73
74 static const char *keyring = "keyring";
75
76 /*----- Data format -------------------------------------------------------*/
77
78 /* --- Overview --- *
79  *
80  * The encrypted message is divided into chunks, each preceded by a two-octet
81  * length.  The chunks don't need to be large -- the idea is that we can
82  * stream the chunks in and out.
83  *
84  * The first chunk is a header.  It contains the decryption key-id, and maybe
85  * the verification key-id if the message is signed.
86  *
87  * Next comes the key-encapsulation chunk.  This is decrypted in some
88  * KEM-specific way to yield a secret hash.  This hash is what is signed if
89  * the message is signed.  The hash is expanded using an MGF (or similar) to
90  * make a symmetric encryption and MAC key.
91  *
92  * If the message is signed, there comes a signature chunk.  The signature is
93  * on the secret hash.  This means that the recipient can modify the message
94  * and still have a valid signature, so it's not useful for proving things to
95  * other people; but it also means that the recipient knows that the message
96  * is from someone who knows the hash, which limits the possiblities to (a)
97  * whoever encrypted the message (good!) and (b) whoever knows the
98  * recipient's private key.
99  *
100  * Then come message chunks.  Each one begins with a MAC over an implicit
101  * sequence number and the ciphertext.  The final chunk's ciphertext is
102  * empty; no other chunk is empty.  Thus can the correct end-of-file be
103  * discerned.
104  */
105
106 /*----- Chunk I/O ---------------------------------------------------------*/
107
108 static void chunk_write(enc *e, buf *b)
109 {
110   octet l[2];
111   size_t n = BLEN(b);
112   assert(n <= MASK16);
113   STORE16(l, n);
114   if (e->ops->write(e, l, 2) ||
115       e->ops->write(e, BBASE(b), BLEN(b)))
116     die(EXIT_FAILURE, "error writing output: %s", strerror(errno));
117 }
118
119 static void chunk_read(enc *e, dstr *d, buf *b)
120 {
121   octet l[2];
122   size_t n;
123
124   dstr_reset(d);
125   errno = 0;
126   if (e->ops->read(e, l, 2) != 2)
127     goto err;
128   n = LOAD16(l);
129   dstr_ensure(d, n);
130   if (e->ops->read(e, d->buf, n) != n)
131     goto err;
132   d->len = n;
133   buf_init(b, d->buf, d->len);
134   return;
135
136 err:
137   if (!errno) die(EXIT_FAILURE, "unexpected end-of-file on input");
138   else die(EXIT_FAILURE, "error reading input: %s", strerror(errno));
139 }
140
141 /*----- Encryption --------------------------------------------------------*/
142
143 static int encrypt(int argc, char *argv[])
144 {
145   const char *of = 0, *kn = "ccrypt";
146   FILE *ofp = 0;
147   FILE *fp = 0;
148   const char *ef = "binary";
149   const char *err;
150   int i;
151   size_t n;
152   dstr d = DSTR_INIT;
153   octet *tag, *ct;
154   buf b;
155   size_t seq;
156   char bb[16384];
157   unsigned f = 0;
158   key_file kf;
159   key *k;
160   kem *km;
161   gcipher *cx, *c;
162   gmac *m;
163   ghash *h;
164   const encops *eo;
165   enc *e;
166
167 #define f_bogus 1u
168
169   for (;;) {
170     static const struct option opt[] = {
171       { "key",          OPTF_ARGREQ,    0,      'k' },
172       { "armour",       0,              0,      'a' },
173       { "armor",        0,              0,      'a' },
174       { "format",       OPTF_ARGREQ,    0,      'f' },
175       { "output",       OPTF_ARGREQ,    0,      'o' },
176       { 0,              0,              0,      0 }
177     };
178     i = mdwopt(argc, argv, "k:af:o:", opt, 0, 0, 0);
179     if (i < 0) break;
180     switch (i) {
181       case 'k': kn = optarg; break;
182       case 'a': ef = "pem"; break;
183       case 'f': ef = optarg; break;
184       case 'o': of = optarg; break;
185       default: f |= f_bogus; break;
186     }
187   }
188   if (argc - optind > 1 || (f & f_bogus))
189     die(EXIT_FAILURE, "Usage: encrypt [-options] [file]");
190
191   if (key_open(&kf, keyring, KOPEN_READ, keyreport, 0))
192     die(EXIT_FAILURE, "can't open keyring `%s'", keyring);
193   if ((k = key_bytag(&kf, kn)) == 0)
194     die(EXIT_FAILURE, "key `%s' not found", kn);
195
196   if ((eo = getenc(ef)) == 0)
197     die(EXIT_FAILURE, "encoding `%s' not found", ef);
198
199   if (optind == argc)
200     fp = stdin;
201   else if (strcmp(argv[optind], "-") == 0) {
202     fp = stdin;
203     optind++;
204   } else if ((fp = fopen(argv[optind], "rb")) == 0) {
205     die(EXIT_FAILURE, "couldn't open file `%s': %s",
206         argv[optind], strerror(errno));
207   } else
208     optind++;
209
210   if (!of || strcmp(of, "-") == 0)
211     ofp = stdout;
212   else if ((ofp = fopen(of, eo->wmode)) == 0) {
213     die(EXIT_FAILURE, "couldn't open file `%s' for output: %s",
214         ofp, strerror(errno));
215   }
216
217   key_fulltag(k, &d);
218   e = initenc(eo, ofp, "CATCRYPT ENCRYPTED MESSAGE", 1);
219   km = getkem(k, "ccrypt", 0);
220   if ((err = km->ops->check(km)) != 0)
221     moan("key `%s' fails check: %s", d.buf, err);
222
223   /* --- Build the header chunk --- */
224
225   dstr_reset(&d);
226   dstr_ensure(&d, 256);
227   buf_init(&b, d.buf, 256);
228   buf_putu32(&b, k->id);
229   assert(BOK(&b));
230   chunk_write(e, &b);
231
232   /* --- Build the KEM chunk --- */
233
234   dstr_reset(&d);
235   if (setupkem(km, &d, &cx, &c, &m))
236     die(EXIT_FAILURE, "failed to encapsulate key");
237   buf_init(&b, d.buf, d.len);
238   BSTEP(&b, d.len);
239   chunk_write(e, &b);
240
241   /* --- Now do the main crypto --- */
242
243   assert(GC_CLASS(c)->blksz <= sizeof(bb));
244   dstr_ensure(&d, sizeof(bb) + GM_CLASS(m)->hashsz);
245   seq = 0;
246   for (;;) {
247     h = GM_INIT(m);
248     STORE32(bb, seq);
249     GH_HASH(h, bb, 4);
250     seq++;
251     if (GC_CLASS(c)->blksz) {
252       GC_ENCRYPT(cx, 0, bb, GC_CLASS(c)->blksz);
253       GC_SETIV(c, bb);
254     }
255     n = fread(bb, 1, sizeof(bb), fp);
256     if (!n) break;
257     buf_init(&b, d.buf, d.sz);
258     tag = buf_get(&b, GM_CLASS(m)->hashsz);
259     ct = buf_get(&b, n);
260     assert(tag); assert(ct);
261     GC_ENCRYPT(c, bb, ct, n);
262     GH_HASH(h, ct, n);
263     GH_DONE(h, tag);
264     GH_DESTROY(h);
265     chunk_write(e, &b);
266   }
267
268   /* --- Final terminator packet --- */
269
270   buf_init(&b, d.buf, d.sz);
271   tag = buf_get(&b, GM_CLASS(m)->hashsz);
272   assert(tag);
273   GH_DONE(h, tag);
274   GH_DESTROY(h);
275   chunk_write(e, &b);
276
277   /* --- All done --- */
278
279   e->ops->encdone(e);
280   GM_DESTROY(m);
281   GC_DESTROY(c);
282   GC_DESTROY(cx);
283   freeenc(e);
284   freekem(km);
285   if (of) fclose(ofp);
286   key_close(&kf);
287   dstr_destroy(&d);
288   return (0);
289
290 #undef f_bogus
291 }
292
293 /*---- Decryption ---------------------------------------------------------*/
294
295 static int decrypt(int argc, char *argv[])
296 {
297   const char *of = 0;
298   FILE *ofp = 0;
299   FILE *fp = 0;
300   const char *ef = "binary";
301   int i;
302   dstr d = DSTR_INIT;
303   buf b;
304   key_file kf;
305   size_t seq;
306   uint32 id;
307   key *k;
308   kem *km;
309   gcipher *cx;
310   gcipher *c;
311   ghash *h;
312   gmac *m;
313   octet *tag;
314   unsigned f = 0;
315   const encops *eo;
316   enc *e;
317
318 #define f_bogus 1u
319
320   for (;;) {
321     static const struct option opt[] = {
322       { "armour",       0,              0,      'a' },
323       { "armor",        0,              0,      'a' },
324       { "format",       OPTF_ARGREQ,    0,      'f' },
325       { "output",       OPTF_ARGREQ,    0,      'o' },
326       { 0,              0,              0,      0 }
327     };
328     i = mdwopt(argc, argv, "af:o:", opt, 0, 0, 0);
329     if (i < 0) break;
330     switch (i) {
331       case 'a': ef = "pem"; break;
332       case 'f': ef = optarg; break;
333       case 'o': of = optarg; break;
334       default: f |= f_bogus; break;
335     }
336   }
337   if (argc - optind > 1 || (f & f_bogus))
338     die(EXIT_FAILURE, "Usage: decrypt [-options] [file]");
339
340   if ((eo = getenc(ef)) == 0)
341     die(EXIT_FAILURE, "encoding `%s' not found", ef);
342
343   if (optind == argc)
344     fp = stdin;
345   else if (strcmp(argv[optind], "-") == 0) {
346     fp = stdin;
347     optind++;
348   } else if ((fp = fopen(argv[optind], eo->rmode)) == 0) {
349     die(EXIT_FAILURE, "couldn't open file `%s': %s",
350         argv[optind], strerror(errno));
351   } else
352     optind++;
353
354   if (key_open(&kf, keyring, KOPEN_READ, keyreport, 0))
355     die(EXIT_FAILURE, "can't open keyring `%s'", keyring);
356
357   e = initenc(eo, fp, "CATCRYPT ENCRYPTED MESSAGE", 0);
358
359   /* --- Read the header chunk --- */
360
361   chunk_read(e, &d, &b);
362   if (buf_getu32(&b, &id))
363     die(EXIT_FAILURE, "malformed header: missing keyid");
364   if (BLEFT(&b))
365     die(EXIT_FAILURE, "malformed header: junk at end");
366
367   /* --- Find the key --- */
368
369   if ((k = key_byid(&kf, id)) == 0)
370     die(EXIT_FAILURE, "key id %08lx not found", (unsigned long)id);
371   km = getkem(k, "ccrypt", 1);
372
373   /* --- Read the KEM chunk --- */
374
375   chunk_read(e, &d, &b);
376   if (setupkem(km, &d, &cx, &c, &m))
377     die(EXIT_FAILURE, "failed to decapsulate key");
378
379   /* --- Now decrypt the main body --- */
380
381   if (!of || strcmp(of, "-") == 0)
382     ofp = stdout;
383   else if ((ofp = fopen(of, "wb")) == 0) {
384     die(EXIT_FAILURE, "couldn't open file `%s' for output: %s",
385         ofp, strerror(errno));
386   }
387
388   seq = 0;
389   dstr_ensure(&d, GC_CLASS(c)->blksz);
390   dstr_ensure(&d, 4);
391   for (;;) {
392     if (GC_CLASS(c)->blksz) {
393       GC_ENCRYPT(cx, 0, d.buf, GC_CLASS(c)->blksz);
394       GC_SETIV(c, d.buf);
395     }
396     h = GM_INIT(m);
397     STORE32(d.buf, seq);
398     GH_HASH(h, d.buf, 4);
399     seq++;
400     chunk_read(e, &d, &b);
401     if ((tag = buf_get(&b, GM_CLASS(m)->hashsz)) == 0)
402       die(EXIT_FAILURE, "bad ciphertext chunk: no tag");
403     GH_HASH(h, BCUR(&b), BLEFT(&b));
404     if (memcmp(tag, GH_DONE(h, 0), GM_CLASS(m)->hashsz) != 0)
405       die(EXIT_FAILURE, "bad ciphertext chunk: authentication failure");
406     if (!BLEFT(&b))
407       break;
408     GC_DECRYPT(c, BCUR(&b), BCUR(&b), BLEFT(&b));
409     if (fwrite(BCUR(&b), 1, BLEFT(&b), ofp) != BLEFT(&b))
410       die(EXIT_FAILURE, "error writing output: %s", strerror(errno));
411   }
412
413   if (fflush(ofp) || ferror(ofp))
414     die(EXIT_FAILURE, "error writing output: %s", strerror(errno));
415     
416   e->ops->decdone(e);
417   freeenc(e);
418   GC_DESTROY(c);
419   GC_DESTROY(cx);
420   GM_DESTROY(m);
421   freekem(km);
422   if (of) fclose(ofp);
423   key_close(&kf);
424   dstr_destroy(&d);
425   return (0);
426
427 #undef f_bogus
428 }
429
430 /*----- Test code ---------------------------------------------------------*/
431
432 static int encode(int argc, char *argv[])
433 {
434   const char *of = 0;
435   FILE *ofp = 0;
436   FILE *fp = 0;
437   const char *ef = "binary";
438   const char *bd = "MESSAGE";
439   int i;
440   size_t n;
441   char buf[4096];
442   unsigned f = 0;
443   const encops *eo;
444   enc *e;
445
446 #define f_bogus 1u
447
448   for (;;) {
449     static const struct option opt[] = {
450       { "format",       OPTF_ARGREQ,    0,      'f' },
451       { "boundary",     OPTF_ARGREQ,    0,      'b' },
452       { "output",       OPTF_ARGREQ,    0,      'o' },
453       { 0,              0,              0,      0 }
454     };
455     i = mdwopt(argc, argv, "f:b:o:", opt, 0, 0, 0);
456     if (i < 0) break;
457     switch (i) {
458       case 'f': ef = optarg; break;
459       case 'b': bd = optarg; break;
460       case 'o': of = optarg; break;
461       default: f |= f_bogus; break;
462     }
463   }
464   if (argc - optind > 1 || (f & f_bogus))
465     die(EXIT_FAILURE, "Usage: encode [-options] [file]");
466
467   if ((eo = getenc(ef)) == 0)
468     die(EXIT_FAILURE, "encoding `%s' not found", ef);
469
470   if (optind == argc)
471     fp = stdin;
472   else if (strcmp(argv[optind], "-") == 0) {
473     fp = stdin;
474     optind++;
475   } else if ((fp = fopen(argv[optind], "rb")) == 0) {
476     die(EXIT_FAILURE, "couldn't open file `%s': %s",
477         argv[optind], strerror(errno));
478   } else
479     optind++;
480
481   if (!of || strcmp(of, "-") == 0)
482     ofp = stdout;
483   else if ((ofp = fopen(of, eo->wmode)) == 0) {
484     die(EXIT_FAILURE, "couldn't open file `%s' for output: %s",
485         ofp, strerror(errno));
486   }
487
488   e = initenc(eo, ofp, bd, 1);
489
490   do {
491     n = fread(buf, 1, sizeof(buf), fp);
492     if (e->ops->write(e, buf, n))
493       die(EXIT_FAILURE, "error writing output: %s", strerror(errno));
494   } while (n == sizeof(buf));
495   e->ops->encdone(e);
496   freeenc(e);
497   return (0);
498
499 #undef f_bogus
500 }
501
502 static int decode(int argc, char *argv[])
503 {
504   const char *of = 0;
505   FILE *ofp = 0;
506   FILE *fp = 0;
507   const char *ef = "binary";
508   const char *bd = 0;
509   int i;
510   char buf[4096];
511   unsigned f = 0;
512   const encops *eo;
513   enc *e;
514
515 #define f_bogus 1u
516
517   for (;;) {
518     static const struct option opt[] = {
519       { "format",       OPTF_ARGREQ,    0,      'f' },
520       { "boundary",     OPTF_ARGREQ,    0,      'b' },
521       { "output",       OPTF_ARGREQ,    0,      'o' },
522       { 0,              0,              0,      0 }
523     };
524     i = mdwopt(argc, argv, "f:b:o:", opt, 0, 0, 0);
525     if (i < 0) break;
526     switch (i) {
527       case 'f': ef = optarg; break;
528       case 'b': bd = optarg; break;
529       case 'o': of = optarg; break;
530       default: f |= f_bogus; break;
531     }
532   }
533   if (argc - optind > 1 || (f & f_bogus))
534     die(EXIT_FAILURE, "Usage: decode [-options] [file]");
535
536   if ((eo = getenc(ef)) == 0)
537     die(EXIT_FAILURE, "encoding `%s' not found", ef);
538
539   if (optind == argc)
540     fp = stdin;
541   else if (strcmp(argv[optind], "-") == 0) {
542     fp = stdin;
543     optind++;
544   } else if ((fp = fopen(argv[optind], eo->rmode)) == 0) {
545     die(EXIT_FAILURE, "couldn't open file `%s': %s",
546         argv[optind], strerror(errno));
547   } else
548     optind++;
549
550   if (!of || strcmp(of, "-") == 0)
551     ofp = stdout;
552   else if ((ofp = fopen(of, "wb")) == 0) {
553     die(EXIT_FAILURE, "couldn't open file `%s' for output: %s",
554         ofp, strerror(errno));
555   }
556
557   e = initenc(eo, fp, bd, 0);
558
559   do {
560     if ((i = e->ops->read(e, buf, sizeof(buf))) < 0)
561       die(EXIT_FAILURE, "error reading input: %s", strerror(errno));
562     if (fwrite(buf, 1, i, ofp) < i)
563       die(EXIT_FAILURE, "error writing output: %s", strerror(errno));
564   } while (i == sizeof(buf));
565   e->ops->decdone(e);
566   freeenc(e);
567   return (0);
568
569 #undef f_bogus
570 }
571
572 /*----- Main code ---------------------------------------------------------*/
573
574 typedef struct cmd {
575   const char *name;
576   int (*func)(int /*argc*/, char */*argv*/[]);
577   const char *usage;
578   const char *help;
579 } cmd;
580
581 static cmd cmdtab[] = {
582   { "encode", encode,
583     "encode [-f format] [-b label] [-o output] [file]",
584     "\
585 Options:\n\
586 \n\
587 -f, --format=FORMAT     Encode to FORMAT.\n\
588 -b, --boundary=LABEL    PEM boundary is LABEL.\n\
589 -o, --output=FILE       Write output to FILE.\n\
590 " },
591   { "decode", decode,
592     "decode [-f format] [-b label] [-o output] [file]",
593     "\
594 Options:\n\
595 \n\
596 -f, --format=FORMAT     Decode from FORMAT.\n\
597 -b, --boundary=LABEL    PEM boundary is LABEL.\n\
598 -o, --output=FILE       Write output to FILE.\n\
599 " },
600   { "encrypt", encrypt,
601     "encrypt [-a] [-k tag] [f format]] [-o output] [file]",
602     "\
603 Options:\n\
604 \n\
605 -a, --armour            Same as `-f pem'.\n\
606 -f, --format=FORMAT     Encode as FORMAT.\n\
607 -k, --key=TAG           Use public key named by TAG.\n\
608 -o, --output=FILE       Write output to FILE.\n\
609 " },
610   { "decrypt", decrypt,
611     "decrypt [-t] [-o output] [file]", "\
612 Options:\n\
613 \n\
614 -t, --text              Read PEM-encoded input.\n\
615 -o, --output=FILE       Write output to FILE.\n\
616 " },
617   { 0, 0, 0 }
618 };
619
620 /* --- @findcmd@ --- *
621  *
622  * Arguments:   @const char *name@ = a command name
623  *
624  * Returns:     Pointer to the command structure.
625  *
626  * Use:         Looks up a command by name.  If the command isn't found, an
627  *              error is reported and the program is terminated.
628  */
629
630 static cmd *findcmd(const char *name)
631 {
632   cmd *c, *chosen = 0;
633   size_t sz = strlen(name);
634
635   for (c = cmdtab; c->name; c++) {
636     if (strncmp(name, c->name, sz) == 0) {
637       if (c->name[sz] == 0) {
638         chosen = c;
639         break;
640       } else if (chosen)
641         die(EXIT_FAILURE, "ambiguous command name `%s'", name);
642       else
643         chosen = c;
644     }
645   }
646   if (!chosen)
647     die(EXIT_FAILURE, "unknown command name `%s'", name);
648   return (chosen);
649 }
650
651 static void version(FILE *fp)
652 {
653   pquis(fp, "$, Catacomb version " VERSION "\n");
654 }
655
656 static void usage(FILE *fp)
657 {
658   pquis(fp, "Usage: $ [-k keyring] command [args]\n");
659 }
660
661 static void help(FILE *fp, char **argv)
662 {
663   cmd *c;
664
665   if (*argv) {
666     c = findcmd(*argv);
667     fprintf(fp, "Usage: %s [-k keyring] %s\n", QUIS, c->usage);
668     if (c->help) {
669       fputc('\n', fp);  
670       fputs(c->help, fp);
671     }
672   } else {
673     version(fp);
674     fputc('\n', fp);
675     usage(fp);
676     fputs("\n\
677 Encrypt and decrypt files.\n\
678 \n", fp);
679     for (c = cmdtab; c->name; c++)
680       fprintf(fp, "%s\n", c->usage);
681   }
682 }
683
684 /* --- @main@ --- *
685  *
686  * Arguments:   @int argc@ = number of command line arguments
687  *              @char *argv[]@ = vector of command line arguments
688  *
689  * Returns:     Zero if successful, nonzero otherwise.
690  *
691  * Use:         Signs or verifies signatures on lists of files.  Useful for
692  *              ensuring that a distribution is unmolested.
693  */
694
695 int main(int argc, char *argv[])
696 {
697   unsigned f = 0;
698
699 #define f_bogus 1u
700
701   /* --- Initialize the library --- */
702
703   ego(argv[0]);
704   sub_init();
705   rand_noisesrc(RAND_GLOBAL, &noise_source);
706   rand_seed(RAND_GLOBAL, 160);
707
708   /* --- Parse options --- */
709
710   for (;;) {
711     static struct option opts[] = {
712       { "help",         0,              0,      'h' },
713       { "version",      0,              0,      'v' },
714       { "usage",        0,              0,      'u' },
715       { "keyring",      OPTF_ARGREQ,    0,      'k' },
716       { 0,              0,              0,      0 }
717     };
718     int i = mdwopt(argc, argv, "+hvu k:", opts, 0, 0, 0);
719     if (i < 0)
720       break;
721     switch (i) {
722       case 'h':
723         help(stdout, argv + optind);
724         exit(0);
725         break;
726       case 'v':
727         version(stdout);
728         exit(0);
729         break;
730       case 'u':
731         usage(stdout);
732         exit(0);
733       case 'k':
734         keyring = optarg;
735         break;
736       default:
737         f |= f_bogus;
738         break;
739     }
740   }
741
742   argc -= optind;
743   argv += optind;
744   optind = 0;
745   if (f & f_bogus || argc < 1) {
746     usage(stderr);
747     exit(EXIT_FAILURE);
748   }
749
750   /* --- Dispatch to the correct subcommand handler --- */
751
752   return (findcmd(argv[0])->func(argc, argv));
753
754 #undef f_bogus
755 }
756
757 /*----- That's all, folks -------------------------------------------------*/