cc-hash.c: New file containing hash-related code from hashsum and dsig.
[u/mdw/catacomb] / catsign.c
CommitLineData
fa54fe1e 1/* -*-c-*-
2 *
3 * $Id$
4 *
5 * Sign files
6 *
7 * (c) 2005 Straylight/Edgeware
8 */
9
45c0fd36 10/*----- Licensing notice --------------------------------------------------*
fa54fe1e 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.
45c0fd36 18 *
fa54fe1e 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.
45c0fd36 23 *
fa54fe1e 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
cd6eca43
MW
32#define _FILE_OFFSET_BITS 64
33
fa54fe1e 34#include "config.h"
35
36#include <ctype.h>
37#include <errno.h>
38#include <stdio.h>
39#include <stdlib.h>
40#include <string.h>
41#include <time.h>
42
43#include <mLib/base64.h>
44#include <mLib/dstr.h>
45#include <mLib/mdwopt.h>
46#include <mLib/quis.h>
47#include <mLib/report.h>
48#include <mLib/sub.h>
49
50#include "buf.h"
51#include "rand.h"
52#include "noise.h"
53#include "mprand.h"
54#include "key.h"
9cea6911 55#include "getdate.h"
fa54fe1e 56#include "cc.h"
57
58#include "ectab.h"
59#include "ptab.h"
60
fa54fe1e 61/*----- Static variables --------------------------------------------------*/
62
63static const char *keyring = "keyring";
64
65/*----- Data formats ------------------------------------------------------*
66 *
67 * Our crypto stuff is split into three parts: a header describing the key,
68 * the message itself, and the signature. In a detached signature, the
69 * message is separate, and the header and signature are combined into one
70 * encoded chunk. In an attached signature, the message is included. There
71 * are two forms of message: binary and text. Binary messages are divided
72 * into chunks, each preceded by a 2-octet length, and terminated by a
73 * zero-length chunk. Text messages are byte-stuffed, as for RFC821, and
74 * trailing whitespace before a newline is ignored.
75 */
76
77typedef struct sigmsg {
78 unsigned f; /* Flags */
79#define F_DETACH 1u
80#define F_BINARY 2u
81#define F_SIGMASK 3u
82#define F_HASHMASK (F_BINARY)
83 uint32 keyid; /* Key identifier */
84 time_t t; /* When the signature was made */
85 dstr kh; /* Key fingerprint (sanity check) */
86 dstr sig; /* Signature */
87 sig *s; /* Signature algorithm */
88} sigmsg;
89
90#define F_MIDLINE 16u
91#define F_EOF 32u
92#define F_NOCLOSE 64u
93#define F_BOGUS 128u
94#define F_BUFFER 256u
95#define F_UTC 512u
946c3f72 96#define F_NOCHECK 1024u
cd6eca43 97#define F_PROGRESS 2048u
fa54fe1e 98
99/*----- Chunk I/O ---------------------------------------------------------*/
100
101static void chunk_write(enc *e, const void *p, size_t n)
102{
103 octet b[2];
104
105 assert(n <= 0xffff);
106 STORE16(b, n);
107 if (e->ops->write(e, b, 2) || e->ops->write(e, p, n))
108 die(EXIT_FAILURE, "error writing output: %s", strerror(errno));
109}
110
111static size_t chunk_read(enc *e, void *p)
112{
113 octet b[2];
114 size_t n;
115
116 if (e->ops->read(e, b, 2) != 2)
117 goto err;
118 n = LOAD16(b);
119 if (n && e->ops->read(e, p, n) != n)
120 goto err;
121 return (n);
122
123err:
124 if (!errno) die(EXIT_FAILURE, "unexpected end-of-file on input");
125 else die(EXIT_FAILURE, "error reading input: %s", strerror(errno));
126 return (0);
127}
128
129/*----- Message canonification --------------------------------------------*/
130
131#define MSGBUFSZ 65536
132#define MSGBUFTHRESH 32768
133
134typedef struct msgcanon {
135 unsigned f;
136 FILE *fp;
137 enc *e;
cd6eca43 138 fprogress ff;
fa54fe1e 139 size_t (*read)(struct msgcanon *, void *);
140 void (*write)(struct msgcanon *, const void *, size_t);
141 void (*close)(struct msgcanon *);
142} msgcanon;
143
144#define MC_INIT { 0 }
145
146static size_t textread(msgcanon *m, void *bp)
147{
148 size_t n = 0, nsp = 0;
149 int ch;
150 char *b = bp;
151 unsigned f = m->f;
152
153 if (f & F_EOF) return (0);
154 for (;;) {
155 if ((ch = getc(m->fp)) == EOF) goto eof;
156 if (!(f & (F_DETACH | F_MIDLINE)) && ch == '.') {
157 ch = getc(m->fp);
158 if (ch == '\n') goto eof;
159 }
160 if (ch == '\n') {
161 n -= nsp; nsp = 0;
162 if (n >= MSGBUFTHRESH) goto full;
163 b[n++] = ch;
164 f &= ~F_MIDLINE;
165 } else if (isspace(ch)) {
166 f |= F_MIDLINE;
167 if (n >= MSGBUFSZ) goto full;
168 b[n++] = ch; nsp++;
169 } else {
170 f |= F_MIDLINE;
171 nsp = 0;
172 if (n >= MSGBUFTHRESH) goto full;
173 b[n++] = ch;
174 }
175 }
176eof:
177 f |= F_EOF;
178 goto done;
179full:
180 ungetc(ch, m->fp);
181done:
182 m->f = f;
cd6eca43 183 if (m->f & F_PROGRESS) fprogress_update(&m->ff, n);
fa54fe1e 184 return (n);
185}
186
187static void textwrite(msgcanon *m, const void *bp, size_t n)
188{
189 const char *p = bp, *l = p + n;
190 unsigned f = m->f;
191
192 while (p < l) {
193 if (!(f & (F_MIDLINE | F_DETACH)) &&
194 (*p == '.' || *p == '-'))
195 putc('.', m->fp);
196 if (*p == '\n') f &= ~F_MIDLINE;
197 else f |= F_MIDLINE;
198 putc(*p, m->fp);
199 p++;
200 }
201 m->f = f;
202}
203
204static size_t binreadembed(msgcanon *m, void *bp)
cd6eca43
MW
205{
206 size_t n = chunk_read(m->e, bp);
207 if (m->f & F_PROGRESS) fprogress_update(&m->ff, n);
208 return (n);
209}
210
fa54fe1e 211static size_t binreaddetach(msgcanon *m, void *bp)
cd6eca43
MW
212{
213 size_t n = fread(bp, 1, MSGBUFSZ - 1, m->fp);
214 if (m->f & F_PROGRESS) fprogress_update(&m->ff, n);
215 return (n);
216}
fa54fe1e 217
218static void binwriteembed(msgcanon *m, const void *bp, size_t n)
219 { chunk_write(m->e, bp, n); }
220
221static void nullwrite(msgcanon *m, const void *bp, size_t n) { ; }
222
223static void mcsetup_readfile(msgcanon *m, unsigned f, const char *fn)
224{
cd6eca43 225 m->f = F_DETACH | (f & (F_BINARY | F_PROGRESS));
fa54fe1e 226 if (!fn || strcmp(fn, "-") == 0) {
227 m->fp = stdin;
228 m->f |= F_NOCLOSE;
229 } else if ((m->fp = fopen(fn, (f & F_BINARY) ? "rb" : "r")) == 0)
230 die(EXIT_FAILURE, "couldn't open file `%s': %s", fn, strerror(errno));
cd6eca43
MW
231 if (m->f & F_PROGRESS) {
232 if (fprogress_init(&m->ff, fn, m->fp)) {
233 die(EXIT_FAILURE, "failed to initialize progress indicator: %s",
234 strerror(errno));
235 }
236 }
fa54fe1e 237 m->read = (f & F_BINARY) ? binreaddetach : textread;
238}
239
240static void mcsetup_writenull(msgcanon *m) { m->write = nullwrite; }
241
cd6eca43
MW
242static void mcsetup_read(msgcanon *m, unsigned f, enc **ee,
243 const char *fn, const char *dfn)
fa54fe1e 244{
245 enc *e = *ee;
246
247 m->f = f | F_NOCLOSE;
248
249 if (dfn && !(f & F_DETACH)) die(EXIT_FAILURE, "signature is not detached");
250 if (f & F_BINARY) {
251 if (!(f & F_DETACH)) {
252 m->e = e;
253 *ee = 0;
254 m->fp = e->fp;
255 m->read = binreadembed;
cd6eca43
MW
256 if (m->f & F_PROGRESS) {
257 if (fprogress_init(&m->ff, fn, m->fp)) {
258 die(EXIT_FAILURE, "failed to initialize progress indicator: %s",
259 strerror(errno));
260 }
261 }
fa54fe1e 262 } else {
263 m->read = binreaddetach;
264 if (!dfn || strcmp(dfn, "-") == 0)
265 m->fp = stdin;
266 else if ((m->fp = fopen(dfn, "rb")) == 0)
267 die(EXIT_FAILURE, "can't open `%s': %s", dfn, strerror(errno));
268 else
269 m->f &= ~F_NOCLOSE;
cd6eca43
MW
270 if (m->f & F_PROGRESS) {
271 if (fprogress_init(&m->ff, dfn, m->fp)) {
272 die(EXIT_FAILURE, "failed to initialize progress indicator: %s",
273 strerror(errno));
274 }
275 }
fa54fe1e 276 }
277 } else {
278 m->read = textread;
279 if (!(f & F_DETACH)) {
280 if (e->ops->decdone(e) || ferror(e->fp))
281 die(EXIT_FAILURE, "error at end of signature header");
282 m->fp = e->fp;
283 e->ops->destroy(e);
284 *ee = 0;
cd6eca43
MW
285 if (m->f & F_PROGRESS) {
286 if (fprogress_init(&m->ff, fn, m->fp)) {
287 die(EXIT_FAILURE, "failed to initialize progress indicator: %s",
288 strerror(errno));
289 }
290 }
291 } else {
292 if (!dfn || strcmp(dfn, "-") == 0)
293 m->fp = stdin;
294 else if ((m->fp = fopen(dfn, "r")) == 0)
295 die(EXIT_FAILURE, "can't read file `%s': %s", dfn, strerror(errno));
296 else
297 m->f &= ~F_NOCLOSE;
298 if (m->f & F_PROGRESS) {
299 if (fprogress_init(&m->ff, dfn, m->fp)) {
300 die(EXIT_FAILURE, "failed to initialize progress indicator: %s",
301 strerror(errno));
302 }
303 }
304 }
fa54fe1e 305 }
306}
307
308static void mcsetup_write(msgcanon *m, unsigned f, enc **ee)
309{
310 enc *e = *ee;
311
312 m->f = f | F_NOCLOSE;
313
314 if (f & F_DETACH)
315 m->write = nullwrite;
316 else if (f & F_BINARY) {
317 m->e = e;
318 *ee = 0;
319 m->fp = e->fp;
320 m->write = binwriteembed;
321 } else {
322 if (e->ops->encdone(e) || ferror(e->fp))
323 die(EXIT_FAILURE, "error at end of signature header");
324 m->fp = e->fp;
325 e->ops->destroy(e);
326 *ee = 0;
327 m->write = textwrite;
328 }
329}
330
331static void mc_endread(msgcanon *m, const encops *eops, enc **ee)
332{
333 if (!(m->f & F_DETACH)) {
334 if (m->f & F_BINARY)
335 *ee = m->e;
336 else
337 *ee = initdec(eops, m->fp, checkbdry, "CATSIGN SIGNATURE");
338 }
339 if (m->fp && !(m->f & F_NOCLOSE)) {
340 if (ferror(m->fp) || fclose(m->fp))
341 die(EXIT_FAILURE, "error closing message file: %s", strerror(errno));
45c0fd36 342 }
cd6eca43 343 if (m->f & F_PROGRESS) fprogress_done(&m->ff);
fa54fe1e 344}
345
346static void mc_endwrite(msgcanon *m, const encops *eops, enc **ee)
347{
348 if (!(m->f & F_BINARY)) {
349 if (m->f & F_MIDLINE) putc('\n', m->fp);
350 if (!(m->f & F_DETACH)) {
351 putc('.', m->fp); putc('\n', m->fp);
352 *ee = initenc(eops, m->fp, "CATSIGN SIGNATURE");
353 }
354 } else if (!(m->f & F_DETACH)) {
355 chunk_write(m->e, 0, 0);
356 *ee = m->e;
357 }
358 if (m->fp && !(m->f & F_NOCLOSE)) {
359 if (fflush(m->fp) || ferror(m->fp) || fclose(m->fp))
360 die(EXIT_FAILURE, "error closing message file: %s", strerror(errno));
45c0fd36 361 }
fa54fe1e 362}
363
364/*----- Signature reading and writing -------------------------------------*/
365
366static void sig_init(sigmsg *s, unsigned f, uint32 keyid)
367{
368 s->f = f & F_SIGMASK;
369 s->keyid = keyid;
370 time(&s->t);
371 s->s = 0;
372 dstr_create(&s->kh);
373 dstr_create(&s->sig);
374}
375
376static void sig_destroy(sigmsg *s)
377{
378 dstr_destroy(&s->kh);
379 dstr_destroy(&s->sig);
380 if (s->s) freesig(s->s);
381}
382
383static void sigtobuffer(sigmsg *s, buf *b, int hashp)
384{
385 kludge64 t;
386
387 ASSIGN64(t, s->t);
388 if (hashp) buf_putu16(b, s->f & F_HASHMASK);
389 else buf_putu16(b, s->f & F_SIGMASK);
390 buf_putu32(b, s->keyid);
391 buf_putu32(b, HI64(t));
392 buf_putu32(b, LO64(t));
f387fcb1 393 buf_putdstr16(b, &s->kh);
fa54fe1e 394 assert(BOK(b));
395}
396
397static void dohash(ghash *h, const void *p, size_t n)
398{
399/* trace_block(1, "hashing", p, n); */
400 GH_HASH(h, p, n);
401}
402
403static void sig_hash(sigmsg *s)
404{
405 octet bb[16384];
406 buf b;
407
408 buf_init(&b, bb, sizeof(bb));
409 sigtobuffer(s, &b, 1);
410 dohash(s->s->h, BBASE(&b), BLEN(&b));
411}
412
413static void keyhash(key *k, sig *s, dstr *d)
414{
415 ghash *h;
416 key_filter kf;
417
02dfbd5b 418 h = GH_INIT(s->ch);
fa54fe1e 419 kf.f = KCAT_PUB;
420 kf.m = KF_CATMASK;
421 key_fingerprint(k, h, &kf);
422 dstr_ensure(d, GH_CLASS(h)->hashsz);
423 GH_DONE(h, d->buf + d->len);
424 d->len += GH_CLASS(h)->hashsz;
425 GH_DESTROY(h);
426}
427
428static void sig_writeheader(enc *e, sigmsg *s)
429{
430 octet bb[16384];
431 buf b;
432
433 buf_init(&b, bb, sizeof(bb));
434 sigtobuffer(s, &b, 0);
435 chunk_write(e, BBASE(&b), BLEN(&b));
436}
437
438static void sig_writesig(enc *e, sigmsg *s)
439 { chunk_write(e, s->sig.buf, s->sig.len); }
440
441static void diechoke(const char *m, void *p)
442 { die(EXIT_FAILURE, "%s%s%s", p, p ? ": " : "", m); }
443
444static void sig_readheader(enc *e, sigmsg *s,
445 void (*choke)(const char *, void *), void *p)
446{
447 uint16 f;
448 octet bb[MSGBUFSZ];
449 uint32 x, y;
450 kludge64 t;
451 buf b;
452 size_t n;
453
454 n = chunk_read(e, bb);
455 buf_init(&b, bb, n);
456 if (buf_getu16(&b, &f)) choke("missing flags", p);
457 if (buf_getu32(&b, &x)) choke("missing keyid", p);
458 sig_init(s, f, x);
459 if (buf_getu32(&b, &x) || buf_getu32(&b, &y))
460 choke("missing datestamp", p);
461 SET64(t, x, y); s->t = GET64(time_t, t);
f387fcb1 462 if (buf_getdstr16(&b, &s->kh))
fa54fe1e 463 choke("missing key hash", p);
464 if (BLEFT(&b))
465 choke("junk at end", p);
466}
467
468static void sig_readsig(enc *e, sigmsg *s)
469{
470 octet bb[MSGBUFSZ];
471 size_t n;
472
473 n = chunk_read(e, bb);
474 dstr_putm(&s->sig, bb, n);
475}
476
477/*----- Signing -----------------------------------------------------------*/
478
479static int sign(int argc, char *argv[])
480{
481 const char *ef = "binary", *fn = 0, *of = 0, *kn = "ccsig", *err;
482 unsigned f = 0;
483 key_file kf;
484 key *k;
485 sigmsg s;
486 FILE *ofp = 0;
487 int i;
488 char bb[MSGBUFSZ];
489 size_t n;
45c0fd36 490 dstr d = DSTR_INIT;
fa54fe1e 491 const encops *eo;
492 msgcanon mc_in = MC_INIT, mc_out = MC_INIT;
493 enc *e;
494
495 for (;;) {
496 static const struct option opt[] = {
497 { "armour", 0, 0, 'a' },
498 { "armor", 0, 0, 'a' },
499 { "binary", 0, 0, 'b' },
500 { "detach", 0, 0, 'd' },
501 { "key", OPTF_ARGREQ, 0, 'k' },
502 { "format", OPTF_ARGREQ, 0, 'f' },
503 { "output", OPTF_ARGREQ, 0, 'o' },
cd6eca43 504 { "progress", 0, 0, 'p' },
fa54fe1e 505 { "text", 0, 0, 't' },
946c3f72 506 { "nocheck", 0, 0, 'C' },
fa54fe1e 507 { 0, 0, 0, 0 }
508 };
cd6eca43 509 i = mdwopt(argc, argv, "k:f:o:abdptC", opt, 0, 0, 0);
fa54fe1e 510 if (i < 0) break;
511 switch (i) {
512 case 'k': kn = optarg; break;
513 case 'f': ef = optarg; break;
514 case 'o': of = optarg; break;
515 case 'a': ef = "pem"; break;
516 case 't': f &= ~F_BINARY; break;
517 case 'b': f |= F_BINARY; break;
518 case 'd': f |= F_DETACH; break;
946c3f72 519 case 'C': f |= F_NOCHECK; break;
cd6eca43 520 case 'p': f |= F_PROGRESS; break;
fa54fe1e 521 default: f |= F_BOGUS; break;
522 }
523 }
524 if (argc - optind > 1 || (f & F_BOGUS))
525 die(EXIT_FAILURE, "Usage: sign [-OPTIONS] [FILE]");
526
213e565f 527 if (key_open(&kf, keyring, KOPEN_READ, key_moan, 0))
fa54fe1e 528 die(EXIT_FAILURE, "can't open keyring `%s'", keyring);
529 if ((k = key_bytag(&kf, kn)) == 0)
530 die(EXIT_FAILURE, "key `%s' not found", kn);
531
532 if ((eo = getenc(ef)) == 0)
533 die(EXIT_FAILURE, "encoding `%s' not found", ef);
534
535 fn = (optind >= argc) ? 0 : argv[optind++];
536
537 if (!of || strcmp(of, "-") == 0)
538 ofp = stdout;
539 else if ((ofp = fopen(of, eo->wmode)) == 0) {
540 die(EXIT_FAILURE, "couldn't open file `%s' for output: %s",
541 ofp, strerror(errno));
542 }
543
544 /* --- Start the work --- */
545
546 sig_init(&s, f, k->id);
547 dstr_reset(&d);
548 key_fulltag(k, &d);
549 s.s = getsig(k, "ccsig", 1);
946c3f72 550 if (!(f & F_NOCHECK) && (err = s.s->ops->check(s.s)) != 0)
fa54fe1e 551 moan("key %s fails check: %s", d.buf, err);
552 keyhash(k, s.s, &s.kh);
553 e = initenc(eo, ofp,
554 (f & F_DETACH) ? "CATSIGN SIGNATURE" :
555 (f & F_BINARY) ? "CATSIGN MESSAGE" :
556 "CATSIGN MESSAGE HEADER");
557 sig_writeheader(e, &s);
558
559 /* --- Hash the message --- */
560
561 mcsetup_readfile(&mc_in, f, fn);
562 mcsetup_write(&mc_out, f, &e);
563 sig_hash(&s);
564 for (;;) {
565 n = mc_in.read(&mc_in, bb);
566 if (!n) break;
567 dohash(s.s->h, bb, n);
568 mc_out.write(&mc_out, bb, n);
569 }
570 mc_endread(&mc_in, 0, 0);
571 mc_endwrite(&mc_out, eo, &e);
572
573 /* --- Write the signature --- */
574
575 if (s.s->ops->doit(s.s, &s.sig))
576 die(EXIT_FAILURE, "signature failed");
577 sig_writesig(e, &s);
578 e->ops->encdone(e);
579 if (fflush(ofp) || ferror(ofp) || fclose(ofp))
580 die(EXIT_FAILURE, "error writing signature: %s", strerror(errno));
581
582 /* --- All done --- */
583
584 freeenc(e);
585 key_close(&kf);
586 sig_destroy(&s);
587 dstr_destroy(&d);
588 return (0);
589}
590
591/*----- Verifying ---------------------------------------------------------*/
592
593typedef struct vrfctx {
594 unsigned f, m;
595 int verb;
596 const char *what;
597} vrfctx;
598
599static int vrfbdry(const char *b, void *p)
600{
601 vrfctx *v = p;
602
603 if (strcmp(b, "CATSIGN MESSAGE") == 0) {
604 v->f |= F_BINARY;
605 v->m |= F_BINARY | F_DETACH;
606 return (1);
607 } else if (strcmp(b, "CATSIGN MESSAGE HEADER") == 0) {
608 v->m |= F_BINARY | F_DETACH;
609 return (1);
610 } else if (strcmp(b, "CATSIGN SIGNATURE") == 0) {
611 v->f |= F_DETACH;
612 v->m |= F_DETACH;
613 return (1);
614 } else
615 return (0);
616}
617
618static void vrfchoke(const char *m, void *p)
619{
620 vrfctx *v = p;
621 if (v->verb) printf("FAIL %s: %s\n", v->what, m);
622 exit(EXIT_FAILURE);
623}
624
625static int verify(int argc, char *argv[])
626{
cd6eca43 627 const char *ef = "binary", *of = 0, *fn, *dfn = 0, *kn = 0, *err;
fa54fe1e 628 vrfctx v = { 0, 0, 1 };
629 key_file kf;
630 key *k, *kk = 0;
631 sigmsg s;
632 FILE *fp, *ofp = 0, *rfp = 0;
cd6eca43 633 fprogress ff;
fa54fe1e 634 struct tm *tm;
635 int i;
636 char bb[MSGBUFSZ];
637 size_t n;
9cea6911 638 time_t t_fresh = 0;
fa54fe1e 639 dstr d = DSTR_INIT, dd = DSTR_INIT;
640 const encops *eo;
641 msgcanon mc_in = MC_INIT;
642 enc *e;
643
644 for (;;) {
645 static const struct option opt[] = {
646 { "armour", 0, 0, 'a' },
647 { "armor", 0, 0, 'a' },
648 { "buffer", 0, 0, 'b' },
649 { "key", OPTF_ARGREQ, 0, 'k' },
650 { "format", OPTF_ARGREQ, 0, 'f' },
651 { "output", OPTF_ARGREQ, 0, 'o' },
cd6eca43 652 { "progress", 0, 0, 'p' },
fa54fe1e 653 { "quiet", 0, 0, 'q' },
654 { "utc", 0, 0, 'u' },
9cea6911 655 { "fresh-time", 0, 0, 't' },
fa54fe1e 656 { "gmt", 0, 0, 'u' },
657 { "verbose", 0, 0, 'v' },
946c3f72 658 { "nocheck", 0, 0, 'C' },
fa54fe1e 659 { 0, 0, 0, 0 }
660 };
cd6eca43 661 i = mdwopt(argc, argv, "k:f:o:abpqt:uvC", opt, 0, 0, 0);
fa54fe1e 662 if (i < 0) break;
663 switch (i) {
664 case 'a': ef = "pem"; break;
665 case 'b': v.f |= F_BUFFER; break;
666 case 'k': kn = optarg; break;
667 case 'f': ef = optarg; break;
668 case 'o': of = optarg; break;
669 case 'u': v.f |= F_UTC; break;
946c3f72 670 case 'C': v.f |= F_NOCHECK; break;
9cea6911 671 case 't':
672 if (strcmp(optarg, "always") == 0) t_fresh = 0;
45c0fd36 673 else if ((t_fresh = get_date(optarg, 0)) < 0)
9cea6911 674 die(EXIT_FAILURE, "bad freshness time");
675 break;
fa54fe1e 676 case 'q': if (v.verb > 0) v.verb--; break;
677 case 'v': if (v.verb < 10) v.verb++; break;
cd6eca43 678 case 'p': v.f |= F_PROGRESS; break;
fa54fe1e 679 default: v.f |= F_BOGUS; break;
680 }
681 }
682 if (argc - optind > 2 || (v.f & F_BOGUS))
683 die(EXIT_FAILURE, "Usage: verify [-OPTIONS] [FILE [MESSAGE]]");
684
685 if ((eo = getenc(ef)) == 0)
686 die(EXIT_FAILURE, "encoding `%s' not found", ef);
687
cd6eca43
MW
688 fn = optind < argc ? argv[optind++] : "-";
689 if (strcmp(fn, "-") == 0)
fa54fe1e 690 fp = stdin;
cd6eca43 691 else if ((fp = fopen(fn, eo->rmode)) == 0) {
fa54fe1e 692 die(EXIT_FAILURE, "couldn't open file `%s': %s",
cd6eca43
MW
693 fn, strerror(errno));
694 }
fa54fe1e 695
213e565f 696 if (key_open(&kf, keyring, KOPEN_READ, key_moan, 0))
fa54fe1e 697 die(EXIT_FAILURE, "can't open keyring `%s'", keyring);
698 if (kn && (kk = key_bytag(&kf, kn)) == 0)
699 die(EXIT_FAILURE, "key `%s' not found", kn);
700
701 e = initdec(eo, fp, vrfbdry, &v);
702
703 /* --- Read the header chunk --- */
704
705 v.what = "malformed header";
706 sig_readheader(e, &s, vrfchoke, &v);
707
708 if (((s.f ^ v.f) & v.m) != 0) {
709 if (v.verb) printf("FAIL boundary string inconsistent with contents\n");
710 exit(EXIT_FAILURE);
711 }
712 v.f |= s.f;
713
714 if ((k = key_byid(&kf, s.keyid)) == 0) {
715 if (v.verb) printf("FAIL key id %08lx not found\n",
716 (unsigned long)s.keyid);
717 exit(EXIT_FAILURE);
718 }
719 if (kk && k->id != kk->id) {
720 if (v.verb) {
721 dstr_reset(&d); key_fulltag(k, &d);
722 dstr_reset(&dd); key_fulltag(kk, &dd);
723 printf("FAIL signing key is %s; expected key %s\n", d.buf, dd.buf);
724 }
725 exit(EXIT_FAILURE);
726 }
727
728 s.s = getsig(k, "ccsig", 0);
729 dstr_reset(&d); key_fulltag(k, &d);
946c3f72 730 if (!(v.f & F_NOCHECK) && v.verb && (err = s.s->ops->check(s.s)) != 0)
fa54fe1e 731 printf("WARN verification key %s fails check: %s\n", d.buf, err);
732
733 dstr_reset(&dd); keyhash(k, s.s, &dd);
734 if (dd.len != s.kh.len || memcmp(dd.buf, s.kh.buf, dd.len) != 0) {
735 if (v.verb) printf("FAIL key hash mismatch\n");
736 exit(EXIT_FAILURE);
45c0fd36 737 }
fa54fe1e 738
739 /* --- Now a merry dance --- */
740
741 if (v.f & F_DETACH)
742 sig_readsig(e, &s);
743 if (optind < argc)
744 dfn = argv[optind++];
cd6eca43 745 mcsetup_read(&mc_in, v.f, &e, fn, dfn);
fa54fe1e 746
747 if (!of && (v.f & F_DETACH)) {
748 rfp = ofp = 0;
749 v.f &= ~F_BUFFER;
750 } else if (!of || strcmp(of, "-") == 0) {
751 v.f |= F_BUFFER;
752 ofp = stdout;
753 }
754 if (of && !(v.f & F_BUFFER)) {
755 if ((ofp = fopen(of, (v.f & F_BINARY) ? "wb" : "w")) == 0) {
756 die(EXIT_FAILURE, "couldn't open file `%s' for output: %s",
757 of, strerror(errno));
758 }
759 rfp = ofp;
760 } else if ((rfp = tmpfile()) == 0)
761 die(EXIT_FAILURE, "couldn't create temporary file: %s", strerror(errno));
762
763 /* --- Read the message and verify the signature --- */
764
765 sig_hash(&s);
766 for (;;) {
767 n = mc_in.read(&mc_in, bb);
768 if (!n) break;
769 dohash(s.s->h, bb, n);
770 if (rfp) fwrite(bb, 1, n, rfp);
771 }
772 mc_endread(&mc_in, eo, &e);
773 if (!(v.f & F_DETACH))
774 sig_readsig(e, &s);
775 if (rfp && (ferror(rfp) || fflush(rfp))) {
776 if (v.verb) printf("FAIL error writing message: %s\n", strerror(errno));
777 exit(EXIT_FAILURE);
778 }
779
780 /* --- Check the signature --- */
781
782 if (s.s->ops->doit(s.s, &s.sig)) {
783 if (v.verb) printf("FAIL signature verification failed\n");
784 exit(EXIT_FAILURE);
785 }
9cea6911 786 if (t_fresh && s.t < t_fresh) {
787 if (v.verb) printf("FAIL signature is stale\n");
788 exit(EXIT_FAILURE);
789 }
790 if (s.t > time(0)) {
791 if (v.verb) printf("FAIL signature timestamp in the future\n");
792 exit(EXIT_FAILURE);
793 }
fa54fe1e 794 if (v.verb) {
795 tm = (v.f & F_UTC) ? gmtime(&s.t) : localtime(&s.t);
796 strftime(bb, sizeof(bb), "%Y-%m-%d %H:%M:%S %Z", tm);
797 printf("INFO good-signature %s\n", d.buf);
798 printf("INFO date %s\n", bb);
799 }
800
801 /* --- Unbuffer buffered input --- */
802
803 if (v.f & F_BUFFER) {
804 if (!ofp && (ofp = fopen(of, "wb")) == 0) {
805 die(EXIT_FAILURE, "couldn't open file `%s' for output: %s",
806 of, strerror(errno));
807 }
808 rewind(rfp);
cd6eca43 809 if (v.f & F_PROGRESS) fprogress_init(&ff, "copying buffer", rfp);
fa54fe1e 810 if (v.verb && ofp == stdout) printf("DATA\n");
811 for (;;) {
812 n = fread(bb, 1, sizeof(bb), rfp);
813 if (!n) break;
cd6eca43
MW
814 if (v.f & F_PROGRESS) fprogress_update(&ff, n);
815 if (fwrite(bb, 1, n, ofp) < n) {
816 if (v.f & F_PROGRESS) fprogress_done(&ff);
fa54fe1e 817 die(EXIT_FAILURE, "error writing output: %s", strerror(errno));
cd6eca43 818 }
fa54fe1e 819 }
cd6eca43 820 if (v.f & F_PROGRESS) fprogress_done(&ff);
fa54fe1e 821 if (ferror(rfp) || fclose(rfp))
822 die(EXIT_FAILURE, "error unbuffering output: %s", strerror(errno));
823 }
824 if (ofp && (fflush(ofp) || ferror(ofp) || fclose(ofp)))
45c0fd36 825 die(EXIT_FAILURE, "error writing output: %s", strerror(errno));
fa54fe1e 826
827 /* --- Tidy up --- */
828
829 e->ops->decdone(e);
830 if (v.verb && ofp != stdout)
831 printf("OK verified successfully\n");
832 freeenc(e);
833 key_close(&kf);
834 sig_destroy(&s);
835 dstr_destroy(&d);
836 dstr_destroy(&dd);
45c0fd36 837 return (0);
fa54fe1e 838}
839
840/*----- Reformatting ------------------------------------------------------*/
841
842static int format(int argc, char *argv[])
843{
844 const char *ief = "binary", *oef = "binary";
cd6eca43 845 const char *fn, *dfn = 0, *of = 0, *mf = 0;
fa54fe1e 846 sigmsg s;
847 FILE *fp, *ofp = 0, *mfp = 0;
848 int i;
849 size_t n;
850 msgcanon mc_in = MC_INIT, mc_out = MC_INIT;
851 char bb[MSGBUFSZ];
852 vrfctx v = { 0, 0, 1 };
853 unsigned f = 0, fm = ~F_SIGMASK, sf;
854 const encops *ieo, *oeo;
855 enc *ie, *oe;
856
857 for (;;) {
858 static const struct option opt[] = {
859 { "armour-in", 0, 0, 'a' },
860 { "armor-in", 0, 0, 'a' },
861 { "armour-out", 0, 0, 'A' },
862 { "armor-out", 0, 0, 'A' },
863 { "detach", 0, 0, 'D' },
864 { "embed", 0, 0, 'E' },
865 { "format-in", OPTF_ARGREQ, 0, 'f' },
866 { "format-out", OPTF_ARGREQ, 0, 'F' },
45c0fd36
MW
867 { "message", OPTF_ARGREQ, 0, 'm' },
868 { "output", OPTF_ARGREQ, 0, 'o' },
cd6eca43 869 { "progress", 0, 0, 'p' },
fa54fe1e 870 { 0, 0, 0, 0 }
871 };
cd6eca43 872 i = mdwopt(argc, argv, "f:F:m:o:apADE", opt, 0, 0, 0);
fa54fe1e 873 if (i < 0) break;
874 switch (i) {
875 case 'a': ief = "pem"; break;
876 case 'A': oef = "pem"; break;
877 case 'f': ief = optarg; break;
878 case 'F': oef = optarg; break;
879 case 'D': f |= F_DETACH; fm |= F_DETACH; break;
880 case 'E': f &= ~F_DETACH; fm |= F_DETACH; break;
881 case 'm': mf = optarg; break;
882 case 'o': of = optarg; break;
cd6eca43 883 case 'p': f |= F_PROGRESS; break;
fa54fe1e 884 default: f |= F_BOGUS; break;
885 }
886 }
887
888 if (argc - optind > 2 || (f & F_BOGUS))
889 die(EXIT_FAILURE, "Usage: format [-OPTIONS] [FILE [MESSAGE]]");
890
891 if ((ieo = getenc(ief)) == 0)
892 die(EXIT_FAILURE, "encoding `%s' not found", ief);
893 if ((oeo = getenc(oef)) == 0)
894 die(EXIT_FAILURE, "encoding `%s' not found", oef);
895
cd6eca43
MW
896 fn = optind < argc ? argv[optind++] : "-";
897 if (strcmp(fn, "-") == 0)
fa54fe1e 898 fp = stdin;
cd6eca43 899 else if ((fp = fopen(fn, ieo->rmode)) == 0) {
fa54fe1e 900 die(EXIT_FAILURE, "couldn't open file `%s': %s",
cd6eca43
MW
901 fn, strerror(errno));
902 }
fa54fe1e 903
904 if (optind < argc)
905 dfn = argv[optind++];
906
907 ie = initdec(ieo, fp, vrfbdry, &v);
908
909 /* --- Read the header chunk --- */
910
911 sig_readheader(ie, &s, diechoke, "malformed header");
912
913 if (((s.f ^ v.f) & v.m) != 0)
914 moan("boundary string inconsistent with contents (ignoring)");
45c0fd36 915
cd6eca43 916 mcsetup_read(&mc_in, s.f, &ie, fn, dfn);
fa54fe1e 917
918 /* --- Prepare the output stuff --- */
919
920 if (!of && !mf) of = "-";
921 sf = s.f;
922 f = (f & fm) | (s.f & ~fm);
923 s.f = f & F_SIGMASK;
924
925 if (sf & F_DETACH)
926 sig_readsig(ie, &s);
927
928 if (!of)
929 mcsetup_writenull(&mc_out);
930 else {
931 if (strcmp(of, "-") == 0)
932 ofp = stdout;
933 else if ((ofp = fopen(of, oeo->wmode)) == 0) {
934 die(EXIT_FAILURE, "couldn't open file `%s' for output: %s",
935 of, strerror(errno));
936 }
937 oe = initenc(oeo, ofp,
938 (f & F_DETACH) ? "CATSIGN SIGNATURE" :
939 (f & F_BINARY) ? "CATSIGN MESSAGE" :
940 "CATSIGN MESSAGE HEADER");
941 sig_writeheader(oe, &s);
942 mcsetup_write(&mc_out, f, &oe);
943 }
944
945 if (mf) {
946 if (strcmp(mf, "-") == 0)
947 mfp = stdout;
948 else if ((mfp = fopen(mf, (f & F_BINARY) ? "wb" : "w")) == 0) {
949 die(EXIT_FAILURE, "couldn't open file `%s' for output: %s",
950 mf, strerror(errno));
951 }
952 }
953
954 /* --- Wade through the message body --- */
955
956 for (;;) {
957 n = mc_in.read(&mc_in, bb);
958 if (!n) break;
959 mc_out.write(&mc_out, bb, n);
960 if (mfp) fwrite(bb, 1, n, mfp);
961 }
962 mc_endread(&mc_in, ieo, &ie);
963 if (of) mc_endwrite(&mc_out, oeo, &oe);
964
965 /* --- Write the signature --- */
966
967 if (!(sf & F_DETACH))
968 sig_readsig(ie, &s);
969 if (of) {
970 sig_writesig(oe, &s);
971 oe->ops->encdone(oe);
972 }
973
974 /* --- All done --- */
975
976 ie->ops->decdone(ie);
977 if (ferror(fp) || fclose(fp))
978 die(EXIT_FAILURE, "error reading input signature: %s", strerror(errno));
979 if (ofp && (fflush(ofp) || ferror(ofp) || fclose(ofp)))
980 die(EXIT_FAILURE, "error writing output signature: %s", strerror(errno));
981 if (mfp && (fflush(mfp) || ferror(mfp) || fclose(mfp)))
982 die(EXIT_FAILURE, "error writing output message: %s", strerror(errno));
983 freeenc(ie);
984 if (of) freeenc(oe);
985 sig_destroy(&s);
986 return (0);
987}
988
989static void infochoke(const char *m, void *p)
990{
991 vrfctx *v = p;
992 printf("BAD %s: %s\n", v->what, m);
993 exit(EXIT_FAILURE);
994}
995
996static void infokeyreport(const char *file, int line,
997 const char *err, void *p)
998{ /*whatever*/; }
999
1000static int info(int argc, char *argv[])
1001{
1002 const char *ef = "binary";
1003 vrfctx v = { 0, 0, 1 };
1004 key_file kf;
1005 key *k;
1006 sigmsg s;
1007 FILE *fp;
1008 int i;
1009 struct tm *tm;
1010 char bb[256];
1011 dstr d = DSTR_INIT;
1012 const encops *eo;
1013 enc *e;
1014
1015 for (;;) {
1016 static const struct option opt[] = {
1017 { "armour", 0, 0, 'a' },
1018 { "armor", 0, 0, 'a' },
1019 { "format", OPTF_ARGREQ, 0, 'f' },
1020 { "gmt", 0, 0, 'u' },
1021 { "utc", 0, 0, 'u' },
1022 { 0, 0, 0, 0 }
1023 };
1024 i = mdwopt(argc, argv, "f:au", opt, 0, 0, 0);
1025 if (i < 0) break;
1026 switch (i) {
1027 case 'a': ef = "pem"; break;
1028 case 'f': ef = optarg; break;
1029 case 'u': v.f |= F_UTC; break;
1030 default: v.f |= F_BOGUS; break;
1031 }
1032 }
1033 if (argc - optind > 1 || (v.f & F_BOGUS))
1034 die(EXIT_FAILURE, "Usage: info [-OPTIONS] [FILE]");
1035
1036 if ((eo = getenc(ef)) == 0)
1037 die(EXIT_FAILURE, "encoding `%s' not found", ef);
1038
1039 if (optind >= argc)
1040 fp = stdin;
1041 else if (strcmp(argv[optind], "-") == 0) {
1042 fp = stdin;
1043 optind++;
1044 } else if ((fp = fopen(argv[optind], eo->rmode)) == 0) {
1045 die(EXIT_FAILURE, "couldn't open file `%s': %s",
1046 argv[optind], strerror(errno));
1047 } else
1048 optind++;
1049
1050 if (key_open(&kf, keyring, KOPEN_READ, infokeyreport, 0)) {
1051 printf("NOTE can't open keyring `%s'\n", keyring);
1052 keyring = 0;
1053 }
1054 e = initdec(eo, fp, vrfbdry, &v);
1055
1056 v.what = "malformed header";
1057 sig_readheader(e, &s, infochoke, &v);
1058
1059 printf("INFO flags %sdetach %sbinary\n",
1060 (s.f & F_DETACH) ? "" : "!",
1061 (s.f & F_BINARY) ? "" : "!");
1062
1063 if (((s.f ^ v.f) & v.m) != 0) {
1064 printf("WARN boundary string inconsistent with contents\n");
1065 printf("INFO expected-flags");
1066 if (v.m & F_DETACH) printf(" %sdetach", (v.f & F_DETACH) ? "" : "!");
1067 if (v.m & F_BINARY) printf(" %sbinary", (v.f & F_BINARY) ? "" : "!");
1068 putchar('\n');
1069 }
1070 v.f |= s.f;
1071
1072 tm = (v.f & F_UTC) ? gmtime(&s.t) : localtime(&s.t);
1073 strftime(bb, sizeof(bb), "%Y-%m-%d %H:%M:%S %Z", tm);
1074 printf("INFO date %s\n", bb);
1075
1076 if (keyring && (k = key_byid(&kf, s.keyid)) != 0) {
1077 dstr_reset(&d); key_fulltag(k, &d);
1078 printf("INFO key %s\n", d.buf);
1079 } else
1080 printf("INFO unknown-key %08lx\n", (unsigned long)s.keyid);
1081
1082 if (keyring) key_close(&kf);
1083 dstr_destroy(&d);
1084 sig_destroy(&s);
1085 return (0);
1086}
1087
1088/*----- Main code ---------------------------------------------------------*/
1089
1090#define LISTS(LI) \
1091 LI("Lists", list, \
1092 listtab[i].name, listtab[i].name) \
1093 LI("Signature schemes", sig, \
1094 sigtab[i].name, sigtab[i].name) \
1095 LI("Encodings", enc, \
1096 enctab[i].name, enctab[i].name) \
1097 LI("Hash functions", hash, \
1098 ghashtab[i], ghashtab[i]->name)
1099
1100MAKELISTTAB(listtab, LISTS)
1101
1102int cmd_show(int argc, char *argv[])
1103{
1104 return (displaylists(listtab, argv + 1));
1105}
1106
1107static int cmd_help(int, char **);
1108
1109static cmd cmdtab[] = {
1110 { "help", cmd_help, "help [COMMAND...]" },
1111 { "show", cmd_show, "show [ITEM...]" },
1112 CMD_ENCODE,
1113 CMD_DECODE,
1114 { "sign", sign,
cd6eca43 1115 "sign [-adptC] [-k TAG] [-f FORMAT] [-o OUTPUT] [FILE]", "\
fa54fe1e 1116Options:\n\
1117\n\
1118-a, --armour Same as `-f pem'.\n\
1119-b, --binary Treat the input message as binary data.\n\
1120-d, --detach Produce a detached signature.\n\
1121-f, --format=FORMAT Encode as FORMAT.\n\
1122-k, --key=TAG Use public encryption key named by TAG.\n\
1123-o, --output=FILE Write output to FILE.\n\
cd6eca43 1124-p, --progress Show progress on large files.\n\
fa54fe1e 1125-t, --text Canonify input message as a text file.\n\
946c3f72 1126-C, --nocheck Don't check the private key.\n\
fa54fe1e 1127" },
1128 { "verify", verify,
cd6eca43 1129 "verify [-abpquvC] [-f FORMAT] [-k TAG] [-o OUTPUT]\n\t\
fa54fe1e 1130[FILE [MESSAGE]]", "\
1131Options:\n\
1132\n\
1133-a, --armour Same as `-f pem'.\n\
1134-b, --buffer Buffer message until signature is verified.\n\
1135-f, --format=FORMAT Decode as FORMAT.\n\
1136-k, --key=TAG Require that the message be signed by key TAG.\n\
1137-o, --output=FILE Write message to FILE.\n\
cd6eca43 1138-p, --progress Show progress on large files.\n\
fa54fe1e 1139-q, --quiet Produce fewer messages.\n\
9cea6911 1140-t, --freshtime=TIME Only accept signatures made after this time.\n\
fa54fe1e 1141-u, --utc Show dates in UTC rather than local time.\n\
1142-v, --verbose Produce more verbose messages.\n\
946c3f72 1143-C, --nocheck Don't check the public key.\n\
fa54fe1e 1144" },
1145 { "info", info,
1146 "info [-au] [-f FORMAT] [FILE]", "\
1147Options:\n\
1148\n\
1149-a, --armour Same as `-f pem'.\n\
1150-f, --format=FORMAT Decode as FORMAT.\n\
1151-u, --utc Show dates in UTC rather than local time.\n\
1152"},
1153 { "format", format,
cd6eca43 1154 "format [-apuADE] [-f FORMAT] [-F format] [-m FILE] [-o FILE]\n\t\
fa54fe1e 1155[FILE [MESSAGE]]", "\
1156Options:\n\
1157\n\
1158-a, --armour-in Same as `-f pem'.\n\
1159-A, --armour-out Same as `-F pem'.\n\
1160-D, --detach Create detached signature.\n\
1161-E, --embed Create signature with embedded message.\n\
1162-f, --format-in=FORMAT Decode input as FORMAT.\n\
1163-F, --format-out=FORMAT Encode output as FORMAT.\n\
1164-m, --message=FILE Write message to FILE.\n\
1165-o, --output=FILE Write new signature to FILE.\n\
cd6eca43 1166-p, --progress Show progress on large files.\n\
fa54fe1e 1167"},
1168 { 0, 0, 0 }
1169}; /* " Emacs seems confused. */
1170
1171static int cmd_help(int argc, char **argv)
1172{
1173 sc_help(cmdtab, stdout, argv + 1);
1174 return (0);
1175}
1176
1177void version(FILE *fp)
1178{
1179 pquis(fp, "$, Catacomb version " VERSION "\n");
1180}
1181
1182static void usage(FILE *fp)
1183{
1184 pquis(fp, "Usage: $ [-k KEYRING] COMMAND [ARGS]\n");
1185}
1186
1187void help_global(FILE *fp)
1188{
1189 usage(fp);
1190 fputs("\n\
1191Sign and verify data.\n\
1192\n\
1193Global command-line options:\n\
1194\n\
1195-h, --help [COMMAND...] Show this help message, or help for COMMANDs.\n\
1196-v, --version Show program version number.\n\
1197-u, --usage Show a terse usage message.\n\
1198\n\
1199-k, --keyring=FILE Read keys from FILE.\n",
1200 fp);
1201}
1202
1203/* --- @main@ --- *
1204 *
1205 * Arguments: @int argc@ = number of command line arguments
1206 * @char *argv[]@ = vector of command line arguments
1207 *
1208 * Returns: Zero if successful, nonzero otherwise.
1209 *
1210 * Use: Encrypts or decrypts files.
1211 */
1212
1213int main(int argc, char *argv[])
1214{
1215 unsigned f = 0;
1216
1217#define f_bogus 1u
1218
1219 /* --- Initialize the library --- */
1220
1221 ego(argv[0]);
1222 sub_init();
1223 rand_noisesrc(RAND_GLOBAL, &noise_source);
1224 rand_seed(RAND_GLOBAL, 160);
1225/* trace_on(stderr, 1); */
1226
1227 /* --- Parse options --- */
1228
1229 for (;;) {
1230 static struct option opts[] = {
1231 { "help", 0, 0, 'h' },
1232 { "version", 0, 0, 'v' },
1233 { "usage", 0, 0, 'u' },
1234 { "keyring", OPTF_ARGREQ, 0, 'k' },
1235 { 0, 0, 0, 0 }
1236 };
1237 int i = mdwopt(argc, argv, "+hvu k:", opts, 0, 0, 0);
1238 if (i < 0)
1239 break;
1240 switch (i) {
1241 case 'h':
1242 sc_help(cmdtab, stdout, argv + optind);
1243 exit(0);
1244 break;
1245 case 'v':
1246 version(stdout);
1247 exit(0);
1248 break;
1249 case 'u':
1250 usage(stdout);
1251 exit(0);
1252 case 'k':
1253 keyring = optarg;
1254 break;
1255 default:
1256 f |= f_bogus;
1257 break;
1258 }
1259 }
1260
1261 argc -= optind;
1262 argv += optind;
1263 optind = 0;
1264 if (f & f_bogus || argc < 1) {
1265 usage(stderr);
1266 exit(EXIT_FAILURE);
1267 }
1268
1269 /* --- Dispatch to the correct subcommand handler --- */
1270
1271 return (findcmd(cmdtab, argv[0])->cmd(argc, argv));
1272
1273#undef f_bogus
1274}
1275
1276/*----- That's all, folks -------------------------------------------------*/