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