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