cf80a25bc5fa9e92faa00c9b408c5c289a7247e0
[gnupg.git] / tools / wks-util.c
1 /* wks-utils.c - Common helper functions for wks tools
2  * Copyright (C) 2016 g10 Code GmbH
3  * Copyright (C) 2016 Bundesamt für Sicherheit in der Informationstechnik
4  *
5  * This file is part of GnuPG.
6  *
7  * This file is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU Lesser General Public License as
9  * published by the Free Software Foundation; either version 2.1 of
10  * the License, or (at your option) any later version.
11  *
12  * This file is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU Lesser General Public License for more details.
16  */
17
18 #include <config.h>
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <string.h>
22
23 #include "../common/util.h"
24 #include "../common/status.h"
25 #include "../common/ccparray.h"
26 #include "../common/exectool.h"
27 #include "../common/mbox-util.h"
28 #include "mime-maker.h"
29 #include "send-mail.h"
30 #include "gpg-wks.h"
31
32 /* The stream to output the status information.  Output is disabled if
33    this is NULL.  */
34 static estream_t statusfp;
35
36
37 \f
38 /* Set the status FD.  */
39 void
40 wks_set_status_fd (int fd)
41 {
42   static int last_fd = -1;
43
44   if (fd != -1 && last_fd == fd)
45     return;
46
47   if (statusfp && statusfp != es_stdout && statusfp != es_stderr)
48     es_fclose (statusfp);
49   statusfp = NULL;
50   if (fd == -1)
51     return;
52
53   if (fd == 1)
54     statusfp = es_stdout;
55   else if (fd == 2)
56     statusfp = es_stderr;
57   else
58     statusfp = es_fdopen (fd, "w");
59   if (!statusfp)
60     {
61       log_fatal ("can't open fd %d for status output: %s\n",
62                  fd, gpg_strerror (gpg_error_from_syserror ()));
63     }
64   last_fd = fd;
65 }
66
67
68 /* Write a status line with code NO followed by the output of the
69  * printf style FORMAT.  The caller needs to make sure that LFs and
70  * CRs are not printed.  */
71 void
72 wks_write_status (int no, const char *format, ...)
73 {
74   va_list arg_ptr;
75
76   if (!statusfp)
77     return;  /* Not enabled.  */
78
79   es_fputs ("[GNUPG:] ", statusfp);
80   es_fputs (get_status_string (no), statusfp);
81   if (format)
82     {
83       es_putc (' ', statusfp);
84       va_start (arg_ptr, format);
85       es_vfprintf (statusfp, format, arg_ptr);
86       va_end (arg_ptr);
87     }
88   es_putc ('\n', statusfp);
89 }
90
91
92 \f
93
94 /* Append UID to LIST and return the new item.  On success LIST is
95  * updated.  On error ERRNO is set and NULL returned. */
96 static uidinfo_list_t
97 append_to_uidinfo_list (uidinfo_list_t *list, const char *uid, time_t created)
98 {
99   uidinfo_list_t r, sl;
100
101   sl = xtrymalloc (sizeof *sl + strlen (uid));
102   if (!sl)
103     return NULL;
104
105   strcpy (sl->uid, uid);
106   sl->created = created;
107   sl->mbox = mailbox_from_userid (uid, 0);
108   sl->next = NULL;
109   if (!*list)
110     *list = sl;
111   else
112     {
113       for (r = *list; r->next; r = r->next )
114         ;
115       r->next = sl;
116     }
117   return sl;
118 }
119
120
121 /* Free the list of uid infos at LIST.  */
122 void
123 free_uidinfo_list (uidinfo_list_t list)
124 {
125   while (list)
126     {
127       uidinfo_list_t tmp = list->next;
128       xfree (list->mbox);
129       xfree (list);
130       list = tmp;
131     }
132 }
133
134
135 \f
136 struct get_key_status_parm_s
137 {
138   const char *fpr;
139   int found;
140   int count;
141 };
142
143
144 static void
145 get_key_status_cb (void *opaque, const char *keyword, char *args)
146 {
147   struct get_key_status_parm_s *parm = opaque;
148
149   /*log_debug ("%s: %s\n", keyword, args);*/
150   if (!strcmp (keyword, "EXPORTED"))
151     {
152       parm->count++;
153       if (!ascii_strcasecmp (args, parm->fpr))
154         parm->found = 1;
155     }
156 }
157
158 /* Get a key by fingerprint from gpg's keyring and make sure that the
159  * mail address ADDRSPEC is included in the key.  If EXACT is set the
160  * returned user id must match Addrspec exactly and not just in the
161  * addr-spec (mailbox) part.  The key is returned as a new memory
162  * stream at R_KEY.  */
163 gpg_error_t
164 wks_get_key (estream_t *r_key, const char *fingerprint, const char *addrspec,
165              int exact)
166 {
167   gpg_error_t err;
168   ccparray_t ccp;
169   const char **argv = NULL;
170   estream_t key = NULL;
171   struct get_key_status_parm_s parm;
172   char *filterexp = NULL;
173
174   memset (&parm, 0, sizeof parm);
175
176   *r_key = NULL;
177
178   key = es_fopenmem (0, "w+b");
179   if (!key)
180     {
181       err = gpg_error_from_syserror ();
182       log_error ("error allocating memory buffer: %s\n", gpg_strerror (err));
183       goto leave;
184     }
185
186   /* Prefix the key with the MIME content type.  */
187   es_fputs ("Content-Type: application/pgp-keys\n"
188             "\n", key);
189
190   filterexp = es_bsprintf ("keep-uid=%s=%s", exact? "uid":"mbox", addrspec);
191   if (!filterexp)
192     {
193       err = gpg_error_from_syserror ();
194       log_error ("error allocating memory buffer: %s\n", gpg_strerror (err));
195       goto leave;
196     }
197
198   ccparray_init (&ccp, 0);
199
200   ccparray_put (&ccp, "--no-options");
201   if (!opt.verbose)
202     ccparray_put (&ccp, "--quiet");
203   else if (opt.verbose > 1)
204     ccparray_put (&ccp, "--verbose");
205   ccparray_put (&ccp, "--batch");
206   ccparray_put (&ccp, "--status-fd=2");
207   ccparray_put (&ccp, "--always-trust");
208   ccparray_put (&ccp, "--armor");
209   ccparray_put (&ccp, "--export-options=export-minimal");
210   ccparray_put (&ccp, "--export-filter");
211   ccparray_put (&ccp, filterexp);
212   ccparray_put (&ccp, "--export");
213   ccparray_put (&ccp, "--");
214   ccparray_put (&ccp, fingerprint);
215
216   ccparray_put (&ccp, NULL);
217   argv = ccparray_get (&ccp, NULL);
218   if (!argv)
219     {
220       err = gpg_error_from_syserror ();
221       goto leave;
222     }
223   parm.fpr = fingerprint;
224   err = gnupg_exec_tool_stream (opt.gpg_program, argv, NULL,
225                                 NULL, key,
226                                 get_key_status_cb, &parm);
227   if (!err && parm.count > 1)
228     err = gpg_error (GPG_ERR_TOO_MANY);
229   else if (!err && !parm.found)
230     err = gpg_error (GPG_ERR_NOT_FOUND);
231   if (err)
232     {
233       log_error ("export failed: %s\n", gpg_strerror (err));
234       goto leave;
235     }
236
237   es_rewind (key);
238   *r_key = key;
239   key = NULL;
240
241  leave:
242   es_fclose (key);
243   xfree (argv);
244   xfree (filterexp);
245   return err;
246 }
247
248
249 \f
250 /* Helper for wks_list_key and wks_filter_uid.  */
251 static void
252 key_status_cb (void *opaque, const char *keyword, char *args)
253 {
254   (void)opaque;
255
256   if (DBG_CRYPTO)
257     log_debug ("gpg status: %s %s\n", keyword, args);
258 }
259
260
261 /* Run gpg on KEY and store the primary fingerprint at R_FPR and the
262  * list of mailboxes at R_MBOXES.  Returns 0 on success; on error NULL
263  * is stored at R_FPR and R_MBOXES and an error code is returned.
264  * R_FPR may be NULL if the fingerprint is not needed.  */
265 gpg_error_t
266 wks_list_key (estream_t key, char **r_fpr, uidinfo_list_t *r_mboxes)
267 {
268   gpg_error_t err;
269   ccparray_t ccp;
270   const char **argv;
271   estream_t listing;
272   char *line = NULL;
273   size_t length_of_line = 0;
274   size_t  maxlen;
275   ssize_t len;
276   char **fields = NULL;
277   int nfields;
278   int lnr;
279   char *fpr = NULL;
280   uidinfo_list_t mboxes = NULL;
281
282   if (r_fpr)
283     *r_fpr = NULL;
284   *r_mboxes = NULL;
285
286   /* Open a memory stream.  */
287   listing = es_fopenmem (0, "w+b");
288   if (!listing)
289     {
290       err = gpg_error_from_syserror ();
291       log_error ("error allocating memory buffer: %s\n", gpg_strerror (err));
292       return err;
293     }
294
295   ccparray_init (&ccp, 0);
296
297   ccparray_put (&ccp, "--no-options");
298   if (!opt.verbose)
299     ccparray_put (&ccp, "--quiet");
300   else if (opt.verbose > 1)
301     ccparray_put (&ccp, "--verbose");
302   ccparray_put (&ccp, "--batch");
303   ccparray_put (&ccp, "--status-fd=2");
304   ccparray_put (&ccp, "--always-trust");
305   ccparray_put (&ccp, "--with-colons");
306   ccparray_put (&ccp, "--dry-run");
307   ccparray_put (&ccp, "--import-options=import-minimal,import-show");
308   ccparray_put (&ccp, "--import");
309
310   ccparray_put (&ccp, NULL);
311   argv = ccparray_get (&ccp, NULL);
312   if (!argv)
313     {
314       err = gpg_error_from_syserror ();
315       goto leave;
316     }
317   err = gnupg_exec_tool_stream (opt.gpg_program, argv, key,
318                                 NULL, listing,
319                                 key_status_cb, NULL);
320   if (err)
321     {
322       log_error ("import failed: %s\n", gpg_strerror (err));
323       goto leave;
324     }
325
326   es_rewind (listing);
327   lnr = 0;
328   maxlen = 2048; /* Set limit.  */
329   while ((len = es_read_line (listing, &line, &length_of_line, &maxlen)) > 0)
330     {
331       lnr++;
332       if (!maxlen)
333         {
334           log_error ("received line too long\n");
335           err = gpg_error (GPG_ERR_LINE_TOO_LONG);
336           goto leave;
337         }
338       /* Strip newline and carriage return, if present.  */
339       while (len > 0
340              && (line[len - 1] == '\n' || line[len - 1] == '\r'))
341         line[--len] = '\0';
342       /* log_debug ("line '%s'\n", line); */
343
344       xfree (fields);
345       fields = strtokenize (line, ":");
346       if (!fields)
347         {
348           err = gpg_error_from_syserror ();
349           log_error ("strtokenize failed: %s\n", gpg_strerror (err));
350           goto leave;
351         }
352       for (nfields = 0; fields[nfields]; nfields++)
353         ;
354       if (!nfields)
355         {
356           err = gpg_error (GPG_ERR_INV_ENGINE);
357           goto leave;
358         }
359       if (!strcmp (fields[0], "sec"))
360         {
361           /* gpg may return "sec" as the first record - but we do not
362            * accept secret keys.  */
363           err = gpg_error (GPG_ERR_NO_PUBKEY);
364           goto leave;
365         }
366       if (lnr == 1 && strcmp (fields[0], "pub"))
367         {
368           /* First record is not a public key.  */
369           err = gpg_error (GPG_ERR_INV_ENGINE);
370           goto leave;
371         }
372       if (lnr > 1 && !strcmp (fields[0], "pub"))
373         {
374           /* More than one public key.  */
375           err = gpg_error (GPG_ERR_TOO_MANY);
376           goto leave;
377         }
378       if (!strcmp (fields[0], "sub") || !strcmp (fields[0], "ssb"))
379         break; /* We can stop parsing here.  */
380
381       if (!strcmp (fields[0], "fpr") && nfields > 9 && !fpr)
382         {
383           fpr = xtrystrdup (fields[9]);
384           if (!fpr)
385             {
386               err = gpg_error_from_syserror ();
387               goto leave;
388             }
389         }
390       else if (!strcmp (fields[0], "uid") && nfields > 9)
391         {
392           /* Fixme: Unescape fields[9] */
393           if (!append_to_uidinfo_list (&mboxes, fields[9],
394                                        parse_timestamp (fields[5], NULL)))
395             {
396               err = gpg_error_from_syserror ();
397               goto leave;
398             }
399         }
400     }
401   if (len < 0 || es_ferror (listing))
402     {
403       err = gpg_error_from_syserror ();
404       log_error ("error reading memory stream\n");
405       goto leave;
406     }
407
408   if (!fpr)
409     {
410       err = gpg_error (GPG_ERR_NO_PUBKEY);
411       goto leave;
412     }
413
414   if (r_fpr)
415     {
416       *r_fpr = fpr;
417       fpr = NULL;
418     }
419   *r_mboxes = mboxes;
420   mboxes = NULL;
421
422  leave:
423   xfree (fpr);
424   free_uidinfo_list (mboxes);
425   xfree (fields);
426   es_free (line);
427   xfree (argv);
428   es_fclose (listing);
429   return err;
430 }
431
432
433 /* Run gpg as a filter on KEY and write the output to a new stream
434  * stored at R_NEWKEY.  The new key will contain only the user id UID.
435  * Returns 0 on success.  Only one key is expected in KEY.  If BINARY
436  * is set the resulting key is returned as a binary (non-armored)
437  * keyblock.  */
438 gpg_error_t
439 wks_filter_uid (estream_t *r_newkey, estream_t key, const char *uid,
440                 int binary)
441 {
442   gpg_error_t err;
443   ccparray_t ccp;
444   const char **argv = NULL;
445   estream_t newkey;
446   char *filterexp = NULL;
447
448   *r_newkey = NULL;
449
450   /* Open a memory stream.  */
451   newkey = es_fopenmem (0, "w+b");
452   if (!newkey)
453     {
454       err = gpg_error_from_syserror ();
455       log_error ("error allocating memory buffer: %s\n", gpg_strerror (err));
456       return err;
457     }
458
459   /* Prefix the key with the MIME content type.  */
460   if (!binary)
461     es_fputs ("Content-Type: application/pgp-keys\n"
462               "\n", newkey);
463
464   filterexp = es_bsprintf ("keep-uid=uid=%s", uid);
465   if (!filterexp)
466     {
467       err = gpg_error_from_syserror ();
468       log_error ("error allocating memory buffer: %s\n", gpg_strerror (err));
469       goto leave;
470     }
471
472   ccparray_init (&ccp, 0);
473
474   ccparray_put (&ccp, "--no-options");
475   if (!opt.verbose)
476     ccparray_put (&ccp, "--quiet");
477   else if (opt.verbose > 1)
478     ccparray_put (&ccp, "--verbose");
479   ccparray_put (&ccp, "--batch");
480   ccparray_put (&ccp, "--status-fd=2");
481   ccparray_put (&ccp, "--always-trust");
482   if (!binary)
483     ccparray_put (&ccp, "--armor");
484   ccparray_put (&ccp, "--import-options=import-export");
485   ccparray_put (&ccp, "--import-filter");
486   ccparray_put (&ccp, filterexp);
487   ccparray_put (&ccp, "--import");
488
489   ccparray_put (&ccp, NULL);
490   argv = ccparray_get (&ccp, NULL);
491   if (!argv)
492     {
493       err = gpg_error_from_syserror ();
494       goto leave;
495     }
496   err = gnupg_exec_tool_stream (opt.gpg_program, argv, key,
497                                 NULL, newkey,
498                                 key_status_cb, NULL);
499   if (err)
500     {
501       log_error ("import/export failed: %s\n", gpg_strerror (err));
502       goto leave;
503     }
504
505   es_rewind (newkey);
506   *r_newkey = newkey;
507   newkey = NULL;
508
509  leave:
510   xfree (filterexp);
511   xfree (argv);
512   es_fclose (newkey);
513   return err;
514 }
515
516
517 /* Helper to write mail to the output(s).  */
518 gpg_error_t
519 wks_send_mime (mime_maker_t mime)
520 {
521   gpg_error_t err;
522   estream_t mail;
523
524   /* Without any option we take a short path.  */
525   if (!opt.use_sendmail && !opt.output)
526     {
527       es_set_binary (es_stdout);
528       return mime_maker_make (mime, es_stdout);
529     }
530
531
532   mail = es_fopenmem (0, "w+b");
533   if (!mail)
534     {
535       err = gpg_error_from_syserror ();
536       return err;
537     }
538
539   err = mime_maker_make (mime, mail);
540
541   if (!err && opt.output)
542     {
543       es_rewind (mail);
544       err = send_mail_to_file (mail, opt.output);
545     }
546
547   if (!err && opt.use_sendmail)
548     {
549       es_rewind (mail);
550       err = send_mail (mail);
551     }
552
553   es_fclose (mail);
554   return err;
555 }
556
557
558 /* Parse the policy flags by reading them from STREAM and storing them
559  * into FLAGS.  If IGNORE_UNKNOWN is set unknown keywords are
560  * ignored.  */
561 gpg_error_t
562 wks_parse_policy (policy_flags_t flags, estream_t stream, int ignore_unknown)
563 {
564   enum tokens {
565     TOK_SUBMISSION_ADDRESS,
566     TOK_MAILBOX_ONLY,
567     TOK_DANE_ONLY,
568     TOK_AUTH_SUBMIT,
569     TOK_MAX_PENDING,
570     TOK_PROTOCOL_VERSION
571   };
572   static struct {
573     const char *name;
574     enum tokens token;
575   } keywords[] = {
576     { "submission-address", TOK_SUBMISSION_ADDRESS },
577     { "mailbox-only", TOK_MAILBOX_ONLY },
578     { "dane-only",    TOK_DANE_ONLY    },
579     { "auth-submit",  TOK_AUTH_SUBMIT  },
580     { "max-pending",  TOK_MAX_PENDING  },
581     { "protocol-version", TOK_PROTOCOL_VERSION }
582   };
583   gpg_error_t err = 0;
584   int lnr = 0;
585   char line[1024];
586   char *p, *keyword, *value;
587   int i, n;
588
589   memset (flags, 0, sizeof *flags);
590
591   while (es_fgets (line, DIM(line)-1, stream) )
592     {
593       lnr++;
594       n = strlen (line);
595       if (!n || line[n-1] != '\n')
596         {
597           err = gpg_error (*line? GPG_ERR_LINE_TOO_LONG
598                            : GPG_ERR_INCOMPLETE_LINE);
599           break;
600         }
601       trim_trailing_spaces (line);
602       /* Skip empty and comment lines. */
603       for (p=line; spacep (p); p++)
604         ;
605       if (!*p || *p == '#')
606         continue;
607
608       if (*p == ':')
609         {
610           err = gpg_error (GPG_ERR_SYNTAX);
611           break;
612         }
613
614       keyword = p;
615       value = NULL;
616       if ((p = strchr (p, ':')))
617         {
618           /* Colon found: Keyword with value.  */
619           *p++ = 0;
620           for (; spacep (p); p++)
621             ;
622           if (!*p)
623             {
624               err = gpg_error (GPG_ERR_MISSING_VALUE);
625               break;
626             }
627           value = p;
628         }
629
630       for (i=0; i < DIM (keywords); i++)
631         if (!ascii_strcasecmp (keywords[i].name, keyword))
632           break;
633       if (!(i < DIM (keywords)))
634         {
635           if (ignore_unknown)
636             continue;
637           err = gpg_error (GPG_ERR_INV_NAME);
638           break;
639         }
640
641       switch (keywords[i].token)
642         {
643         case TOK_SUBMISSION_ADDRESS:
644           if (!value || !*value)
645             {
646               err = gpg_error (GPG_ERR_SYNTAX);
647               goto leave;
648             }
649           xfree (flags->submission_address);
650           flags->submission_address = xtrystrdup (value);
651           if (!flags->submission_address)
652             {
653               err = gpg_error_from_syserror ();
654               goto leave;
655             }
656           break;
657         case TOK_MAILBOX_ONLY: flags->mailbox_only = 1; break;
658         case TOK_DANE_ONLY:    flags->dane_only = 1;    break;
659         case TOK_AUTH_SUBMIT:  flags->auth_submit = 1;  break;
660         case TOK_MAX_PENDING:
661           if (!value)
662             {
663               err = gpg_error (GPG_ERR_SYNTAX);
664               goto leave;
665             }
666           /* FIXME: Define whether these are seconds, hours, or days
667            * and decide whether to allow other units.  */
668           flags->max_pending = atoi (value);
669           break;
670         case TOK_PROTOCOL_VERSION:
671           if (!value)
672             {
673               err = gpg_error (GPG_ERR_SYNTAX);
674               goto leave;
675             }
676           flags->protocol_version = atoi (value);
677           break;
678         }
679     }
680
681   if (!err && !es_feof (stream))
682     err = gpg_error_from_syserror ();
683
684  leave:
685   if (err)
686     log_error ("error reading '%s', line %d: %s\n",
687                es_fname_get (stream), lnr, gpg_strerror (err));
688
689   return err;
690 }
691
692
693 void
694 wks_free_policy (policy_flags_t policy)
695 {
696   if (policy)
697     {
698       xfree (policy->submission_address);
699       memset (policy, 0, sizeof *policy);
700     }
701 }