wks: Print the UID creation time with gpg-wks-client --check.
[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 outout 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);
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 /* Helper for wks_list_key.  */
137 static void
138 list_key_status_cb (void *opaque, const char *keyword, char *args)
139 {
140   (void)opaque;
141
142   if (DBG_CRYPTO)
143     log_debug ("gpg status: %s %s\n", keyword, args);
144 }
145
146
147 /* Run gpg on KEY and store the primary fingerprint at R_FPR and the
148  * list of mailboxes at R_MBOXES.  Returns 0 on success; on error NULL
149  * is stored at R_FPR and R_MBOXES and an error code is returned.  */
150 gpg_error_t
151 wks_list_key (estream_t key, char **r_fpr, uidinfo_list_t *r_mboxes)
152 {
153   gpg_error_t err;
154   ccparray_t ccp;
155   const char **argv;
156   estream_t listing;
157   char *line = NULL;
158   size_t length_of_line = 0;
159   size_t  maxlen;
160   ssize_t len;
161   char **fields = NULL;
162   int nfields;
163   int lnr;
164   char *fpr = NULL;
165   uidinfo_list_t mboxes = NULL;
166
167   *r_fpr = NULL;
168   *r_mboxes = NULL;
169
170   /* Open a memory stream.  */
171   listing = es_fopenmem (0, "w+b");
172   if (!listing)
173     {
174       err = gpg_error_from_syserror ();
175       log_error ("error allocating memory buffer: %s\n", gpg_strerror (err));
176       return err;
177     }
178
179   ccparray_init (&ccp, 0);
180
181   ccparray_put (&ccp, "--no-options");
182   if (!opt.verbose)
183     ccparray_put (&ccp, "--quiet");
184   else if (opt.verbose > 1)
185     ccparray_put (&ccp, "--verbose");
186   ccparray_put (&ccp, "--batch");
187   ccparray_put (&ccp, "--status-fd=2");
188   ccparray_put (&ccp, "--always-trust");
189   ccparray_put (&ccp, "--with-colons");
190   ccparray_put (&ccp, "--dry-run");
191   ccparray_put (&ccp, "--import-options=import-minimal,import-show");
192   ccparray_put (&ccp, "--import");
193
194   ccparray_put (&ccp, NULL);
195   argv = ccparray_get (&ccp, NULL);
196   if (!argv)
197     {
198       err = gpg_error_from_syserror ();
199       goto leave;
200     }
201   err = gnupg_exec_tool_stream (opt.gpg_program, argv, key,
202                                 NULL, listing,
203                                 list_key_status_cb, NULL);
204   if (err)
205     {
206       log_error ("import failed: %s\n", gpg_strerror (err));
207       goto leave;
208     }
209
210   es_rewind (listing);
211   lnr = 0;
212   maxlen = 2048; /* Set limit.  */
213   while ((len = es_read_line (listing, &line, &length_of_line, &maxlen)) > 0)
214     {
215       lnr++;
216       if (!maxlen)
217         {
218           log_error ("received line too long\n");
219           err = gpg_error (GPG_ERR_LINE_TOO_LONG);
220           goto leave;
221         }
222       /* Strip newline and carriage return, if present.  */
223       while (len > 0
224              && (line[len - 1] == '\n' || line[len - 1] == '\r'))
225         line[--len] = '\0';
226       /* log_debug ("line '%s'\n", line); */
227
228       xfree (fields);
229       fields = strtokenize (line, ":");
230       if (!fields)
231         {
232           err = gpg_error_from_syserror ();
233           log_error ("strtokenize failed: %s\n", gpg_strerror (err));
234           goto leave;
235         }
236       for (nfields = 0; fields[nfields]; nfields++)
237         ;
238       if (!nfields)
239         {
240           err = gpg_error (GPG_ERR_INV_ENGINE);
241           goto leave;
242         }
243       if (!strcmp (fields[0], "sec"))
244         {
245           /* gpg may return "sec" as the first record - but we do not
246            * accept secret keys.  */
247           err = gpg_error (GPG_ERR_NO_PUBKEY);
248           goto leave;
249         }
250       if (lnr == 1 && strcmp (fields[0], "pub"))
251         {
252           /* First record is not a public key.  */
253           err = gpg_error (GPG_ERR_INV_ENGINE);
254           goto leave;
255         }
256       if (lnr > 1 && !strcmp (fields[0], "pub"))
257         {
258           /* More than one public key.  */
259           err = gpg_error (GPG_ERR_TOO_MANY);
260           goto leave;
261         }
262       if (!strcmp (fields[0], "sub") || !strcmp (fields[0], "ssb"))
263         break; /* We can stop parsing here.  */
264
265       if (!strcmp (fields[0], "fpr") && nfields > 9 && !fpr)
266         {
267           fpr = xtrystrdup (fields[9]);
268           if (!fpr)
269             {
270               err = gpg_error_from_syserror ();
271               goto leave;
272             }
273         }
274       else if (!strcmp (fields[0], "uid") && nfields > 9)
275         {
276           /* Fixme: Unescape fields[9] */
277           if (!append_to_uidinfo_list (&mboxes, fields[9],
278                                        parse_timestamp (fields[5], NULL)))
279             {
280               err = gpg_error_from_syserror ();
281               goto leave;
282             }
283         }
284     }
285   if (len < 0 || es_ferror (listing))
286     {
287       err = gpg_error_from_syserror ();
288       log_error ("error reading memory stream\n");
289       goto leave;
290     }
291
292   *r_fpr = fpr;
293   fpr = NULL;
294   *r_mboxes = mboxes;
295   mboxes = NULL;
296
297  leave:
298   xfree (fpr);
299   free_uidinfo_list (mboxes);
300   xfree (fields);
301   es_free (line);
302   xfree (argv);
303   es_fclose (listing);
304   return err;
305 }
306
307
308 /* Helper to write mail to the output(s).  */
309 gpg_error_t
310 wks_send_mime (mime_maker_t mime)
311 {
312   gpg_error_t err;
313   estream_t mail;
314
315   /* Without any option we take a short path.  */
316   if (!opt.use_sendmail && !opt.output)
317     {
318       es_set_binary (es_stdout);
319       return mime_maker_make (mime, es_stdout);
320     }
321
322
323   mail = es_fopenmem (0, "w+b");
324   if (!mail)
325     {
326       err = gpg_error_from_syserror ();
327       return err;
328     }
329
330   err = mime_maker_make (mime, mail);
331
332   if (!err && opt.output)
333     {
334       es_rewind (mail);
335       err = send_mail_to_file (mail, opt.output);
336     }
337
338   if (!err && opt.use_sendmail)
339     {
340       es_rewind (mail);
341       err = send_mail (mail);
342     }
343
344   es_fclose (mail);
345   return err;
346 }
347
348
349 /* Parse the policy flags by reading them from STREAM and storing them
350  * into FLAGS.  If IGNORE_UNKNOWN is iset unknown keywords are
351  * ignored.  */
352 gpg_error_t
353 wks_parse_policy (policy_flags_t flags, estream_t stream, int ignore_unknown)
354 {
355   enum tokens {
356     TOK_MAILBOX_ONLY,
357     TOK_DANE_ONLY,
358     TOK_AUTH_SUBMIT,
359     TOK_MAX_PENDING,
360     TOK_PROTOCOL_VERSION
361   };
362   static struct {
363     const char *name;
364     enum tokens token;
365   } keywords[] = {
366     { "mailbox-only", TOK_MAILBOX_ONLY },
367     { "dane-only",    TOK_DANE_ONLY    },
368     { "auth-submit",  TOK_AUTH_SUBMIT  },
369     { "max-pending",  TOK_MAX_PENDING  },
370     { "protocol-version", TOK_PROTOCOL_VERSION }
371   };
372   gpg_error_t err = 0;
373   int lnr = 0;
374   char line[1024];
375   char *p, *keyword, *value;
376   int i, n;
377
378   memset (flags, 0, sizeof *flags);
379
380   while (es_fgets (line, DIM(line)-1, stream) )
381     {
382       lnr++;
383       n = strlen (line);
384       if (!n || line[n-1] != '\n')
385         {
386           err = gpg_error (*line? GPG_ERR_LINE_TOO_LONG
387                            : GPG_ERR_INCOMPLETE_LINE);
388           break;
389         }
390       trim_trailing_spaces (line);
391       /* Skip empty and comment lines. */
392       for (p=line; spacep (p); p++)
393         ;
394       if (!*p || *p == '#')
395         continue;
396
397       if (*p == ':')
398         {
399           err = gpg_error (GPG_ERR_SYNTAX);
400           break;
401         }
402
403       keyword = p;
404       value = NULL;
405       if ((p = strchr (p, ':')))
406         {
407           /* Colon found: Keyword with value.  */
408           *p++ = 0;
409           for (; spacep (p); p++)
410             ;
411           if (!*p)
412             {
413               err = gpg_error (GPG_ERR_MISSING_VALUE);
414               break;
415             }
416           value = p;
417         }
418
419       for (i=0; i < DIM (keywords); i++)
420         if (!ascii_strcasecmp (keywords[i].name, keyword))
421           break;
422       if (!(i < DIM (keywords)))
423         {
424           if (ignore_unknown)
425             continue;
426           err = gpg_error (GPG_ERR_INV_NAME);
427           break;
428         }
429
430       switch (keywords[i].token)
431         {
432         case TOK_MAILBOX_ONLY: flags->mailbox_only = 1; break;
433         case TOK_DANE_ONLY:    flags->dane_only = 1;    break;
434         case TOK_AUTH_SUBMIT:  flags->auth_submit = 1;  break;
435         case TOK_MAX_PENDING:
436           if (!value)
437             {
438               err = gpg_error (GPG_ERR_SYNTAX);
439               goto leave;
440             }
441           /* FIXME: Define whether these are seconds, hours, or days
442            * and decide whether to allow other units.  */
443           flags->max_pending = atoi (value);
444           break;
445         case TOK_PROTOCOL_VERSION:
446           if (!value)
447             {
448               err = gpg_error (GPG_ERR_SYNTAX);
449               goto leave;
450             }
451           flags->protocol_version = atoi (value);
452           break;
453         }
454     }
455
456   if (!err && !es_feof (stream))
457     err = gpg_error_from_syserror ();
458
459  leave:
460   if (err)
461     log_error ("error reading '%s', line %d: %s\n",
462                es_fname_get (stream), lnr, gpg_strerror (err));
463
464   return err;
465 }