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