tools: Call sendmail directly from the wks tools.
[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;
295   estream_t key;
296   struct get_key_status_parm_s parm;
297
298   (void)addrspec;  /* FIXME - need to use it.  */
299
300   memset (&parm, 0, sizeof parm);
301
302   *r_key = NULL;
303
304   key = es_fopenmem (0, "w+b");
305   if (!key)
306     {
307       err = gpg_error_from_syserror ();
308       log_error ("error allocating memory buffer: %s\n", gpg_strerror (err));
309       return err;
310     }
311
312   ccparray_init (&ccp, 0);
313
314   ccparray_put (&ccp, "--no-options");
315   if (!opt.verbose)
316     ccparray_put (&ccp, "--quiet");
317   else if (opt.verbose > 1)
318     ccparray_put (&ccp, "--verbose");
319   ccparray_put (&ccp, "--batch");
320   ccparray_put (&ccp, "--status-fd=2");
321   ccparray_put (&ccp, "--always-trust");
322   ccparray_put (&ccp, "--armor");
323   ccparray_put (&ccp, "--export-options=export-minimal");
324   ccparray_put (&ccp, "--export");
325   ccparray_put (&ccp, "--");
326   ccparray_put (&ccp, fingerprint);
327
328   ccparray_put (&ccp, NULL);
329   argv = ccparray_get (&ccp, NULL);
330   if (!argv)
331     {
332       err = gpg_error_from_syserror ();
333       goto leave;
334     }
335   parm.fpr = fingerprint;
336   err = gnupg_exec_tool_stream (opt.gpg_program, argv, NULL,
337                                 NULL, key,
338                                 get_key_status_cb, &parm);
339   if (!err && parm.count > 1)
340     err = gpg_error (GPG_ERR_TOO_MANY);
341   else if (!err && !parm.found)
342     err = gpg_error (GPG_ERR_NOT_FOUND);
343   if (err)
344     {
345       log_error ("export failed: %s\n", gpg_strerror (err));
346       goto leave;
347     }
348
349   es_rewind (key);
350   *r_key = key;
351   key = NULL;
352
353  leave:
354   es_fclose (key);
355   xfree (argv);
356   return err;
357 }
358
359
360 \f
361 /* Locate the key by fingerprint and userid and send a publication
362  * request.  */
363 static gpg_error_t
364 command_send (const char *fingerprint, char *userid)
365 {
366   gpg_error_t err;
367   KEYDB_SEARCH_DESC desc;
368   char *addrspec = NULL;
369   estream_t key = NULL;
370   char *submission_to = NULL;
371   mime_maker_t mime = NULL;
372
373   if (classify_user_id (fingerprint, &desc, 1)
374       || !(desc.mode == KEYDB_SEARCH_MODE_FPR
375            || desc.mode == KEYDB_SEARCH_MODE_FPR20))
376     {
377       log_error (_("\"%s\" is not a fingerprint\n"), fingerprint);
378       err = gpg_error (GPG_ERR_INV_NAME);
379       goto leave;
380     }
381   addrspec = mailbox_from_userid (userid);
382   if (!addrspec)
383     {
384       log_error (_("\"%s\" is not a proper mail address\n"), userid);
385       err = gpg_error (GPG_ERR_INV_USER_ID);
386       goto leave;
387     }
388   err = get_key (&key, fingerprint, addrspec);
389   if (err)
390     goto leave;
391   log_debug ("fixme: Check that the key has the requested user-id.\n");
392
393   /* Get the submission address.  */
394   err = wkd_get_submission_address (addrspec, &submission_to);
395   if (err)
396     goto leave;
397   log_info ("submitting request to '%s'\n", submission_to);
398
399   /* Send the key.  */
400   err = mime_maker_new (&mime, NULL);
401   if (err)
402     goto leave;
403   err = mime_maker_add_header (mime, "From", addrspec);
404   if (err)
405     goto leave;
406   err = mime_maker_add_header (mime, "To", submission_to);
407   if (err)
408     goto leave;
409   err = mime_maker_add_header (mime, "Subject", "Key publishing request");
410   if (err)
411     goto leave;
412
413   err = mime_maker_add_header (mime, "Content-type", "application/pgp-keys");
414   if (err)
415     goto leave;
416
417   err = mime_maker_add_stream (mime, &key);
418   if (err)
419     goto leave;
420
421   err = wks_send_mime (mime);
422
423  leave:
424   mime_maker_release (mime);
425   xfree (submission_to);
426   es_fclose (key);
427   xfree (addrspec);
428   return err;
429 }
430
431
432 \f
433 static gpg_error_t
434 send_confirmation_response (const char *sender, const char *address,
435                             const char *nonce)
436 {
437   gpg_error_t err;
438   estream_t body = NULL;
439   /* FIXME: Encrypt and sign the response.  */
440   /* estream_t bodyenc = NULL; */
441   mime_maker_t mime = NULL;
442
443   body = es_fopenmem (0, "w+b");
444   if (!body)
445     {
446       err = gpg_error_from_syserror ();
447       log_error ("error allocating memory buffer: %s\n", gpg_strerror (err));
448       return err;
449     }
450   /* It is fine to use 8 bit encosind because that is encrypted and
451    * only our client will see it.  */
452   /* es_fputs ("Content-Type: application/vnd.gnupg.wks\n" */
453   /*           "Content-Transfer-Encoding: 8bit\n" */
454   /*           "\n", */
455   /*           body); */
456
457   es_fprintf (body, ("type: confirmation-response\n"
458                      "sender: %s\n"
459                      "address: %s\n"
460                      "nonce: %s\n"),
461               sender,
462               address,
463               nonce);
464
465   es_rewind (body);
466   /* err = encrypt_stream (&bodyenc, body, ctx->fpr); */
467   /* if (err) */
468   /*   goto leave; */
469   /* es_fclose (body); */
470   /* body = NULL; */
471
472
473   err = mime_maker_new (&mime, NULL);
474   if (err)
475     goto leave;
476   err = mime_maker_add_header (mime, "From", address);
477   if (err)
478     goto leave;
479   err = mime_maker_add_header (mime, "To", sender);
480   if (err)
481     goto leave;
482   err = mime_maker_add_header (mime, "Subject", "Key publication confirmation");
483   if (err)
484     goto leave;
485
486   /* err = mime_maker_add_header (mime, "Content-Type", */
487   /*                              "multipart/encrypted; " */
488   /*                              "protocol=\"application/pgp-encrypted\""); */
489   /* if (err) */
490   /*   goto leave; */
491   /* err = mime_maker_add_container (mime, "multipart/encrypted"); */
492   /* if (err) */
493   /*   goto leave; */
494
495   /* err = mime_maker_add_header (mime, "Content-Type", */
496   /*                              "application/pgp-encrypted"); */
497   /* if (err) */
498   /*   goto leave; */
499   /* err = mime_maker_add_body (mime, "Version: 1\n"); */
500   /* if (err) */
501   /*   goto leave; */
502   /* err = mime_maker_add_header (mime, "Content-Type", */
503   /*                              "application/octet-stream"); */
504   /* if (err) */
505   /*   goto leave; */
506
507   err = mime_maker_add_header (mime, "Content-Type",
508                                "application/vnd.gnupg.wks");
509   if (err)
510     goto leave;
511
512   err = mime_maker_add_stream (mime, &body);
513   if (err)
514     goto leave;
515
516   err = wks_send_mime (mime);
517
518  leave:
519   mime_maker_release (mime);
520   /* xfree (bodyenc); */
521   xfree (body);
522   return err;
523 }
524
525
526 /* Reply to a confirmation request.  The MSG has already been
527  * decrypted and we only need to send the nonce back.  */
528 static gpg_error_t
529 process_confirmation_request (estream_t msg)
530 {
531   gpg_error_t err;
532   nvc_t nvc;
533   nve_t item;
534   const char *value, *sender, *address, *nonce;
535
536   err = nvc_parse (&nvc, NULL, msg);
537   if (err)
538     {
539       log_error ("parsing the WKS message failed: %s\n", gpg_strerror (err));
540       goto leave;
541     }
542
543   if (opt.debug)
544     {
545       log_debug ("request follows:\n");
546       nvc_write (nvc, log_get_stream ());
547     }
548
549   /* Check that this is a confirmation request.  */
550   if (!((item = nvc_lookup (nvc, "type:")) && (value = nve_value (item))
551         && !strcmp (value, "confirmation-request")))
552     {
553       if (item && value)
554         log_error ("received unexpected wks message '%s'\n", value);
555       else
556         log_error ("received invalid wks message: %s\n", "'type' missing");
557       err = gpg_error (GPG_ERR_UNEXPECTED_MSG);
558       goto leave;
559     }
560
561   /* FIXME: Check that the fingerprint matches the key used to decrypt the
562    * message.  */
563
564   /* Get the address.  */
565   if (!((item = nvc_lookup (nvc, "address:")) && (value = nve_value (item))
566         && is_valid_mailbox (value)))
567     {
568       log_error ("received invalid wks message: %s\n",
569                  "'address' missing or invalid");
570       err = gpg_error (GPG_ERR_INV_DATA);
571       goto leave;
572     }
573   address = value;
574   /* FIXME: Check that the "address" matches the User ID we want to
575    * publish.  */
576
577   /* Get the sender.  */
578   if (!((item = nvc_lookup (nvc, "sender:")) && (value = nve_value (item))
579         && is_valid_mailbox (value)))
580     {
581       log_error ("received invalid wks message: %s\n",
582                  "'sender' missing or invalid");
583       err = gpg_error (GPG_ERR_INV_DATA);
584       goto leave;
585     }
586   sender = value;
587   /* FIXME: Check that the "sender" matches the From: address.  */
588
589   /* Get the nonce.  */
590   if (!((item = nvc_lookup (nvc, "nonce:")) && (value = nve_value (item))
591         && strlen (value) > 16))
592     {
593       log_error ("received invalid wks message: %s\n",
594                  "'nonce' missing or too short");
595       err = gpg_error (GPG_ERR_INV_DATA);
596       goto leave;
597     }
598   nonce = value;
599
600   err = send_confirmation_response (sender, address, nonce);
601
602
603  leave:
604   nvc_release (nvc);
605   return err;
606 }
607
608
609 /* Called from the MIME receiver to process the plain text data in MSG.  */
610 static gpg_error_t
611 command_receive_cb (void *opaque, const char *mediatype, estream_t msg)
612 {
613   gpg_error_t err;
614
615   (void)opaque;
616
617   if (!strcmp (mediatype, "application/vnd.gnupg.wks"))
618     err = process_confirmation_request (msg);
619   else
620     {
621       log_info ("ignoring unexpected message of type '%s'\n", mediatype);
622       err = gpg_error (GPG_ERR_UNEXPECTED_MSG);
623     }
624
625   return err;
626 }