diff --git a/tools/gpg-wks-client.c b/tools/gpg-wks-client.c index 143dbc846..f4257ec5f 100644 --- a/tools/gpg-wks-client.c +++ b/tools/gpg-wks-client.c @@ -91,6 +91,8 @@ static ARGPARSE_OPTS opts[] = { /* The list of supported debug flags. */ static struct debug_flags_s debug_flags [] = { + { DBG_MIME_VALUE , "mime" }, + { DBG_PARSER_VALUE , "parser" }, { DBG_CRYPTO_VALUE , "crypto" }, { DBG_MEMORY_VALUE , "memory" }, { DBG_MEMSTAT_VALUE, "memstat" }, @@ -103,9 +105,10 @@ static struct debug_flags_s debug_flags [] = static void wrong_args (const char *text) GPGRT_ATTR_NORETURN; static gpg_error_t command_supported (char *userid); static gpg_error_t command_send (const char *fingerprint, char *userid); -static gpg_error_t process_confirmation_request (estream_t msg); +static gpg_error_t read_confirmation_request (estream_t msg); static gpg_error_t command_receive_cb (void *opaque, - const char *mediatype, estream_t fp); + const char *mediatype, estream_t fp, + unsigned int flags); @@ -269,7 +272,7 @@ main (int argc, char **argv) case aRead: if (argc) wrong_args ("--read < WKS-DATA"); - err = process_confirmation_request (es_stdin); + err = read_confirmation_request (es_stdin); if (err) log_error ("processing mail failed: %s\n", gpg_strerror (err)); break; @@ -393,6 +396,83 @@ get_key (estream_t *r_key, const char *fingerprint, const char *addrspec) } + +static void +decrypt_stream_status_cb (void *opaque, const char *keyword, char *args) +{ + (void)opaque; + + if (DBG_CRYPTO) + log_debug ("gpg status: %s %s\n", keyword, args); +} + + +/* Decrypt the INPUT stream to a new stream which is stored at success + * at R_OUTPUT. */ +static gpg_error_t +decrypt_stream (estream_t *r_output, estream_t input) +{ + gpg_error_t err; + ccparray_t ccp; + const char **argv; + estream_t output; + + *r_output = NULL; + + output = es_fopenmem (0, "w+b"); + if (!output) + { + err = gpg_error_from_syserror (); + log_error ("error allocating memory buffer: %s\n", gpg_strerror (err)); + return err; + } + + ccparray_init (&ccp, 0); + + ccparray_put (&ccp, "--no-options"); + /* We limit the output to 64 KiB to avoid DoS using compression + * tricks. A regular client will anyway only send a minimal key; + * that is one w/o key signatures and attribute packets. */ + ccparray_put (&ccp, "--max-output=0x10000"); + if (!opt.verbose) + ccparray_put (&ccp, "--quiet"); + else if (opt.verbose > 1) + ccparray_put (&ccp, "--verbose"); + ccparray_put (&ccp, "--batch"); + ccparray_put (&ccp, "--status-fd=2"); + ccparray_put (&ccp, "--decrypt"); + ccparray_put (&ccp, "--"); + + ccparray_put (&ccp, NULL); + argv = ccparray_get (&ccp, NULL); + if (!argv) + { + err = gpg_error_from_syserror (); + goto leave; + } + err = gnupg_exec_tool_stream (opt.gpg_program, argv, input, + NULL, output, + decrypt_stream_status_cb, NULL); + if (err) + { + log_error ("decryption failed: %s\n", gpg_strerror (err)); + goto leave; + } + else if (opt.verbose) + log_info ("decryption succeeded\n"); + + es_rewind (output); + *r_output = output; + output = NULL; + + leave: + es_fclose (output); + xfree (argv); + return err; +} + + + /* Check whether the provider supports the WKS protocol. */ static gpg_error_t @@ -517,6 +597,11 @@ command_send (const char *fingerprint, char *userid) if (err) goto leave; + /* Tell server that we support draft version 3. */ + err = mime_maker_add_header (mime, "Wks-Draft-Version", "3"); + if (err) + goto leave; + err = mime_maker_add_stream (mime, &key); if (err) goto leave; @@ -539,8 +624,8 @@ encrypt_response_status_cb (void *opaque, const char *keyword, char *args) gpg_error_t *failure = opaque; char *fields[2]; - if (opt.debug) - log_debug ("%s: %s\n", keyword, args); + if (DBG_CRYPTO) + log_debug ("gpg status: %s %s\n", keyword, args); if (!strcmp (keyword, "FAILURE")) { @@ -747,7 +832,7 @@ process_confirmation_request (estream_t msg) goto leave; } - if (opt.debug) + if (DBG_MIME) { log_debug ("request follows:\n"); nvc_write (nvc, log_get_stream ()); @@ -822,16 +907,62 @@ process_confirmation_request (estream_t msg) } +/* Read a confirmation request and decrypt it if needed. This + * function may not be used with a mail or MIME message but only with + * the actual encrypted or plaintext WKS data. */ +static gpg_error_t +read_confirmation_request (estream_t msg) +{ + gpg_error_t err; + int c; + estream_t plaintext = NULL; + + /* We take a really simple approach to check whether MSG is + * encrypted: We know that an encrypted message is always armored + * and thus starts with a few dashes. It is even sufficient to + * check for a single dash, because that can never be a proper first + * WKS data octet. We need to skip leading spaces, though. */ + while ((c = es_fgetc (msg)) == ' ' || c == '\t' || c == '\r' || c == '\n') + ; + if (c == EOF) + { + log_error ("can't process an empty message\n"); + return gpg_error (GPG_ERR_INV_DATA); + } + if (es_ungetc (c, msg) != c) + { + log_error ("error ungetting octet from message\n"); + return gpg_error (GPG_ERR_INTERNAL); + } + + if (c != '-') + err = process_confirmation_request (msg); + else + { + err = decrypt_stream (&plaintext, msg); + if (err) + log_error ("decryption failed: %s\n", gpg_strerror (err)); + else + err = process_confirmation_request (plaintext); + } + + es_fclose (plaintext); + return err; +} + + /* Called from the MIME receiver to process the plain text data in MSG. */ static gpg_error_t -command_receive_cb (void *opaque, const char *mediatype, estream_t msg) +command_receive_cb (void *opaque, const char *mediatype, + estream_t msg, unsigned int flags) { gpg_error_t err; (void)opaque; + (void)flags; if (!strcmp (mediatype, "application/vnd.gnupg.wks")) - err = process_confirmation_request (msg); + err = read_confirmation_request (msg); else { log_info ("ignoring unexpected message of type '%s'\n", mediatype); diff --git a/tools/gpg-wks-server.c b/tools/gpg-wks-server.c index 96e5e05a5..408e3f549 100644 --- a/tools/gpg-wks-server.c +++ b/tools/gpg-wks-server.c @@ -102,6 +102,8 @@ static ARGPARSE_OPTS opts[] = { /* The list of supported debug flags. */ static struct debug_flags_s debug_flags [] = { + { DBG_MIME_VALUE , "mime" }, + { DBG_PARSER_VALUE , "parser" }, { DBG_CRYPTO_VALUE , "crypto" }, { DBG_MEMORY_VALUE , "memory" }, { DBG_MEMSTAT_VALUE, "memstat" }, @@ -116,6 +118,7 @@ struct server_ctx_s { char *fpr; strlist_t mboxes; /* List of addr-specs taken from the UIDs. */ + unsigned int draft_version_2:1; /* Client supports the draft 2. */ }; typedef struct server_ctx_s *server_ctx_t; @@ -123,7 +126,8 @@ typedef struct server_ctx_s *server_ctx_t; static gpg_error_t get_domain_list (strlist_t *r_list); static gpg_error_t command_receive_cb (void *opaque, - const char *mediatype, estream_t fp); + const char *mediatype, estream_t fp, + unsigned int flags); static gpg_error_t command_list_domains (void); static gpg_error_t command_cron (void); @@ -350,8 +354,8 @@ list_key_status_cb (void *opaque, const char *keyword, char *args) { server_ctx_t ctx = opaque; (void)ctx; - if (opt.debug) - log_debug ("%s: %s\n", keyword, args); + if (DBG_CRYPTO) + log_debug ("gpg status: %s %s\n", keyword, args); } @@ -629,8 +633,8 @@ encrypt_stream_status_cb (void *opaque, const char *keyword, char *args) { (void)opaque; - if (opt.debug) - log_debug ("%s: %s\n", keyword, args); + if (DBG_CRYPTO) + log_debug ("gpg status: %s %s\n", keyword, args); } @@ -698,6 +702,78 @@ encrypt_stream (estream_t *r_output, estream_t input, const char *keyfile) } +static void +sign_stream_status_cb (void *opaque, const char *keyword, char *args) +{ + (void)opaque; + + if (DBG_CRYPTO) + log_debug ("gpg status: %s %s\n", keyword, args); +} + +/* Sign the INPUT stream to a new stream which is stored at success at + * R_OUTPUT. A detached signature is created using the key specified + * by USERID. */ +static gpg_error_t +sign_stream (estream_t *r_output, estream_t input, const char *userid) +{ + gpg_error_t err; + ccparray_t ccp; + const char **argv; + estream_t output; + + *r_output = NULL; + + output = es_fopenmem (0, "w+b"); + if (!output) + { + err = gpg_error_from_syserror (); + log_error ("error allocating memory buffer: %s\n", gpg_strerror (err)); + return err; + } + + ccparray_init (&ccp, 0); + + ccparray_put (&ccp, "--no-options"); + if (!opt.verbose) + ccparray_put (&ccp, "--quiet"); + else if (opt.verbose > 1) + ccparray_put (&ccp, "--verbose"); + ccparray_put (&ccp, "--batch"); + ccparray_put (&ccp, "--status-fd=2"); + ccparray_put (&ccp, "--armor"); + ccparray_put (&ccp, "--local-user"); + ccparray_put (&ccp, userid); + ccparray_put (&ccp, "--detach-sign"); + ccparray_put (&ccp, "--"); + + ccparray_put (&ccp, NULL); + argv = ccparray_get (&ccp, NULL); + if (!argv) + { + err = gpg_error_from_syserror (); + goto leave; + } + err = gnupg_exec_tool_stream (opt.gpg_program, argv, input, + NULL, output, + sign_stream_status_cb, NULL); + if (err) + { + log_error ("signing failed: %s\n", gpg_strerror (err)); + goto leave; + } + + es_rewind (output); + *r_output = output; + output = NULL; + + leave: + es_fclose (output); + xfree (argv); + return err; +} + + /* Get the submission address for address MBOX. Caller must free the * value. If no address can be found NULL is returned. */ static char * @@ -933,6 +1009,8 @@ send_confirmation_request (server_ctx_t ctx, gpg_error_t err; estream_t body = NULL; estream_t bodyenc = NULL; + estream_t signeddata = NULL; + estream_t signature = NULL; mime_maker_t mime = NULL; char *from_buffer = NULL; const char *from; @@ -958,12 +1036,16 @@ send_confirmation_request (server_ctx_t ctx, log_error ("error allocating memory buffer: %s\n", gpg_strerror (err)); goto leave; } - /* It is fine to use 8 bit encoding because that is encrypted and - * only our client will see it. */ - es_fputs ("Content-Type: application/vnd.gnupg.wks\n" - "Content-Transfer-Encoding: 8bit\n" - "\n", - body); + + if (!ctx->draft_version_2) + { + /* It is fine to use 8 bit encoding because that is encrypted and + * only our client will see it. */ + es_fputs ("Content-Type: application/vnd.gnupg.wks\n" + "Content-Transfer-Encoding: 8bit\n" + "\n", + body); + } es_fprintf (body, ("type: confirmation-request\n" "sender: %s\n" @@ -1002,35 +1084,117 @@ send_confirmation_request (server_ctx_t ctx, goto leave; } - err = mime_maker_add_header (mime, "Content-Type", - "multipart/encrypted; " - "protocol=\"application/pgp-encrypted\""); - if (err) - goto leave; - err = mime_maker_add_container (mime); - if (err) - goto leave; + if (!ctx->draft_version_2) + { + err = mime_maker_add_header (mime, "Content-Type", + "multipart/encrypted; " + "protocol=\"application/pgp-encrypted\""); + if (err) + goto leave; + err = mime_maker_add_container (mime); + if (err) + goto leave; - err = mime_maker_add_header (mime, "Content-Type", - "application/pgp-encrypted"); - if (err) - goto leave; - err = mime_maker_add_body (mime, "Version: 1\n"); - if (err) - goto leave; - err = mime_maker_add_header (mime, "Content-Type", - "application/octet-stream"); - if (err) - goto leave; + err = mime_maker_add_header (mime, "Content-Type", + "application/pgp-encrypted"); + if (err) + goto leave; + err = mime_maker_add_body (mime, "Version: 1\n"); + if (err) + goto leave; + err = mime_maker_add_header (mime, "Content-Type", + "application/octet-stream"); + if (err) + goto leave; - err = mime_maker_add_stream (mime, &bodyenc); - if (err) - goto leave; + err = mime_maker_add_stream (mime, &bodyenc); + if (err) + goto leave; + + } + else + { + unsigned int partid; + + /* FIXME: Add micalg. */ + err = mime_maker_add_header (mime, "Content-Type", + "multipart/signed; " + "protocol=\"application/pgp-signature\""); + if (err) + goto leave; + err = mime_maker_add_container (mime); + if (err) + goto leave; + + err = mime_maker_add_header (mime, "Content-Type", "multipart/mixed"); + if (err) + goto leave; + + err = mime_maker_add_container (mime); + if (err) + goto leave; + partid = mime_maker_get_partid (mime); + + err = mime_maker_add_header (mime, "Content-Type", "text/plain"); + if (err) + goto leave; + + err = mime_maker_add_body + (mime, + "This message has been send to confirm your request\n" + "to publish your key. If you did not request a key\n" + "publication, simply ignore this message.\n" + "\n" + "Most mail software can handle this kind of message\n" + "automatically and thus you would not have seen this\n" + "message. It seems that your client does not fully\n" + "support this service. The web page\n" + "\n" + " https://gnupg.org/faq/wkd.html\n" + "\n" + "explains how you can process this message anyway in\n" + "a few manual steps.\n"); + if (err) + goto leave; + + err = mime_maker_add_header (mime, "Content-Type", + "application/vnd.gnupg.wks"); + if (err) + goto leave; + + err = mime_maker_add_stream (mime, &bodyenc); + if (err) + goto leave; + + err = mime_maker_end_container (mime); + if (err) + goto leave; + + mime_maker_dump_tree (mime); + err = mime_maker_get_part (mime, partid, &signeddata); + if (err) + goto leave; + + err = sign_stream (&signature, signeddata, from); + if (err) + goto leave; + + err = mime_maker_add_header (mime, "Content-Type", + "application/pgp-signature"); + if (err) + goto leave; + + err = mime_maker_add_stream (mime, &signature); + if (err) + goto leave; + } err = wks_send_mime (mime); leave: mime_maker_release (mime); + es_fclose (signature); + es_fclose (signeddata); es_fclose (bodyenc); es_fclose (body); xfree (from_buffer); @@ -1478,15 +1642,18 @@ process_confirmation_response (server_ctx_t ctx, estream_t msg) /* Called from the MIME receiver to process the plain text data in MSG . */ static gpg_error_t -command_receive_cb (void *opaque, const char *mediatype, estream_t msg) +command_receive_cb (void *opaque, const char *mediatype, + estream_t msg, unsigned int flags) { gpg_error_t err; struct server_ctx_s ctx; - memset (&ctx, 0, sizeof ctx); - (void)opaque; + memset (&ctx, 0, sizeof ctx); + if ((flags & WKS_RECEIVE_DRAFT2)) + ctx.draft_version_2 = 1; + if (!strcmp (mediatype, "application/pgp-keys")) err = process_new_key (&ctx, msg); else if (!strcmp (mediatype, "application/vnd.gnupg.wks")) diff --git a/tools/gpg-wks.h b/tools/gpg-wks.h index 85000cc71..f8b6cfdc4 100644 --- a/tools/gpg-wks.h +++ b/tools/gpg-wks.h @@ -39,12 +39,18 @@ struct } opt; /* Debug values and macros. */ +#define DBG_MIME_VALUE 1 /* Debug the MIME structure. */ +#define DBG_PARSER_VALUE 2 /* Debug the Mail parser. */ #define DBG_CRYPTO_VALUE 4 /* Debug low level crypto. */ #define DBG_MEMORY_VALUE 32 /* Debug memory allocation stuff. */ #define DBG_MEMSTAT_VALUE 128 /* Show memory statistics. */ #define DBG_IPC_VALUE 1024 /* Debug assuan communication. */ #define DBG_EXTPROG_VALUE 16384 /* debug external program calls */ +#define DBG_MIME (opt.debug & DBG_MIME_VALUE) +#define DBG_PARSER (opt.debug & DBG_PARSER_VALUE) +#define DBG_CRYPTO (opt.debug & DBG_CRYPTO_VALUE) + /* The parsed policy flags. */ struct policy_flags_s @@ -64,10 +70,15 @@ gpg_error_t wks_parse_policy (policy_flags_t flags, estream_t stream, int ignore_unknown); /*-- wks-receive.c --*/ + +/* Flag values for the receive callback. */ +#define WKS_RECEIVE_DRAFT2 1 + gpg_error_t wks_receive (estream_t fp, gpg_error_t (*result_cb)(void *opaque, const char *mediatype, - estream_t data), + estream_t data, + unsigned int flags), void *cb_data); diff --git a/tools/wks-receive.c b/tools/wks-receive.c index 59141fcdc..0deca9b20 100644 --- a/tools/wks-receive.c +++ b/tools/wks-receive.c @@ -26,6 +26,7 @@ #include "ccparray.h" #include "exectool.h" #include "gpg-wks.h" +#include "rfc822parse.h" #include "mime-parser.h" @@ -41,6 +42,7 @@ /* Data for a received object. */ struct receive_ctx_s { + mime_parser_t parser; estream_t encrypted; estream_t plaintext; estream_t signeddata; @@ -49,6 +51,8 @@ struct receive_ctx_s estream_t wkd_data; unsigned int collect_key_data:1; unsigned int collect_wkd_data:1; + unsigned int draft_version_2:1; /* This is a draft version 2 request. */ + unsigned int multipart_mixed_seen:1; }; typedef struct receive_ctx_s *receive_ctx_t; @@ -59,7 +63,8 @@ decrypt_data_status_cb (void *opaque, const char *keyword, char *args) { receive_ctx_t ctx = opaque; (void)ctx; - log_debug ("%s: %s\n", keyword, args); + if (DBG_CRYPTO) + log_debug ("gpg status: %s %s\n", keyword, args); } @@ -86,6 +91,7 @@ decrypt_data (receive_ctx_t ctx) ccparray_init (&ccp, 0); + ccparray_put (&ccp, "--no-options"); /* We limit the output to 64 KiB to avoid DoS using compression * tricks. A regular client will anyway only send a minimal key; * that is one w/o key signatures and attribute packets. */ @@ -113,7 +119,7 @@ decrypt_data (receive_ctx_t ctx) goto leave; } - if (opt.debug) + if (DBG_CRYPTO) { es_rewind (ctx->plaintext); log_debug ("plaintext: '"); @@ -133,7 +139,8 @@ verify_signature_status_cb (void *opaque, const char *keyword, char *args) { receive_ctx_t ctx = opaque; (void)ctx; - log_debug ("%s: %s\n", keyword, args); + if (DBG_CRYPTO) + log_debug ("gpg status: %s %s\n", keyword, args); } /* Verify the signed data. */ @@ -151,6 +158,7 @@ verify_signature (receive_ctx_t ctx) ccparray_init (&ccp, 0); + ccparray_put (&ccp, "--no-options"); ccparray_put (&ccp, "--batch"); if (opt.verbose) ccparray_put (&ccp, "--verbose"); @@ -177,6 +185,8 @@ verify_signature (receive_ctx_t ctx) goto leave; } + log_debug ("Fixme: Verification result is not used\n"); + leave: xfree (argv); } @@ -264,6 +274,22 @@ new_part (void *cookie, const char *mediatype, const char *mediasubtype) } else { + rfc822parse_t msg = mime_parser_rfc822parser (ctx->parser); + if (msg) + { + char *value; + size_t valueoff; + + value = rfc822parse_get_field (msg, "Wks-Draft-Version", + -1, &valueoff); + if (value) + { + if (atoi(value+valueoff) >= 2 ) + ctx->draft_version_2 = 1; + free (value); + } + } + ctx->key_data = es_fopenmem (0, "w+b"); if (!ctx->key_data) { @@ -303,6 +329,19 @@ new_part (void *cookie, const char *mediatype, const char *mediasubtype) } } } + else if (!strcmp (mediatype, "multipart") + && !strcmp (mediasubtype, "mixed")) + { + ctx->multipart_mixed_seen = 1; + } + else if (!strcmp (mediatype, "text")) + { + /* Check that we receive a text part only after a + * application/mixed. This is actually a too simple test and we + * should eventually employ a strict MIME structure check. */ + if (!ctx->multipart_mixed_seen) + err = gpg_error (GPG_ERR_UNEXPECTED_MSG); + } else { log_error ("unexpected '%s/%s' message part\n", mediatype, mediasubtype); @@ -320,7 +359,7 @@ part_data (void *cookie, const void *data, size_t datalen) if (data) { - if (opt.debug) + if (DBG_MIME) log_debug ("part_data: '%.*s'\n", (int)datalen, (const char*)data); if (ctx->collect_key_data) { @@ -337,7 +376,7 @@ part_data (void *cookie, const void *data, size_t datalen) } else { - if (opt.debug) + if (DBG_MIME) log_debug ("part_data: finished\n"); ctx->collect_key_data = 0; ctx->collect_wkd_data = 0; @@ -353,7 +392,8 @@ gpg_error_t wks_receive (estream_t fp, gpg_error_t (*result_cb)(void *opaque, const char *mediatype, - estream_t data), + estream_t data, + unsigned int flags), void *cb_data) { gpg_error_t err; @@ -361,6 +401,7 @@ wks_receive (estream_t fp, mime_parser_t parser; estream_t plaintext = NULL; int c; + unsigned int flags = 0; ctx = xtrycalloc (1, sizeof *ctx); if (!ctx) @@ -369,14 +410,16 @@ wks_receive (estream_t fp, err = mime_parser_new (&parser, ctx); if (err) goto leave; - if (opt.verbose > 1 || opt.debug) - mime_parser_set_verbose (parser, opt.debug? 10: 1); + if (DBG_PARSER) + mime_parser_set_verbose (parser, 1); mime_parser_set_new_part (parser, new_part); mime_parser_set_part_data (parser, part_data); mime_parser_set_collect_encrypted (parser, collect_encrypted); mime_parser_set_collect_signeddata (parser, collect_signeddata); mime_parser_set_collect_signature (parser, collect_signature); + ctx->parser = parser; + err = mime_parser_parse (parser, fp); if (err) goto leave; @@ -385,6 +428,11 @@ wks_receive (estream_t fp, log_info ("key data found\n"); if (ctx->wkd_data) log_info ("wkd data found\n"); + if (ctx->draft_version_2) + { + log_info ("draft version 2 requested\n"); + flags |= WKS_RECEIVE_DRAFT2; + } if (ctx->plaintext) { @@ -412,7 +460,7 @@ wks_receive (estream_t fp, if (ctx->key_data) { - if (opt.debug) + if (DBG_MIME) { es_rewind (ctx->key_data); log_debug ("Key: '"); @@ -424,14 +472,15 @@ wks_receive (estream_t fp, if (result_cb) { es_rewind (ctx->key_data); - err = result_cb (cb_data, "application/pgp-keys", ctx->key_data); + err = result_cb (cb_data, "application/pgp-keys", + ctx->key_data, flags); if (err) goto leave; } } if (ctx->wkd_data) { - if (opt.debug) + if (DBG_MIME) { es_rewind (ctx->wkd_data); log_debug ("WKD: '"); @@ -443,7 +492,8 @@ wks_receive (estream_t fp, if (result_cb) { es_rewind (ctx->wkd_data); - err = result_cb (cb_data, "application/vnd.gnupg.wks", ctx->wkd_data); + err = result_cb (cb_data, "application/vnd.gnupg.wks", + ctx->wkd_data, flags); if (err) goto leave; } @@ -453,6 +503,7 @@ wks_receive (estream_t fp, leave: es_fclose (plaintext); mime_parser_release (parser); + ctx->parser = NULL; es_fclose (ctx->encrypted); es_fclose (ctx->plaintext); es_fclose (ctx->signeddata);