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