wks: Partly implement draft-koch-openpgp-webkey-service-02.
[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     aSupported,
53     aCreate,
54     aReceive,
55     aRead,
56
57     oGpgProgram,
58     oSend,
59
60     oDummy
61   };
62
63
64 /* The list of commands and options. */
65 static ARGPARSE_OPTS opts[] = {
66   ARGPARSE_group (300, ("@Commands:\n ")),
67
68   ARGPARSE_c (aSupported, "supported",
69               ("check whether provider supports WKS")),
70   ARGPARSE_c (aCreate,   "create",
71               ("create a publication request")),
72   ARGPARSE_c (aReceive,   "receive",
73               ("receive a MIME confirmation request")),
74   ARGPARSE_c (aRead,      "read",
75               ("receive a plain text confirmation request")),
76
77   ARGPARSE_group (301, ("@\nOptions:\n ")),
78
79   ARGPARSE_s_n (oVerbose, "verbose", ("verbose")),
80   ARGPARSE_s_n (oQuiet, "quiet",  ("be somewhat more quiet")),
81   ARGPARSE_s_s (oDebug, "debug", "@"),
82   ARGPARSE_s_s (oGpgProgram, "gpg", "@"),
83   ARGPARSE_s_n (oSend, "send", "send the mail using sendmail"),
84   ARGPARSE_s_s (oOutput, "output", "|FILE|write the mail to FILE"),
85
86
87   ARGPARSE_end ()
88 };
89
90
91 /* The list of supported debug flags.  */
92 static struct debug_flags_s debug_flags [] =
93   {
94     { DBG_MIME_VALUE   , "mime"    },
95     { DBG_PARSER_VALUE , "parser"  },
96     { DBG_CRYPTO_VALUE , "crypto"  },
97     { DBG_MEMORY_VALUE , "memory"  },
98     { DBG_MEMSTAT_VALUE, "memstat" },
99     { DBG_IPC_VALUE    , "ipc"     },
100     { DBG_EXTPROG_VALUE, "extprog" },
101     { 0, NULL }
102   };
103
104
105 static void wrong_args (const char *text) GPGRT_ATTR_NORETURN;
106 static gpg_error_t command_supported (char *userid);
107 static gpg_error_t command_send (const char *fingerprint, char *userid);
108 static gpg_error_t read_confirmation_request (estream_t msg);
109 static gpg_error_t command_receive_cb (void *opaque,
110                                        const char *mediatype, estream_t fp,
111                                        unsigned int flags);
112
113
114 \f
115 /* Print usage information and and provide strings for help. */
116 static const char *
117 my_strusage( int level )
118 {
119   const char *p;
120
121   switch (level)
122     {
123     case 11: p = "gpg-wks-client (@GNUPG@)";
124       break;
125     case 13: p = VERSION; break;
126     case 17: p = PRINTABLE_OS_NAME; break;
127     case 19: p = ("Please report bugs to <@EMAIL@>.\n"); break;
128
129     case 1:
130     case 40:
131       p = ("Usage: gpg-wks-client [command] [options] [args] (-h for help)");
132       break;
133     case 41:
134       p = ("Syntax: gpg-wks-client [command] [options] [args]\n"
135            "Client for the Web Key Service\n");
136       break;
137
138     default: p = NULL; break;
139     }
140   return p;
141 }
142
143
144 static void
145 wrong_args (const char *text)
146 {
147   es_fprintf (es_stderr, _("usage: %s [options] %s\n"), strusage (11), text);
148   exit (2);
149 }
150
151
152 \f
153 /* Command line parsing.  */
154 static enum cmd_and_opt_values
155 parse_arguments (ARGPARSE_ARGS *pargs, ARGPARSE_OPTS *popts)
156 {
157   enum cmd_and_opt_values cmd = 0;
158   int no_more_options = 0;
159
160   while (!no_more_options && optfile_parse (NULL, NULL, NULL, pargs, popts))
161     {
162       switch (pargs->r_opt)
163         {
164         case oQuiet:     opt.quiet = 1; break;
165         case oVerbose:   opt.verbose++; break;
166         case oDebug:
167           if (parse_debug_flag (pargs->r.ret_str, &opt.debug, debug_flags))
168             {
169               pargs->r_opt = ARGPARSE_INVALID_ARG;
170               pargs->err = ARGPARSE_PRINT_ERROR;
171             }
172           break;
173
174         case oGpgProgram:
175           opt.gpg_program = pargs->r.ret_str;
176           break;
177         case oSend:
178           opt.use_sendmail = 1;
179           break;
180         case oOutput:
181           opt.output = pargs->r.ret_str;
182           break;
183
184         case aSupported:
185         case aCreate:
186         case aReceive:
187         case aRead:
188           cmd = pargs->r_opt;
189           break;
190
191         default: pargs->err = 2; break;
192         }
193     }
194
195   return cmd;
196 }
197
198
199 \f
200 /* gpg-wks-client main. */
201 int
202 main (int argc, char **argv)
203 {
204   gpg_error_t err;
205   ARGPARSE_ARGS pargs;
206   enum cmd_and_opt_values cmd;
207
208   gnupg_reopen_std ("gpg-wks-client");
209   set_strusage (my_strusage);
210   log_set_prefix ("gpg-wks-client", GPGRT_LOG_WITH_PREFIX);
211
212   /* Make sure that our subsystems are ready.  */
213   i18n_init();
214   init_common_subsystems (&argc, &argv);
215
216   assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT);
217   setup_libassuan_logging (&opt.debug, NULL);
218
219   /* Parse the command line. */
220   pargs.argc  = &argc;
221   pargs.argv  = &argv;
222   pargs.flags = ARGPARSE_FLAG_KEEP;
223   cmd = parse_arguments (&pargs, opts);
224
225   if (log_get_errorcount (0))
226     exit (2);
227
228   /* Print a warning if an argument looks like an option.  */
229   if (!opt.quiet && !(pargs.flags & ARGPARSE_FLAG_STOP_SEEN))
230     {
231       int i;
232
233       for (i=0; i < argc; i++)
234         if (argv[i][0] == '-' && argv[i][1] == '-')
235           log_info (("NOTE: '%s' is not considered an option\n"), argv[i]);
236     }
237
238   /* Set defaults for non given options.  */
239   if (!opt.gpg_program)
240     opt.gpg_program = gnupg_module_name (GNUPG_MODULE_NAME_GPG);
241
242   /* Tell call-dirmngr what options we want.  */
243   set_dirmngr_options (opt.verbose, (opt.debug & DBG_IPC_VALUE), 1);
244
245   /* Run the selected command.  */
246   switch (cmd)
247     {
248     case aSupported:
249       if (argc != 1)
250         wrong_args ("--supported USER-ID");
251       err = command_supported (argv[0]);
252       if (err && gpg_err_code (err) != GPG_ERR_FALSE)
253         log_error ("checking support failed: %s\n", gpg_strerror (err));
254       break;
255
256     case aCreate:
257       if (argc != 2)
258         wrong_args ("--create FINGERPRINT USER-ID");
259       err = command_send (argv[0], argv[1]);
260       if (err)
261         log_error ("creating request failed: %s\n", gpg_strerror (err));
262       break;
263
264     case aReceive:
265       if (argc)
266         wrong_args ("--receive < MIME-DATA");
267       err = wks_receive (es_stdin, command_receive_cb, NULL);
268       if (err)
269         log_error ("processing mail failed: %s\n", gpg_strerror (err));
270       break;
271
272     case aRead:
273       if (argc)
274         wrong_args ("--read < WKS-DATA");
275       err = read_confirmation_request (es_stdin);
276       if (err)
277         log_error ("processing mail failed: %s\n", gpg_strerror (err));
278       break;
279
280     default:
281       usage (1);
282       break;
283     }
284
285   return log_get_errorcount (0)? 1:0;
286 }
287
288
289 \f
290 struct get_key_status_parm_s
291 {
292   const char *fpr;
293   int found;
294   int count;
295 };
296
297 static void
298 get_key_status_cb (void *opaque, const char *keyword, char *args)
299 {
300   struct get_key_status_parm_s *parm = opaque;
301
302   /*log_debug ("%s: %s\n", keyword, args);*/
303   if (!strcmp (keyword, "EXPORTED"))
304     {
305       parm->count++;
306       if (!ascii_strcasecmp (args, parm->fpr))
307         parm->found = 1;
308     }
309 }
310
311
312 /* Get a key by fingerprint from gpg's keyring and make sure that the
313  * mail address ADDRSPEC is included in the key.  The key is returned
314  * as a new memory stream at R_KEY.
315  *
316  * Fixme: After we have implemented import and export filters for gpg
317  * this function shall only return a key with just this user id.  */
318 static gpg_error_t
319 get_key (estream_t *r_key, const char *fingerprint, const char *addrspec)
320 {
321   gpg_error_t err;
322   ccparray_t ccp;
323   const char **argv = NULL;
324   estream_t key = NULL;
325   struct get_key_status_parm_s parm;
326   char *filterexp = NULL;
327
328   memset (&parm, 0, sizeof parm);
329
330   *r_key = NULL;
331
332   key = es_fopenmem (0, "w+b");
333   if (!key)
334     {
335       err = gpg_error_from_syserror ();
336       log_error ("error allocating memory buffer: %s\n", gpg_strerror (err));
337       goto leave;
338     }
339
340   filterexp = es_bsprintf ("keep-uid=mbox = %s", addrspec);
341   if (!filterexp)
342     {
343       err = gpg_error_from_syserror ();
344       log_error ("error allocating memory buffer: %s\n", gpg_strerror (err));
345       goto leave;
346     }
347
348   ccparray_init (&ccp, 0);
349
350   ccparray_put (&ccp, "--no-options");
351   if (!opt.verbose)
352     ccparray_put (&ccp, "--quiet");
353   else if (opt.verbose > 1)
354     ccparray_put (&ccp, "--verbose");
355   ccparray_put (&ccp, "--batch");
356   ccparray_put (&ccp, "--status-fd=2");
357   ccparray_put (&ccp, "--always-trust");
358   ccparray_put (&ccp, "--armor");
359   ccparray_put (&ccp, "--export-options=export-minimal");
360   ccparray_put (&ccp, "--export-filter");
361   ccparray_put (&ccp, filterexp);
362   ccparray_put (&ccp, "--export");
363   ccparray_put (&ccp, "--");
364   ccparray_put (&ccp, fingerprint);
365
366   ccparray_put (&ccp, NULL);
367   argv = ccparray_get (&ccp, NULL);
368   if (!argv)
369     {
370       err = gpg_error_from_syserror ();
371       goto leave;
372     }
373   parm.fpr = fingerprint;
374   err = gnupg_exec_tool_stream (opt.gpg_program, argv, NULL,
375                                 NULL, key,
376                                 get_key_status_cb, &parm);
377   if (!err && parm.count > 1)
378     err = gpg_error (GPG_ERR_TOO_MANY);
379   else if (!err && !parm.found)
380     err = gpg_error (GPG_ERR_NOT_FOUND);
381   if (err)
382     {
383       log_error ("export failed: %s\n", gpg_strerror (err));
384       goto leave;
385     }
386
387   es_rewind (key);
388   *r_key = key;
389   key = NULL;
390
391  leave:
392   es_fclose (key);
393   xfree (argv);
394   xfree (filterexp);
395   return err;
396 }
397
398
399 \f
400 static void
401 decrypt_stream_status_cb (void *opaque, const char *keyword, char *args)
402 {
403   (void)opaque;
404
405   if (DBG_CRYPTO)
406     log_debug ("gpg status: %s %s\n", keyword, args);
407 }
408
409
410 /* Decrypt the INPUT stream to a new stream which is stored at success
411  * at R_OUTPUT.  */
412 static gpg_error_t
413 decrypt_stream (estream_t *r_output, estream_t input)
414 {
415   gpg_error_t err;
416   ccparray_t ccp;
417   const char **argv;
418   estream_t output;
419
420   *r_output = NULL;
421
422   output = es_fopenmem (0, "w+b");
423   if (!output)
424     {
425       err = gpg_error_from_syserror ();
426       log_error ("error allocating memory buffer: %s\n", gpg_strerror (err));
427       return err;
428     }
429
430   ccparray_init (&ccp, 0);
431
432   ccparray_put (&ccp, "--no-options");
433   /* We limit the output to 64 KiB to avoid DoS using compression
434    * tricks.  A regular client will anyway only send a minimal key;
435    * that is one w/o key signatures and attribute packets.  */
436   ccparray_put (&ccp, "--max-output=0x10000");
437   if (!opt.verbose)
438     ccparray_put (&ccp, "--quiet");
439   else if (opt.verbose > 1)
440     ccparray_put (&ccp, "--verbose");
441   ccparray_put (&ccp, "--batch");
442   ccparray_put (&ccp, "--status-fd=2");
443   ccparray_put (&ccp, "--decrypt");
444   ccparray_put (&ccp, "--");
445
446   ccparray_put (&ccp, NULL);
447   argv = ccparray_get (&ccp, NULL);
448   if (!argv)
449     {
450       err = gpg_error_from_syserror ();
451       goto leave;
452     }
453   err = gnupg_exec_tool_stream (opt.gpg_program, argv, input,
454                                 NULL, output,
455                                 decrypt_stream_status_cb, NULL);
456   if (err)
457     {
458       log_error ("decryption failed: %s\n", gpg_strerror (err));
459       goto leave;
460     }
461   else if (opt.verbose)
462     log_info ("decryption succeeded\n");
463
464   es_rewind (output);
465   *r_output = output;
466   output = NULL;
467
468  leave:
469   es_fclose (output);
470   xfree (argv);
471   return err;
472 }
473
474
475
476 \f
477 /* Check whether the  provider supports the WKS protocol.  */
478 static gpg_error_t
479 command_supported (char *userid)
480 {
481   gpg_error_t err;
482   char *addrspec = NULL;
483   char *submission_to = NULL;
484
485   addrspec = mailbox_from_userid (userid);
486   if (!addrspec)
487     {
488       log_error (_("\"%s\" is not a proper mail address\n"), userid);
489       err = gpg_error (GPG_ERR_INV_USER_ID);
490       goto leave;
491     }
492
493   /* Get the submission address.  */
494   err = wkd_get_submission_address (addrspec, &submission_to);
495   if (err)
496     {
497       if (gpg_err_code (err) == GPG_ERR_NO_DATA
498           || gpg_err_code (err) == GPG_ERR_UNKNOWN_HOST)
499         {
500           if (opt.verbose)
501             log_info ("provider for '%s' does NOT support WKS (%s)\n",
502                       addrspec, gpg_strerror (err));
503           err = gpg_error (GPG_ERR_FALSE);
504           log_inc_errorcount ();
505         }
506       goto leave;
507     }
508   if (opt.verbose)
509     log_info ("provider for '%s' supports WKS\n", addrspec);
510
511  leave:
512   xfree (submission_to);
513   xfree (addrspec);
514   return err;
515 }
516
517
518 \f
519 /* Locate the key by fingerprint and userid and send a publication
520  * request.  */
521 static gpg_error_t
522 command_send (const char *fingerprint, char *userid)
523 {
524   gpg_error_t err;
525   KEYDB_SEARCH_DESC desc;
526   char *addrspec = NULL;
527   estream_t key = NULL;
528   char *submission_to = NULL;
529   mime_maker_t mime = NULL;
530   struct policy_flags_s policy;
531
532   memset (&policy, 0, sizeof policy);
533
534   if (classify_user_id (fingerprint, &desc, 1)
535       || !(desc.mode == KEYDB_SEARCH_MODE_FPR
536            || desc.mode == KEYDB_SEARCH_MODE_FPR20))
537     {
538       log_error (_("\"%s\" is not a fingerprint\n"), fingerprint);
539       err = gpg_error (GPG_ERR_INV_NAME);
540       goto leave;
541     }
542   addrspec = mailbox_from_userid (userid);
543   if (!addrspec)
544     {
545       log_error (_("\"%s\" is not a proper mail address\n"), userid);
546       err = gpg_error (GPG_ERR_INV_USER_ID);
547       goto leave;
548     }
549   err = get_key (&key, fingerprint, addrspec);
550   if (err)
551     goto leave;
552
553   /* Get the submission address.  */
554   err = wkd_get_submission_address (addrspec, &submission_to);
555   if (err)
556     goto leave;
557   log_info ("submitting request to '%s'\n", submission_to);
558
559   /* Get the policy flags.  */
560   {
561     estream_t mbuf;
562
563     err = wkd_get_policy_flags (addrspec, &mbuf);
564     if (err)
565       {
566         log_error ("error reading policy flags for '%s': %s\n",
567                    submission_to, gpg_strerror (err));
568         goto leave;
569       }
570     if (mbuf)
571       {
572         err = wks_parse_policy (&policy, mbuf, 1);
573         es_fclose (mbuf);
574         if (err)
575           goto leave;
576       }
577   }
578
579   if (policy.auth_submit)
580     log_info ("no confirmation required for '%s'\n", addrspec);
581
582   /* Send the key.  */
583   err = mime_maker_new (&mime, NULL);
584   if (err)
585     goto leave;
586   err = mime_maker_add_header (mime, "From", addrspec);
587   if (err)
588     goto leave;
589   err = mime_maker_add_header (mime, "To", submission_to);
590   if (err)
591     goto leave;
592   err = mime_maker_add_header (mime, "Subject", "Key publishing request");
593   if (err)
594     goto leave;
595
596   err = mime_maker_add_header (mime, "Content-type", "application/pgp-keys");
597   if (err)
598     goto leave;
599
600   /* Tell server that we support draft version 3.  */
601   err = mime_maker_add_header (mime, "Wks-Draft-Version", "3");
602   if (err)
603     goto leave;
604
605   err = mime_maker_add_stream (mime, &key);
606   if (err)
607     goto leave;
608
609   err = wks_send_mime (mime);
610
611  leave:
612   mime_maker_release (mime);
613   xfree (submission_to);
614   es_fclose (key);
615   xfree (addrspec);
616   return err;
617 }
618
619
620 \f
621 static void
622 encrypt_response_status_cb (void *opaque, const char *keyword, char *args)
623 {
624   gpg_error_t *failure = opaque;
625   char *fields[2];
626
627   if (DBG_CRYPTO)
628     log_debug ("gpg status: %s %s\n", keyword, args);
629
630   if (!strcmp (keyword, "FAILURE"))
631     {
632       if (split_fields (args, fields, DIM (fields)) >= 2
633           && !strcmp (fields[0], "encrypt"))
634         *failure = strtoul (fields[1], NULL, 10);
635     }
636
637 }
638
639
640 /* Encrypt the INPUT stream to a new stream which is stored at success
641  * at R_OUTPUT.  Encryption is done for ADDRSPEC.  We currently
642  * retrieve that key from the WKD, DANE, or from "local".  "local" is
643  * last to prefer the latest key version but use a local copy in case
644  * we are working offline.  It might be useful for the server to send
645  * the fingerprint of its encryption key - or even the entire key
646  * back.  */
647 static gpg_error_t
648 encrypt_response (estream_t *r_output, estream_t input, const char *addrspec)
649 {
650   gpg_error_t err;
651   ccparray_t ccp;
652   const char **argv;
653   estream_t output;
654   gpg_error_t gpg_err = 0;
655
656   *r_output = NULL;
657
658   output = es_fopenmem (0, "w+b");
659   if (!output)
660     {
661       err = gpg_error_from_syserror ();
662       log_error ("error allocating memory buffer: %s\n", gpg_strerror (err));
663       return err;
664     }
665
666   ccparray_init (&ccp, 0);
667
668   ccparray_put (&ccp, "--no-options");
669   if (!opt.verbose)
670     ccparray_put (&ccp, "--quiet");
671   else if (opt.verbose > 1)
672     ccparray_put (&ccp, "--verbose");
673   ccparray_put (&ccp, "--batch");
674   ccparray_put (&ccp, "--status-fd=2");
675   ccparray_put (&ccp, "--always-trust");
676   ccparray_put (&ccp, "--armor");
677   ccparray_put (&ccp, "--auto-key-locate=clear,wkd,dane,local");
678   ccparray_put (&ccp, "--recipient");
679   ccparray_put (&ccp, addrspec);
680   ccparray_put (&ccp, "--encrypt");
681   ccparray_put (&ccp, "--");
682
683   ccparray_put (&ccp, NULL);
684   argv = ccparray_get (&ccp, NULL);
685   if (!argv)
686     {
687       err = gpg_error_from_syserror ();
688       goto leave;
689     }
690   err = gnupg_exec_tool_stream (opt.gpg_program, argv, input,
691                                 NULL, output,
692                                 encrypt_response_status_cb, &gpg_err);
693   if (err)
694     {
695       if (gpg_err)
696         err = gpg_err;
697       log_error ("encryption failed: %s\n", gpg_strerror (err));
698       goto leave;
699     }
700
701   es_rewind (output);
702   *r_output = output;
703   output = NULL;
704
705  leave:
706   es_fclose (output);
707   xfree (argv);
708   return err;
709 }
710
711
712 static gpg_error_t
713 send_confirmation_response (const char *sender, const char *address,
714                             const char *nonce, int encrypt)
715 {
716   gpg_error_t err;
717   estream_t body = NULL;
718   estream_t bodyenc = NULL;
719   mime_maker_t mime = NULL;
720
721   body = es_fopenmem (0, "w+b");
722   if (!body)
723     {
724       err = gpg_error_from_syserror ();
725       log_error ("error allocating memory buffer: %s\n", gpg_strerror (err));
726       return err;
727     }
728
729   /* It is fine to use 8 bit encoding because that is encrypted and
730    * only our client will see it.  */
731   if (encrypt)
732     {
733       es_fputs ("Content-Type: application/vnd.gnupg.wks\n"
734                 "Content-Transfer-Encoding: 8bit\n"
735                 "\n",
736                 body);
737     }
738
739   es_fprintf (body, ("type: confirmation-response\n"
740                      "sender: %s\n"
741                      "address: %s\n"
742                      "nonce: %s\n"),
743               sender,
744               address,
745               nonce);
746
747   es_rewind (body);
748   if (encrypt)
749     {
750       err = encrypt_response (&bodyenc, body, sender);
751       if (err)
752         goto leave;
753       es_fclose (body);
754       body = NULL;
755     }
756
757   err = mime_maker_new (&mime, NULL);
758   if (err)
759     goto leave;
760   err = mime_maker_add_header (mime, "From", address);
761   if (err)
762     goto leave;
763   err = mime_maker_add_header (mime, "To", sender);
764   if (err)
765     goto leave;
766   err = mime_maker_add_header (mime, "Subject", "Key publication confirmation");
767   if (err)
768     goto leave;
769
770   if (encrypt)
771     {
772       err = mime_maker_add_header (mime, "Content-Type",
773                                    "multipart/encrypted; "
774                                    "protocol=\"application/pgp-encrypted\"");
775       if (err)
776         goto leave;
777       err = mime_maker_add_container (mime);
778       if (err)
779         goto leave;
780
781       err = mime_maker_add_header (mime, "Content-Type",
782                                    "application/pgp-encrypted");
783       if (err)
784         goto leave;
785       err = mime_maker_add_body (mime, "Version: 1\n");
786       if (err)
787         goto leave;
788       err = mime_maker_add_header (mime, "Content-Type",
789                                    "application/octet-stream");
790       if (err)
791         goto leave;
792
793       err = mime_maker_add_stream (mime, &bodyenc);
794       if (err)
795         goto leave;
796     }
797   else
798     {
799       err = mime_maker_add_header (mime, "Content-Type",
800                                    "application/vnd.gnupg.wks");
801       if (err)
802         goto leave;
803       err = mime_maker_add_stream (mime, &body);
804       if (err)
805         goto leave;
806     }
807
808   err = wks_send_mime (mime);
809
810  leave:
811   mime_maker_release (mime);
812   es_fclose (bodyenc);
813   es_fclose (body);
814   return err;
815 }
816
817
818 /* Reply to a confirmation request.  The MSG has already been
819  * decrypted and we only need to send the nonce back.  */
820 static gpg_error_t
821 process_confirmation_request (estream_t msg)
822 {
823   gpg_error_t err;
824   nvc_t nvc;
825   nve_t item;
826   const char *value, *sender, *address, *nonce;
827
828   err = nvc_parse (&nvc, NULL, msg);
829   if (err)
830     {
831       log_error ("parsing the WKS message failed: %s\n", gpg_strerror (err));
832       goto leave;
833     }
834
835   if (DBG_MIME)
836     {
837       log_debug ("request follows:\n");
838       nvc_write (nvc, log_get_stream ());
839     }
840
841   /* Check that this is a confirmation request.  */
842   if (!((item = nvc_lookup (nvc, "type:")) && (value = nve_value (item))
843         && !strcmp (value, "confirmation-request")))
844     {
845       if (item && value)
846         log_error ("received unexpected wks message '%s'\n", value);
847       else
848         log_error ("received invalid wks message: %s\n", "'type' missing");
849       err = gpg_error (GPG_ERR_UNEXPECTED_MSG);
850       goto leave;
851     }
852
853   /* FIXME: Check that the fingerprint matches the key used to decrypt the
854    * message.  */
855
856   /* Get the address.  */
857   if (!((item = nvc_lookup (nvc, "address:")) && (value = nve_value (item))
858         && is_valid_mailbox (value)))
859     {
860       log_error ("received invalid wks message: %s\n",
861                  "'address' missing or invalid");
862       err = gpg_error (GPG_ERR_INV_DATA);
863       goto leave;
864     }
865   address = value;
866   /* FIXME: Check that the "address" matches the User ID we want to
867    * publish.  Also get the "fingerprint" and compare that to our to
868    * be published key.  Further we should make sure that we actually
869    * decrypted using that fingerprint (which is a bit problematic if
870    * --read is used). */
871
872   /* Get the sender.  */
873   if (!((item = nvc_lookup (nvc, "sender:")) && (value = nve_value (item))
874         && is_valid_mailbox (value)))
875     {
876       log_error ("received invalid wks message: %s\n",
877                  "'sender' missing or invalid");
878       err = gpg_error (GPG_ERR_INV_DATA);
879       goto leave;
880     }
881   sender = value;
882   /* FIXME: Check that the "sender" matches the From: address.  */
883
884   /* Get the nonce.  */
885   if (!((item = nvc_lookup (nvc, "nonce:")) && (value = nve_value (item))
886         && strlen (value) > 16))
887     {
888       log_error ("received invalid wks message: %s\n",
889                  "'nonce' missing or too short");
890       err = gpg_error (GPG_ERR_INV_DATA);
891       goto leave;
892     }
893   nonce = value;
894
895   /* Send the confirmation.  If no key was found, try again without
896    * encryption.  */
897   err = send_confirmation_response (sender, address, nonce, 1);
898   if (gpg_err_code (err) == GPG_ERR_NO_PUBKEY)
899     {
900       log_info ("no encryption key found - sending response in the clear\n");
901       err = send_confirmation_response (sender, address, nonce, 0);
902     }
903
904  leave:
905   nvc_release (nvc);
906   return err;
907 }
908
909
910 /* Read a confirmation request and decrypt it if needed.  This
911  * function may not be used with a mail or MIME message but only with
912  * the actual encrypted or plaintext WKS data.  */
913 static gpg_error_t
914 read_confirmation_request (estream_t msg)
915 {
916   gpg_error_t err;
917   int c;
918   estream_t plaintext = NULL;
919
920   /* We take a really simple approach to check whether MSG is
921    * encrypted: We know that an encrypted message is always armored
922    * and thus starts with a few dashes.  It is even sufficient to
923    * check for a single dash, because that can never be a proper first
924    * WKS data octet.  We need to skip leading spaces, though. */
925   while ((c = es_fgetc (msg)) == ' ' || c == '\t' || c == '\r' || c == '\n')
926     ;
927   if (c == EOF)
928     {
929       log_error ("can't process an empty message\n");
930       return gpg_error (GPG_ERR_INV_DATA);
931     }
932   if (es_ungetc (c, msg) != c)
933     {
934       log_error ("error ungetting octet from message\n");
935       return gpg_error (GPG_ERR_INTERNAL);
936     }
937
938   if (c != '-')
939     err = process_confirmation_request (msg);
940   else
941     {
942       err = decrypt_stream (&plaintext, msg);
943       if (err)
944         log_error ("decryption failed: %s\n", gpg_strerror (err));
945       else
946         err = process_confirmation_request (plaintext);
947     }
948
949   es_fclose (plaintext);
950   return err;
951 }
952
953
954 /* Called from the MIME receiver to process the plain text data in MSG.  */
955 static gpg_error_t
956 command_receive_cb (void *opaque, const char *mediatype,
957                     estream_t msg, unsigned int flags)
958 {
959   gpg_error_t err;
960
961   (void)opaque;
962   (void)flags;
963
964   if (!strcmp (mediatype, "application/vnd.gnupg.wks"))
965     err = read_confirmation_request (msg);
966   else
967     {
968       log_info ("ignoring unexpected message of type '%s'\n", mediatype);
969       err = gpg_error (GPG_ERR_UNEXPECTED_MSG);
970     }
971
972   return err;
973 }