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