1 /* gpg-wks-client.c - A client for the Web Key Service protocols.
2 * Copyright (C) 2016 Werner Koch
4 * This file is part of GnuPG.
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.
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.
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/>.
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"
41 /* Constants to identify the commands and options. */
42 enum cmd_and_opt_values
63 /* The list of commands and options. */
64 static ARGPARSE_OPTS opts[] = {
65 ARGPARSE_group (300, ("@Commands:\n ")),
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")),
74 ARGPARSE_group (301, ("@\nOptions:\n ")),
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"),
88 /* The list of supported debug flags. */
89 static struct debug_flags_s debug_flags [] =
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" },
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);
108 /* Print usage information and and provide strings for help. */
110 my_strusage( int level )
116 case 11: p = "gpg-wks-client (@GNUPG@)";
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;
124 p = ("Usage: gpg-wks-client [command] [options] [args] (-h for help)");
127 p = ("Syntax: gpg-wks-client [command] [options] [args]\n"
128 "Client for the Web Key Service\n");
131 default: p = NULL; break;
138 wrong_args (const char *text)
140 es_fprintf (es_stderr, _("usage: %s [options] %s\n"), strusage (11), text);
146 /* Command line parsing. */
147 static enum cmd_and_opt_values
148 parse_arguments (ARGPARSE_ARGS *pargs, ARGPARSE_OPTS *popts)
150 enum cmd_and_opt_values cmd = 0;
151 int no_more_options = 0;
153 while (!no_more_options && optfile_parse (NULL, NULL, NULL, pargs, popts))
155 switch (pargs->r_opt)
157 case oQuiet: opt.quiet = 1; break;
158 case oVerbose: opt.verbose++; break;
160 if (parse_debug_flag (pargs->r.ret_str, &opt.debug, debug_flags))
162 pargs->r_opt = ARGPARSE_INVALID_ARG;
163 pargs->err = ARGPARSE_PRINT_ERROR;
168 opt.gpg_program = pargs->r.ret_str;
171 opt.use_sendmail = 1;
174 opt.output = pargs->r.ret_str;
183 default: pargs->err = 2; break;
192 /* gpg-wks-client main. */
194 main (int argc, char **argv)
198 enum cmd_and_opt_values cmd;
200 gnupg_reopen_std ("gpg-wks-client");
201 set_strusage (my_strusage);
202 log_set_prefix ("gpg-wks-client", GPGRT_LOG_WITH_PREFIX);
204 /* Make sure that our subsystems are ready. */
206 init_common_subsystems (&argc, &argv);
208 assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT);
209 setup_libassuan_logging (&opt.debug);
211 /* Parse the command line. */
214 pargs.flags = ARGPARSE_FLAG_KEEP;
215 cmd = parse_arguments (&pargs, opts);
217 if (log_get_errorcount (0))
220 /* Print a warning if an argument looks like an option. */
221 if (!opt.quiet && !(pargs.flags & ARGPARSE_FLAG_STOP_SEEN))
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]);
230 /* Set defaults for non given options. */
231 if (!opt.gpg_program)
232 opt.gpg_program = gnupg_module_name (GNUPG_MODULE_NAME_GPG);
234 /* Tell call-dirmngr what options we want. */
235 set_dirmngr_options (opt.verbose, (opt.debug & DBG_IPC_VALUE), 1);
237 /* Run the selected command. */
242 wrong_args ("--create FINGERPRINT USER-ID");
243 err = command_send (argv[0], argv[1]);
245 log_error ("creating request failed: %s\n", gpg_strerror (err));
250 wrong_args ("--receive < MIME-DATA");
251 err = wks_receive (es_stdin, command_receive_cb, NULL);
253 log_error ("processing mail failed: %s\n", gpg_strerror (err));
258 wrong_args ("--read < WKS-DATA");
259 err = process_confirmation_request (es_stdin);
261 log_error ("processing mail failed: %s\n", gpg_strerror (err));
269 return log_get_errorcount (0)? 1:0;
274 struct get_key_status_parm_s
282 get_key_status_cb (void *opaque, const char *keyword, char *args)
284 struct get_key_status_parm_s *parm = opaque;
286 /*log_debug ("%s: %s\n", keyword, args);*/
287 if (!strcmp (keyword, "EXPORTED"))
290 if (!ascii_strcasecmp (args, parm->fpr))
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.
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. */
303 get_key (estream_t *r_key, const char *fingerprint, const char *addrspec)
307 const char **argv = NULL;
308 estream_t key = NULL;
309 struct get_key_status_parm_s parm;
310 char *filterexp = NULL;
312 memset (&parm, 0, sizeof parm);
316 key = es_fopenmem (0, "w+b");
319 err = gpg_error_from_syserror ();
320 log_error ("error allocating memory buffer: %s\n", gpg_strerror (err));
324 filterexp = es_bsprintf ("keep-uid=mbox = %s", addrspec);
327 err = gpg_error_from_syserror ();
328 log_error ("error allocating memory buffer: %s\n", gpg_strerror (err));
332 ccparray_init (&ccp, 0);
334 ccparray_put (&ccp, "--no-options");
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);
350 ccparray_put (&ccp, NULL);
351 argv = ccparray_get (&ccp, NULL);
354 err = gpg_error_from_syserror ();
357 parm.fpr = fingerprint;
358 err = gnupg_exec_tool_stream (opt.gpg_program, argv, NULL,
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);
367 log_error ("export failed: %s\n", gpg_strerror (err));
384 /* Locate the key by fingerprint and userid and send a publication
387 command_send (const char *fingerprint, char *userid)
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;
396 if (classify_user_id (fingerprint, &desc, 1)
397 || !(desc.mode == KEYDB_SEARCH_MODE_FPR
398 || desc.mode == KEYDB_SEARCH_MODE_FPR20))
400 log_error (_("\"%s\" is not a fingerprint\n"), fingerprint);
401 err = gpg_error (GPG_ERR_INV_NAME);
404 addrspec = mailbox_from_userid (userid);
407 log_error (_("\"%s\" is not a proper mail address\n"), userid);
408 err = gpg_error (GPG_ERR_INV_USER_ID);
411 err = get_key (&key, fingerprint, addrspec);
415 /* Get the submission address. */
416 err = wkd_get_submission_address (addrspec, &submission_to);
419 log_info ("submitting request to '%s'\n", submission_to);
422 err = mime_maker_new (&mime, NULL);
425 err = mime_maker_add_header (mime, "From", addrspec);
428 err = mime_maker_add_header (mime, "To", submission_to);
431 err = mime_maker_add_header (mime, "Subject", "Key publishing request");
435 err = mime_maker_add_header (mime, "Content-type", "application/pgp-keys");
439 err = mime_maker_add_stream (mime, &key);
443 err = wks_send_mime (mime);
446 mime_maker_release (mime);
447 xfree (submission_to);
456 encrypt_response_status_cb (void *opaque, const char *keyword, char *args)
458 gpg_error_t *failure = opaque;
462 log_debug ("%s: %s\n", keyword, args);
464 if (!strcmp (keyword, "FAILURE"))
466 if (split_fields (args, fields, DIM (fields)) >= 2
467 && !strcmp (fields[0], "encrypt"))
468 *failure = strtoul (fields[1], NULL, 10);
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
482 encrypt_response (estream_t *r_output, estream_t input, const char *addrspec)
488 gpg_error_t gpg_err = 0;
492 output = es_fopenmem (0, "w+b");
495 err = gpg_error_from_syserror ();
496 log_error ("error allocating memory buffer: %s\n", gpg_strerror (err));
500 ccparray_init (&ccp, 0);
502 ccparray_put (&ccp, "--no-options");
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, "--");
517 ccparray_put (&ccp, NULL);
518 argv = ccparray_get (&ccp, NULL);
521 err = gpg_error_from_syserror ();
524 err = gnupg_exec_tool_stream (opt.gpg_program, argv, input,
526 encrypt_response_status_cb, &gpg_err);
531 log_error ("encryption failed: %s\n", gpg_strerror (err));
547 send_confirmation_response (const char *sender, const char *address,
548 const char *nonce, int encrypt)
551 estream_t body = NULL;
552 estream_t bodyenc = NULL;
553 mime_maker_t mime = NULL;
555 body = es_fopenmem (0, "w+b");
558 err = gpg_error_from_syserror ();
559 log_error ("error allocating memory buffer: %s\n", gpg_strerror (err));
563 /* It is fine to use 8 bit encoding because that is encrypted and
564 * only our client will see it. */
567 es_fputs ("Content-Type: application/vnd.gnupg.wks\n"
568 "Content-Transfer-Encoding: 8bit\n"
573 es_fprintf (body, ("type: confirmation-response\n"
584 err = encrypt_response (&bodyenc, body, sender);
591 err = mime_maker_new (&mime, NULL);
594 err = mime_maker_add_header (mime, "From", address);
597 err = mime_maker_add_header (mime, "To", sender);
600 err = mime_maker_add_header (mime, "Subject", "Key publication confirmation");
606 err = mime_maker_add_header (mime, "Content-Type",
607 "multipart/encrypted; "
608 "protocol=\"application/pgp-encrypted\"");
611 err = mime_maker_add_container (mime, "multipart/encrypted");
615 err = mime_maker_add_header (mime, "Content-Type",
616 "application/pgp-encrypted");
619 err = mime_maker_add_body (mime, "Version: 1\n");
622 err = mime_maker_add_header (mime, "Content-Type",
623 "application/octet-stream");
627 err = mime_maker_add_stream (mime, &bodyenc);
633 err = mime_maker_add_header (mime, "Content-Type",
634 "application/vnd.gnupg.wks");
637 err = mime_maker_add_stream (mime, &body);
642 err = wks_send_mime (mime);
645 mime_maker_release (mime);
652 /* Reply to a confirmation request. The MSG has already been
653 * decrypted and we only need to send the nonce back. */
655 process_confirmation_request (estream_t msg)
660 const char *value, *sender, *address, *nonce;
662 err = nvc_parse (&nvc, NULL, msg);
665 log_error ("parsing the WKS message failed: %s\n", gpg_strerror (err));
671 log_debug ("request follows:\n");
672 nvc_write (nvc, log_get_stream ());
675 /* Check that this is a confirmation request. */
676 if (!((item = nvc_lookup (nvc, "type:")) && (value = nve_value (item))
677 && !strcmp (value, "confirmation-request")))
680 log_error ("received unexpected wks message '%s'\n", value);
682 log_error ("received invalid wks message: %s\n", "'type' missing");
683 err = gpg_error (GPG_ERR_UNEXPECTED_MSG);
687 /* FIXME: Check that the fingerprint matches the key used to decrypt the
690 /* Get the address. */
691 if (!((item = nvc_lookup (nvc, "address:")) && (value = nve_value (item))
692 && is_valid_mailbox (value)))
694 log_error ("received invalid wks message: %s\n",
695 "'address' missing or invalid");
696 err = gpg_error (GPG_ERR_INV_DATA);
700 /* FIXME: Check that the "address" matches the User ID we want to
703 /* Get the sender. */
704 if (!((item = nvc_lookup (nvc, "sender:")) && (value = nve_value (item))
705 && is_valid_mailbox (value)))
707 log_error ("received invalid wks message: %s\n",
708 "'sender' missing or invalid");
709 err = gpg_error (GPG_ERR_INV_DATA);
713 /* FIXME: Check that the "sender" matches the From: address. */
716 if (!((item = nvc_lookup (nvc, "nonce:")) && (value = nve_value (item))
717 && strlen (value) > 16))
719 log_error ("received invalid wks message: %s\n",
720 "'nonce' missing or too short");
721 err = gpg_error (GPG_ERR_INV_DATA);
726 /* Send the confirmation. If no key was found, try again without
728 err = send_confirmation_response (sender, address, nonce, 1);
729 if (gpg_err_code (err) == GPG_ERR_NO_PUBKEY)
731 log_info ("no encryption key found - sending response in the clear\n");
732 err = send_confirmation_response (sender, address, nonce, 0);
741 /* Called from the MIME receiver to process the plain text data in MSG. */
743 command_receive_cb (void *opaque, const char *mediatype, estream_t msg)
749 if (!strcmp (mediatype, "application/vnd.gnupg.wks"))
750 err = process_confirmation_request (msg);
753 log_info ("ignoring unexpected message of type '%s'\n", mediatype);
754 err = gpg_error (GPG_ERR_UNEXPECTED_MSG);