chiark / gitweb /
Initial import.
[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 -------------------------------------------------*/