scd: More cleanup of old code.
[gnupg.git] / tools / wks-util.c
1 /* wks-utils.c - Common helper fucntions for wks tools
2  * Copyright (C) 2016 g10 Code GmbH
3  *
4  * This file is part of GnuPG.
5  *
6  * GnuPG is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * GnuPG is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, see <https://www.gnu.org/licenses/>.
18  */
19
20 #include <config.h>
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24
25 #include "util.h"
26 #include "status.h"
27 #include "ccparray.h"
28 #include "exectool.h"
29 #include "mbox-util.h"
30 #include "mime-maker.h"
31 #include "send-mail.h"
32 #include "gpg-wks.h"
33
34 /* The stream to output the status information.  Output is disabled if
35    this is NULL.  */
36 static estream_t statusfp;
37
38
39 \f
40 /* Set the status FD.  */
41 void
42 wks_set_status_fd (int fd)
43 {
44   static int last_fd = -1;
45
46   if (fd != -1 && last_fd == fd)
47     return;
48
49   if (statusfp && statusfp != es_stdout && statusfp != es_stderr)
50     es_fclose (statusfp);
51   statusfp = NULL;
52   if (fd == -1)
53     return;
54
55   if (fd == 1)
56     statusfp = es_stdout;
57   else if (fd == 2)
58     statusfp = es_stderr;
59   else
60     statusfp = es_fdopen (fd, "w");
61   if (!statusfp)
62     {
63       log_fatal ("can't open fd %d for status output: %s\n",
64                  fd, gpg_strerror (gpg_error_from_syserror ()));
65     }
66   last_fd = fd;
67 }
68
69
70 /* Write a status line with code NO followed by the outout of the
71  * printf style FORMAT.  The caller needs to make sure that LFs and
72  * CRs are not printed.  */
73 void
74 wks_write_status (int no, const char *format, ...)
75 {
76   va_list arg_ptr;
77
78   if (!statusfp)
79     return;  /* Not enabled.  */
80
81   es_fputs ("[GNUPG:] ", statusfp);
82   es_fputs (get_status_string (no), statusfp);
83   if (format)
84     {
85       es_putc (' ', statusfp);
86       va_start (arg_ptr, format);
87       es_vfprintf (statusfp, format, arg_ptr);
88       va_end (arg_ptr);
89     }
90   es_putc ('\n', statusfp);
91 }
92
93
94 \f
95 /* Helper for wks_list_key.  */
96 static void
97 list_key_status_cb (void *opaque, const char *keyword, char *args)
98 {
99   (void)opaque;
100
101   if (DBG_CRYPTO)
102     log_debug ("gpg status: %s %s\n", keyword, args);
103 }
104
105
106 /* Run gpg on KEY and store the primary fingerprint at R_FPR and the
107  * list of mailboxes at R_MBOXES.  Returns 0 on success; on error NULL
108  * is stored at R_FPR and R_MBOXES and an error code is returned.  */
109 gpg_error_t
110 wks_list_key (estream_t key, char **r_fpr, strlist_t *r_mboxes)
111 {
112   gpg_error_t err;
113   ccparray_t ccp;
114   const char **argv;
115   estream_t listing;
116   char *line = NULL;
117   size_t length_of_line = 0;
118   size_t  maxlen;
119   ssize_t len;
120   char **fields = NULL;
121   int nfields;
122   int lnr;
123   char *mbox = NULL;
124   char *fpr = NULL;
125   strlist_t mboxes = NULL;
126
127   *r_fpr = NULL;
128   *r_mboxes = NULL;
129
130   /* Open a memory stream.  */
131   listing = es_fopenmem (0, "w+b");
132   if (!listing)
133     {
134       err = gpg_error_from_syserror ();
135       log_error ("error allocating memory buffer: %s\n", gpg_strerror (err));
136       return err;
137     }
138
139   ccparray_init (&ccp, 0);
140
141   ccparray_put (&ccp, "--no-options");
142   if (!opt.verbose)
143     ccparray_put (&ccp, "--quiet");
144   else if (opt.verbose > 1)
145     ccparray_put (&ccp, "--verbose");
146   ccparray_put (&ccp, "--batch");
147   ccparray_put (&ccp, "--status-fd=2");
148   ccparray_put (&ccp, "--always-trust");
149   ccparray_put (&ccp, "--with-colons");
150   ccparray_put (&ccp, "--dry-run");
151   ccparray_put (&ccp, "--import-options=import-minimal,import-show");
152   ccparray_put (&ccp, "--import");
153
154   ccparray_put (&ccp, NULL);
155   argv = ccparray_get (&ccp, NULL);
156   if (!argv)
157     {
158       err = gpg_error_from_syserror ();
159       goto leave;
160     }
161   err = gnupg_exec_tool_stream (opt.gpg_program, argv, key,
162                                 NULL, listing,
163                                 list_key_status_cb, NULL);
164   if (err)
165     {
166       log_error ("import failed: %s\n", gpg_strerror (err));
167       goto leave;
168     }
169
170   es_rewind (listing);
171   lnr = 0;
172   maxlen = 2048; /* Set limit.  */
173   while ((len = es_read_line (listing, &line, &length_of_line, &maxlen)) > 0)
174     {
175       lnr++;
176       if (!maxlen)
177         {
178           log_error ("received line too long\n");
179           err = gpg_error (GPG_ERR_LINE_TOO_LONG);
180           goto leave;
181         }
182       /* Strip newline and carriage return, if present.  */
183       while (len > 0
184              && (line[len - 1] == '\n' || line[len - 1] == '\r'))
185         line[--len] = '\0';
186       /* log_debug ("line '%s'\n", line); */
187
188       xfree (fields);
189       fields = strtokenize (line, ":");
190       if (!fields)
191         {
192           err = gpg_error_from_syserror ();
193           log_error ("strtokenize failed: %s\n", gpg_strerror (err));
194           goto leave;
195         }
196       for (nfields = 0; fields[nfields]; nfields++)
197         ;
198       if (!nfields)
199         {
200           err = gpg_error (GPG_ERR_INV_ENGINE);
201           goto leave;
202         }
203       if (!strcmp (fields[0], "sec"))
204         {
205           /* gpg may return "sec" as the first record - but we do not
206            * accept secret keys.  */
207           err = gpg_error (GPG_ERR_NO_PUBKEY);
208           goto leave;
209         }
210       if (lnr == 1 && strcmp (fields[0], "pub"))
211         {
212           /* First record is not a public key.  */
213           err = gpg_error (GPG_ERR_INV_ENGINE);
214           goto leave;
215         }
216       if (lnr > 1 && !strcmp (fields[0], "pub"))
217         {
218           /* More than one public key.  */
219           err = gpg_error (GPG_ERR_TOO_MANY);
220           goto leave;
221         }
222       if (!strcmp (fields[0], "sub") || !strcmp (fields[0], "ssb"))
223         break; /* We can stop parsing here.  */
224
225       if (!strcmp (fields[0], "fpr") && nfields > 9 && !fpr)
226         {
227           fpr = xtrystrdup (fields[9]);
228           if (!fpr)
229             {
230               err = gpg_error_from_syserror ();
231               goto leave;
232             }
233         }
234       else if (!strcmp (fields[0], "uid") && nfields > 9)
235         {
236           /* Fixme: Unescape fields[9] */
237           xfree (mbox);
238           mbox = mailbox_from_userid (fields[9]);
239           if (mbox && !append_to_strlist_try (&mboxes, mbox))
240             {
241               err = gpg_error_from_syserror ();
242               goto leave;
243             }
244         }
245     }
246   if (len < 0 || es_ferror (listing))
247     {
248       err = gpg_error_from_syserror ();
249       log_error ("error reading memory stream\n");
250       goto leave;
251     }
252
253   *r_fpr = fpr;
254   fpr = NULL;
255   *r_mboxes = mboxes;
256   mboxes = NULL;
257
258  leave:
259   xfree (fpr);
260   xfree (mboxes);
261   xfree (mbox);
262   xfree (fields);
263   es_free (line);
264   xfree (argv);
265   es_fclose (listing);
266   return err;
267 }
268
269
270 /* Helper to write mail to the output(s).  */
271 gpg_error_t
272 wks_send_mime (mime_maker_t mime)
273 {
274   gpg_error_t err;
275   estream_t mail;
276
277   /* Without any option we take a short path.  */
278   if (!opt.use_sendmail && !opt.output)
279     return mime_maker_make (mime, es_stdout);
280
281   mail = es_fopenmem (0, "w+b");
282   if (!mail)
283     {
284       err = gpg_error_from_syserror ();
285       return err;
286     }
287
288   err = mime_maker_make (mime, mail);
289
290   if (!err && opt.output)
291     {
292       es_rewind (mail);
293       err = send_mail_to_file (mail, opt.output);
294     }
295
296   if (!err && opt.use_sendmail)
297     {
298       es_rewind (mail);
299       err = send_mail (mail);
300     }
301
302   es_fclose (mail);
303   return err;
304 }
305
306
307 /* Parse the policy flags by reading them from STREAM and storing them
308  * into FLAGS.  If IGNORE_UNKNOWN is iset unknown keywords are
309  * ignored.  */
310 gpg_error_t
311 wks_parse_policy (policy_flags_t flags, estream_t stream, int ignore_unknown)
312 {
313   enum tokens {
314     TOK_MAILBOX_ONLY,
315     TOK_DANE_ONLY,
316     TOK_AUTH_SUBMIT,
317     TOK_MAX_PENDING
318   };
319   static struct {
320     const char *name;
321     enum tokens token;
322   } keywords[] = {
323     { "mailbox-only", TOK_MAILBOX_ONLY },
324     { "dane-only",    TOK_DANE_ONLY    },
325     { "auth-submit",  TOK_AUTH_SUBMIT  },
326     { "max-pending",  TOK_MAX_PENDING  }
327   };
328   gpg_error_t err = 0;
329   int lnr = 0;
330   char line[1024];
331   char *p, *keyword, *value;
332   int i, n;
333
334   memset (flags, 0, sizeof *flags);
335
336   while (es_fgets (line, DIM(line)-1, stream) )
337     {
338       lnr++;
339       n = strlen (line);
340       if (!n || line[n-1] != '\n')
341         {
342           err = gpg_error (*line? GPG_ERR_LINE_TOO_LONG
343                            : GPG_ERR_INCOMPLETE_LINE);
344           break;
345         }
346       trim_trailing_spaces (line);
347       /* Skip empty and comment lines. */
348       for (p=line; spacep (p); p++)
349         ;
350       if (!*p || *p == '#')
351         continue;
352
353       if (*p == ':')
354         {
355           err = gpg_error (GPG_ERR_SYNTAX);
356           break;
357         }
358
359       keyword = p;
360       value = NULL;
361       if ((p = strchr (p, ':')))
362         {
363           /* Colon found: Keyword with value.  */
364           *p++ = 0;
365           for (; spacep (p); p++)
366             ;
367           if (!*p)
368             {
369               err = gpg_error (GPG_ERR_MISSING_VALUE);
370               break;
371             }
372           value = p;
373         }
374
375       for (i=0; i < DIM (keywords); i++)
376         if (!ascii_strcasecmp (keywords[i].name, keyword))
377           break;
378       if (!(i < DIM (keywords)))
379         {
380           if (ignore_unknown)
381             continue;
382           err = gpg_error (GPG_ERR_INV_NAME);
383           break;
384         }
385
386       switch (keywords[i].token)
387         {
388         case TOK_MAILBOX_ONLY: flags->mailbox_only = 1; break;
389         case TOK_DANE_ONLY:    flags->dane_only = 1;    break;
390         case TOK_AUTH_SUBMIT:  flags->auth_submit = 1;  break;
391         case TOK_MAX_PENDING:
392           if (!value)
393             {
394               err = gpg_error (GPG_ERR_SYNTAX);
395               goto leave;
396             }
397           /* FIXME: Define whether these are seconds, hours, or days
398            * and decide whether to allow other units.  */
399           flags->max_pending = atoi (value);
400           break;
401         }
402     }
403
404   if (!err && !es_feof (stream))
405     err = gpg_error_from_syserror ();
406
407  leave:
408   if (err)
409     log_error ("error reading '%s', line %d: %s\n",
410                es_fname_get (stream), lnr, gpg_strerror (err));
411
412   return err;
413 }