tools: Move a function from gpg-wks-server to wks-util.c.
[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 "ccparray.h"
27 #include "exectool.h"
28 #include "mbox-util.h"
29 #include "mime-maker.h"
30 #include "send-mail.h"
31 #include "gpg-wks.h"
32
33
34 \f
35 /* Helper for wks_list_key.  */
36 static void
37 list_key_status_cb (void *opaque, const char *keyword, char *args)
38 {
39   (void)opaque;
40
41   if (DBG_CRYPTO)
42     log_debug ("gpg status: %s %s\n", keyword, args);
43 }
44
45
46 /* Run gpg on KEY and store the primary fingerprint at R_FPR and the
47  * list of mailboxes at R_MBOXES.  Returns 0 on success; on error NULL
48  * is stored at R_FPR and R_MBOXES and an error code is returned.  */
49 gpg_error_t
50 wks_list_key (estream_t key, char **r_fpr, strlist_t *r_mboxes)
51 {
52   gpg_error_t err;
53   ccparray_t ccp;
54   const char **argv;
55   estream_t listing;
56   char *line = NULL;
57   size_t length_of_line = 0;
58   size_t  maxlen;
59   ssize_t len;
60   char **fields = NULL;
61   int nfields;
62   int lnr;
63   char *mbox = NULL;
64   char *fpr = NULL;
65   strlist_t mboxes = NULL;
66
67   *r_fpr = NULL;
68   *r_mboxes = NULL;
69
70   /* Open a memory stream.  */
71   listing = es_fopenmem (0, "w+b");
72   if (!listing)
73     {
74       err = gpg_error_from_syserror ();
75       log_error ("error allocating memory buffer: %s\n", gpg_strerror (err));
76       return err;
77     }
78
79   ccparray_init (&ccp, 0);
80
81   ccparray_put (&ccp, "--no-options");
82   if (!opt.verbose)
83     ccparray_put (&ccp, "--quiet");
84   else if (opt.verbose > 1)
85     ccparray_put (&ccp, "--verbose");
86   ccparray_put (&ccp, "--batch");
87   ccparray_put (&ccp, "--status-fd=2");
88   ccparray_put (&ccp, "--always-trust");
89   ccparray_put (&ccp, "--with-colons");
90   ccparray_put (&ccp, "--dry-run");
91   ccparray_put (&ccp, "--import-options=import-minimal,import-show");
92   ccparray_put (&ccp, "--import");
93
94   ccparray_put (&ccp, NULL);
95   argv = ccparray_get (&ccp, NULL);
96   if (!argv)
97     {
98       err = gpg_error_from_syserror ();
99       goto leave;
100     }
101   err = gnupg_exec_tool_stream (opt.gpg_program, argv, key,
102                                 NULL, listing,
103                                 list_key_status_cb, NULL);
104   if (err)
105     {
106       log_error ("import failed: %s\n", gpg_strerror (err));
107       goto leave;
108     }
109
110   es_rewind (listing);
111   lnr = 0;
112   maxlen = 2048; /* Set limit.  */
113   while ((len = es_read_line (listing, &line, &length_of_line, &maxlen)) > 0)
114     {
115       lnr++;
116       if (!maxlen)
117         {
118           log_error ("received line too long\n");
119           err = gpg_error (GPG_ERR_LINE_TOO_LONG);
120           goto leave;
121         }
122       /* Strip newline and carriage return, if present.  */
123       while (len > 0
124              && (line[len - 1] == '\n' || line[len - 1] == '\r'))
125         line[--len] = '\0';
126       /* log_debug ("line '%s'\n", line); */
127
128       xfree (fields);
129       fields = strtokenize (line, ":");
130       if (!fields)
131         {
132           err = gpg_error_from_syserror ();
133           log_error ("strtokenize failed: %s\n", gpg_strerror (err));
134           goto leave;
135         }
136       for (nfields = 0; fields[nfields]; nfields++)
137         ;
138       if (!nfields)
139         {
140           err = gpg_error (GPG_ERR_INV_ENGINE);
141           goto leave;
142         }
143       if (!strcmp (fields[0], "sec"))
144         {
145           /* gpg may return "sec" as the first record - but we do not
146            * accept secret keys.  */
147           err = gpg_error (GPG_ERR_NO_PUBKEY);
148           goto leave;
149         }
150       if (lnr == 1 && strcmp (fields[0], "pub"))
151         {
152           /* First record is not a public key.  */
153           err = gpg_error (GPG_ERR_INV_ENGINE);
154           goto leave;
155         }
156       if (lnr > 1 && !strcmp (fields[0], "pub"))
157         {
158           /* More than one public key.  */
159           err = gpg_error (GPG_ERR_TOO_MANY);
160           goto leave;
161         }
162       if (!strcmp (fields[0], "sub") || !strcmp (fields[0], "ssb"))
163         break; /* We can stop parsing here.  */
164
165       if (!strcmp (fields[0], "fpr") && nfields > 9 && !fpr)
166         {
167           fpr = xtrystrdup (fields[9]);
168           if (!fpr)
169             {
170               err = gpg_error_from_syserror ();
171               goto leave;
172             }
173         }
174       else if (!strcmp (fields[0], "uid") && nfields > 9)
175         {
176           /* Fixme: Unescape fields[9] */
177           xfree (mbox);
178           mbox = mailbox_from_userid (fields[9]);
179           if (mbox && !append_to_strlist_try (&mboxes, mbox))
180             {
181               err = gpg_error_from_syserror ();
182               goto leave;
183             }
184         }
185     }
186   if (len < 0 || es_ferror (listing))
187     {
188       err = gpg_error_from_syserror ();
189       log_error ("error reading memory stream\n");
190       goto leave;
191     }
192
193   *r_fpr = fpr;
194   fpr = NULL;
195   *r_mboxes = mboxes;
196   mboxes = NULL;
197
198  leave:
199   xfree (fpr);
200   xfree (mboxes);
201   xfree (mbox);
202   xfree (fields);
203   es_free (line);
204   xfree (argv);
205   es_fclose (listing);
206   return err;
207 }
208
209
210 /* Helper to write mail to the output(s).  */
211 gpg_error_t
212 wks_send_mime (mime_maker_t mime)
213 {
214   gpg_error_t err;
215   estream_t mail;
216
217   /* Without any option we take a short path.  */
218   if (!opt.use_sendmail && !opt.output)
219     return mime_maker_make (mime, es_stdout);
220
221   mail = es_fopenmem (0, "w+b");
222   if (!mail)
223     {
224       err = gpg_error_from_syserror ();
225       return err;
226     }
227
228   err = mime_maker_make (mime, mail);
229
230   if (!err && opt.output)
231     {
232       es_rewind (mail);
233       err = send_mail_to_file (mail, opt.output);
234     }
235
236   if (!err && opt.use_sendmail)
237     {
238       es_rewind (mail);
239       err = send_mail (mail);
240     }
241
242   es_fclose (mail);
243   return err;
244 }
245
246
247 /* Parse the policy flags by reading them from STREAM and storing them
248  * into FLAGS.  If IGNORE_UNKNOWN is iset unknown keywords are
249  * ignored.  */
250 gpg_error_t
251 wks_parse_policy (policy_flags_t flags, estream_t stream, int ignore_unknown)
252 {
253   enum tokens {
254     TOK_MAILBOX_ONLY,
255     TOK_DANE_ONLY,
256     TOK_AUTH_SUBMIT,
257     TOK_MAX_PENDING
258   };
259   static struct {
260     const char *name;
261     enum tokens token;
262   } keywords[] = {
263     { "mailbox-only", TOK_MAILBOX_ONLY },
264     { "dane-only",    TOK_DANE_ONLY    },
265     { "auth-submit",  TOK_AUTH_SUBMIT  },
266     { "max-pending",  TOK_MAX_PENDING  }
267   };
268   gpg_error_t err = 0;
269   int lnr = 0;
270   char line[1024];
271   char *p, *keyword, *value;
272   int i, n;
273
274   memset (flags, 0, sizeof *flags);
275
276   while (es_fgets (line, DIM(line)-1, stream) )
277     {
278       lnr++;
279       n = strlen (line);
280       if (!n || line[n-1] != '\n')
281         {
282           err = gpg_error (*line? GPG_ERR_LINE_TOO_LONG
283                            : GPG_ERR_INCOMPLETE_LINE);
284           break;
285         }
286       trim_trailing_spaces (line);
287       /* Skip empty and comment lines. */
288       for (p=line; spacep (p); p++)
289         ;
290       if (!*p || *p == '#')
291         continue;
292
293       if (*p == ':')
294         {
295           err = gpg_error (GPG_ERR_SYNTAX);
296           break;
297         }
298
299       keyword = p;
300       value = NULL;
301       if ((p = strchr (p, ':')))
302         {
303           /* Colon found: Keyword with value.  */
304           *p++ = 0;
305           for (; spacep (p); p++)
306             ;
307           if (!*p)
308             {
309               err = gpg_error (GPG_ERR_MISSING_VALUE);
310               break;
311             }
312           value = p;
313         }
314
315       for (i=0; i < DIM (keywords); i++)
316         if (!ascii_strcasecmp (keywords[i].name, keyword))
317           break;
318       if (!(i < DIM (keywords)))
319         {
320           if (ignore_unknown)
321             continue;
322           err = gpg_error (GPG_ERR_INV_NAME);
323           break;
324         }
325
326       switch (keywords[i].token)
327         {
328         case TOK_MAILBOX_ONLY: flags->mailbox_only = 1; break;
329         case TOK_DANE_ONLY:    flags->dane_only = 1;    break;
330         case TOK_AUTH_SUBMIT:  flags->auth_submit = 1;  break;
331         case TOK_MAX_PENDING:
332           if (!value)
333             {
334               err = gpg_error (GPG_ERR_SYNTAX);
335               goto leave;
336             }
337           /* FIXME: Define whether these are seconds, hours, or days
338            * and decide whether to allow other units.  */
339           flags->max_pending = atoi (value);
340           break;
341         }
342     }
343
344   if (!err && !es_feof (stream))
345     err = gpg_error_from_syserror ();
346
347  leave:
348   if (err)
349     log_error ("error reading '%s', line %d: %s\n",
350                es_fname_get (stream), lnr, gpg_strerror (err));
351
352   return err;
353 }