chiark / gitweb /
bf3bf95f3366dfb1edc60e5bbe418af7199e3000
[catacomb] / key / key-io.c
1 /* -*-c-*-
2  *
3  * Adding new keys to a key file
4  *
5  * (c) 1999 Straylight/Edgeware
6  */
7
8 /*----- Licensing notice --------------------------------------------------*
9  *
10  * This file is part of Catacomb.
11  *
12  * Catacomb is free software; you can redistribute it and/or modify
13  * it under the terms of the GNU Library General Public License as
14  * published by the Free Software Foundation; either version 2 of the
15  * License, or (at your option) any later version.
16  *
17  * Catacomb is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20  * GNU Library General Public License for more details.
21  *
22  * You should have received a copy of the GNU Library General Public
23  * License along with Catacomb; if not, write to the Free
24  * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
25  * MA 02111-1307, USA.
26  */
27
28 /*----- Header files ------------------------------------------------------*/
29
30 #include <ctype.h>
31 #include <errno.h>
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include <time.h>
36
37 #include <mLib/bits.h>
38 #include <mLib/crc32.h>
39 #include <mLib/dstr.h>
40 #include <mLib/hash.h>
41 #include <mLib/str.h>
42 #include <mLib/sub.h>
43 #include <mLib/sym.h>
44 #include <mLib/url.h>
45
46 #include "key.h"
47
48 /*----- Tweakable macros --------------------------------------------------*/
49
50 #define KEY_INITSZ 16
51
52 /*----- Low-level functions -----------------------------------------------*/
53
54 /* --- @insert@ --- *
55  *
56  * Arguments:   @key_file *f@ = pointer to file structure
57  *              @key *k@ = pointer to key block to insert
58  *
59  * Returns:     Error code (one of the @KERR@ code).
60  *
61  * Use:         Links a new key block into the complicated data structure
62  *              which is a keyring file.
63  */
64
65 static int insert(key_file *f, key *k)
66 {
67   key_ref *kr = 0;
68   unsigned found;
69
70   /* --- Sanity preservatives --- */
71
72   if (key_chkident(k->type))
73     return (KERR_BADTYPE);
74   else if (k->tag && key_chkident(k->tag))
75     return (KERR_BADTAG);
76
77   /* --- Insert into the tag table --- */
78
79   if (k->tag) {
80     kr = sym_find(&f->bytag, k->tag, -1, sizeof(*kr), &found);
81     if (found)
82       return (KERR_DUPTAG);
83     kr->k = k;
84   }
85
86   /* --- Insert into the id table --- */
87
88   {
89     hash_base **bin, *b;
90
91     bin = HASH_BIN(&f->byid, k->id);
92     for (b = *bin; b; b = b->next) {
93       if (b->hash == k->id) {
94         if (kr)
95           sym_remove(&f->bytag, kr);
96         return (KERR_DUPID);
97       }
98     }
99
100     k->_b.next = *bin;
101     *bin = &k->_b;
102     k->_b.hash = k->id;
103   }
104
105   /* --- Extend the table --- */
106
107   if (f->idload > 0)
108     f->idload--;
109   else if (hash_extend(&f->byid))
110     f->idload = SYM_LIMIT(f->byid.mask / 2);
111
112   /* --- Insert into the type table --- */
113
114   kr = sym_find(&f->bytype, k->type, -1, sizeof(*kr), &found);
115   if (!found) {
116     kr->k = k;
117     k->next = 0;
118   } else {
119     key **p = &kr->k;
120     if (k->exp != KEXP_FOREVER) {
121       while (*p && (*p)->exp != KEXP_EXPIRE && (*p)->exp > k->exp)
122         p = &(*p)->next;
123     }
124     k->next = *p;
125     *p = k;
126   }
127
128   return (KERR_OK);
129 }
130
131 /*----- Reading and writing keys ------------------------------------------*/
132
133 /* --- @exptime@ --- *
134  *
135  * Arguments:   @const char *p@ = pointer to string
136  *
137  * Returns:     Time value.
138  *
139  * Use:         Translates an expiry or deletion time.
140  */
141
142 static time_t exptime(const char *p)
143 {
144   size_t sz = strlen(p);
145   if (strncmp(p, "expired", sz) == 0)
146     return (KEXP_EXPIRE);
147   else if (strncmp(p, "forever", sz) == 0)
148     return (KEXP_FOREVER);
149   else
150     return (atol(p));
151 }
152
153 /* --- @merge_core@ --- *
154  *
155  * Arguments:   @key_file *f@ = pointer to file structure
156  *              @const char *file@ = name of file (for error messages)
157  *              @int lno@ = line number
158  *              @char *p@ = pointer into the line buffer (which will be
159  *                      clobbered)
160  *              @key_reporter *rep@ = error reporting function
161  *              @void *arg@ = argument for function
162  *              @dstr *n, *v@ = scratch dynamic strings, which must be empty
163  *                      on entry and are left empty on exit
164  *
165  * Returns:     ---
166  *
167  * Use:         This is the common core of @key_merge@ and @key_mergeline@.
168  *
169  *              It is assumed that the caller has already verified that the
170  *              keyring is writable.
171  */
172
173 static void merge_core(key_file *f, const char *file, int lno, char *p,
174                        key_reporter *rep, void *arg, dstr *n, dstr *v)
175 {
176   char *vf[6];
177   key *k = 0;
178
179   /* --- Skip blank lines and comments --- *
180    *
181    * Quite what they're doing in what ought to be an automatically-
182    * maintained file I don't know.
183    */
184
185   while (isspace((unsigned char)*p))
186     p++;
187   if (!*p || *p == '#')
188     goto skip;
189
190   /* --- Break the line into fields --- *
191    *
192    * There are currently six fields of interest:
193    *
194    *     * The key's identification (id, tag and type).
195    *     * The actual key data itself.
196    *     * The key expiry time.
197    *     * The key deletion time.
198    *     * The attributes field.
199    *     * Any further comments.
200    *
201    * All but the last field can contain no spaces.
202    */
203
204   {
205     int n = str_split(p, vf, 5, &vf[5]);
206     if (n < 4) {
207       if (rep)
208         rep(file, lno, "too few fields", arg);
209       goto skip;
210     }
211   }
212
213   /* --- Allocate a new key block --- */
214
215   k = CREATE(key);
216   k->k = 0;
217   k->tag = 0;
218   k->type = 0;
219
220   /* --- Extract the key data into the block --- */
221
222   if ((k->k = key_read(vf[1], 0)) == 0) {
223     if (rep)
224       rep(file, lno, "bad key data", arg);
225     goto skip;
226   }
227
228   /* --- Decode the identification field --- *
229    *
230    * For compatibility, derive a keyid from the key data.  This can only be
231    * done if the key encoding is binary (and presumably old-encoding binary
232    * at that).
233    */
234
235   {
236     char *q = strchr(vf[0], ':');
237     char *qq;
238
239     if (!q) {
240       if (k->k->e != KENC_BINARY) {
241         if (rep)
242           rep(file, lno, "new-style key encoding but no keyid", arg);
243         goto skip;
244       }
245       k->id = crc32(0, k->k->u.k.k, k->k->u.k.sz);
246       k->type = xstrdup(vf[0]);
247       k->tag = 0;
248     } else {
249       *q++ = 0;
250       k->id = strtoul(p, 0, 16);
251       if ((qq = strchr(q, ':')) == 0 || !qq[1]) {
252         if (qq)
253           *qq = 0;
254         k->tag = 0;
255       } else {
256         *qq++ = 0;
257         k->tag = xstrdup(qq);
258       }
259       k->type = xstrdup(q);
260     }
261   }
262
263   /* --- Get a key block for the new key --- */
264
265   k->exp = exptime(vf[2]);
266   k->del = exptime(vf[3]);
267
268   /* --- Insert the key block into the table --- */
269
270   {
271     int err;
272
273   again:
274     if ((err = insert(f, k)) < 0) {
275       if (err == KERR_DUPTAG) {
276         if (rep)
277           rep(file, lno, "duplicate key tag stripped", arg);
278         xfree(k->tag);
279         k->tag = 0;
280         goto again;
281       }
282       if (rep)
283         rep(file, lno, key_strerror(err), arg);
284       goto skip;
285     }
286   }
287
288   /* --- Parse up the attributes, if specified --- */
289
290   sym_create(&k->a);
291   if (vf[4] && strcmp(vf[4], "-") != 0) {
292     url_dctx uc;
293     for (url_initdec(&uc, vf[4]); url_dec(&uc, n, v); ) {
294       key_putattr(f, k, n->buf, v->buf);
295       DRESET(n); DRESET(v);
296     }
297   }
298
299   /* --- Insert the comment --- */
300
301   if (vf[5])
302     k->c = xstrdup(vf[5]);
303   else
304     k->c = 0;
305
306   /* --- Done --- */
307
308   f->f |= KF_MODIFIED;
309   return;
310
311   /* --- Tidy up after something going wrong --- */
312
313 skip:
314   if (k) {
315     if (k->tag) xfree(k->tag);
316     if (k->type) xfree(k->type);
317     if (k->k) key_drop(k->k);
318     DESTROY(k);
319   }
320 }
321
322 /* --- @key_merge@, @key_mergeline@ --- *
323  *
324  * Arguments:   @key_file *f@ = pointer to file structure
325  *              @const char *file@ = name of file (for error messages)
326  *              @int lno@ = line number (for error messages, @key_mergeline@)
327  *              @FILE *fp@ = file handle to read from (@key_merge@)
328  *              @const char *line@ = line from the input (@key_mergeline@)
329  *              @key_reporter *rep@ = error reporting function
330  *              @void *arg@ = argument for function
331  *
332  * Returns:     Error code (one of the @KERR@ constants).
333  *
334  * Use:         The @key_merge@ function reads keys from a file, and inserts
335  *              them into the keyring.
336  *
337  *              The @key_mergeline@ function reads a key from a single input
338  *              line (which may, but need not, have a final newline), and
339  *              adds it to the keyring.
340  *
341  *              The @key_mergeline@ function is intended to help with
342  *              interfacing Catacomb to runtimes which don't use C's @stdio@
343  *              streams, rather than as a general-purpose service, though if
344  *              it turns out to be useful in other ways then so much the
345  *              better.
346  */
347
348 int key_merge(key_file *f, const char *file, FILE *fp,
349               key_reporter *rep, void *arg)
350 {
351   dstr n = DSTR_INIT, v = DSTR_INIT;
352   dstr l = DSTR_INIT;
353   int lno = 1;
354
355   if (!(f->f & KF_WRITE))
356     return (KERR_READONLY);
357
358   for (; dstr_putline(&l, fp) != EOF; DRESET(&l))
359     merge_core(f, file, lno++, l.buf, rep, arg, &n, &v);
360
361   dstr_destroy(&l);
362   dstr_destroy(&n); dstr_destroy(&v);
363   return (0);
364 }
365
366 int key_mergeline(key_file *f, const char *file, int lno, const char *line,
367                   key_reporter *rep, void *arg)
368 {
369   dstr n = DSTR_INIT, v = DSTR_INIT;
370   size_t len = strlen(line);
371   char *p;
372
373   if (!(f->f & KF_WRITE)) return (KERR_READONLY);
374
375   if (len && line[len - 1] == '\n') len--;
376   p = xmalloc(len); memcpy(p, line, len); p[len] = 0;
377   merge_core(f, file, lno, p, rep, arg, &n, &v);
378   xfree(p); dstr_destroy(&n); dstr_destroy(&v);
379   return (0);
380 }
381
382 /* --- @key_extract@, @key_extractline@ --- *
383  *
384  * Arguments:   @key_file *f@ = pointer to file structure
385  *              @key *k@ = key to extract
386  *              @FILE *fp@ = file to write on (@key_extract@)
387  *              @dstr *d@ = string to write on (@key_extractline@)
388  *              @const key_filter *kf@ = pointer to key selection block
389  *
390  * Returns:     @key_extract@ returns zero if OK, EOF on error.
391  *              @key_extractline@ does not return a value.
392  *
393  * Use:         Extracts a key to an ouptut file or buffer.
394  *
395  *              The @key_extractline@ includes a final newline in its output.
396  *
397  *              The @key_extractline@ function is intended to help with
398  *              interfacing Catacomb to runtimes which don't use C's @stdio@
399  *              streams, rather than as a general-purpose service, though if
400  *              it turns out to be useful in other ways then so much the
401  *              better.
402  */
403
404 void key_extractline(key_file *f, key *k, dstr *d, const key_filter *kf)
405 {
406   time_t t = time(0);
407
408   /* --- Skip the key if it's deleted or unselected--- */
409
410   if (KEY_EXPIRED(t, k->del) || !key_match(k->k, kf))
411     return;
412
413   /* --- Encode the key and write the easy stuff --- */
414
415   key_fulltag(k, d);
416   DPUTC(d, ' ');
417   if (!key_write(k->k, d, kf)) dstr_puts(d, "struct:[]");
418   DPUTC(d, ' ');
419
420   /* --- Write out the expiry and deletion times --- */
421
422   if (KEY_EXPIRED(t, k->exp))
423     dstr_puts(d, "expired ");
424   else if (k->exp == KEXP_FOREVER)
425     dstr_puts(d, "forever ");
426   else
427     dstr_putf(d, "%li ", (long)k->exp);
428
429   if (k->del == KEXP_FOREVER)
430     dstr_puts(d, "forever ");
431   else
432     dstr_putf(d, "%li ", (long)k->del);
433
434   /* --- Output the attributes --- */
435
436   {
437     int none = 1;
438     sym_iter i;
439     key_attr *a;
440     url_ectx uc;
441
442     url_initenc(&uc);
443     for (sym_mkiter(&i, &k->a); (a = sym_next(&i)) != 0; ) {
444       none = 0;
445       url_enc(&uc, d, SYM_NAME(a), a->p);
446     }
447     if (none)
448       DPUTC(d, '-');
449   }
450
451   if (k->c)
452     dstr_putf(d, " %s", k->c);
453
454   DPUTC(d, '\n'); DPUTZ(d);
455 }
456
457 int key_extract(key_file *f, key *k, FILE *fp, const key_filter *kf)
458 {
459   dstr d = DSTR_INIT;
460
461   key_extractline(f, k, &d, kf);
462   dstr_write(&d, fp);
463   return (ferror(fp) ? EOF : 0);
464 }
465
466 /*----- Opening and closing files -----------------------------------------*/
467
468 /* --- @key_open@ --- *
469  *
470  * Arguments:   @key_file *f@ = pointer to file structure to initialize
471  *              @const char *file@ = pointer to the file name
472  *              @unsigned how@ = opening options (@KOPEN_*@).
473  *              @key_reporter *rep@ = error reporting function
474  *              @void *arg@ = argument for function
475  *
476  * Returns:     Zero if it worked, nonzero otherwise.
477  *
478  * Use:         Opens a key file, reads its contents, and stores them in a
479  *              structure.  The file is locked appropriately until closed
480  *              using @key_close@.  On an error, everything is cleared away
481  *              tidily.  If the file is opened with @KOPEN_WRITE@, it's
482  *              created if necessary, with read and write permissions for its
483  *              owner only.
484  */
485
486 int key_open(key_file *f, const char *file, unsigned how,
487              key_reporter *rep, void *arg)
488 {
489   if (key_lockfile(f, file, how)) {
490     rep(file, 0, strerror(errno), arg);
491     return (-1);
492   }
493   f->f = 0;
494   f->name = xstrdup(file);
495
496   hash_create(&f->byid, KEY_INITSZ);
497   f->idload = SYM_LIMIT(KEY_INITSZ);
498   sym_create(&f->bytype);
499   sym_create(&f->bytag);
500   f->f |= KF_WRITE;
501   if (f->fp)
502     key_merge(f, file, f->fp, rep, arg);
503   f->f &= ~KF_MODIFIED;
504
505   if ((how & KOPEN_MASK) == KOPEN_READ) {
506     f->f &= ~KF_WRITE;
507     if (f->fp) fclose(f->fp);
508     f->fp = 0;
509   }
510
511   return (0);
512 }
513
514 /* --- @key_discard@ --- *
515  *
516  * Arguments:   @key_file *f@ = pointer to key file block
517  *
518  * Returns:     ---
519  *
520  * Use:         Frees all the key data, without writing changes.
521  */
522
523 void key_discard(key_file *f)
524 {
525   hash_base *b;
526   hash_iter i;
527
528   /* --- Free all the individual keys --- */
529
530   for (hash_mkiter(&i, &f->byid); (b = hash_next(&i)) != 0; ) {
531     sym_iter j;
532     key_attr *a;
533     key *k = (key *)b;
534
535     if (k->k) key_drop(k->k);
536     xfree(k->type);
537     xfree(k->tag);
538     if (k->c)
539       xfree(k->c);
540     for (sym_mkiter(&j, &k->a); (a = sym_next(&j)) != 0; )
541       xfree(a->p);
542     sym_destroy(&k->a);
543     DESTROY(k);
544   }
545   hash_destroy(&f->byid);
546   sym_destroy(&f->bytype);
547   sym_destroy(&f->bytag);
548
549   if (f->fp)
550     fclose(f->fp);
551   xfree(f->name);
552 }
553
554 /* --- @key_close@ --- *
555  *
556  * Arguments:   @key_file *f@ = pointer to key file block
557  *
558  * Returns:     A @KWRITE_@ code indicating how it went.
559  *
560  * Use:         Frees all the key data, writes any changes.  Make sure that
561  *              all hell breaks loose if this returns @KWRITE_BROKEN@.
562  */
563
564 int key_close(key_file *f)
565 {
566   int e;
567
568   if (f->fp && (e = key_save(f)) != KWRITE_OK)
569     return (e);
570   key_discard(f);
571   return (KWRITE_OK);
572 }
573
574 /* --- @key_new@ ---
575  *
576  * Arguments:   @key_file *f@ = pointer to key file
577  *              @uint32 id@ = keyid to set
578  *              @const char *type@ = the type of this key
579  *              @time_t exp@ = when the key expires
580  *              @key *kk@ = where to put the key pointer
581  *
582  * Returns:     Error code (one of the @KERR@ constants).
583  *
584  * Use:         Attaches a new key to a key file.  You must have a writable
585  *              key file for this to work.
586  *
587  *              The type is a key type string.  This interface doesn't care
588  *              about how type strings are formatted: it just treats them as
589  *              opaque gobs of text.  Clients are advised to choose some
590  *              standard for representing key types, though.
591  *
592  *              The expiry time should either be a time in the future, or the
593  *              magic value @KEXP_FOREVER@ which means `never expire this
594  *              key'.  Be careful with `forever' keys.  If I were you, I'd
595  *              use a more sophisticated key management system than this for
596  *              them.
597  */
598
599 int key_new(key_file *f, uint32 id, const char *type, time_t exp, key **kk)
600 {
601   key *k = 0;
602   time_t t = time(0);
603   int e = KERR_OK;
604
605   /* --- Make sure the file is writable --- */
606
607   if (!(f->f & KF_WRITE))
608     e = KERR_READONLY;
609   else if (KEY_EXPIRED(t, exp))
610     e = KERR_EXPIRED;
611   else if (key_chkident(type))
612     e = KERR_BADTYPE;
613   else {
614     k = CREATE(key);
615     k->id = id;
616     k->tag = 0;
617     k->exp = k->del = exp;
618     k->c = 0;
619     k->type = (char *)type; /* temporarily */
620     if ((e = insert(f, k)) != 0)
621       DESTROY(k);
622     else {
623       k->k = key_newstring(KCAT_SHARE, "<unset>");
624       k->type = xstrdup(type);
625       sym_create(&k->a);
626       *kk = k;
627       f->f |= KF_MODIFIED;
628     }
629   }
630
631   return (e);
632 }
633
634 /*----- That's all, folks -------------------------------------------------*/