UTF-8 in mail headers (namely FROM) sent by bugzilla
[notmuch-archives.git] / 13 / 086868f1fa72d94673817cba0794c70f35c566
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
8 X-Spam-Flag: NO\r
9 X-Spam-Score: -1.921\r
10 X-Spam-Level: \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
13         autolearn=disabled\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
19         [131.215.239.19])\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
35         with --verify.\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
43 Precedence: list\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
54 \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
59 \r
60 At the moment only a json part_sigstatus formatting function is\r
61 available.  Emacs support to follow.\r
62 \r
63 The original work for this patch was done by\r
64 \r
65   Daniel Kahn Gillmor <dkg@fifthhorseman.net>\r
66 \r
67 whose help with this functionality I greatly appreciate.\r
68 ---\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
75  notmuch.c               |    8 ++++\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
79 \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
85         notmuch-show.c          \\r
86         notmuch-tag.c           \\r
87         notmuch-time.c          \\r
88 +       notmuch-gmime-session.c \\r
89         query-string.c          \\r
90         show-message.c          \\r
91         json.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
99                         int *part_count);\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
105      int entire_thread;\r
106      int raw;\r
107      int part;\r
108 +    GMimeCipherContext* cryptoctx;\r
109  } notmuch_show_params_t;\r
110  \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
113  notmuch_bool_t\r
114  debugger_is_active (void);\r
115  \r
116 +GType\r
117 +notmuch_gmime_session_get_type (void);\r
118 +\r
119  #endif\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
123 --- /dev/null\r
124 +++ b/notmuch-gmime-session.c\r
125 @@ -0,0 +1,49 @@\r
126 +#include "notmuch-client.h"\r
127 +\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
131 +\r
132 +struct _NotmuchGmimeSession {\r
133 +    GMimeSession parent_object;\r
134 +};\r
135 +\r
136 +struct _NotmuchGmimeSessionClass {\r
137 +    GMimeSessionClass parent_class;\r
138 +};\r
139 +\r
140 +static void notmuch_gmime_session_class_init (NotmuchGmimeSessionClass *klass);\r
141 +\r
142 +static GMimeSessionClass *parent_class = NULL;\r
143 +\r
144 +GType\r
145 +notmuch_gmime_session_get_type (void)\r
146 +{\r
147 +    static GType type = 0;\r
148 +\r
149 +    if (!type) {\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
161 +       };\r
162 +       type = g_type_register_static (GMIME_TYPE_SESSION, "NotmuchGmimeSession", &info, 0);\r
163 +    }\r
164 +    return type;\r
165 +}\r
166 +\r
167 +static void\r
168 +notmuch_gmime_session_class_init (NotmuchGmimeSessionClass *klass)\r
169 +{\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
173 +}\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
180             "", NULL, "",\r
181             "",\r
182                 NULL,\r
183 +               NULL,\r
184                 reply_part_content,\r
185                 NULL,\r
186                 "",\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
190      params.part = -1;\r
191 +    params.cryptoctx = NULL;\r
192  \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
201             "\fbody{\n",\r
202                 format_part_start_text,\r
203 +               NULL,\r
204                 format_part_content_text,\r
205                 format_part_end_text,\r
206                 "",\r
207 @@ -65,6 +66,9 @@ format_part_start_json (unused (GMimeObject *part),\r
208                         int *part_count);\r
209  \r
210  static void\r
211 +format_part_sigstatus_json (const GMimeSignatureValidity* validity);\r
212 +\r
213 +static void\r
214  format_part_content_json (GMimeObject *part);\r
215  \r
216  static void\r
217 @@ -76,6 +80,7 @@ static const notmuch_show_format_t format_json = {\r
218             ", \"headers\": {", format_headers_json, "}",\r
219             ", \"body\": [",\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
224                 ", ",\r
225 @@ -97,6 +102,7 @@ static const notmuch_show_format_t format_mbox = {\r
226                  NULL,\r
227                  NULL,\r
228                  NULL,\r
229 +                NULL,\r
230                  "",\r
231              "",\r
232          "", "",\r
233 @@ -112,6 +118,7 @@ static const notmuch_show_format_t format_raw = {\r
234             "", NULL, "",\r
235              "",\r
236                  NULL,\r
237 +                NULL,\r
238                  format_part_content_raw,\r
239                  NULL,\r
240                  "",\r
241 @@ -396,6 +403,22 @@ show_part_content (GMimeObject *part, GMimeStream *stream_out)\r
242         g_object_unref(stream_filter);\r
243  }\r
244  \r
245 +static const char*\r
246 +signerstatustostring (GMimeSignerStatus x)\r
247 +{\r
248 +    switch (x) {\r
249 +    case GMIME_SIGNER_STATUS_NONE:\r
250 +       return "none";\r
251 +    case GMIME_SIGNER_STATUS_GOOD:\r
252 +       return "good";\r
253 +    case GMIME_SIGNER_STATUS_BAD:\r
254 +       return "bad";\r
255 +    case GMIME_SIGNER_STATUS_ERROR:\r
256 +       return "error";\r
257 +    }\r
258 +    return "unknown";\r
259 +}\r
260 +\r
261  static void\r
262  format_part_start_text (GMimeObject *part, int *part_count)\r
263  {\r
264 @@ -473,6 +496,65 @@ format_part_start_json (unused (GMimeObject *part), int *part_count)\r
265  }\r
266  \r
267  static void\r
268 +format_part_sigstatus_json (const GMimeSignatureValidity* validity)\r
269 +{\r
270 +    printf (", \"sigstatus\": [");\r
271 +\r
272 +    if (!validity) {\r
273 +       printf ("]");\r
274 +       return;\r
275 +    }\r
276 +\r
277 +    const GMimeSigner *signer = g_mime_signature_validity_get_signers (validity);\r
278 +    int first = 1;\r
279 +    void *ctx_quote = talloc_new (NULL);\r
280 +\r
281 +    while (signer) {\r
282 +       if (first)\r
283 +           first = 0;\r
284 +       else\r
285 +           printf (", ");\r
286 +\r
287 +       printf ("{");\r
288 +\r
289 +       /* status */\r
290 +       printf ("\"status\": %s", json_quote_str (ctx_quote, signerstatustostring(signer->status)));\r
291 +\r
292 +       if (signer->status == GMIME_SIGNER_STATUS_GOOD)\r
293 +       {\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
308 +           }\r
309 +       } else {\r
310 +           if (signer->keyid)\r
311 +               printf (", \"keyid\": %s", json_quote_str (ctx_quote, signer->keyid));\r
312 +       }\r
313 +       if (signer->errors != GMIME_SIGNER_ERROR_NONE) {\r
314 +           printf (", \"errors\": %x", signer->errors);\r
315 +       }\r
316 +\r
317 +       printf ("}");\r
318 +       signer = signer->next;\r
319 +    }\r
320 +\r
321 +    printf ("]");\r
322 +\r
323 +    talloc_free (ctx_quote);\r
324 +}\r
325 +\r
326 +static void\r
327  format_part_content_json (GMimeObject *part)\r
328  {\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
332      params.raw = 0;\r
333      params.part = -1;\r
334 +    params.cryptoctx = NULL;\r
335  \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
347 +               else\r
348 +                   g_mime_gpg_context_set_always_trust((GMimeGpgContext*)params.cryptoctx, FALSE);\r
349 +               g_object_unref (session);\r
350 +               session = NULL;\r
351 +           }\r
352         } else {\r
353             fprintf (stderr, "Unrecognized option: %s\n", argv[i]);\r
354             return 1;\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
358  \r
359 +    if (params.cryptoctx)\r
360 +       g_object_unref(params.cryptoctx);\r
361 +\r
362      return 0;\r
363  }\r
364 diff --git a/notmuch.1 b/notmuch.1\r
365 index a1c7fa8..daa9c6f 100644\r
366 --- a/notmuch.1\r
367 +++ b/notmuch.1\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
370  .RE\r
371  \r
372 +.RS 4\r
373 +.TP 4\r
374 +.B \-\-verify\r
375 +\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
380 +signed data.\r
381 +.RE\r
382 +\r
383  A common use of\r
384  .B notmuch show\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
388 --- a/notmuch.c\r
389 +++ b/notmuch.c\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
393        "\n"\r
394 +      "\t--verify\n"\r
395 +      "\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
401 +      "\n"\r
402        "\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
410  \r
411         if (format->part_start)\r
412             format->part_start (part, &(state->part_count));\r
413 -       format->part_content (part);\r
414      }\r
415  \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
420 +\r
421 +       if (GMIME_IS_MULTIPART_SIGNED (part))\r
422 +       {\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
425 +               fprintf (stderr,\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
428 +           } else {\r
429 +               /* For some reason the GMimeSignatureValidity returned\r
430 +                * here is not a const (inconsistent with that\r
431 +                * returned by\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
438 +               }\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
443 +               if (sigvalidity)\r
444 +                   g_mime_signature_validity_free (sigvalidity);\r
445 +           }\r
446 +       }\r
447 +\r
448 +       if (err)\r
449 +           g_error_free (err);\r
450 +    }\r
451 +    /* end handle PGP/MIME parts */\r
452 +\r
453 +    if (selected || state->in_zone)\r
454 +       format->part_content (part);\r
455 +\r
456      if (GMIME_IS_MULTIPART (part)) {\r
457         GMimeMultipart *multipart = GMIME_MULTIPART (part);\r
458         int i;\r
459 -- \r
460 1.7.4.4\r
461 \r