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