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