wks: Move a few server functions to wks-util.
[gnupg.git] / tools / wks-util.c
1 /* wks-utils.c - Common helper functions for wks tools
2  * Copyright (C) 2016 g10 Code GmbH
3  * Copyright (C) 2016 Bundesamt für Sicherheit in der Informationstechnik
4  *
5  * This file is part of GnuPG.
6  *
7  * This file is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU Lesser General Public License as
9  * published by the Free Software Foundation; either version 2.1 of
10  * the License, or (at your option) any later version.
11  *
12  * This file is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU Lesser General Public License for more details.
16  */
17
18 #include <config.h>
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <string.h>
22
23 #include "../common/util.h"
24 #include "../common/status.h"
25 #include "../common/ccparray.h"
26 #include "../common/exectool.h"
27 #include "../common/zb32.h"
28 #include "../common/userids.h"
29 #include "../common/mbox-util.h"
30 #include "../common/sysutils.h"
31 #include "mime-maker.h"
32 #include "send-mail.h"
33 #include "gpg-wks.h"
34
35 /* The stream to output the status information.  Output is disabled if
36    this is NULL.  */
37 static estream_t statusfp;
38
39
40 \f
41 /* Set the status FD.  */
42 void
43 wks_set_status_fd (int fd)
44 {
45   static int last_fd = -1;
46
47   if (fd != -1 && last_fd == fd)
48     return;
49
50   if (statusfp && statusfp != es_stdout && statusfp != es_stderr)
51     es_fclose (statusfp);
52   statusfp = NULL;
53   if (fd == -1)
54     return;
55
56   if (fd == 1)
57     statusfp = es_stdout;
58   else if (fd == 2)
59     statusfp = es_stderr;
60   else
61     statusfp = es_fdopen (fd, "w");
62   if (!statusfp)
63     {
64       log_fatal ("can't open fd %d for status output: %s\n",
65                  fd, gpg_strerror (gpg_error_from_syserror ()));
66     }
67   last_fd = fd;
68 }
69
70
71 /* Write a status line with code NO followed by the output of the
72  * printf style FORMAT.  The caller needs to make sure that LFs and
73  * CRs are not printed.  */
74 void
75 wks_write_status (int no, const char *format, ...)
76 {
77   va_list arg_ptr;
78
79   if (!statusfp)
80     return;  /* Not enabled.  */
81
82   es_fputs ("[GNUPG:] ", statusfp);
83   es_fputs (get_status_string (no), statusfp);
84   if (format)
85     {
86       es_putc (' ', statusfp);
87       va_start (arg_ptr, format);
88       es_vfprintf (statusfp, format, arg_ptr);
89       va_end (arg_ptr);
90     }
91   es_putc ('\n', statusfp);
92 }
93
94
95 \f
96
97 /* Append UID to LIST and return the new item.  On success LIST is
98  * updated.  On error ERRNO is set and NULL returned. */
99 static uidinfo_list_t
100 append_to_uidinfo_list (uidinfo_list_t *list, const char *uid, time_t created)
101 {
102   uidinfo_list_t r, sl;
103
104   sl = xtrymalloc (sizeof *sl + strlen (uid));
105   if (!sl)
106     return NULL;
107
108   strcpy (sl->uid, uid);
109   sl->created = created;
110   sl->mbox = mailbox_from_userid (uid, 0);
111   sl->next = NULL;
112   if (!*list)
113     *list = sl;
114   else
115     {
116       for (r = *list; r->next; r = r->next )
117         ;
118       r->next = sl;
119     }
120   return sl;
121 }
122
123
124 /* Free the list of uid infos at LIST.  */
125 void
126 free_uidinfo_list (uidinfo_list_t list)
127 {
128   while (list)
129     {
130       uidinfo_list_t tmp = list->next;
131       xfree (list->mbox);
132       xfree (list);
133       list = tmp;
134     }
135 }
136
137
138 \f
139 struct get_key_status_parm_s
140 {
141   const char *fpr;
142   int found;
143   int count;
144 };
145
146
147 static void
148 get_key_status_cb (void *opaque, const char *keyword, char *args)
149 {
150   struct get_key_status_parm_s *parm = opaque;
151
152   /*log_debug ("%s: %s\n", keyword, args);*/
153   if (!strcmp (keyword, "EXPORTED"))
154     {
155       parm->count++;
156       if (!ascii_strcasecmp (args, parm->fpr))
157         parm->found = 1;
158     }
159 }
160
161 /* Get a key by fingerprint from gpg's keyring and make sure that the
162  * mail address ADDRSPEC is included in the key.  If EXACT is set the
163  * returned user id must match Addrspec exactly and not just in the
164  * addr-spec (mailbox) part.  The key is returned as a new memory
165  * stream at R_KEY.  */
166 gpg_error_t
167 wks_get_key (estream_t *r_key, const char *fingerprint, const char *addrspec,
168              int exact)
169 {
170   gpg_error_t err;
171   ccparray_t ccp;
172   const char **argv = NULL;
173   estream_t key = NULL;
174   struct get_key_status_parm_s parm;
175   char *filterexp = NULL;
176
177   memset (&parm, 0, sizeof parm);
178
179   *r_key = NULL;
180
181   key = es_fopenmem (0, "w+b");
182   if (!key)
183     {
184       err = gpg_error_from_syserror ();
185       log_error ("error allocating memory buffer: %s\n", gpg_strerror (err));
186       goto leave;
187     }
188
189   /* Prefix the key with the MIME content type.  */
190   es_fputs ("Content-Type: application/pgp-keys\n"
191             "\n", key);
192
193   filterexp = es_bsprintf ("keep-uid=%s=%s", exact? "uid":"mbox", addrspec);
194   if (!filterexp)
195     {
196       err = gpg_error_from_syserror ();
197       log_error ("error allocating memory buffer: %s\n", gpg_strerror (err));
198       goto leave;
199     }
200
201   ccparray_init (&ccp, 0);
202
203   ccparray_put (&ccp, "--no-options");
204   if (!opt.verbose)
205     ccparray_put (&ccp, "--quiet");
206   else if (opt.verbose > 1)
207     ccparray_put (&ccp, "--verbose");
208   ccparray_put (&ccp, "--batch");
209   ccparray_put (&ccp, "--status-fd=2");
210   ccparray_put (&ccp, "--always-trust");
211   ccparray_put (&ccp, "--armor");
212   ccparray_put (&ccp, "--export-options=export-minimal");
213   ccparray_put (&ccp, "--export-filter");
214   ccparray_put (&ccp, filterexp);
215   ccparray_put (&ccp, "--export");
216   ccparray_put (&ccp, "--");
217   ccparray_put (&ccp, fingerprint);
218
219   ccparray_put (&ccp, NULL);
220   argv = ccparray_get (&ccp, NULL);
221   if (!argv)
222     {
223       err = gpg_error_from_syserror ();
224       goto leave;
225     }
226   parm.fpr = fingerprint;
227   err = gnupg_exec_tool_stream (opt.gpg_program, argv, NULL,
228                                 NULL, key,
229                                 get_key_status_cb, &parm);
230   if (!err && parm.count > 1)
231     err = gpg_error (GPG_ERR_TOO_MANY);
232   else if (!err && !parm.found)
233     err = gpg_error (GPG_ERR_NOT_FOUND);
234   if (err)
235     {
236       log_error ("export failed: %s\n", gpg_strerror (err));
237       goto leave;
238     }
239
240   es_rewind (key);
241   *r_key = key;
242   key = NULL;
243
244  leave:
245   es_fclose (key);
246   xfree (argv);
247   xfree (filterexp);
248   return err;
249 }
250
251
252 \f
253 /* Helper for wks_list_key and wks_filter_uid.  */
254 static void
255 key_status_cb (void *opaque, const char *keyword, char *args)
256 {
257   (void)opaque;
258
259   if (DBG_CRYPTO)
260     log_debug ("gpg status: %s %s\n", keyword, args);
261 }
262
263
264 /* Run gpg on KEY and store the primary fingerprint at R_FPR and the
265  * list of mailboxes at R_MBOXES.  Returns 0 on success; on error NULL
266  * is stored at R_FPR and R_MBOXES and an error code is returned.
267  * R_FPR may be NULL if the fingerprint is not needed.  */
268 gpg_error_t
269 wks_list_key (estream_t key, char **r_fpr, uidinfo_list_t *r_mboxes)
270 {
271   gpg_error_t err;
272   ccparray_t ccp;
273   const char **argv;
274   estream_t listing;
275   char *line = NULL;
276   size_t length_of_line = 0;
277   size_t  maxlen;
278   ssize_t len;
279   char **fields = NULL;
280   int nfields;
281   int lnr;
282   char *fpr = NULL;
283   uidinfo_list_t mboxes = NULL;
284
285   if (r_fpr)
286     *r_fpr = NULL;
287   *r_mboxes = NULL;
288
289   /* Open a memory stream.  */
290   listing = es_fopenmem (0, "w+b");
291   if (!listing)
292     {
293       err = gpg_error_from_syserror ();
294       log_error ("error allocating memory buffer: %s\n", gpg_strerror (err));
295       return err;
296     }
297
298   ccparray_init (&ccp, 0);
299
300   ccparray_put (&ccp, "--no-options");
301   if (!opt.verbose)
302     ccparray_put (&ccp, "--quiet");
303   else if (opt.verbose > 1)
304     ccparray_put (&ccp, "--verbose");
305   ccparray_put (&ccp, "--batch");
306   ccparray_put (&ccp, "--status-fd=2");
307   ccparray_put (&ccp, "--always-trust");
308   ccparray_put (&ccp, "--with-colons");
309   ccparray_put (&ccp, "--dry-run");
310   ccparray_put (&ccp, "--import-options=import-minimal,import-show");
311   ccparray_put (&ccp, "--import");
312
313   ccparray_put (&ccp, NULL);
314   argv = ccparray_get (&ccp, NULL);
315   if (!argv)
316     {
317       err = gpg_error_from_syserror ();
318       goto leave;
319     }
320   err = gnupg_exec_tool_stream (opt.gpg_program, argv, key,
321                                 NULL, listing,
322                                 key_status_cb, NULL);
323   if (err)
324     {
325       log_error ("import failed: %s\n", gpg_strerror (err));
326       goto leave;
327     }
328
329   es_rewind (listing);
330   lnr = 0;
331   maxlen = 2048; /* Set limit.  */
332   while ((len = es_read_line (listing, &line, &length_of_line, &maxlen)) > 0)
333     {
334       lnr++;
335       if (!maxlen)
336         {
337           log_error ("received line too long\n");
338           err = gpg_error (GPG_ERR_LINE_TOO_LONG);
339           goto leave;
340         }
341       /* Strip newline and carriage return, if present.  */
342       while (len > 0
343              && (line[len - 1] == '\n' || line[len - 1] == '\r'))
344         line[--len] = '\0';
345       /* log_debug ("line '%s'\n", line); */
346
347       xfree (fields);
348       fields = strtokenize (line, ":");
349       if (!fields)
350         {
351           err = gpg_error_from_syserror ();
352           log_error ("strtokenize failed: %s\n", gpg_strerror (err));
353           goto leave;
354         }
355       for (nfields = 0; fields[nfields]; nfields++)
356         ;
357       if (!nfields)
358         {
359           err = gpg_error (GPG_ERR_INV_ENGINE);
360           goto leave;
361         }
362       if (!strcmp (fields[0], "sec"))
363         {
364           /* gpg may return "sec" as the first record - but we do not
365            * accept secret keys.  */
366           err = gpg_error (GPG_ERR_NO_PUBKEY);
367           goto leave;
368         }
369       if (lnr == 1 && strcmp (fields[0], "pub"))
370         {
371           /* First record is not a public key.  */
372           err = gpg_error (GPG_ERR_INV_ENGINE);
373           goto leave;
374         }
375       if (lnr > 1 && !strcmp (fields[0], "pub"))
376         {
377           /* More than one public key.  */
378           err = gpg_error (GPG_ERR_TOO_MANY);
379           goto leave;
380         }
381       if (!strcmp (fields[0], "sub") || !strcmp (fields[0], "ssb"))
382         break; /* We can stop parsing here.  */
383
384       if (!strcmp (fields[0], "fpr") && nfields > 9 && !fpr)
385         {
386           fpr = xtrystrdup (fields[9]);
387           if (!fpr)
388             {
389               err = gpg_error_from_syserror ();
390               goto leave;
391             }
392         }
393       else if (!strcmp (fields[0], "uid") && nfields > 9)
394         {
395           /* Fixme: Unescape fields[9] */
396           if (!append_to_uidinfo_list (&mboxes, fields[9],
397                                        parse_timestamp (fields[5], NULL)))
398             {
399               err = gpg_error_from_syserror ();
400               goto leave;
401             }
402         }
403     }
404   if (len < 0 || es_ferror (listing))
405     {
406       err = gpg_error_from_syserror ();
407       log_error ("error reading memory stream\n");
408       goto leave;
409     }
410
411   if (!fpr)
412     {
413       err = gpg_error (GPG_ERR_NO_PUBKEY);
414       goto leave;
415     }
416
417   if (r_fpr)
418     {
419       *r_fpr = fpr;
420       fpr = NULL;
421     }
422   *r_mboxes = mboxes;
423   mboxes = NULL;
424
425  leave:
426   xfree (fpr);
427   free_uidinfo_list (mboxes);
428   xfree (fields);
429   es_free (line);
430   xfree (argv);
431   es_fclose (listing);
432   return err;
433 }
434
435
436 /* Run gpg as a filter on KEY and write the output to a new stream
437  * stored at R_NEWKEY.  The new key will contain only the user id UID.
438  * Returns 0 on success.  Only one key is expected in KEY.  If BINARY
439  * is set the resulting key is returned as a binary (non-armored)
440  * keyblock.  */
441 gpg_error_t
442 wks_filter_uid (estream_t *r_newkey, estream_t key, const char *uid,
443                 int binary)
444 {
445   gpg_error_t err;
446   ccparray_t ccp;
447   const char **argv = NULL;
448   estream_t newkey;
449   char *filterexp = NULL;
450
451   *r_newkey = NULL;
452
453   /* Open a memory stream.  */
454   newkey = es_fopenmem (0, "w+b");
455   if (!newkey)
456     {
457       err = gpg_error_from_syserror ();
458       log_error ("error allocating memory buffer: %s\n", gpg_strerror (err));
459       return err;
460     }
461
462   /* Prefix the key with the MIME content type.  */
463   if (!binary)
464     es_fputs ("Content-Type: application/pgp-keys\n"
465               "\n", newkey);
466
467   filterexp = es_bsprintf ("keep-uid=uid=%s", uid);
468   if (!filterexp)
469     {
470       err = gpg_error_from_syserror ();
471       log_error ("error allocating memory buffer: %s\n", gpg_strerror (err));
472       goto leave;
473     }
474
475   ccparray_init (&ccp, 0);
476
477   ccparray_put (&ccp, "--no-options");
478   if (!opt.verbose)
479     ccparray_put (&ccp, "--quiet");
480   else if (opt.verbose > 1)
481     ccparray_put (&ccp, "--verbose");
482   ccparray_put (&ccp, "--batch");
483   ccparray_put (&ccp, "--status-fd=2");
484   ccparray_put (&ccp, "--always-trust");
485   if (!binary)
486     ccparray_put (&ccp, "--armor");
487   ccparray_put (&ccp, "--import-options=import-export");
488   ccparray_put (&ccp, "--import-filter");
489   ccparray_put (&ccp, filterexp);
490   ccparray_put (&ccp, "--import");
491
492   ccparray_put (&ccp, NULL);
493   argv = ccparray_get (&ccp, NULL);
494   if (!argv)
495     {
496       err = gpg_error_from_syserror ();
497       goto leave;
498     }
499   err = gnupg_exec_tool_stream (opt.gpg_program, argv, key,
500                                 NULL, newkey,
501                                 key_status_cb, NULL);
502   if (err)
503     {
504       log_error ("import/export failed: %s\n", gpg_strerror (err));
505       goto leave;
506     }
507
508   es_rewind (newkey);
509   *r_newkey = newkey;
510   newkey = NULL;
511
512  leave:
513   xfree (filterexp);
514   xfree (argv);
515   es_fclose (newkey);
516   return err;
517 }
518
519
520 /* Helper to write mail to the output(s).  */
521 gpg_error_t
522 wks_send_mime (mime_maker_t mime)
523 {
524   gpg_error_t err;
525   estream_t mail;
526
527   /* Without any option we take a short path.  */
528   if (!opt.use_sendmail && !opt.output)
529     {
530       es_set_binary (es_stdout);
531       return mime_maker_make (mime, es_stdout);
532     }
533
534
535   mail = es_fopenmem (0, "w+b");
536   if (!mail)
537     {
538       err = gpg_error_from_syserror ();
539       return err;
540     }
541
542   err = mime_maker_make (mime, mail);
543
544   if (!err && opt.output)
545     {
546       es_rewind (mail);
547       err = send_mail_to_file (mail, opt.output);
548     }
549
550   if (!err && opt.use_sendmail)
551     {
552       es_rewind (mail);
553       err = send_mail (mail);
554     }
555
556   es_fclose (mail);
557   return err;
558 }
559
560
561 /* Parse the policy flags by reading them from STREAM and storing them
562  * into FLAGS.  If IGNORE_UNKNOWN is set unknown keywords are
563  * ignored.  */
564 gpg_error_t
565 wks_parse_policy (policy_flags_t flags, estream_t stream, int ignore_unknown)
566 {
567   enum tokens {
568     TOK_SUBMISSION_ADDRESS,
569     TOK_MAILBOX_ONLY,
570     TOK_DANE_ONLY,
571     TOK_AUTH_SUBMIT,
572     TOK_MAX_PENDING,
573     TOK_PROTOCOL_VERSION
574   };
575   static struct {
576     const char *name;
577     enum tokens token;
578   } keywords[] = {
579     { "submission-address", TOK_SUBMISSION_ADDRESS },
580     { "mailbox-only", TOK_MAILBOX_ONLY },
581     { "dane-only",    TOK_DANE_ONLY    },
582     { "auth-submit",  TOK_AUTH_SUBMIT  },
583     { "max-pending",  TOK_MAX_PENDING  },
584     { "protocol-version", TOK_PROTOCOL_VERSION }
585   };
586   gpg_error_t err = 0;
587   int lnr = 0;
588   char line[1024];
589   char *p, *keyword, *value;
590   int i, n;
591
592   memset (flags, 0, sizeof *flags);
593
594   while (es_fgets (line, DIM(line)-1, stream) )
595     {
596       lnr++;
597       n = strlen (line);
598       if (!n || line[n-1] != '\n')
599         {
600           err = gpg_error (*line? GPG_ERR_LINE_TOO_LONG
601                            : GPG_ERR_INCOMPLETE_LINE);
602           break;
603         }
604       trim_trailing_spaces (line);
605       /* Skip empty and comment lines. */
606       for (p=line; spacep (p); p++)
607         ;
608       if (!*p || *p == '#')
609         continue;
610
611       if (*p == ':')
612         {
613           err = gpg_error (GPG_ERR_SYNTAX);
614           break;
615         }
616
617       keyword = p;
618       value = NULL;
619       if ((p = strchr (p, ':')))
620         {
621           /* Colon found: Keyword with value.  */
622           *p++ = 0;
623           for (; spacep (p); p++)
624             ;
625           if (!*p)
626             {
627               err = gpg_error (GPG_ERR_MISSING_VALUE);
628               break;
629             }
630           value = p;
631         }
632
633       for (i=0; i < DIM (keywords); i++)
634         if (!ascii_strcasecmp (keywords[i].name, keyword))
635           break;
636       if (!(i < DIM (keywords)))
637         {
638           if (ignore_unknown)
639             continue;
640           err = gpg_error (GPG_ERR_INV_NAME);
641           break;
642         }
643
644       switch (keywords[i].token)
645         {
646         case TOK_SUBMISSION_ADDRESS:
647           if (!value || !*value)
648             {
649               err = gpg_error (GPG_ERR_SYNTAX);
650               goto leave;
651             }
652           xfree (flags->submission_address);
653           flags->submission_address = xtrystrdup (value);
654           if (!flags->submission_address)
655             {
656               err = gpg_error_from_syserror ();
657               goto leave;
658             }
659           break;
660         case TOK_MAILBOX_ONLY: flags->mailbox_only = 1; break;
661         case TOK_DANE_ONLY:    flags->dane_only = 1;    break;
662         case TOK_AUTH_SUBMIT:  flags->auth_submit = 1;  break;
663         case TOK_MAX_PENDING:
664           if (!value)
665             {
666               err = gpg_error (GPG_ERR_SYNTAX);
667               goto leave;
668             }
669           /* FIXME: Define whether these are seconds, hours, or days
670            * and decide whether to allow other units.  */
671           flags->max_pending = atoi (value);
672           break;
673         case TOK_PROTOCOL_VERSION:
674           if (!value)
675             {
676               err = gpg_error (GPG_ERR_SYNTAX);
677               goto leave;
678             }
679           flags->protocol_version = atoi (value);
680           break;
681         }
682     }
683
684   if (!err && !es_feof (stream))
685     err = gpg_error_from_syserror ();
686
687  leave:
688   if (err)
689     log_error ("error reading '%s', line %d: %s\n",
690                es_fname_get (stream), lnr, gpg_strerror (err));
691
692   return err;
693 }
694
695
696 void
697 wks_free_policy (policy_flags_t policy)
698 {
699   if (policy)
700     {
701       xfree (policy->submission_address);
702       memset (policy, 0, sizeof *policy);
703     }
704 }
705
706
707 /* Write the content of SRC to the new file FNAME.  */
708 static gpg_error_t
709 write_to_file (estream_t src, const char *fname)
710 {
711   gpg_error_t err;
712   estream_t dst;
713   char buffer[4096];
714   size_t nread, written;
715
716   dst = es_fopen (fname, "wb");
717   if (!dst)
718     return gpg_error_from_syserror ();
719
720   do
721     {
722       nread = es_fread (buffer, 1, sizeof buffer, src);
723       if (!nread)
724         break;
725       written = es_fwrite (buffer, 1, nread, dst);
726       if (written != nread)
727         break;
728     }
729   while (!es_feof (src) && !es_ferror (src) && !es_ferror (dst));
730   if (!es_feof (src) || es_ferror (src) || es_ferror (dst))
731     {
732       err = gpg_error_from_syserror ();
733       es_fclose (dst);
734       gnupg_remove (fname);
735       return err;
736     }
737
738   if (es_fclose (dst))
739     {
740       err = gpg_error_from_syserror ();
741       log_error ("error closing '%s': %s\n", fname, gpg_strerror (err));
742       return err;
743     }
744
745   return 0;
746 }
747
748
749 /* Return the filename and optionally the addrspec for USERID at
750  * R_FNAME and R_ADDRSPEC.  R_ADDRSPEC might also be set on error.  */
751 gpg_error_t
752 wks_fname_from_userid (const char *userid, char **r_fname, char **r_addrspec)
753 {
754   gpg_error_t err;
755   char *addrspec = NULL;
756   const char *domain;
757   char *hash = NULL;
758   const char *s;
759   char shaxbuf[32]; /* Used for SHA-1 and SHA-256 */
760
761   *r_fname = NULL;
762   if (r_addrspec)
763     *r_addrspec = NULL;
764
765   addrspec = mailbox_from_userid (userid, 0);
766   if (!addrspec)
767     {
768       if (opt.verbose)
769         log_info ("\"%s\" is not a proper mail address\n", userid);
770       err = gpg_error (GPG_ERR_INV_USER_ID);
771       goto leave;
772     }
773
774   domain = strchr (addrspec, '@');
775   log_assert (domain);
776   domain++;
777
778   /* Hash user ID and create filename.  */
779   s = strchr (addrspec, '@');
780   log_assert (s);
781   gcry_md_hash_buffer (GCRY_MD_SHA1, shaxbuf, addrspec, s - addrspec);
782   hash = zb32_encode (shaxbuf, 8*20);
783   if (!hash)
784     {
785       err = gpg_error_from_syserror ();
786       goto leave;
787     }
788
789   *r_fname = make_filename_try (opt.directory, domain, "hu", hash, NULL);
790   if (!*r_fname)
791     err = gpg_error_from_syserror ();
792   else
793     err = 0;
794
795  leave:
796   if (r_addrspec && addrspec)
797     *r_addrspec = addrspec;
798   else
799     xfree (addrspec);
800   xfree (hash);
801   return err;
802 }
803
804
805 /* Compute the the full file name for the key with ADDRSPEC and return
806  * it at R_FNAME.  */
807 gpg_error_t
808 wks_compute_hu_fname (char **r_fname, const char *addrspec)
809 {
810   gpg_error_t err;
811   char *hash;
812   const char *domain;
813   char sha1buf[20];
814
815   *r_fname = NULL;
816
817   domain = strchr (addrspec, '@');
818   if (!domain || !domain[1] || domain == addrspec)
819     return gpg_error (GPG_ERR_INV_ARG);
820   domain++;
821
822   gcry_md_hash_buffer (GCRY_MD_SHA1, sha1buf, addrspec, domain - addrspec - 1);
823   hash = zb32_encode (sha1buf, 8*20);
824   if (!hash)
825     return gpg_error_from_syserror ();
826
827   *r_fname = make_filename_try (opt.directory, domain, "hu", hash, NULL);
828   if (!*r_fname)
829     err = gpg_error_from_syserror ();
830   else
831     err = 0;
832
833   xfree (hash);
834   return err;
835 }
836
837
838 /* Install a single key into the WKD by reading FNAME and extracting
839  * USERID.  */
840 gpg_error_t
841 wks_cmd_install_key (const char *fname, const char *userid)
842 {
843   gpg_error_t err;
844   KEYDB_SEARCH_DESC desc;
845   estream_t fp = NULL;
846   char *addrspec = NULL;
847   char *fpr = NULL;
848   uidinfo_list_t uidlist = NULL;
849   uidinfo_list_t uid, thisuid;
850   time_t thistime;
851   char *huname = NULL;
852   int any;
853
854   addrspec = mailbox_from_userid (userid, 0);
855   if (!addrspec)
856     {
857       log_error ("\"%s\" is not a proper mail address\n", userid);
858       err = gpg_error (GPG_ERR_INV_USER_ID);
859       goto leave;
860     }
861
862   if (!classify_user_id (fname, &desc, 1)
863       && (desc.mode == KEYDB_SEARCH_MODE_FPR
864           || desc.mode == KEYDB_SEARCH_MODE_FPR20))
865     {
866       /* FNAME looks like a fingerprint.  Get the key from the
867        * standard keyring.  */
868       err = wks_get_key (&fp, fname, addrspec, 0);
869       if (err)
870         {
871           log_error ("error getting key '%s' (uid='%s'): %s\n",
872                      fname, addrspec, gpg_strerror (err));
873           goto leave;
874         }
875     }
876   else /* Take it from the file */
877     {
878       fp = es_fopen (fname, "rb");
879       if (!fp)
880         {
881           err = gpg_error_from_syserror ();
882           log_error ("error reading '%s': %s\n", fname, gpg_strerror (err));
883           goto leave;
884         }
885     }
886
887   /* List the key so that we can figure out the newest UID with the
888    * requested addrspec.  */
889   err = wks_list_key (fp, &fpr, &uidlist);
890   if (err)
891     {
892       log_error ("error parsing key: %s\n", gpg_strerror (err));
893       err = gpg_error (GPG_ERR_NO_PUBKEY);
894       goto leave;
895     }
896   thistime = 0;
897   thisuid = NULL;
898   any = 0;
899   for (uid = uidlist; uid; uid = uid->next)
900     {
901       if (!uid->mbox)
902         continue; /* Should not happen anyway.  */
903       if (ascii_strcasecmp (uid->mbox, addrspec))
904         continue; /* Not the requested addrspec.  */
905       any = 1;
906       if (uid->created > thistime)
907         {
908           thistime = uid->created;
909           thisuid = uid;
910         }
911     }
912   if (!thisuid)
913     thisuid = uidlist;  /* This is the case for a missing timestamp.  */
914   if (!any)
915     {
916       log_error ("public key in '%s' has no mail address '%s'\n",
917                  fname, addrspec);
918       err = gpg_error (GPG_ERR_INV_USER_ID);
919       goto leave;
920     }
921
922   if (opt.verbose)
923     log_info ("using key with user id '%s'\n", thisuid->uid);
924
925   {
926     estream_t fp2;
927
928     es_rewind (fp);
929     err = wks_filter_uid (&fp2, fp, thisuid->uid, 1);
930     if (err)
931       {
932         log_error ("error filtering key: %s\n", gpg_strerror (err));
933         err = gpg_error (GPG_ERR_NO_PUBKEY);
934         goto leave;
935       }
936     es_fclose (fp);
937     fp = fp2;
938   }
939
940   /* Hash user ID and create filename.  */
941   err = wks_compute_hu_fname (&huname, addrspec);
942   if (err)
943     goto leave;
944
945   /* Publish.  */
946   err = write_to_file (fp, huname);
947   if (err)
948     {
949       log_error ("copying key to '%s' failed: %s\n", huname,gpg_strerror (err));
950       goto leave;
951     }
952
953   /* Make sure it is world readable.  */
954   if (gnupg_chmod (huname, "-rwxr--r--"))
955     log_error ("can't set permissions of '%s': %s\n",
956                huname, gpg_strerror (gpg_err_code_from_syserror()));
957
958   if (!opt.quiet)
959     log_info ("key %s published for '%s'\n", fpr, addrspec);
960
961  leave:
962   xfree (huname);
963   free_uidinfo_list (uidlist);
964   xfree (fpr);
965   xfree (addrspec);
966   es_fclose (fp);
967   return err;
968 }
969
970
971 /* Remove the key with mail address in USERID.  */
972 gpg_error_t
973 wks_cmd_remove_key (const char *userid)
974 {
975   gpg_error_t err;
976   char *addrspec = NULL;
977   char *fname = NULL;
978
979   err = wks_fname_from_userid (userid, &fname, &addrspec);
980   if (err)
981     goto leave;
982
983   if (gnupg_remove (fname))
984     {
985       err = gpg_error_from_syserror ();
986       if (gpg_err_code (err) == GPG_ERR_ENOENT)
987         {
988           if (!opt.quiet)
989             log_info ("key for '%s' is not installed\n", addrspec);
990           log_inc_errorcount ();
991           err = 0;
992         }
993       else
994         log_error ("error removing '%s': %s\n", fname, gpg_strerror (err));
995       goto leave;
996     }
997
998   if (opt.verbose)
999     log_info ("key for '%s' removed\n", addrspec);
1000   err = 0;
1001
1002  leave:
1003   xfree (fname);
1004   xfree (addrspec);
1005   return err;
1006 }