1 Return-Path: <jrollins@servo.finestructure.net>
\r
2 X-Original-To: notmuch@notmuchmail.org
\r
3 Delivered-To: notmuch@notmuchmail.org
\r
4 Received: from localhost (localhost [127.0.0.1])
\r
5 by olra.theworths.org (Postfix) with ESMTP id 6C579429E5A
\r
6 for <notmuch@notmuchmail.org>; Wed, 25 May 2011 18:01:46 -0700 (PDT)
\r
7 X-Virus-Scanned: Debian amavisd-new at olra.theworths.org
\r
11 X-Spam-Status: No, score=-1.921 tagged_above=-999 required=5
\r
12 tests=[NO_DNS_FOR_FROM=0.379, RCVD_IN_DNSWL_MED=-2.3]
\r
14 Received: from olra.theworths.org ([127.0.0.1])
\r
15 by localhost (olra.theworths.org [127.0.0.1]) (amavisd-new, port 10024)
\r
16 with ESMTP id V6WZpmSd4Ove for <notmuch@notmuchmail.org>;
\r
17 Wed, 25 May 2011 18:01:43 -0700 (PDT)
\r
18 Received: from outgoing-mail.its.caltech.edu (outgoing-mail.its.caltech.edu
\r
20 by olra.theworths.org (Postfix) with ESMTP id 1CA9A429E30
\r
21 for <notmuch@notmuchmail.org>; Wed, 25 May 2011 18:01:37 -0700 (PDT)
\r
22 Received: from earth-doxen.imss.caltech.edu (localhost [127.0.0.1])
\r
23 by earth-doxen-postvirus (Postfix) with ESMTP id AC13666E049A;
\r
24 Wed, 25 May 2011 18:01:35 -0700 (PDT)
\r
25 X-Spam-Scanned: at Caltech-IMSS on earth-doxen by amavisd-new
\r
26 Received: from servo.finestructure.net (gwave-104.ligo.caltech.edu
\r
27 [131.215.114.104]) (Authenticated sender: jrollins)
\r
28 by earth-doxen-submit (Postfix) with ESMTP id AC04366E04AC;
\r
29 Wed, 25 May 2011 18:01:27 -0700 (PDT)
\r
30 Received: by servo.finestructure.net (Postfix, from userid 1000)
\r
31 id 801DD7CA; Wed, 25 May 2011 18:01:26 -0700 (PDT)
\r
32 From: Jameson Graef Rollins <jrollins@finestructure.net>
\r
33 To: notmuch@notmuchmail.org
\r
34 Subject: [PATCH 08/11] Add signature verification of PGP/MIME-signed parts
\r
36 Date: Wed, 25 May 2011 18:01:17 -0700
\r
37 Message-Id: <1306371680-19441-9-git-send-email-jrollins@finestructure.net>
\r
38 X-Mailer: git-send-email 1.7.4.4
\r
39 In-Reply-To: <1306371680-19441-1-git-send-email-jrollins@finestructure.net>
\r
40 References: <1306371680-19441-1-git-send-email-jrollins@finestructure.net>
\r
41 X-BeenThere: notmuch@notmuchmail.org
\r
42 X-Mailman-Version: 2.1.13
\r
44 List-Id: "Use and development of the notmuch mail system."
\r
45 <notmuch.notmuchmail.org>
\r
46 List-Unsubscribe: <http://notmuchmail.org/mailman/options/notmuch>,
\r
47 <mailto:notmuch-request@notmuchmail.org?subject=unsubscribe>
\r
48 List-Archive: <http://notmuchmail.org/pipermail/notmuch>
\r
49 List-Post: <mailto:notmuch@notmuchmail.org>
\r
50 List-Help: <mailto:notmuch-request@notmuchmail.org?subject=help>
\r
51 List-Subscribe: <http://notmuchmail.org/mailman/listinfo/notmuch>,
\r
52 <mailto:notmuch-request@notmuchmail.org?subject=subscribe>
\r
53 X-List-Received-Date: Thu, 26 May 2011 01:01:47 -0000
\r
55 This is primarily for notmuch-show, although the functionality is
\r
56 added to show-message. Once signatures are processed a new
\r
57 part_sigstatus formatter is emitted, and the entire multipart/signed
\r
58 part is replaced with the contents of the signed part.
\r
60 At the moment only a json part_sigstatus formatting function is
\r
61 available. Emacs support to follow.
\r
63 The original work for this patch was done by
\r
65 Daniel Kahn Gillmor <dkg@fifthhorseman.net>
\r
67 whose help with this functionality I greatly appreciate.
\r
69 Makefile.local | 1 +
\r
70 notmuch-client.h | 5 ++
\r
71 notmuch-gmime-session.c | 49 ++++++++++++++++++++++++
\r
72 notmuch-reply.c | 2 +
\r
73 notmuch-show.c | 96 +++++++++++++++++++++++++++++++++++++++++++++++
\r
74 notmuch.1 | 11 +++++
\r
76 show-message.c | 41 +++++++++++++++++++-
\r
77 8 files changed, 212 insertions(+), 1 deletions(-)
\r
78 create mode 100644 notmuch-gmime-session.c
\r
80 diff --git a/Makefile.local b/Makefile.local
\r
81 index 8a8832d..f726f1f 100644
\r
82 --- a/Makefile.local
\r
83 +++ b/Makefile.local
\r
84 @@ -246,6 +246,7 @@ notmuch_client_srcs = \
\r
88 + notmuch-gmime-session.c \
\r
92 diff --git a/notmuch-client.h b/notmuch-client.h
\r
93 index b278bc7..dc4ed7a 100644
\r
94 --- a/notmuch-client.h
\r
95 +++ b/notmuch-client.h
\r
96 @@ -67,6 +67,7 @@ typedef struct notmuch_show_format {
\r
97 const char *body_start;
\r
98 void (*part_start) (GMimeObject *part,
\r
100 + void (*part_sigstatus) (const GMimeSignatureValidity* validity);
\r
101 void (*part_content) (GMimeObject *part);
\r
102 void (*part_end) (GMimeObject *part);
\r
103 const char *part_sep;
\r
104 @@ -80,6 +81,7 @@ typedef struct notmuch_show_params {
\r
108 + GMimeCipherContext* cryptoctx;
\r
109 } notmuch_show_params_t;
\r
111 /* There's no point in continuing when we've detected that we've done
\r
112 @@ -233,4 +235,7 @@ notmuch_config_set_maildir_synchronize_flags (notmuch_config_t *config,
\r
114 debugger_is_active (void);
\r
117 +notmuch_gmime_session_get_type (void);
\r
120 diff --git a/notmuch-gmime-session.c b/notmuch-gmime-session.c
\r
121 new file mode 100644
\r
122 index 0000000..d83d9b3
\r
124 +++ b/notmuch-gmime-session.c
\r
126 +#include "notmuch-client.h"
\r
128 +/* CRUFTY BOILERPLATE for GMimeSession (dkg thinks this will go away once GMime 2.6 comes out) */
\r
129 +typedef struct _NotmuchGmimeSession NotmuchGmimeSession;
\r
130 +typedef struct _NotmuchGmimeSessionClass NotmuchGmimeSessionClass;
\r
132 +struct _NotmuchGmimeSession {
\r
133 + GMimeSession parent_object;
\r
136 +struct _NotmuchGmimeSessionClass {
\r
137 + GMimeSessionClass parent_class;
\r
140 +static void notmuch_gmime_session_class_init (NotmuchGmimeSessionClass *klass);
\r
142 +static GMimeSessionClass *parent_class = NULL;
\r
145 +notmuch_gmime_session_get_type (void)
\r
147 + static GType type = 0;
\r
150 + static const GTypeInfo info = {
\r
151 + sizeof (NotmuchGmimeSessionClass),
\r
152 + NULL, /* base_class_init */
\r
153 + NULL, /* base_class_finalize */
\r
154 + (GClassInitFunc) notmuch_gmime_session_class_init,
\r
155 + NULL, /* class_finalize */
\r
156 + NULL, /* class_data */
\r
157 + sizeof (NotmuchGmimeSession),
\r
158 + 0, /* n_preallocs */
\r
159 + NULL, /* object_init */
\r
160 + NULL, /* value_table */
\r
162 + type = g_type_register_static (GMIME_TYPE_SESSION, "NotmuchGmimeSession", &info, 0);
\r
168 +notmuch_gmime_session_class_init (NotmuchGmimeSessionClass *klass)
\r
170 + GMimeSessionClass *session_class = GMIME_SESSION_CLASS (klass);
\r
171 + parent_class = g_type_class_ref (GMIME_TYPE_SESSION);
\r
172 + session_class->request_passwd = NULL;
\r
174 +/* END CRUFTY BOILERPLATE */
\r
175 diff --git a/notmuch-reply.c b/notmuch-reply.c
\r
176 index 9c35475..99bb15f 100644
\r
177 --- a/notmuch-reply.c
\r
178 +++ b/notmuch-reply.c
\r
179 @@ -33,6 +33,7 @@ static const notmuch_show_format_t format_reply = {
\r
184 reply_part_content,
\r
187 @@ -447,6 +448,7 @@ notmuch_reply_format_default(void *ctx, notmuch_config_t *config, notmuch_query_
\r
188 const notmuch_show_format_t *format = &format_reply;
\r
189 notmuch_show_params_t params;
\r
191 + params.cryptoctx = NULL;
\r
193 for (messages = notmuch_query_search_messages (query);
\r
194 notmuch_messages_valid (messages);
\r
195 diff --git a/notmuch-show.c b/notmuch-show.c
\r
196 index 363cdbf..bb54e56 100644
\r
197 --- a/notmuch-show.c
\r
198 +++ b/notmuch-show.c
\r
199 @@ -44,6 +44,7 @@ static const notmuch_show_format_t format_text = {
\r
200 "\fheader{\n", format_headers_text, "\fheader}\n",
\r
202 format_part_start_text,
\r
204 format_part_content_text,
\r
205 format_part_end_text,
\r
207 @@ -65,6 +66,9 @@ format_part_start_json (unused (GMimeObject *part),
\r
211 +format_part_sigstatus_json (const GMimeSignatureValidity* validity);
\r
214 format_part_content_json (GMimeObject *part);
\r
217 @@ -76,6 +80,7 @@ static const notmuch_show_format_t format_json = {
\r
218 ", \"headers\": {", format_headers_json, "}",
\r
220 format_part_start_json,
\r
221 + format_part_sigstatus_json,
\r
222 format_part_content_json,
\r
223 format_part_end_json,
\r
225 @@ -97,6 +102,7 @@ static const notmuch_show_format_t format_mbox = {
\r
233 @@ -112,6 +118,7 @@ static const notmuch_show_format_t format_raw = {
\r
238 format_part_content_raw,
\r
241 @@ -396,6 +403,22 @@ show_part_content (GMimeObject *part, GMimeStream *stream_out)
\r
242 g_object_unref(stream_filter);
\r
245 +static const char*
\r
246 +signerstatustostring (GMimeSignerStatus x)
\r
249 + case GMIME_SIGNER_STATUS_NONE:
\r
251 + case GMIME_SIGNER_STATUS_GOOD:
\r
253 + case GMIME_SIGNER_STATUS_BAD:
\r
255 + case GMIME_SIGNER_STATUS_ERROR:
\r
258 + return "unknown";
\r
262 format_part_start_text (GMimeObject *part, int *part_count)
\r
264 @@ -473,6 +496,65 @@ format_part_start_json (unused (GMimeObject *part), int *part_count)
\r
268 +format_part_sigstatus_json (const GMimeSignatureValidity* validity)
\r
270 + printf (", \"sigstatus\": [");
\r
277 + const GMimeSigner *signer = g_mime_signature_validity_get_signers (validity);
\r
279 + void *ctx_quote = talloc_new (NULL);
\r
290 + printf ("\"status\": %s", json_quote_str (ctx_quote, signerstatustostring(signer->status)));
\r
292 + if (signer->status == GMIME_SIGNER_STATUS_GOOD)
\r
294 + if (signer->fingerprint)
\r
295 + printf (", \"fingerprint\": %s", json_quote_str (ctx_quote, signer->fingerprint));
\r
296 + /* these dates are seconds since the epoch; should we
\r
297 + * provide a more human-readable format string? */
\r
298 + if (signer->created)
\r
299 + printf (", \"created\": %d", (int) signer->created);
\r
300 + if (signer->expires)
\r
301 + printf (", \"expires\": %d", (int) signer->expires);
\r
302 + /* output user id only if validity is FULL or ULTIMATE. */
\r
303 + /* note that gmime is using the term "trust" here, which
\r
304 + * is WRONG. It's actually user id "validity". */
\r
305 + if ((signer->name) && (signer->trust)) {
\r
306 + if ((signer->trust == GMIME_SIGNER_TRUST_FULLY) || (signer->trust == GMIME_SIGNER_TRUST_ULTIMATE))
\r
307 + printf (", \"userid\": %s", json_quote_str (ctx_quote, signer->name));
\r
310 + if (signer->keyid)
\r
311 + printf (", \"keyid\": %s", json_quote_str (ctx_quote, signer->keyid));
\r
313 + if (signer->errors != GMIME_SIGNER_ERROR_NONE) {
\r
314 + printf (", \"errors\": %x", signer->errors);
\r
318 + signer = signer->next;
\r
323 + talloc_free (ctx_quote);
\r
327 format_part_content_json (GMimeObject *part)
\r
329 GMimeContentType *content_type = g_mime_object_get_content_type (GMIME_OBJECT (part));
\r
330 @@ -739,6 +821,7 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[]))
\r
331 params.entire_thread = 0;
\r
334 + params.cryptoctx = NULL;
\r
336 for (i = 0; i < argc && argv[i][0] == '-'; i++) {
\r
337 if (strcmp (argv[i], "--") == 0) {
\r
338 @@ -767,6 +850,16 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[]))
\r
339 params.part = atoi(argv[i] + sizeof ("--part=") - 1);
\r
340 } else if (STRNCMP_LITERAL (argv[i], "--entire-thread") == 0) {
\r
341 params.entire_thread = 1;
\r
342 + } else if (STRNCMP_LITERAL (argv[i], "--verify") == 0) {
\r
343 + if (params.cryptoctx == NULL) {
\r
344 + GMimeSession* session = g_object_new(notmuch_gmime_session_get_type(), NULL);
\r
345 + if (NULL == (params.cryptoctx = g_mime_gpg_context_new(session, "gpg")))
\r
346 + fprintf (stderr, "Failed to construct gpg context.\n");
\r
348 + g_mime_gpg_context_set_always_trust((GMimeGpgContext*)params.cryptoctx, FALSE);
\r
349 + g_object_unref (session);
\r
353 fprintf (stderr, "Unrecognized option: %s\n", argv[i]);
\r
355 @@ -824,5 +917,8 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[]))
\r
356 notmuch_query_destroy (query);
\r
357 notmuch_database_close (notmuch);
\r
359 + if (params.cryptoctx)
\r
360 + g_object_unref(params.cryptoctx);
\r
364 diff --git a/notmuch.1 b/notmuch.1
\r
365 index a1c7fa8..daa9c6f 100644
\r
368 @@ -321,6 +321,17 @@ a depth-first walk of the message MIME structure, and are identified
\r
369 in the 'json' or 'text' output formats.
\r
376 +Compute and report the validity of any MIME cryptographic signatures
\r
377 +found in the selected content (ie. "multipart/signed" parts). Status
\r
378 +of the signature will be reported (currently only supported with
\r
379 +--format=json), and the multipart/signed part will be replaced by the
\r
385 is to display a single thread of email messages. For this, use a
\r
386 diff --git a/notmuch.c b/notmuch.c
\r
387 index 262d677..cd3cb1b 100644
\r
390 @@ -294,6 +294,14 @@ static command_t commands[] = {
\r
391 "\t\tmessage MIME structure, and are identified in the 'json' or\n"
\r
392 "\t\t'text' output formats.\n"
\r
396 + "\t\tCompute and report the validity of any MIME cryptographic\n"
\r
397 + "\t\tsignatures found in the selected content (ie.\n"
\r
398 + "\t\t\"multipart/signed\" parts). Status of the signature will be\n"
\r
399 + "\t\treported (currently only supported with --format=json) and\n"
\r
400 + "\t\tthe multipart/signed part will be replaced by the signed data.\n"
\r
403 "\tA common use of \"notmuch show\" is to display a single\n"
\r
404 "\tthread of email messages. For this, use a search term of\n"
\r
405 diff --git a/show-message.c b/show-message.c
\r
406 index fbae530..c90f310 100644
\r
407 --- a/show-message.c
\r
408 +++ b/show-message.c
\r
409 @@ -51,9 +51,48 @@ show_message_part (GMimeObject *part,
\r
411 if (format->part_start)
\r
412 format->part_start (part, &(state->part_count));
\r
413 - format->part_content (part);
\r
416 + /* handle PGP/MIME parts */
\r
417 + if (GMIME_IS_MULTIPART (part) && params->cryptoctx) {
\r
418 + GMimeMultipart *multipart = GMIME_MULTIPART (part);
\r
419 + GError* err = NULL;
\r
421 + if (GMIME_IS_MULTIPART_SIGNED (part))
\r
423 + if ( g_mime_multipart_get_count (multipart) != 2 ) {
\r
424 + /* this violates RFC 3156 section 5, so we won't bother with it. */
\r
426 + "Error: %d part(s) for a multipart/signed message (should be exactly 2)\n",
\r
427 + g_mime_multipart_get_count (multipart));
\r
429 + /* For some reason the GMimeSignatureValidity returned
\r
430 + * here is not a const (inconsistent with that
\r
432 + * g_mime_multipart_encrypted_get_signature_validity,
\r
433 + * and therefore needs to be properly disposed of.
\r
434 + * Hopefully the API will become more consistent. */
\r
435 + GMimeSignatureValidity *sigvalidity = g_mime_multipart_signed_verify (GMIME_MULTIPART_SIGNED (part), params->cryptoctx, &err);
\r
436 + if (!sigvalidity) {
\r
437 + fprintf (stderr, "Failed to verify signed part: %s\n", (err ? err->message : "no error explanation given"));
\r
439 + if ((selected || state->in_zone) && format->part_sigstatus)
\r
440 + format->part_sigstatus (sigvalidity);
\r
441 + /* extract only data part, and ignore signature part */
\r
442 + part = g_mime_multipart_get_part (multipart, 0);
\r
444 + g_mime_signature_validity_free (sigvalidity);
\r
449 + g_error_free (err);
\r
451 + /* end handle PGP/MIME parts */
\r
453 + if (selected || state->in_zone)
\r
454 + format->part_content (part);
\r
456 if (GMIME_IS_MULTIPART (part)) {
\r
457 GMimeMultipart *multipart = GMIME_MULTIPART (part);
\r