Return-Path: X-Original-To: notmuch@notmuchmail.org Delivered-To: notmuch@notmuchmail.org Received: from localhost (localhost [127.0.0.1]) by olra.theworths.org (Postfix) with ESMTP id D1255429E53 for ; Wed, 25 May 2011 18:01:39 -0700 (PDT) X-Virus-Scanned: Debian amavisd-new at olra.theworths.org X-Spam-Flag: NO X-Spam-Score: -1.921 X-Spam-Level: X-Spam-Status: No, score=-1.921 tagged_above=-999 required=5 tests=[NO_DNS_FOR_FROM=0.379, RCVD_IN_DNSWL_MED=-2.3] autolearn=disabled Received: from olra.theworths.org ([127.0.0.1]) by localhost (olra.theworths.org [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id LBv5ZjnunMC2 for ; Wed, 25 May 2011 18:01:36 -0700 (PDT) Received: from outgoing-mail.its.caltech.edu (outgoing-mail.its.caltech.edu [131.215.239.19]) by olra.theworths.org (Postfix) with ESMTP id 4A7D2429E39 for ; Wed, 25 May 2011 18:01:34 -0700 (PDT) Received: from fire-doxen.imss.caltech.edu (localhost [127.0.0.1]) by fire-doxen-postvirus (Postfix) with ESMTP id 4BFAF3281DB; Wed, 25 May 2011 17:55:19 -0700 (PDT) X-Spam-Scanned: at Caltech-IMSS on fire-doxen by amavisd-new Received: from servo.finestructure.net (gwave-104.ligo.caltech.edu [131.215.114.104]) (Authenticated sender: jrollins) by fire-doxen-submit (Postfix) with ESMTP id 37D343282CE; Wed, 25 May 2011 17:55:14 -0700 (PDT) Received: by servo.finestructure.net (Postfix, from userid 1000) id 82A837CB; Wed, 25 May 2011 18:01:26 -0700 (PDT) From: Jameson Graef Rollins To: notmuch@notmuchmail.org Subject: [PATCH 09/11] Add decryption of PGP/MIME-encrypted parts with --decrypt. Date: Wed, 25 May 2011 18:01:18 -0700 Message-Id: <1306371680-19441-10-git-send-email-jrollins@finestructure.net> X-Mailer: git-send-email 1.7.4.4 In-Reply-To: <1306371680-19441-1-git-send-email-jrollins@finestructure.net> References: <1306371680-19441-1-git-send-email-jrollins@finestructure.net> X-BeenThere: notmuch@notmuchmail.org X-Mailman-Version: 2.1.13 Precedence: list List-Id: "Use and development of the notmuch mail system." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Thu, 26 May 2011 01:01:40 -0000 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 dc4ed7a..8a27260 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 99bb15f..5265af6 100644 --- a/notmuch-reply.c +++ b/notmuch-reply.c @@ -34,6 +34,7 @@ static const notmuch_show_format_t format_reply = { "", NULL, NULL, + NULL, reply_part_content, NULL, "", @@ -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 bb54e56..e90f07e 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, "", @@ -66,6 +67,9 @@ 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); static void @@ -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, "", "", "", "", @@ -119,6 +125,7 @@ static const notmuch_show_format_t format_raw = { "", NULL, NULL, + NULL, format_part_content_raw, NULL, "", @@ -496,6 +503,18 @@ format_part_start_json (unused (GMimeObject *part), int *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) { printf (", \"sigstatus\": ["); @@ -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 daa9c6f..c1aa4e3 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 cd3cb1b..93f3194 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 c90f310..37252b2 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. */ -- 1.7.4.4