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