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