From 2e653db38fc38dee92b2ee0564e27921132e7232 Mon Sep 17 00:00:00 2001 From: Jameson Graef Rollins Date: Wed, 25 May 2011 18:01:18 -0700 Subject: [PATCH] Add decryption of PGP/MIME-encrypted parts with --decrypt. This adds support for decrypting PGP/MIME-encrypted parts to notmuch-show and notmuch-reply. The --decrypt option implies --verify. Once decryption (and possibly signature verification) is done, a new part_encstatus formatter is emitted, the part_sigstatus formatter is emitted, and the entire multipart/encrypted part is replaced by the contents of the encrypted part. At the moment only a json part_encstatus formatting function is available, even though decryption is done for all formats. Emacs support to follow. --- notmuch-client.h | 2 ++ notmuch-reply.c | 36 ++++++++++++++++++++++++++++-------- notmuch-show.c | 25 ++++++++++++++++++++++++- notmuch.1 | 11 +++++++++++ notmuch.c | 8 ++++++++ show-message.c | 29 ++++++++++++++++++++++++++++- 6 files changed, 101 insertions(+), 10 deletions(-) diff --git a/notmuch-client.h b/notmuch-client.h index dc4ed7aa..8a272603 100644 --- a/notmuch-client.h +++ b/notmuch-client.h @@ -67,6 +67,7 @@ typedef struct notmuch_show_format { const char *body_start; void (*part_start) (GMimeObject *part, int *part_count); + void (*part_encstatus) (int status); void (*part_sigstatus) (const GMimeSignatureValidity* validity); void (*part_content) (GMimeObject *part); void (*part_end) (GMimeObject *part); @@ -82,6 +83,7 @@ typedef struct notmuch_show_params { int raw; int part; GMimeCipherContext* cryptoctx; + int decrypt; } notmuch_show_params_t; /* There's no point in continuing when we've detected that we've done diff --git a/notmuch-reply.c b/notmuch-reply.c index 99bb15fc..5265af6d 100644 --- a/notmuch-reply.c +++ b/notmuch-reply.c @@ -32,6 +32,7 @@ static const notmuch_show_format_t format_reply = { "", NULL, "", NULL, "", "", + NULL, NULL, NULL, reply_part_content, @@ -438,7 +439,10 @@ guess_from_received_header (notmuch_config_t *config, notmuch_message_t *message } static int -notmuch_reply_format_default(void *ctx, notmuch_config_t *config, notmuch_query_t *query) +notmuch_reply_format_default(void *ctx, + notmuch_config_t *config, + notmuch_query_t *query, + notmuch_show_params_t *params) { GMimeMessage *reply; notmuch_messages_t *messages; @@ -446,9 +450,6 @@ notmuch_reply_format_default(void *ctx, notmuch_config_t *config, notmuch_query_ const char *subject, *from_addr = NULL; const char *in_reply_to, *orig_references, *references; const notmuch_show_format_t *format = &format_reply; - notmuch_show_params_t params; - params.part = -1; - params.cryptoctx = NULL; for (messages = notmuch_query_search_messages (query); notmuch_messages_valid (messages); @@ -508,7 +509,7 @@ notmuch_reply_format_default(void *ctx, notmuch_config_t *config, notmuch_query_ notmuch_message_get_header (message, "from")); show_message_body (notmuch_message_get_filename (message), - format, ¶ms); + format, params); notmuch_message_destroy (message); } @@ -517,7 +518,10 @@ notmuch_reply_format_default(void *ctx, notmuch_config_t *config, notmuch_query_ /* This format is currently tuned for a git send-email --notmuch hook */ static int -notmuch_reply_format_headers_only(void *ctx, notmuch_config_t *config, notmuch_query_t *query) +notmuch_reply_format_headers_only(void *ctx, + notmuch_config_t *config, + notmuch_query_t *query, + unused (notmuch_show_params_t *params)) { GMimeMessage *reply; notmuch_messages_t *messages; @@ -579,9 +583,12 @@ notmuch_reply_command (void *ctx, int argc, char *argv[]) notmuch_query_t *query; char *opt, *query_string; int i, ret = 0; - int (*reply_format_func)(void *ctx, notmuch_config_t *config, notmuch_query_t *query); + int (*reply_format_func)(void *ctx, notmuch_config_t *config, notmuch_query_t *query, notmuch_show_params_t *params); + notmuch_show_params_t params; reply_format_func = notmuch_reply_format_default; + params.part = -1; + params.cryptoctx = NULL; for (i = 0; i < argc && argv[i][0] == '-'; i++) { if (strcmp (argv[i], "--") == 0) { @@ -598,6 +605,16 @@ notmuch_reply_command (void *ctx, int argc, char *argv[]) fprintf (stderr, "Invalid value for --format: %s\n", opt); return 1; } + } else if ((STRNCMP_LITERAL (argv[i], "--decrypt") == 0)) { + if (params.cryptoctx == NULL) { + GMimeSession* session = g_object_new(notmuch_gmime_session_get_type(), NULL); + if (NULL == (params.cryptoctx = g_mime_gpg_context_new(session, "gpg"))) + fprintf (stderr, "Failed to construct gpg context.\n"); + else + g_mime_gpg_context_set_always_trust((GMimeGpgContext*)params.cryptoctx, FALSE); + g_object_unref (session); + session = NULL; + } } else { fprintf (stderr, "Unrecognized option: %s\n", argv[i]); return 1; @@ -633,11 +650,14 @@ notmuch_reply_command (void *ctx, int argc, char *argv[]) return 1; } - if (reply_format_func (ctx, config, query) != 0) + if (reply_format_func (ctx, config, query, ¶ms) != 0) return 1; notmuch_query_destroy (query); notmuch_database_close (notmuch); + if (params.cryptoctx) + g_object_unref(params.cryptoctx); + return ret; } diff --git a/notmuch-show.c b/notmuch-show.c index bb54e563..e90f07e3 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -45,6 +45,7 @@ static const notmuch_show_format_t format_text = { "\fbody{\n", format_part_start_text, NULL, + NULL, format_part_content_text, format_part_end_text, "", @@ -65,6 +66,9 @@ static void format_part_start_json (unused (GMimeObject *part), int *part_count); +static void +format_part_encstatus_json (int status); + static void format_part_sigstatus_json (const GMimeSignatureValidity* validity); @@ -80,6 +84,7 @@ static const notmuch_show_format_t format_json = { ", \"headers\": {", format_headers_json, "}", ", \"body\": [", format_part_start_json, + format_part_encstatus_json, format_part_sigstatus_json, format_part_content_json, format_part_end_json, @@ -103,6 +108,7 @@ static const notmuch_show_format_t format_mbox = { NULL, NULL, NULL, + NULL, "", "", "", "", @@ -117,6 +123,7 @@ static const notmuch_show_format_t format_raw = { "", NULL, "", NULL, "", "", + NULL, NULL, NULL, format_part_content_raw, @@ -495,6 +502,18 @@ format_part_start_json (unused (GMimeObject *part), int *part_count) printf ("{\"id\": %d", *part_count); } +static void +format_part_encstatus_json (int status) +{ + printf (", \"encstatus\": [{\"status\": "); + if (status) { + printf ("\"good\""); + } else { + printf ("\"bad\""); + } + printf ("}]"); +} + static void format_part_sigstatus_json (const GMimeSignatureValidity* validity) { @@ -822,6 +841,7 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[])) params.raw = 0; params.part = -1; params.cryptoctx = NULL; + params.decrypt = 0; for (i = 0; i < argc && argv[i][0] == '-'; i++) { if (strcmp (argv[i], "--") == 0) { @@ -850,7 +870,8 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[])) params.part = atoi(argv[i] + sizeof ("--part=") - 1); } else if (STRNCMP_LITERAL (argv[i], "--entire-thread") == 0) { params.entire_thread = 1; - } else if (STRNCMP_LITERAL (argv[i], "--verify") == 0) { + } else if ((STRNCMP_LITERAL (argv[i], "--verify") == 0) || + (STRNCMP_LITERAL (argv[i], "--decrypt") == 0)) { if (params.cryptoctx == NULL) { GMimeSession* session = g_object_new(notmuch_gmime_session_get_type(), NULL); if (NULL == (params.cryptoctx = g_mime_gpg_context_new(session, "gpg"))) @@ -860,6 +881,8 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[])) g_object_unref (session); session = NULL; } + if (STRNCMP_LITERAL (argv[i], "--decrypt") == 0) + params.decrypt = 1; } else { fprintf (stderr, "Unrecognized option: %s\n", argv[i]); return 1; diff --git a/notmuch.1 b/notmuch.1 index daa9c6fb..c1aa4e32 100644 --- a/notmuch.1 +++ b/notmuch.1 @@ -332,6 +332,17 @@ of the signature will be reported (currently only supported with signed data. .RE +.RS 4 +.TP 4 +.B \-\-decrypt + +Decrypt any MIME encrypted parts found in the selected content +(ie. "multipart/encrypted" parts). Status of the decryption will be +reported (currently only supported with --format=json) and the +multipart/encrypted part will be replaced by the decrypted +content. +.RE + A common use of .B notmuch show is to display a single thread of email messages. For this, use a diff --git a/notmuch.c b/notmuch.c index cd3cb1b5..93f31944 100644 --- a/notmuch.c +++ b/notmuch.c @@ -302,6 +302,14 @@ static command_t commands[] = { "\t\treported (currently only supported with --format=json) and\n" "\t\tthe multipart/signed part will be replaced by the signed data.\n" "\n" + "\t--decrypt\n" + "\n" + "\t\tDecrypt any MIME encrypted parts found in the selected content\n" + "\t\t(ie. \"multipart/encrypted\" parts). Status of the decryption\n" + "\t\twill be reported (currently only supported with --format=json)\n" + "\t\tand the multipart/encrypted part will be replaced by the\n" + "\t\tdecrypted content.\n" + "\n" "\n" "\tA common use of \"notmuch show\" is to display a single\n" "\tthread of email messages. For this, use a search term of\n" diff --git a/show-message.c b/show-message.c index c90f310c..37252b22 100644 --- a/show-message.c +++ b/show-message.c @@ -58,7 +58,34 @@ show_message_part (GMimeObject *part, GMimeMultipart *multipart = GMIME_MULTIPART (part); GError* err = NULL; - if (GMIME_IS_MULTIPART_SIGNED (part)) + if (GMIME_IS_MULTIPART_ENCRYPTED (part) && params->decrypt) + { + if ( g_mime_multipart_get_count (multipart) != 2 ) { + /* this violates RFC 3156 section 4, so we won't bother with it. */ + fprintf (stderr, + "Error: %d part(s) for a multipart/encrypted message (should be exactly 2)\n", + g_mime_multipart_get_count (multipart)); + } else { + GMimeMultipartEncrypted *encrypteddata = GMIME_MULTIPART_ENCRYPTED (part); + GMimeObject *decryptedpart = g_mime_multipart_encrypted_decrypt (encrypteddata, params->cryptoctx, &err); + if (decryptedpart) { + if ((selected || state->in_zone) && format->part_encstatus) + format->part_encstatus (1); + const GMimeSignatureValidity *sigvalidity = g_mime_multipart_encrypted_get_signature_validity (encrypteddata); + if (!sigvalidity) + fprintf (stderr, "Failed to verify signed part: %s\n", (err ? err->message : "no error explanation given")); + if ((selected || state->in_zone) && format->part_sigstatus) + format->part_sigstatus (sigvalidity); + /* swap the part with the decrypted part */ + part = decryptedpart; + } else { + fprintf (stderr, "Failed to decrypt part: %s\n", (err ? err->message : "no error explanation given")); + if ((selected || state->in_zone) && format->part_encstatus) + format->part_encstatus (0); + } + } + } + else if (GMIME_IS_MULTIPART_SIGNED (part)) { if ( g_mime_multipart_get_count (multipart) != 2 ) { /* this violates RFC 3156 section 5, so we won't bother with it. */ -- 2.43.0