Initial import.
[u/mdw/catacomb] / keyutil.c
1 /* -*-c-*-
2 *
3 * $Id: keyutil.c,v 1.1 1999/09/03 08:41:12 mdw Exp $
4 *
5 * Simple key manager program
6 *
7 * (c) 1999 Mark Wooding
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 /*----- Revision history --------------------------------------------------*
31 *
32 * $Log: keyutil.c,v $
33 * Revision 1.1 1999/09/03 08:41:12 mdw
34 * Initial import.
35 *
36 */
37
38 /*----- Header files ------------------------------------------------------*/
39
40 #include "config.h"
41
42 #include <errno.h>
43 #include <stdio.h>
44 #include <stdlib.h>
45 #include <string.h>
46 #include <time.h>
47
48 #include <mLib/mdwopt.h>
49 #include <mLib/quis.h>
50 #include <mLib/report.h>
51 #include <mLib/sub.h>
52
53 #include <noise.h>
54 #include <rand.h>
55
56 #include "getdate.h"
57 #include "key.h"
58
59 /*----- Handy global state ------------------------------------------------*/
60
61 static const char *keyfile = "keyring";
62
63 /*----- Useful shared functions -------------------------------------------*/
64
65 /* --- @doopen@ --- *
66 *
67 * Arguments: @key_file *f@ = pointer to key file block
68 * @unsigned how@ = method to open file with
69 *
70 * Returns: ---
71 *
72 * Use: Opens a key file and handles errors by panicking
73 * appropriately.
74 */
75
76 static void doopen(key_file *f, unsigned how)
77 {
78 if (key_open(f, keyfile, how))
79 die(1, "couldn't open file `%s': %s", keyfile, strerror(errno));
80 }
81
82 /* --- @doclose@ --- *
83 *
84 * Arguments: @key_file *f@ = pointer to key file block
85 *
86 * Returns: ---
87 *
88 * Use: Closes a key file and handles errors by panicking
89 * appropriately.
90 */
91
92 static void doclose(key_file *f)
93 {
94 switch (key_close(f)) {
95 case KWRITE_FAIL:
96 die(EXIT_FAILURE, "couldn't write file `%s': %s",
97 keyfile, strerror(errno));
98 case KWRITE_BROKEN:
99 die(EXIT_FAILURE, "keyring file `%s' broken: %s (repair manually)",
100 keyfile, strerror(errno));
101 }
102 }
103
104 /* --- @setattr@ --- *
105 *
106 * Arguments: @key_file *f@ = pointer to key file block
107 * @key *k@ = pointer to key block
108 * @char *v[]@ = array of assignments (overwritten!)
109 *
110 * Returns: ---
111 *
112 * Use: Applies the attribute assignments to the key.
113 */
114
115 static void setattr(key_file *f, key *k, char *v[])
116 {
117 while (*v) {
118 char *p = *v;
119 size_t eq = strcspn(p, "=");
120 if (p[eq] == 0)
121 moan("invalid assignment: `%s'", p);
122 p[eq] = 0;
123 p += eq + 1;
124 key_putattr(f, k, *v, *p ? p : 0);
125 v++;
126 }
127 }
128
129 /*----- Command implementation --------------------------------------------*/
130
131 /* --- @cmd_add@ --- */
132
133 static int cmd_add(int argc, char *argv[])
134 {
135 key_file f;
136 key *k;
137 int bits = 128;
138 time_t exp = KEXP_EXPIRE;
139 unsigned fl = 0;
140 unsigned char *p;
141 size_t sz;
142 const char *c = 0;
143
144 /* --- Various useful flag bits --- */
145
146 enum {
147 f_bogus = 1
148 };
149
150 /* --- Parse options for the subcommand --- */
151
152 for (;;) {
153 static struct option opt[] = {
154 { "bits", OPTF_ARGREQ, 0, 'b' },
155 { "expire", OPTF_ARGREQ, 0, 'e' },
156 { "comment", OPTF_ARGREQ, 0, 'c' },
157 { 0, 0, 0, 0 }
158 };
159 int i = mdwopt(argc, argv, "+b:e:c:", opt, 0, 0, 0);
160 if (i < 0)
161 break;
162
163 /* --- Handle the various options --- */
164
165 switch (i) {
166
167 /* --- Bits must be nonzero and a multiple of 8 --- */
168
169 case 'b':
170 if (!(bits = atoi(optarg)) || bits % 8)
171 die(EXIT_FAILURE, "bad number of bits: `%s'", optarg);
172 break;
173
174 /* --- Expiry dates get passed to @get_date@ for parsing --- */
175
176 case 'e':
177 if (strcmp(optarg, "forever") == 0)
178 exp = KEXP_FOREVER;
179 else {
180 exp = get_date(optarg, 0);
181 if (exp == -1)
182 die(EXIT_FAILURE, "bad expiry date: `%s'", optarg);
183 }
184 break;
185
186 /* --- Store comments without interpretation --- */
187
188 case 'c':
189 if (key_chkcomment(c))
190 die(EXIT_FAILURE, "bad comment string: `%s'", optarg);
191 c = optarg;
192 break;
193
194 /* --- Other things are bogus --- */
195
196 default:
197 fl |= f_bogus;
198 break;
199 }
200 }
201
202 /* --- Various sorts of bogusity --- */
203
204 if (fl & f_bogus || optind + 1 > argc) {
205 die(EXIT_FAILURE,
206 "Usage: add [-b BITS] [-e EXPIRE] [-c COMMENT] TYPE [ATTR...]");
207 }
208 if (key_chktype(argv[optind]))
209 die(EXIT_FAILURE, "bad key type: `%s'", argv[optind]);
210 if (exp == KEXP_EXPIRE)
211 exp = time(0) + 14 * 24 * 60 * 60;
212
213 /* --- Initialize the Catacomb random number generator --- */
214
215 rand_init(RAND_GLOBAL);
216 rand_noisesrc(RAND_GLOBAL, &noise_source);
217
218 /* --- Extract the key data from the generator --- */
219
220 sz = bits / 8;
221 p = xmalloc(sz);
222 rand_getgood(RAND_GLOBAL, p, sz);
223
224 /* --- Open the file, add the key, set attributes, close, return --- */
225
226 doopen(&f, KOPEN_WRITE);
227 if (!(k = key_new(&f, argv[optind], p, sz, exp, c)))
228 moan("key not added: expiry date in past?");
229 setattr(&f, k, argv + optind + 1);
230 doclose(&f);
231 return (0);
232 }
233
234 /* --- @cmd_expire@ --- */
235
236 static int cmd_expire(int argc, char *argv[])
237 {
238 key_file f;
239 key *k;
240 uint32 id;
241 int i;
242 int rc = 0;
243
244 if (argc < 2)
245 die(EXIT_FAILURE, "Usage: expire KEYID...");
246 doopen(&f, KOPEN_WRITE);
247 for (i = 1; i < argc; i++) {
248 id = (uint32)strtoul(argv[i], 0, 16);
249 if ((k = key_byid(&f, id)) != 0)
250 key_expire(&f, k);
251 else {
252 moan("keyid %lx not found", (unsigned long)id);
253 rc = 1;
254 }
255 }
256 doclose(&f);
257 return (rc);
258 }
259
260 /* --- @cmd_delete@ --- */
261
262 static int cmd_delete(int argc, char *argv[])
263 {
264 key_file f;
265 key *k;
266 uint32 id;
267 int i;
268 int rc = 0;
269
270 if (argc < 2)
271 die(EXIT_FAILURE, "Usage: delete KEYID...");
272 doopen(&f, KOPEN_WRITE);
273 for (i = 1; i < argc; i++) {
274 id = (uint32)strtoul(argv[i], 0, 16);
275 if ((k = key_byid(&f, id)) != 0)
276 key_delete(&f, k);
277 else {
278 moan("keyid %lx not found", (unsigned long)id);
279 rc = 1;
280 }
281 }
282 doclose(&f);
283 return (rc);
284 }
285
286 /* --- @cmd_setattr@ --- */
287
288 static int cmd_setattr(int argc, char *argv[])
289 {
290 key_file f;
291 key *k;
292 uint32 id;
293
294 if (argc < 3)
295 die(EXIT_FAILURE, "Usage: setattr KEYID ATTR...");
296 doopen(&f, KOPEN_WRITE);
297 id = (uint32)strtoul(argv[1], 0, 16);
298 if ((k = key_byid(&f, id)) == 0)
299 die(EXIT_FAILURE, "keyid %lx not found", (unsigned long)id);
300 setattr(&f, k, argv + 2);
301 doclose(&f);
302 return (0);
303 }
304
305 /* --- @cmd_comment@ --- */
306
307 static int cmd_comment(int argc, char *argv[])
308 {
309 uint32 id;
310 key_file f;
311 key *k;
312
313 if (argc < 2 || argc > 3)
314 die(EXIT_FAILURE, "Usage: comment KEYID [COMMENT]");
315 doopen(&f, KOPEN_WRITE);
316 id = (uint32)strtoul(argv[1], 0, 16);
317 if ((k = key_byid(&f, id)) == 0)
318 die(EXIT_FAILURE, "keyid %lx not found", (unsigned long)id);
319 if (key_chkcomment(argv[2]))
320 die(EXIT_FAILURE, "bad comment: `%s'", argv[2]);
321 key_setcomment(&f, k, argv[2]);
322 doclose(&f);
323 return (0);
324 }
325
326 /* --- @cmd_list@ --- */
327
328 static int cmd_list(int argc, char *argv[])
329 {
330 key_file f;
331 key *k;
332 key_iter i;
333 unsigned fl = 0;
334 const char *tfmt;
335 int v = 0;
336 time_t t;
337
338 enum {
339 f_bogus = 1,
340 f_newline = 2,
341 f_attr = 4
342 };
343
344 /* --- Parse subcommand options --- */
345
346 for (;;) {
347 static struct option opt[] = {
348 { "quiet", 0, 0, 'q' },
349 { "verbose", 0, 0, 'v' },
350 { 0, 0, 0, 0 }
351 };
352 int i = mdwopt(argc, argv, "qv", opt, 0, 0, 0);
353 if (i < 0)
354 break;
355
356 switch (i) {
357 case 'q':
358 if (v)
359 v--;
360 break;
361 case 'v':
362 v++;
363 break;
364 default:
365 fl |= f_bogus;
366 break;
367 }
368 }
369
370 if (fl & f_bogus || optind != argc)
371 die(EXIT_FAILURE, "Usage: list [-qv]");
372
373 /* --- Open the key file --- */
374
375 doopen(&f, KOPEN_READ);
376 t = time(0);
377
378 /* --- Write the header --- */
379
380 tfmt = v ? "%Y-%m-%d %H:%M:%S" : "%Y-%m-%d";
381
382 /* --- Now iterate through the keys --- */
383
384 for (key_mkiter(&i, &f); (k = key_next(&i)) != 0; ) {
385 char ebuf[24], dbuf[24];
386 struct tm *tm;
387
388 /* --- Sort out the expiry times --- */
389
390 if (KEY_EXPIRED(t, k->exp)) {
391 strcpy(ebuf, "expired");
392 if (KEY_DELETED(t, k->del)) {
393 strcpy(dbuf, "deleted");
394 goto donetime;
395 } else
396 goto deltime;
397 }
398
399 if (k->exp == KEXP_FOREVER)
400 strcpy(ebuf, "forever");
401 else {
402 tm = localtime(&k->exp);
403 strftime(ebuf, sizeof(ebuf), tfmt, tm);
404 }
405
406 /* --- Sort out the delete times --- */
407
408 deltime:
409 if (k->del == KEXP_UNUSED)
410 strcpy(dbuf, "on expiry");
411 else if (k->del == KEXP_FOREVER)
412 strcpy(dbuf, "forever");
413 else {
414 tm = localtime(&k->del);
415 strftime(dbuf, sizeof(dbuf), tfmt, tm);
416 }
417
418 donetime:;
419
420 /* --- Display the data obtained so far --- */
421
422 if (!v) {
423 if (!(fl & f_newline)) {
424 printf("%8s %-20s %-10s %-10s %-23s\n",
425 "Id", "Type", "Expire", "Delete", "Comment");
426 }
427 printf("%08lx %-20s %-10s %-10s %-23s\n",
428 (unsigned long)k->id, k->type, ebuf, dbuf,
429 k->c ? k->c : "<none>");
430 fl |= f_newline;
431 } else {
432
433 /* --- Display the standard header --- */
434
435 if (fl & f_newline)
436 fputc('\n', stdout);
437 printf("keyid: %08lx\n", (unsigned long)k->id);
438 printf("type: %s\n", k->type);
439 printf("expiry: %s\n", ebuf);
440 printf("delete: %s\n", dbuf);
441 printf("comment: %s\n", k->c ? k->c : "<none>");
442
443 /* --- Display the attributes --- */
444
445 if (v > 1) {
446 key_attriter i;
447 const char *av, *an;
448
449 fl &= ~f_attr;
450 printf("attributes:");
451 for (key_mkattriter(&i, &f, k); key_nextattr(&i, &an, &av); ) {
452 printf("\n\t%s = %s", an, av);
453 fl |= f_attr;
454 }
455 if (fl & f_attr)
456 fputc('\n', stdout);
457 else
458 puts(" <none>");
459 }
460
461 /* --- If dumping requested, dump the raw key data in hex --- */
462
463 if (v > 2) {
464 unsigned char *p = k->k;
465 unsigned char *l = p + k->ksz;
466 size_t sz = 0;
467
468 fputs("key:", stdout);
469 while (p < l) {
470 if (sz % 16 == 0)
471 fputs("\n\t", stdout);
472 else if (sz % 8 == 0)
473 fputs(" ", stdout);
474 else
475 fputc(' ', stdout);
476 printf("%02x", *p++);
477 sz++;
478 }
479 fputc('\n', stdout);
480 }
481 }
482 }
483
484 doclose(&f);
485 return (0);
486 }
487
488 /* --- @cmd_extract@ --- */
489
490 static int cmd_extract(int argc, char *argv[])
491 {
492 key_file f;
493 key *k;
494 uint32 id;
495 int i;
496 int rc = 0;
497 FILE *fp;
498
499 if (argc < 3)
500 die(EXIT_FAILURE, "Usage: extract FILE KEYID...");
501 if (strcmp(argv[1], "-") == 0)
502 fp = stdout;
503 else if (!(fp = fopen(argv[1], "w"))) {
504 die(EXIT_FAILURE, "couldn't open `%s' for writing: %s",
505 argv[1], strerror(errno));
506 }
507
508 doopen(&f, KOPEN_WRITE);
509 for (i = 2; i < argc; i++) {
510 id = (uint32)strtoul(argv[i], 0, 16);
511 if ((k = key_byid(&f, id)) != 0)
512 key_extract(&f, k, fp);
513 else {
514 moan("keyid %lx not found", (unsigned long)id);
515 rc = 1;
516 }
517 }
518 doclose(&f);
519 return (rc);
520 }
521
522 /* --- @cmd_tidy@ --- */
523
524 static int cmd_tidy(int argc, char *argv[])
525 {
526 key_file f;
527 if (argc != 1)
528 die(EXIT_FAILURE, "usage: tidy");
529 doopen(&f, KOPEN_WRITE);
530 f.f |= KF_MODIFIED; /* Nasty hack */
531 doclose(&f);
532 return (0);
533 }
534
535 /* --- @cmd_merge@ --- */
536
537 static int cmd_merge(int argc, char *argv[])
538 {
539 key_file f;
540 FILE *fp;
541
542 if (argc != 2)
543 die(EXIT_FAILURE, "Usage: merge FILE");
544 if (strcmp(argv[1], "-") == 0)
545 fp = stdin;
546 else if (!(fp = fopen(argv[1], "r"))) {
547 die(EXIT_FAILURE, "couldn't open `%s' for writing: %s",
548 argv[1], strerror(errno));
549 }
550
551 doopen(&f, KOPEN_WRITE);
552 key_merge(&f, argv[1], fp);
553 doclose(&f);
554 return (0);
555 }
556
557 /*----- Main command table ------------------------------------------------*/
558
559 static struct cmd {
560 const char *name;
561 int (*cmd)(int /*argc*/, char */*argv*/[]);
562 const char *help;
563 } cmds[] = {
564 { "add", cmd_add,
565 "add [-b BITS] [-e EXPIRE] [-c COMMENT] TYPE [ATTR...]" },
566 { "expire", cmd_expire, "expire KEYID..." },
567 { "delete", cmd_delete, "delete KEYID..." },
568 { "setattr", cmd_setattr, "setattr KEYID ATTR..." },
569 { "comment", cmd_comment, "comment KEYID [COMMENT]" },
570 { "list", cmd_list, "list [-qv]" },
571 { "tidy", cmd_tidy, "tidy" },
572 { "extract", cmd_extract, "extract FILE KEYID..." },
573 { "merge", cmd_merge, "merge FILE" },
574 { 0, 0, 0 }
575 };
576
577 typedef struct cmd cmd;
578
579 /*----- Main code ---------------------------------------------------------*/
580
581 /* --- Helpful GNUy functions --- */
582
583 void usage(FILE *fp)
584 {
585 fprintf(fp, "Usage: %s [-k file] command [args]\n", QUIS);
586 }
587
588 void version(FILE *fp)
589 {
590 fprintf(fp, "%s, Catacomb version " VERSION "\n", QUIS);
591 }
592
593 void help(FILE *fp)
594 {
595 cmd *c;
596 version(fp);
597 fputc('\n', fp);
598 usage(fp);
599 fputs("\n\
600 Performs various simple key management operations. Command line options\n\
601 recognized are:\n\
602 \n\
603 -h, --help Display this help text.\n\
604 -v, --version Display version number.\n\
605 -u, --usage Display short usage summary.\n\
606 \n\
607 -k, --keyring=FILE Read and write keys in FILE.\n\
608 \n\
609 The following commands are understood:\n\n",
610 fp);
611 for (c = cmds; c->name; c++)
612 fprintf(fp, "%s\n", c->help);
613 }
614
615 /* --- @main@ --- *
616 *
617 * Arguments: @int argc@ = number of command line arguments
618 * @char *argv[]@ = array of command line arguments
619 *
620 * Returns: Nonzero on failure.
621 *
622 * Use: Main program. Performs simple key management functions.
623 */
624
625 int main(int argc, char *argv[])
626 {
627 unsigned f = 0;
628
629 enum {
630 f_bogus = 1
631 };
632
633 /* --- Initialization --- */
634
635 ego(argv[0]);
636 sub_init();
637
638 /* --- Parse command line options --- */
639
640 for (;;) {
641 static struct option opt[] = {
642
643 /* --- Standard GNUy help options --- */
644
645 { "help", 0, 0, 'h' },
646 { "version", 0, 0, 'v' },
647 { "usage", 0, 0, 'u' },
648
649 /* --- Real live useful options --- */
650
651 { "keyring", OPTF_ARGREQ, 0, 'k' },
652
653 /* --- Magic terminator --- */
654
655 { 0, 0, 0, 0 }
656 };
657 int i = mdwopt(argc, argv, "+hvu k:", opt, 0, 0, 0);
658
659 if (i < 0)
660 break;
661 switch (i) {
662
663 /* --- GNU help options --- */
664 case 'h':
665 help(stdout);
666 exit(0);
667 case 'v':
668 version(stdout);
669 exit(0);
670 case 'u':
671 usage(stdout);
672 exit(0);
673
674 /* --- Real useful options --- */
675
676 case 'k':
677 keyfile = optarg;
678 break;
679
680 /* --- Bogosity --- */
681
682 default:
683 f |= f_bogus;
684 break;
685 }
686 }
687
688 /* --- Complain about excessive bogons --- */
689
690 if (f & f_bogus || optind == argc) {
691 usage(stderr);
692 exit(1);
693 }
694
695 /* --- Dispatch to appropriate command handler --- */
696
697 argc -= optind;
698 argv += optind;
699 optind = 0;
700
701 {
702 cmd *c, *chosen = 0;
703 size_t sz = strlen(argv[0]);
704
705 for (c = cmds; c->name; c++) {
706 if (strncmp(argv[0], c->name, sz) == 0) {
707 if (c->name[sz] == 0) {
708 chosen = c;
709 break;
710 } else if (chosen)
711 die(EXIT_FAILURE, "ambiguous command name `%s'", argv[0]);
712 else
713 chosen = c;
714 }
715 }
716 if (!chosen)
717 die(EXIT_FAILURE, "unknown command name `%s'", argv[0]);
718 return (chosen->cmd(argc, argv));
719 }
720 }
721
722 /*----- That's all, folks -------------------------------------------------*/