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