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