312f58bc18af996aa0706c4b1326e6930cb5757c
[gnupg.git] / tools / gpg-wks-client.c
1 /* gpg-wks-client.c - A client for the Web Key Service protocols.
2  * Copyright (C) 2016 Werner Koch
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 <http://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 "i18n.h"
27 #include "sysutils.h"
28 #include "init.h"
29 #include "asshelp.h"
30 #include "userids.h"
31 #include "ccparray.h"
32 #include "exectool.h"
33 #include "mbox-util.h"
34 #include "name-value.h"
35 #include "call-dirmngr.h"
36 #include "mime-maker.h"
37 #include "send-mail.h"
38 #include "gpg-wks.h"
39
40
41 /* Constants to identify the commands and options. */
42 enum cmd_and_opt_values
43   {
44     aNull = 0,
45
46     oQuiet      = 'q',
47     oVerbose    = 'v',
48     oOutput     = 'o',
49
50     oDebug      = 500,
51
52     aCreate,
53     aReceive,
54
55     oGpgProgram,
56     oSend,
57
58     oDummy
59   };
60
61
62 /* The list of commands and options. */
63 static ARGPARSE_OPTS opts[] = {
64   ARGPARSE_group (300, ("@Commands:\n ")),
65
66   ARGPARSE_c (aCreate,   "create",
67               ("create a publication request")),
68   ARGPARSE_c (aReceive,   "receive",
69               ("receive a confirmation request")),
70
71   ARGPARSE_group (301, ("@\nOptions:\n ")),
72
73   ARGPARSE_s_n (oVerbose, "verbose", ("verbose")),
74   ARGPARSE_s_n (oQuiet, "quiet",  ("be somewhat more quiet")),
75   ARGPARSE_s_s (oDebug, "debug", "@"),
76   ARGPARSE_s_s (oGpgProgram, "gpg", "@"),
77   ARGPARSE_s_n (oSend, "send", "send the mail using sendmail"),
78   ARGPARSE_s_s (oOutput, "output", "|FILE|write the mail to FILE"),
79
80
81   ARGPARSE_end ()
82 };
83
84
85 /* The list of supported debug flags.  */
86 static struct debug_flags_s debug_flags [] =
87   {
88     { DBG_CRYPTO_VALUE , "crypto"  },
89     { DBG_MEMORY_VALUE , "memory"  },
90     { DBG_MEMSTAT_VALUE, "memstat" },
91     { DBG_IPC_VALUE    , "ipc"     },
92     { DBG_EXTPROG_VALUE, "extprog" },
93     { 0, NULL }
94   };
95
96
97 static void wrong_args (const char *text) GPGRT_ATTR_NORETURN;
98 static gpg_error_t command_send (const char *fingerprint, char *userid);
99 static gpg_error_t command_receive_cb (void *opaque,
100                                        const char *mediatype, estream_t fp);
101
102
103 \f
104 /* Print usage information and and provide strings for help. */
105 static const char *
106 my_strusage( int level )
107 {
108   const char *p;
109
110   switch (level)
111     {
112     case 11: p = "gpg-wks-client (@GNUPG@)";
113       break;
114     case 13: p = VERSION; break;
115     case 17: p = PRINTABLE_OS_NAME; break;
116     case 19: p = ("Please report bugs to <@EMAIL@>.\n"); break;
117
118     case 1:
119     case 40:
120       p = ("Usage: gpg-wks-client [command] [options] [args] (-h for help)");
121       break;
122     case 41:
123       p = ("Syntax: gpg-wks-client [command] [options] [args]\n"
124            "Client for the Web Key Service\n");
125       break;
126
127     default: p = NULL; break;
128     }
129   return p;
130 }
131
132
133 static void
134 wrong_args (const char *text)
135 {
136   es_fprintf (es_stderr, _("usage: %s [options] %s\n"), strusage (11), text);
137   exit (2);
138 }
139
140
141 \f
142 /* Command line parsing.  */
143 static enum cmd_and_opt_values
144 parse_arguments (ARGPARSE_ARGS *pargs, ARGPARSE_OPTS *popts)
145 {
146   enum cmd_and_opt_values cmd = 0;
147   int no_more_options = 0;
148
149   while (!no_more_options && optfile_parse (NULL, NULL, NULL, pargs, popts))
150     {
151       switch (pargs->r_opt)
152         {
153         case oQuiet:     opt.quiet = 1; break;
154         case oVerbose:   opt.verbose++; break;
155         case oDebug:
156           if (parse_debug_flag (pargs->r.ret_str, &opt.debug, debug_flags))
157             {
158               pargs->r_opt = ARGPARSE_INVALID_ARG;
159               pargs->err = ARGPARSE_PRINT_ERROR;
160             }
161           break;
162
163         case oGpgProgram:
164           opt.gpg_program = pargs->r.ret_str;
165           break;
166         case oSend:
167           opt.use_sendmail = 1;
168           break;
169         case oOutput:
170           opt.output = pargs->r.ret_str;
171           break;
172
173         case aCreate:
174         case aReceive:
175           cmd = pargs->r_opt;
176           break;
177
178         default: pargs->err = 2; break;
179         }
180     }
181
182   return cmd;
183 }
184
185
186 \f
187 /* gpg-wks-client main. */
188 int
189 main (int argc, char **argv)
190 {
191   gpg_error_t err;
192   ARGPARSE_ARGS pargs;
193   enum cmd_and_opt_values cmd;
194
195   gnupg_reopen_std ("gpg-wks-client");
196   set_strusage (my_strusage);
197   log_set_prefix ("gpg-wks-client", GPGRT_LOG_WITH_PREFIX);
198
199   /* Make sure that our subsystems are ready.  */
200   i18n_init();
201   init_common_subsystems (&argc, &argv);
202
203   assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT);
204   setup_libassuan_logging (&opt.debug);
205
206   /* Parse the command line. */
207   pargs.argc  = &argc;
208   pargs.argv  = &argv;
209   pargs.flags = ARGPARSE_FLAG_KEEP;
210   cmd = parse_arguments (&pargs, opts);
211
212   if (log_get_errorcount (0))
213     exit (2);
214
215   /* Print a warning if an argument looks like an option.  */
216   if (!opt.quiet && !(pargs.flags & ARGPARSE_FLAG_STOP_SEEN))
217     {
218       int i;
219
220       for (i=0; i < argc; i++)
221         if (argv[i][0] == '-' && argv[i][1] == '-')
222           log_info (("NOTE: '%s' is not considered an option\n"), argv[i]);
223     }
224
225   /* Set defaults for non given options.  */
226   if (!opt.gpg_program)
227     opt.gpg_program = gnupg_module_name (GNUPG_MODULE_NAME_GPG);
228
229   /* Tell call-dirmngr what options we want.  */
230   set_dirmngr_options (opt.verbose, (opt.debug & DBG_IPC_VALUE), 1);
231
232   /* Run the selected command.  */
233   switch (cmd)
234     {
235     case aCreate:
236       if (argc != 2)
237         wrong_args ("--create FINGERPRINT USER-ID");
238       err = command_send (argv[0], argv[1]);
239       if (err)
240         log_error ("creating request failed: %s\n", gpg_strerror (err));
241       break;
242
243     case aReceive:
244       if (argc)
245         wrong_args ("--receive");
246       err = wks_receive (es_stdin, command_receive_cb, NULL);
247       if (err)
248         log_error ("processing mail failed: %s\n", gpg_strerror (err));
249       break;
250
251     default:
252       usage (1);
253       break;
254     }
255
256   return log_get_errorcount (0)? 1:0;
257 }
258
259
260 \f
261 struct get_key_status_parm_s
262 {
263   const char *fpr;
264   int found;
265   int count;
266 };
267
268 static void
269 get_key_status_cb (void *opaque, const char *keyword, char *args)
270 {
271   struct get_key_status_parm_s *parm = opaque;
272
273   /*log_debug ("%s: %s\n", keyword, args);*/
274   if (!strcmp (keyword, "EXPORTED"))
275     {
276       parm->count++;
277       if (!ascii_strcasecmp (args, parm->fpr))
278         parm->found = 1;
279     }
280 }
281
282
283 /* Get a key by fingerprint from gpg's keyring and make sure that the
284  * mail address ADDRSPEC is included in the key.  The key is returned
285  * as a new memory stream at R_KEY.
286  *
287  * Fixme: After we have implemented import and export filters for gpg
288  * this function shall only return a key with just this user id.  */
289 static gpg_error_t
290 get_key (estream_t *r_key, const char *fingerprint, const char *addrspec)
291 {
292   gpg_error_t err;
293   ccparray_t ccp;
294   const char **argv = NULL;
295   estream_t key = NULL;
296   struct get_key_status_parm_s parm;
297   char *filterexp = NULL;
298
299   memset (&parm, 0, sizeof parm);
300
301   *r_key = NULL;
302
303   key = es_fopenmem (0, "w+b");
304   if (!key)
305     {
306       err = gpg_error_from_syserror ();
307       log_error ("error allocating memory buffer: %s\n", gpg_strerror (err));
308       goto leave;
309     }
310
311   filterexp = es_bsprintf ("keep-uid=mbox = %s", addrspec);
312   if (!filterexp)
313     {
314       err = gpg_error_from_syserror ();
315       log_error ("error allocating memory buffer: %s\n", gpg_strerror (err));
316       goto leave;
317     }
318
319   ccparray_init (&ccp, 0);
320
321   ccparray_put (&ccp, "--no-options");
322   if (!opt.verbose)
323     ccparray_put (&ccp, "--quiet");
324   else if (opt.verbose > 1)
325     ccparray_put (&ccp, "--verbose");
326   ccparray_put (&ccp, "--batch");
327   ccparray_put (&ccp, "--status-fd=2");
328   ccparray_put (&ccp, "--always-trust");
329   ccparray_put (&ccp, "--armor");
330   ccparray_put (&ccp, "--export-options=export-minimal");
331   ccparray_put (&ccp, "--export-filter");
332   ccparray_put (&ccp, filterexp);
333   ccparray_put (&ccp, "--export");
334   ccparray_put (&ccp, "--");
335   ccparray_put (&ccp, fingerprint);
336
337   ccparray_put (&ccp, NULL);
338   argv = ccparray_get (&ccp, NULL);
339   if (!argv)
340     {
341       err = gpg_error_from_syserror ();
342       goto leave;
343     }
344   parm.fpr = fingerprint;
345   err = gnupg_exec_tool_stream (opt.gpg_program, argv, NULL,
346                                 NULL, key,
347                                 get_key_status_cb, &parm);
348   if (!err && parm.count > 1)
349     err = gpg_error (GPG_ERR_TOO_MANY);
350   else if (!err && !parm.found)
351     err = gpg_error (GPG_ERR_NOT_FOUND);
352   if (err)
353     {
354       log_error ("export failed: %s\n", gpg_strerror (err));
355       goto leave;
356     }
357
358   es_rewind (key);
359   *r_key = key;
360   key = NULL;
361
362  leave:
363   es_fclose (key);
364   xfree (argv);
365   xfree (filterexp);
366   return err;
367 }
368
369
370 \f
371 /* Locate the key by fingerprint and userid and send a publication
372  * request.  */
373 static gpg_error_t
374 command_send (const char *fingerprint, char *userid)
375 {
376   gpg_error_t err;
377   KEYDB_SEARCH_DESC desc;
378   char *addrspec = NULL;
379   estream_t key = NULL;
380   char *submission_to = NULL;
381   mime_maker_t mime = NULL;
382
383   if (classify_user_id (fingerprint, &desc, 1)
384       || !(desc.mode == KEYDB_SEARCH_MODE_FPR
385            || desc.mode == KEYDB_SEARCH_MODE_FPR20))
386     {
387       log_error (_("\"%s\" is not a fingerprint\n"), fingerprint);
388       err = gpg_error (GPG_ERR_INV_NAME);
389       goto leave;
390     }
391   addrspec = mailbox_from_userid (userid);
392   if (!addrspec)
393     {
394       log_error (_("\"%s\" is not a proper mail address\n"), userid);
395       err = gpg_error (GPG_ERR_INV_USER_ID);
396       goto leave;
397     }
398   err = get_key (&key, fingerprint, addrspec);
399   if (err)
400     goto leave;
401
402   /* Get the submission address.  */
403   err = wkd_get_submission_address (addrspec, &submission_to);
404   if (err)
405     goto leave;
406   log_info ("submitting request to '%s'\n", submission_to);
407
408   /* Send the key.  */
409   err = mime_maker_new (&mime, NULL);
410   if (err)
411     goto leave;
412   err = mime_maker_add_header (mime, "From", addrspec);
413   if (err)
414     goto leave;
415   err = mime_maker_add_header (mime, "To", submission_to);
416   if (err)
417     goto leave;
418   err = mime_maker_add_header (mime, "Subject", "Key publishing request");
419   if (err)
420     goto leave;
421
422   err = mime_maker_add_header (mime, "Content-type", "application/pgp-keys");
423   if (err)
424     goto leave;
425
426   err = mime_maker_add_stream (mime, &key);
427   if (err)
428     goto leave;
429
430   err = wks_send_mime (mime);
431
432  leave:
433   mime_maker_release (mime);
434   xfree (submission_to);
435   es_fclose (key);
436   xfree (addrspec);
437   return err;
438 }
439
440
441 \f
442 static gpg_error_t
443 send_confirmation_response (const char *sender, const char *address,
444                             const char *nonce)
445 {
446   gpg_error_t err;
447   estream_t body = NULL;
448   /* FIXME: Encrypt and sign the response.  */
449   /* estream_t bodyenc = NULL; */
450   mime_maker_t mime = NULL;
451
452   body = es_fopenmem (0, "w+b");
453   if (!body)
454     {
455       err = gpg_error_from_syserror ();
456       log_error ("error allocating memory buffer: %s\n", gpg_strerror (err));
457       return err;
458     }
459   /* It is fine to use 8 bit encosind because that is encrypted and
460    * only our client will see it.  */
461   /* es_fputs ("Content-Type: application/vnd.gnupg.wks\n" */
462   /*           "Content-Transfer-Encoding: 8bit\n" */
463   /*           "\n", */
464   /*           body); */
465
466   es_fprintf (body, ("type: confirmation-response\n"
467                      "sender: %s\n"
468                      "address: %s\n"
469                      "nonce: %s\n"),
470               sender,
471               address,
472               nonce);
473
474   es_rewind (body);
475   /* err = encrypt_stream (&bodyenc, body, ctx->fpr); */
476   /* if (err) */
477   /*   goto leave; */
478   /* es_fclose (body); */
479   /* body = NULL; */
480
481
482   err = mime_maker_new (&mime, NULL);
483   if (err)
484     goto leave;
485   err = mime_maker_add_header (mime, "From", address);
486   if (err)
487     goto leave;
488   err = mime_maker_add_header (mime, "To", sender);
489   if (err)
490     goto leave;
491   err = mime_maker_add_header (mime, "Subject", "Key publication confirmation");
492   if (err)
493     goto leave;
494
495   /* err = mime_maker_add_header (mime, "Content-Type", */
496   /*                              "multipart/encrypted; " */
497   /*                              "protocol=\"application/pgp-encrypted\""); */
498   /* if (err) */
499   /*   goto leave; */
500   /* err = mime_maker_add_container (mime, "multipart/encrypted"); */
501   /* if (err) */
502   /*   goto leave; */
503
504   /* err = mime_maker_add_header (mime, "Content-Type", */
505   /*                              "application/pgp-encrypted"); */
506   /* if (err) */
507   /*   goto leave; */
508   /* err = mime_maker_add_body (mime, "Version: 1\n"); */
509   /* if (err) */
510   /*   goto leave; */
511   /* err = mime_maker_add_header (mime, "Content-Type", */
512   /*                              "application/octet-stream"); */
513   /* if (err) */
514   /*   goto leave; */
515
516   err = mime_maker_add_header (mime, "Content-Type",
517                                "application/vnd.gnupg.wks");
518   if (err)
519     goto leave;
520
521   err = mime_maker_add_stream (mime, &body);
522   if (err)
523     goto leave;
524
525   err = wks_send_mime (mime);
526
527  leave:
528   mime_maker_release (mime);
529   /* xfree (bodyenc); */
530   xfree (body);
531   return err;
532 }
533
534
535 /* Reply to a confirmation request.  The MSG has already been
536  * decrypted and we only need to send the nonce back.  */
537 static gpg_error_t
538 process_confirmation_request (estream_t msg)
539 {
540   gpg_error_t err;
541   nvc_t nvc;
542   nve_t item;
543   const char *value, *sender, *address, *nonce;
544
545   err = nvc_parse (&nvc, NULL, msg);
546   if (err)
547     {
548       log_error ("parsing the WKS message failed: %s\n", gpg_strerror (err));
549       goto leave;
550     }
551
552   if (opt.debug)
553     {
554       log_debug ("request follows:\n");
555       nvc_write (nvc, log_get_stream ());
556     }
557
558   /* Check that this is a confirmation request.  */
559   if (!((item = nvc_lookup (nvc, "type:")) && (value = nve_value (item))
560         && !strcmp (value, "confirmation-request")))
561     {
562       if (item && value)
563         log_error ("received unexpected wks message '%s'\n", value);
564       else
565         log_error ("received invalid wks message: %s\n", "'type' missing");
566       err = gpg_error (GPG_ERR_UNEXPECTED_MSG);
567       goto leave;
568     }
569
570   /* FIXME: Check that the fingerprint matches the key used to decrypt the
571    * message.  */
572
573   /* Get the address.  */
574   if (!((item = nvc_lookup (nvc, "address:")) && (value = nve_value (item))
575         && is_valid_mailbox (value)))
576     {
577       log_error ("received invalid wks message: %s\n",
578                  "'address' missing or invalid");
579       err = gpg_error (GPG_ERR_INV_DATA);
580       goto leave;
581     }
582   address = value;
583   /* FIXME: Check that the "address" matches the User ID we want to
584    * publish.  */
585
586   /* Get the sender.  */
587   if (!((item = nvc_lookup (nvc, "sender:")) && (value = nve_value (item))
588         && is_valid_mailbox (value)))
589     {
590       log_error ("received invalid wks message: %s\n",
591                  "'sender' missing or invalid");
592       err = gpg_error (GPG_ERR_INV_DATA);
593       goto leave;
594     }
595   sender = value;
596   /* FIXME: Check that the "sender" matches the From: address.  */
597
598   /* Get the nonce.  */
599   if (!((item = nvc_lookup (nvc, "nonce:")) && (value = nve_value (item))
600         && strlen (value) > 16))
601     {
602       log_error ("received invalid wks message: %s\n",
603                  "'nonce' missing or too short");
604       err = gpg_error (GPG_ERR_INV_DATA);
605       goto leave;
606     }
607   nonce = value;
608
609   err = send_confirmation_response (sender, address, nonce);
610
611
612  leave:
613   nvc_release (nvc);
614   return err;
615 }
616
617
618 /* Called from the MIME receiver to process the plain text data in MSG.  */
619 static gpg_error_t
620 command_receive_cb (void *opaque, const char *mediatype, estream_t msg)
621 {
622   gpg_error_t err;
623
624   (void)opaque;
625
626   if (!strcmp (mediatype, "application/vnd.gnupg.wks"))
627     err = process_confirmation_request (msg);
628   else
629     {
630       log_info ("ignoring unexpected message of type '%s'\n", mediatype);
631       err = gpg_error (GPG_ERR_UNEXPECTED_MSG);
632     }
633
634   return err;
635 }