--- /dev/null
+Return-Path: <jrollins@servo.finestructure.net>\r
+X-Original-To: notmuch@notmuchmail.org\r
+Delivered-To: notmuch@notmuchmail.org\r
+Received: from localhost (localhost [127.0.0.1])\r
+ by olra.theworths.org (Postfix) with ESMTP id 6C579429E5A\r
+ for <notmuch@notmuchmail.org>; Wed, 25 May 2011 18:01:46 -0700 (PDT)\r
+X-Virus-Scanned: Debian amavisd-new at olra.theworths.org\r
+X-Spam-Flag: NO\r
+X-Spam-Score: -1.921\r
+X-Spam-Level: \r
+X-Spam-Status: No, score=-1.921 tagged_above=-999 required=5\r
+ tests=[NO_DNS_FOR_FROM=0.379, RCVD_IN_DNSWL_MED=-2.3]\r
+ autolearn=disabled\r
+Received: from olra.theworths.org ([127.0.0.1])\r
+ by localhost (olra.theworths.org [127.0.0.1]) (amavisd-new, port 10024)\r
+ with ESMTP id V6WZpmSd4Ove for <notmuch@notmuchmail.org>;\r
+ Wed, 25 May 2011 18:01:43 -0700 (PDT)\r
+Received: from outgoing-mail.its.caltech.edu (outgoing-mail.its.caltech.edu\r
+ [131.215.239.19])\r
+ by olra.theworths.org (Postfix) with ESMTP id 1CA9A429E30\r
+ for <notmuch@notmuchmail.org>; Wed, 25 May 2011 18:01:37 -0700 (PDT)\r
+Received: from earth-doxen.imss.caltech.edu (localhost [127.0.0.1])\r
+ by earth-doxen-postvirus (Postfix) with ESMTP id AC13666E049A;\r
+ Wed, 25 May 2011 18:01:35 -0700 (PDT)\r
+X-Spam-Scanned: at Caltech-IMSS on earth-doxen by amavisd-new\r
+Received: from servo.finestructure.net (gwave-104.ligo.caltech.edu\r
+ [131.215.114.104]) (Authenticated sender: jrollins)\r
+ by earth-doxen-submit (Postfix) with ESMTP id AC04366E04AC;\r
+ Wed, 25 May 2011 18:01:27 -0700 (PDT)\r
+Received: by servo.finestructure.net (Postfix, from userid 1000)\r
+ id 801DD7CA; Wed, 25 May 2011 18:01:26 -0700 (PDT)\r
+From: Jameson Graef Rollins <jrollins@finestructure.net>\r
+To: notmuch@notmuchmail.org\r
+Subject: [PATCH 08/11] Add signature verification of PGP/MIME-signed parts\r
+ with --verify.\r
+Date: Wed, 25 May 2011 18:01:17 -0700\r
+Message-Id: <1306371680-19441-9-git-send-email-jrollins@finestructure.net>\r
+X-Mailer: git-send-email 1.7.4.4\r
+In-Reply-To: <1306371680-19441-1-git-send-email-jrollins@finestructure.net>\r
+References: <1306371680-19441-1-git-send-email-jrollins@finestructure.net>\r
+X-BeenThere: notmuch@notmuchmail.org\r
+X-Mailman-Version: 2.1.13\r
+Precedence: list\r
+List-Id: "Use and development of the notmuch mail system."\r
+ <notmuch.notmuchmail.org>\r
+List-Unsubscribe: <http://notmuchmail.org/mailman/options/notmuch>,\r
+ <mailto:notmuch-request@notmuchmail.org?subject=unsubscribe>\r
+List-Archive: <http://notmuchmail.org/pipermail/notmuch>\r
+List-Post: <mailto:notmuch@notmuchmail.org>\r
+List-Help: <mailto:notmuch-request@notmuchmail.org?subject=help>\r
+List-Subscribe: <http://notmuchmail.org/mailman/listinfo/notmuch>,\r
+ <mailto:notmuch-request@notmuchmail.org?subject=subscribe>\r
+X-List-Received-Date: Thu, 26 May 2011 01:01:47 -0000\r
+\r
+This is primarily for notmuch-show, although the functionality is\r
+added to show-message. Once signatures are processed a new\r
+part_sigstatus formatter is emitted, and the entire multipart/signed\r
+part is replaced with the contents of the signed part.\r
+\r
+At the moment only a json part_sigstatus formatting function is\r
+available. Emacs support to follow.\r
+\r
+The original work for this patch was done by\r
+\r
+ Daniel Kahn Gillmor <dkg@fifthhorseman.net>\r
+\r
+whose help with this functionality I greatly appreciate.\r
+---\r
+ Makefile.local | 1 +\r
+ notmuch-client.h | 5 ++\r
+ notmuch-gmime-session.c | 49 ++++++++++++++++++++++++\r
+ notmuch-reply.c | 2 +\r
+ notmuch-show.c | 96 +++++++++++++++++++++++++++++++++++++++++++++++\r
+ notmuch.1 | 11 +++++\r
+ notmuch.c | 8 ++++\r
+ show-message.c | 41 +++++++++++++++++++-\r
+ 8 files changed, 212 insertions(+), 1 deletions(-)\r
+ create mode 100644 notmuch-gmime-session.c\r
+\r
+diff --git a/Makefile.local b/Makefile.local\r
+index 8a8832d..f726f1f 100644\r
+--- a/Makefile.local\r
++++ b/Makefile.local\r
+@@ -246,6 +246,7 @@ notmuch_client_srcs = \\r
+ notmuch-show.c \\r
+ notmuch-tag.c \\r
+ notmuch-time.c \\r
++ notmuch-gmime-session.c \\r
+ query-string.c \\r
+ show-message.c \\r
+ json.c \\r
+diff --git a/notmuch-client.h b/notmuch-client.h\r
+index b278bc7..dc4ed7a 100644\r
+--- a/notmuch-client.h\r
++++ b/notmuch-client.h\r
+@@ -67,6 +67,7 @@ typedef struct notmuch_show_format {\r
+ const char *body_start;\r
+ void (*part_start) (GMimeObject *part,\r
+ int *part_count);\r
++ void (*part_sigstatus) (const GMimeSignatureValidity* validity);\r
+ void (*part_content) (GMimeObject *part);\r
+ void (*part_end) (GMimeObject *part);\r
+ const char *part_sep;\r
+@@ -80,6 +81,7 @@ typedef struct notmuch_show_params {\r
+ int entire_thread;\r
+ int raw;\r
+ int part;\r
++ GMimeCipherContext* cryptoctx;\r
+ } notmuch_show_params_t;\r
+ \r
+ /* There's no point in continuing when we've detected that we've done\r
+@@ -233,4 +235,7 @@ notmuch_config_set_maildir_synchronize_flags (notmuch_config_t *config,\r
+ notmuch_bool_t\r
+ debugger_is_active (void);\r
+ \r
++GType\r
++notmuch_gmime_session_get_type (void);\r
++\r
+ #endif\r
+diff --git a/notmuch-gmime-session.c b/notmuch-gmime-session.c\r
+new file mode 100644\r
+index 0000000..d83d9b3\r
+--- /dev/null\r
++++ b/notmuch-gmime-session.c\r
+@@ -0,0 +1,49 @@\r
++#include "notmuch-client.h"\r
++\r
++/* CRUFTY BOILERPLATE for GMimeSession (dkg thinks this will go away once GMime 2.6 comes out) */\r
++typedef struct _NotmuchGmimeSession NotmuchGmimeSession;\r
++typedef struct _NotmuchGmimeSessionClass NotmuchGmimeSessionClass;\r
++\r
++struct _NotmuchGmimeSession {\r
++ GMimeSession parent_object;\r
++};\r
++\r
++struct _NotmuchGmimeSessionClass {\r
++ GMimeSessionClass parent_class;\r
++};\r
++\r
++static void notmuch_gmime_session_class_init (NotmuchGmimeSessionClass *klass);\r
++\r
++static GMimeSessionClass *parent_class = NULL;\r
++\r
++GType\r
++notmuch_gmime_session_get_type (void)\r
++{\r
++ static GType type = 0;\r
++\r
++ if (!type) {\r
++ static const GTypeInfo info = {\r
++ sizeof (NotmuchGmimeSessionClass),\r
++ NULL, /* base_class_init */\r
++ NULL, /* base_class_finalize */\r
++ (GClassInitFunc) notmuch_gmime_session_class_init,\r
++ NULL, /* class_finalize */\r
++ NULL, /* class_data */\r
++ sizeof (NotmuchGmimeSession),\r
++ 0, /* n_preallocs */\r
++ NULL, /* object_init */\r
++ NULL, /* value_table */\r
++ };\r
++ type = g_type_register_static (GMIME_TYPE_SESSION, "NotmuchGmimeSession", &info, 0);\r
++ }\r
++ return type;\r
++}\r
++\r
++static void\r
++notmuch_gmime_session_class_init (NotmuchGmimeSessionClass *klass)\r
++{\r
++ GMimeSessionClass *session_class = GMIME_SESSION_CLASS (klass);\r
++ parent_class = g_type_class_ref (GMIME_TYPE_SESSION);\r
++ session_class->request_passwd = NULL;\r
++}\r
++/* END CRUFTY BOILERPLATE */\r
+diff --git a/notmuch-reply.c b/notmuch-reply.c\r
+index 9c35475..99bb15f 100644\r
+--- a/notmuch-reply.c\r
++++ b/notmuch-reply.c\r
+@@ -33,6 +33,7 @@ static const notmuch_show_format_t format_reply = {\r
+ "", NULL, "",\r
+ "",\r
+ NULL,\r
++ NULL,\r
+ reply_part_content,\r
+ NULL,\r
+ "",\r
+@@ -447,6 +448,7 @@ notmuch_reply_format_default(void *ctx, notmuch_config_t *config, notmuch_query_\r
+ const notmuch_show_format_t *format = &format_reply;\r
+ notmuch_show_params_t params;\r
+ params.part = -1;\r
++ params.cryptoctx = NULL;\r
+ \r
+ for (messages = notmuch_query_search_messages (query);\r
+ notmuch_messages_valid (messages);\r
+diff --git a/notmuch-show.c b/notmuch-show.c\r
+index 363cdbf..bb54e56 100644\r
+--- a/notmuch-show.c\r
++++ b/notmuch-show.c\r
+@@ -44,6 +44,7 @@ static const notmuch_show_format_t format_text = {\r
+ "\fheader{\n", format_headers_text, "\fheader}\n",\r
+ "\fbody{\n",\r
+ format_part_start_text,\r
++ NULL,\r
+ format_part_content_text,\r
+ format_part_end_text,\r
+ "",\r
+@@ -65,6 +66,9 @@ format_part_start_json (unused (GMimeObject *part),\r
+ int *part_count);\r
+ \r
+ static void\r
++format_part_sigstatus_json (const GMimeSignatureValidity* validity);\r
++\r
++static void\r
+ format_part_content_json (GMimeObject *part);\r
+ \r
+ static void\r
+@@ -76,6 +80,7 @@ static const notmuch_show_format_t format_json = {\r
+ ", \"headers\": {", format_headers_json, "}",\r
+ ", \"body\": [",\r
+ format_part_start_json,\r
++ format_part_sigstatus_json,\r
+ format_part_content_json,\r
+ format_part_end_json,\r
+ ", ",\r
+@@ -97,6 +102,7 @@ static const notmuch_show_format_t format_mbox = {\r
+ NULL,\r
+ NULL,\r
+ NULL,\r
++ NULL,\r
+ "",\r
+ "",\r
+ "", "",\r
+@@ -112,6 +118,7 @@ static const notmuch_show_format_t format_raw = {\r
+ "", NULL, "",\r
+ "",\r
+ NULL,\r
++ NULL,\r
+ format_part_content_raw,\r
+ NULL,\r
+ "",\r
+@@ -396,6 +403,22 @@ show_part_content (GMimeObject *part, GMimeStream *stream_out)\r
+ g_object_unref(stream_filter);\r
+ }\r
+ \r
++static const char*\r
++signerstatustostring (GMimeSignerStatus x)\r
++{\r
++ switch (x) {\r
++ case GMIME_SIGNER_STATUS_NONE:\r
++ return "none";\r
++ case GMIME_SIGNER_STATUS_GOOD:\r
++ return "good";\r
++ case GMIME_SIGNER_STATUS_BAD:\r
++ return "bad";\r
++ case GMIME_SIGNER_STATUS_ERROR:\r
++ return "error";\r
++ }\r
++ return "unknown";\r
++}\r
++\r
+ static void\r
+ format_part_start_text (GMimeObject *part, int *part_count)\r
+ {\r
+@@ -473,6 +496,65 @@ format_part_start_json (unused (GMimeObject *part), int *part_count)\r
+ }\r
+ \r
+ static void\r
++format_part_sigstatus_json (const GMimeSignatureValidity* validity)\r
++{\r
++ printf (", \"sigstatus\": [");\r
++\r
++ if (!validity) {\r
++ printf ("]");\r
++ return;\r
++ }\r
++\r
++ const GMimeSigner *signer = g_mime_signature_validity_get_signers (validity);\r
++ int first = 1;\r
++ void *ctx_quote = talloc_new (NULL);\r
++\r
++ while (signer) {\r
++ if (first)\r
++ first = 0;\r
++ else\r
++ printf (", ");\r
++\r
++ printf ("{");\r
++\r
++ /* status */\r
++ printf ("\"status\": %s", json_quote_str (ctx_quote, signerstatustostring(signer->status)));\r
++\r
++ if (signer->status == GMIME_SIGNER_STATUS_GOOD)\r
++ {\r
++ if (signer->fingerprint)\r
++ printf (", \"fingerprint\": %s", json_quote_str (ctx_quote, signer->fingerprint));\r
++ /* these dates are seconds since the epoch; should we\r
++ * provide a more human-readable format string? */\r
++ if (signer->created)\r
++ printf (", \"created\": %d", (int) signer->created);\r
++ if (signer->expires)\r
++ printf (", \"expires\": %d", (int) signer->expires);\r
++ /* output user id only if validity is FULL or ULTIMATE. */\r
++ /* note that gmime is using the term "trust" here, which\r
++ * is WRONG. It's actually user id "validity". */\r
++ if ((signer->name) && (signer->trust)) {\r
++ if ((signer->trust == GMIME_SIGNER_TRUST_FULLY) || (signer->trust == GMIME_SIGNER_TRUST_ULTIMATE))\r
++ printf (", \"userid\": %s", json_quote_str (ctx_quote, signer->name));\r
++ }\r
++ } else {\r
++ if (signer->keyid)\r
++ printf (", \"keyid\": %s", json_quote_str (ctx_quote, signer->keyid));\r
++ }\r
++ if (signer->errors != GMIME_SIGNER_ERROR_NONE) {\r
++ printf (", \"errors\": %x", signer->errors);\r
++ }\r
++\r
++ printf ("}");\r
++ signer = signer->next;\r
++ }\r
++\r
++ printf ("]");\r
++\r
++ talloc_free (ctx_quote);\r
++}\r
++\r
++static void\r
+ format_part_content_json (GMimeObject *part)\r
+ {\r
+ GMimeContentType *content_type = g_mime_object_get_content_type (GMIME_OBJECT (part));\r
+@@ -739,6 +821,7 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[]))\r
+ params.entire_thread = 0;\r
+ params.raw = 0;\r
+ params.part = -1;\r
++ params.cryptoctx = NULL;\r
+ \r
+ for (i = 0; i < argc && argv[i][0] == '-'; i++) {\r
+ if (strcmp (argv[i], "--") == 0) {\r
+@@ -767,6 +850,16 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[]))\r
+ params.part = atoi(argv[i] + sizeof ("--part=") - 1);\r
+ } else if (STRNCMP_LITERAL (argv[i], "--entire-thread") == 0) {\r
+ params.entire_thread = 1;\r
++ } else if (STRNCMP_LITERAL (argv[i], "--verify") == 0) {\r
++ if (params.cryptoctx == NULL) {\r
++ GMimeSession* session = g_object_new(notmuch_gmime_session_get_type(), NULL);\r
++ if (NULL == (params.cryptoctx = g_mime_gpg_context_new(session, "gpg")))\r
++ fprintf (stderr, "Failed to construct gpg context.\n");\r
++ else\r
++ g_mime_gpg_context_set_always_trust((GMimeGpgContext*)params.cryptoctx, FALSE);\r
++ g_object_unref (session);\r
++ session = NULL;\r
++ }\r
+ } else {\r
+ fprintf (stderr, "Unrecognized option: %s\n", argv[i]);\r
+ return 1;\r
+@@ -824,5 +917,8 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[]))\r
+ notmuch_query_destroy (query);\r
+ notmuch_database_close (notmuch);\r
+ \r
++ if (params.cryptoctx)\r
++ g_object_unref(params.cryptoctx);\r
++\r
+ return 0;\r
+ }\r
+diff --git a/notmuch.1 b/notmuch.1\r
+index a1c7fa8..daa9c6f 100644\r
+--- a/notmuch.1\r
++++ b/notmuch.1\r
+@@ -321,6 +321,17 @@ a depth-first walk of the message MIME structure, and are identified\r
+ in the 'json' or 'text' output formats.\r
+ .RE\r
+ \r
++.RS 4\r
++.TP 4\r
++.B \-\-verify\r
++\r
++Compute and report the validity of any MIME cryptographic signatures\r
++found in the selected content (ie. "multipart/signed" parts). Status\r
++of the signature will be reported (currently only supported with\r
++--format=json), and the multipart/signed part will be replaced by the\r
++signed data.\r
++.RE\r
++\r
+ A common use of\r
+ .B notmuch show\r
+ is to display a single thread of email messages. For this, use a\r
+diff --git a/notmuch.c b/notmuch.c\r
+index 262d677..cd3cb1b 100644\r
+--- a/notmuch.c\r
++++ b/notmuch.c\r
+@@ -294,6 +294,14 @@ static command_t commands[] = {\r
+ "\t\tmessage MIME structure, and are identified in the 'json' or\n"\r
+ "\t\t'text' output formats.\n"\r
+ "\n"\r
++ "\t--verify\n"\r
++ "\n"\r
++ "\t\tCompute and report the validity of any MIME cryptographic\n"\r
++ "\t\tsignatures found in the selected content (ie.\n"\r
++ "\t\t\"multipart/signed\" parts). Status of the signature will be\n"\r
++ "\t\treported (currently only supported with --format=json) and\n"\r
++ "\t\tthe multipart/signed part will be replaced by the signed data.\n"\r
++ "\n"\r
+ "\n"\r
+ "\tA common use of \"notmuch show\" is to display a single\n"\r
+ "\tthread of email messages. For this, use a search term of\n"\r
+diff --git a/show-message.c b/show-message.c\r
+index fbae530..c90f310 100644\r
+--- a/show-message.c\r
++++ b/show-message.c\r
+@@ -51,9 +51,48 @@ show_message_part (GMimeObject *part,\r
+ \r
+ if (format->part_start)\r
+ format->part_start (part, &(state->part_count));\r
+- format->part_content (part);\r
+ }\r
+ \r
++ /* handle PGP/MIME parts */\r
++ if (GMIME_IS_MULTIPART (part) && params->cryptoctx) {\r
++ GMimeMultipart *multipart = GMIME_MULTIPART (part);\r
++ GError* err = NULL;\r
++\r
++ if (GMIME_IS_MULTIPART_SIGNED (part))\r
++ {\r
++ if ( g_mime_multipart_get_count (multipart) != 2 ) {\r
++ /* this violates RFC 3156 section 5, so we won't bother with it. */\r
++ fprintf (stderr,\r
++ "Error: %d part(s) for a multipart/signed message (should be exactly 2)\n",\r
++ g_mime_multipart_get_count (multipart));\r
++ } else {\r
++ /* For some reason the GMimeSignatureValidity returned\r
++ * here is not a const (inconsistent with that\r
++ * returned by\r
++ * g_mime_multipart_encrypted_get_signature_validity,\r
++ * and therefore needs to be properly disposed of.\r
++ * Hopefully the API will become more consistent. */\r
++ GMimeSignatureValidity *sigvalidity = g_mime_multipart_signed_verify (GMIME_MULTIPART_SIGNED (part), params->cryptoctx, &err);\r
++ if (!sigvalidity) {\r
++ fprintf (stderr, "Failed to verify signed part: %s\n", (err ? err->message : "no error explanation given"));\r
++ }\r
++ if ((selected || state->in_zone) && format->part_sigstatus)\r
++ format->part_sigstatus (sigvalidity);\r
++ /* extract only data part, and ignore signature part */\r
++ part = g_mime_multipart_get_part (multipart, 0);\r
++ if (sigvalidity)\r
++ g_mime_signature_validity_free (sigvalidity);\r
++ }\r
++ }\r
++\r
++ if (err)\r
++ g_error_free (err);\r
++ }\r
++ /* end handle PGP/MIME parts */\r
++\r
++ if (selected || state->in_zone)\r
++ format->part_content (part);\r
++\r
+ if (GMIME_IS_MULTIPART (part)) {\r
+ GMimeMultipart *multipart = GMIME_MULTIPART (part);\r
+ int i;\r
+-- \r
+1.7.4.4\r
+\r