0949d9fdc23eb5517695a10c352eabce9a66bf7b
[notmuch] / notmuch-reply.c
1 /* notmuch - Not much of an email program, (just index and search)
2  *
3  * Copyright © 2009 Carl Worth
4  * Copyright © 2009 Keith Packard
5  *
6  * This program is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program.  If not, see http://www.gnu.org/licenses/ .
18  *
19  * Authors: Carl Worth <cworth@cworth.org>
20  *          Keith Packard <keithp@keithp.com>
21  */
22
23 #include "notmuch-client.h"
24 #include "gmime-filter-headers.h"
25
26 static void
27 show_reply_headers (GMimeMessage *message)
28 {
29     GMimeStream *stream_stdout = NULL, *stream_filter = NULL;
30
31     stream_stdout = g_mime_stream_file_new (stdout);
32     if (stream_stdout) {
33         g_mime_stream_file_set_owner (GMIME_STREAM_FILE (stream_stdout), FALSE);
34         stream_filter = g_mime_stream_filter_new(stream_stdout);
35         if (stream_filter) {
36                 g_mime_stream_filter_add(GMIME_STREAM_FILTER(stream_filter),
37                                          g_mime_filter_headers_new());
38                 g_mime_object_write_to_stream(GMIME_OBJECT(message), stream_filter);
39                 g_object_unref(stream_filter);
40         }
41         g_object_unref(stream_stdout);
42     }
43 }
44
45 static void
46 format_part_reply (mime_node_t *node)
47 {
48     int i;
49
50     if (node->envelope_file) {
51         printf ("On %s, %s wrote:\n",
52                 notmuch_message_get_header (node->envelope_file, "date"),
53                 notmuch_message_get_header (node->envelope_file, "from"));
54     } else if (GMIME_IS_MESSAGE (node->part)) {
55         GMimeMessage *message = GMIME_MESSAGE (node->part);
56         InternetAddressList *recipients;
57         const char *recipients_string;
58
59         printf ("> From: %s\n", g_mime_message_get_sender (message));
60         recipients = g_mime_message_get_recipients (message, GMIME_RECIPIENT_TYPE_TO);
61         recipients_string = internet_address_list_to_string (recipients, 0);
62         if (recipients_string)
63             printf ("> To: %s\n",
64                     recipients_string);
65         recipients = g_mime_message_get_recipients (message, GMIME_RECIPIENT_TYPE_CC);
66         recipients_string = internet_address_list_to_string (recipients, 0);
67         if (recipients_string)
68             printf ("> Cc: %s\n",
69                     recipients_string);
70         printf ("> Subject: %s\n", g_mime_message_get_subject (message));
71         printf ("> Date: %s\n", g_mime_message_get_date_as_string (message));
72         printf (">\n");
73     } else if (GMIME_IS_PART (node->part)) {
74         GMimeContentType *content_type = g_mime_object_get_content_type (node->part);
75         GMimeContentDisposition *disposition = g_mime_object_get_content_disposition (node->part);
76
77         if (g_mime_content_type_is_type (content_type, "application", "pgp-encrypted") ||
78             g_mime_content_type_is_type (content_type, "application", "pgp-signature")) {
79             /* Ignore PGP/MIME cruft parts */
80         } else if (g_mime_content_type_is_type (content_type, "text", "*") &&
81                    !g_mime_content_type_is_type (content_type, "text", "html")) {
82             GMimeStream *stream_stdout = g_mime_stream_file_new (stdout);
83             g_mime_stream_file_set_owner (GMIME_STREAM_FILE (stream_stdout), FALSE);
84             show_text_part_content (node->part, stream_stdout, NOTMUCH_SHOW_TEXT_PART_REPLY);
85             g_object_unref(stream_stdout);
86         } else if (disposition &&
87                    strcmp (disposition->disposition, GMIME_DISPOSITION_ATTACHMENT) == 0) {
88             const char *filename = g_mime_part_get_filename (GMIME_PART (node->part));
89             printf ("Attachment: %s (%s)\n", filename,
90                     g_mime_content_type_to_string (content_type));
91         } else {
92             printf ("Non-text part: %s\n",
93                     g_mime_content_type_to_string (content_type));
94         }
95     }
96
97     for (i = 0; i < node->nchildren; i++)
98         format_part_reply (mime_node_child (node, i));
99 }
100
101 /* Is the given address configured as one of the user's "personal" or
102  * "other" addresses. */
103 static int
104 address_is_users (const char *address, notmuch_config_t *config)
105 {
106     const char *primary;
107     const char **other;
108     size_t i, other_len;
109
110     primary = notmuch_config_get_user_primary_email (config);
111     if (strcasecmp (primary, address) == 0)
112         return 1;
113
114     other = notmuch_config_get_user_other_email (config, &other_len);
115     for (i = 0; i < other_len; i++)
116         if (strcasecmp (other[i], address) == 0)
117             return 1;
118
119     return 0;
120 }
121
122 /* Scan addresses in 'list'.
123  *
124  * If 'message' is non-NULL, then for each address in 'list' that is
125  * not configured as one of the user's addresses in 'config', add that
126  * address to 'message' as an address of 'type'.
127  *
128  * If 'user_from' is non-NULL and *user_from is NULL, *user_from will
129  * be set to the first address encountered in 'list' that is the
130  * user's address.
131  *
132  * Return the number of addresses added to 'message'. (If 'message' is
133  * NULL, the function returns 0 by definition.)
134  */
135 static unsigned int
136 scan_address_list (InternetAddressList *list,
137                    notmuch_config_t *config,
138                    GMimeMessage *message,
139                    GMimeRecipientType type,
140                    const char **user_from)
141 {
142     InternetAddress *address;
143     int i;
144     unsigned int n = 0;
145
146     for (i = 0; i < internet_address_list_length (list); i++) {
147         address = internet_address_list_get_address (list, i);
148         if (INTERNET_ADDRESS_IS_GROUP (address)) {
149             InternetAddressGroup *group;
150             InternetAddressList *group_list;
151
152             group = INTERNET_ADDRESS_GROUP (address);
153             group_list = internet_address_group_get_members (group);
154             if (group_list == NULL)
155                 continue;
156
157             n += scan_address_list (group_list, config, message, type, user_from);
158         } else {
159             InternetAddressMailbox *mailbox;
160             const char *name;
161             const char *addr;
162
163             mailbox = INTERNET_ADDRESS_MAILBOX (address);
164
165             name = internet_address_get_name (address);
166             addr = internet_address_mailbox_get_addr (mailbox);
167
168             if (address_is_users (addr, config)) {
169                 if (user_from && *user_from == NULL)
170                     *user_from = addr;
171             } else if (message) {
172                 g_mime_message_add_recipient (message, type, name, addr);
173                 n++;
174             }
175         }
176     }
177
178     return n;
179 }
180
181 /* Scan addresses in 'recipients'.
182  *
183  * See the documentation of scan_address_list() above. This function
184  * does exactly the same, but converts 'recipients' to an
185  * InternetAddressList first.
186  */
187 static unsigned int
188 scan_address_string (const char *recipients,
189                      notmuch_config_t *config,
190                      GMimeMessage *message,
191                      GMimeRecipientType type,
192                      const char **user_from)
193 {
194     InternetAddressList *list;
195
196     if (recipients == NULL)
197         return 0;
198
199     list = internet_address_list_parse_string (recipients);
200     if (list == NULL)
201         return 0;
202
203     return scan_address_list (list, config, message, type, user_from);
204 }
205
206 /* Does the address in the Reply-To header of 'message' already appear
207  * in either the 'To' or 'Cc' header of the message?
208  */
209 static int
210 reply_to_header_is_redundant (notmuch_message_t *message)
211 {
212     const char *reply_to, *to, *cc, *addr;
213     InternetAddressList *list;
214     InternetAddress *address;
215     InternetAddressMailbox *mailbox;
216
217     reply_to = notmuch_message_get_header (message, "reply-to");
218     if (reply_to == NULL || *reply_to == '\0')
219         return 0;
220
221     list = internet_address_list_parse_string (reply_to);
222
223     if (internet_address_list_length (list) != 1)
224         return 0;
225
226     address = internet_address_list_get_address (list, 0);
227     if (INTERNET_ADDRESS_IS_GROUP (address))
228         return 0;
229
230     mailbox = INTERNET_ADDRESS_MAILBOX (address);
231     addr = internet_address_mailbox_get_addr (mailbox);
232
233     to = notmuch_message_get_header (message, "to");
234     cc = notmuch_message_get_header (message, "cc");
235
236     if ((to && strstr (to, addr) != 0) ||
237         (cc && strstr (cc, addr) != 0))
238     {
239         return 1;
240     }
241
242     return 0;
243 }
244
245 /* Augment the recipients of 'reply' from the "Reply-to:", "From:",
246  * "To:", "Cc:", and "Bcc:" headers of 'message'.
247  *
248  * If 'reply_all' is true, use sender and all recipients, otherwise
249  * scan the headers for the first that contains something other than
250  * the user's addresses and add the recipients from this header
251  * (typically this would be reply-to-sender, but also handles reply to
252  * user's own message in a sensible way).
253  *
254  * If any of the user's addresses were found in these headers, the
255  * first of these returned, otherwise NULL is returned.
256  */
257 static const char *
258 add_recipients_from_message (GMimeMessage *reply,
259                              notmuch_config_t *config,
260                              notmuch_message_t *message,
261                              notmuch_bool_t reply_all)
262 {
263     struct {
264         const char *header;
265         const char *fallback;
266         GMimeRecipientType recipient_type;
267     } reply_to_map[] = {
268         { "reply-to", "from", GMIME_RECIPIENT_TYPE_TO  },
269         { "to",         NULL, GMIME_RECIPIENT_TYPE_TO  },
270         { "cc",         NULL, GMIME_RECIPIENT_TYPE_CC  },
271         { "bcc",        NULL, GMIME_RECIPIENT_TYPE_BCC }
272     };
273     const char *from_addr = NULL;
274     unsigned int i;
275     unsigned int n = 0;
276
277     /* Some mailing lists munge the Reply-To header despite it being A Bad
278      * Thing, see http://www.unicom.com/pw/reply-to-harmful.html
279      *
280      * The munging is easy to detect, because it results in a
281      * redundant reply-to header, (with an address that already exists
282      * in either To or Cc). So in this case, we ignore the Reply-To
283      * field and use the From header. This ensures the original sender
284      * will get the reply even if not subscribed to the list. Note
285      * that the address in the Reply-To header will always appear in
286      * the reply.
287      */
288     if (reply_to_header_is_redundant (message)) {
289         reply_to_map[0].header = "from";
290         reply_to_map[0].fallback = NULL;
291     }
292
293     for (i = 0; i < ARRAY_SIZE (reply_to_map); i++) {
294         const char *recipients;
295
296         recipients = notmuch_message_get_header (message,
297                                                  reply_to_map[i].header);
298         if ((recipients == NULL || recipients[0] == '\0') && reply_to_map[i].fallback)
299             recipients = notmuch_message_get_header (message,
300                                                      reply_to_map[i].fallback);
301
302         n += scan_address_string (recipients, config, reply,
303                                   reply_to_map[i].recipient_type, &from_addr);
304
305         if (!reply_all && n) {
306             /* Stop adding new recipients in reply-to-sender mode if
307              * we have added some recipient(s) above.
308              *
309              * This also handles the case of user replying to his own
310              * message, where reply-to/from is not a recipient. In
311              * this case there may be more than one recipient even if
312              * not replying to all.
313              */
314             reply = NULL;
315
316             /* From address and some recipients are enough, bail out. */
317             if (from_addr)
318                 break;
319         }
320     }
321
322     return from_addr;
323 }
324
325 static const char *
326 guess_from_received_header (notmuch_config_t *config, notmuch_message_t *message)
327 {
328     const char *received,*primary,*by;
329     const char **other;
330     char *tohdr;
331     char *mta,*ptr,*token;
332     char *domain=NULL;
333     char *tld=NULL;
334     const char *delim=". \t";
335     size_t i,j,other_len;
336
337     const char *to_headers[] = {"Envelope-to", "X-Original-To"};
338
339     primary = notmuch_config_get_user_primary_email (config);
340     other = notmuch_config_get_user_other_email (config, &other_len);
341
342     /* sadly, there is no standard way to find out to which email
343      * address a mail was delivered - what is in the headers depends
344      * on the MTAs used along the way. So we are trying a number of
345      * heuristics which hopefully will answer this question.
346
347      * We only got here if none of the users email addresses are in
348      * the To: or Cc: header. From here we try the following in order:
349      * 1) check for an Envelope-to: header
350      * 2) check for an X-Original-To: header
351      * 3) check for a (for <email@add.res>) clause in Received: headers
352      * 4) check for the domain part of known email addresses in the
353      *    'by' part of Received headers
354      * If none of these work, we give up and return NULL
355      */
356     for (i = 0; i < sizeof(to_headers)/sizeof(*to_headers); i++) {
357         tohdr = xstrdup(notmuch_message_get_header (message, to_headers[i]));
358         if (tohdr && *tohdr) {
359             /* tohdr is potentialy a list of email addresses, so here we
360              * check if one of the email addresses is a substring of tohdr
361              */
362             if (strcasestr(tohdr, primary)) {
363                 free(tohdr);
364                 return primary;
365             }
366             for (j = 0; j < other_len; j++)
367                 if (strcasestr (tohdr, other[j])) {
368                     free(tohdr);
369                     return other[j];
370                 }
371             free(tohdr);
372         }
373     }
374
375     /* We get the concatenated Received: headers and search from the
376      * front (last Received: header added) and try to extract from
377      * them indications to which email address this message was
378      * delivered.
379      * The Received: header is special in our get_header function
380      * and is always concatenated.
381      */
382     received = notmuch_message_get_header (message, "received");
383     if (received == NULL)
384         return NULL;
385
386     /* First we look for a " for <email@add.res>" in the received
387      * header
388      */
389     ptr = strstr (received, " for ");
390     if (ptr) {
391         /* the text following is potentialy a list of email addresses,
392          * so again we check if one of the email addresses is a
393          * substring of ptr
394          */
395         if (strcasestr(ptr, primary)) {
396             return primary;
397         }
398         for (i = 0; i < other_len; i++)
399             if (strcasestr (ptr, other[i])) {
400                 return other[i];
401             }
402     }
403     /* Finally, we parse all the " by MTA ..." headers to guess the
404      * email address that this was originally delivered to.
405      * We extract just the MTA here by removing leading whitespace and
406      * assuming that the MTA name ends at the next whitespace.
407      * We test for *(by+4) to be non-'\0' to make sure there's
408      * something there at all - and then assume that the first
409      * whitespace delimited token that follows is the receiving
410      * system in this step of the receive chain
411      */
412     by = received;
413     while((by = strstr (by, " by ")) != NULL) {
414         by += 4;
415         if (*by == '\0')
416             break;
417         mta = xstrdup (by);
418         token = strtok(mta," \t");
419         if (token == NULL) {
420             free (mta);
421             break;
422         }
423         /* Now extract the last two components of the MTA host name
424          * as domain and tld.
425          */
426         domain = tld = NULL;
427         while ((ptr = strsep (&token, delim)) != NULL) {
428             if (*ptr == '\0')
429                 continue;
430             domain = tld;
431             tld = ptr;
432         }
433
434         if (domain) {
435             /* Recombine domain and tld and look for it among the configured
436              * email addresses.
437              * This time we have a known domain name and nothing else - so
438              * the test is the other way around: we check if this is a
439              * substring of one of the email addresses.
440              */
441             *(tld-1) = '.';
442
443             if (strcasestr(primary, domain)) {
444                 free(mta);
445                 return primary;
446             }
447             for (i = 0; i < other_len; i++)
448                 if (strcasestr (other[i],domain)) {
449                     free(mta);
450                     return other[i];
451                 }
452         }
453         free (mta);
454     }
455
456     return NULL;
457 }
458
459 static GMimeMessage *
460 create_reply_message(void *ctx,
461                      notmuch_config_t *config,
462                      notmuch_message_t *message,
463                      notmuch_bool_t reply_all)
464 {
465     const char *subject, *from_addr = NULL;
466     const char *in_reply_to, *orig_references, *references;
467
468     /* The 1 means we want headers in a "pretty" order. */
469     GMimeMessage *reply = g_mime_message_new (1);
470     if (reply == NULL) {
471         fprintf (stderr, "Out of memory\n");
472         return NULL;
473     }
474
475     subject = notmuch_message_get_header (message, "subject");
476     if (subject) {
477         if (strncasecmp (subject, "Re:", 3))
478             subject = talloc_asprintf (ctx, "Re: %s", subject);
479         g_mime_message_set_subject (reply, subject);
480     }
481
482     from_addr = add_recipients_from_message (reply, config,
483                                              message, reply_all);
484
485     if (from_addr == NULL)
486         from_addr = guess_from_received_header (config, message);
487
488     if (from_addr == NULL)
489         from_addr = notmuch_config_get_user_primary_email (config);
490
491     from_addr = talloc_asprintf (ctx, "%s <%s>",
492                                  notmuch_config_get_user_name (config),
493                                  from_addr);
494     g_mime_object_set_header (GMIME_OBJECT (reply),
495                               "From", from_addr);
496
497     in_reply_to = talloc_asprintf (ctx, "<%s>",
498                                    notmuch_message_get_message_id (message));
499
500     g_mime_object_set_header (GMIME_OBJECT (reply),
501                               "In-Reply-To", in_reply_to);
502
503     orig_references = notmuch_message_get_header (message, "references");
504     references = talloc_asprintf (ctx, "%s%s%s",
505                                   orig_references ? orig_references : "",
506                                   orig_references ? " " : "",
507                                   in_reply_to);
508     g_mime_object_set_header (GMIME_OBJECT (reply),
509                               "References", references);
510
511     return reply;
512 }
513
514 static int
515 notmuch_reply_format_default(void *ctx,
516                              notmuch_config_t *config,
517                              notmuch_query_t *query,
518                              notmuch_show_params_t *params,
519                              notmuch_bool_t reply_all)
520 {
521     GMimeMessage *reply;
522     notmuch_messages_t *messages;
523     notmuch_message_t *message;
524     mime_node_t *root;
525
526     for (messages = notmuch_query_search_messages (query);
527          notmuch_messages_valid (messages);
528          notmuch_messages_move_to_next (messages))
529     {
530         message = notmuch_messages_get (messages);
531
532         reply = create_reply_message (ctx, config, message, reply_all);
533
534         /* If reply creation failed, we're out of memory, so don't
535          * bother trying any more messages.
536          */
537         if (!reply) {
538             notmuch_message_destroy (message);
539             return 1;
540         }
541
542         show_reply_headers (reply);
543
544         g_object_unref (G_OBJECT (reply));
545         reply = NULL;
546
547         if (mime_node_open (ctx, message, params->cryptoctx, params->decrypt,
548                             &root) == NOTMUCH_STATUS_SUCCESS) {
549             format_part_reply (root);
550             talloc_free (root);
551         }
552
553         notmuch_message_destroy (message);
554     }
555     return 0;
556 }
557
558 static int
559 notmuch_reply_format_json(void *ctx,
560                           notmuch_config_t *config,
561                           notmuch_query_t *query,
562                           notmuch_show_params_t *params,
563                           notmuch_bool_t reply_all)
564 {
565     GMimeMessage *reply;
566     notmuch_messages_t *messages;
567     notmuch_message_t *message;
568     mime_node_t *node;
569
570     if (notmuch_query_count_messages (query) != 1) {
571         fprintf (stderr, "Error: search term did not match precisely one message.\n");
572         return 1;
573     }
574
575     messages = notmuch_query_search_messages (query);
576     message = notmuch_messages_get (messages);
577     if (mime_node_open (ctx, message, params->cryptoctx, params->decrypt,
578                         &node) != NOTMUCH_STATUS_SUCCESS)
579         return 1;
580
581     reply = create_reply_message (ctx, config, message, reply_all);
582     if (!reply)
583         return 1;
584
585     /* The headers of the reply message we've created */
586     printf ("{\"reply-headers\": ");
587     format_headers_json (ctx, reply, TRUE);
588     g_object_unref (G_OBJECT (reply));
589     reply = NULL;
590
591     /* Start the original */
592     printf (", \"original\": ");
593
594     format_part_json (ctx, node, TRUE);
595
596     /* End */
597     printf ("}\n");
598     notmuch_message_destroy (message);
599
600     return 0;
601 }
602
603 /* This format is currently tuned for a git send-email --notmuch hook */
604 static int
605 notmuch_reply_format_headers_only(void *ctx,
606                                   notmuch_config_t *config,
607                                   notmuch_query_t *query,
608                                   unused (notmuch_show_params_t *params),
609                                   notmuch_bool_t reply_all)
610 {
611     GMimeMessage *reply;
612     notmuch_messages_t *messages;
613     notmuch_message_t *message;
614     const char *in_reply_to, *orig_references, *references;
615     char *reply_headers;
616
617     for (messages = notmuch_query_search_messages (query);
618          notmuch_messages_valid (messages);
619          notmuch_messages_move_to_next (messages))
620     {
621         message = notmuch_messages_get (messages);
622
623         /* The 0 means we do not want headers in a "pretty" order. */
624         reply = g_mime_message_new (0);
625         if (reply == NULL) {
626             fprintf (stderr, "Out of memory\n");
627             return 1;
628         }
629
630         in_reply_to = talloc_asprintf (ctx, "<%s>",
631                              notmuch_message_get_message_id (message));
632
633         g_mime_object_set_header (GMIME_OBJECT (reply),
634                                   "In-Reply-To", in_reply_to);
635
636
637         orig_references = notmuch_message_get_header (message, "references");
638
639         /* We print In-Reply-To followed by References because git format-patch treats them
640          * specially.  Git does not interpret the other headers specially
641          */
642         references = talloc_asprintf (ctx, "%s%s%s",
643                                       orig_references ? orig_references : "",
644                                       orig_references ? " " : "",
645                                       in_reply_to);
646         g_mime_object_set_header (GMIME_OBJECT (reply),
647                                   "References", references);
648
649         (void)add_recipients_from_message (reply, config, message, reply_all);
650
651         reply_headers = g_mime_object_to_string (GMIME_OBJECT (reply));
652         printf ("%s", reply_headers);
653         free (reply_headers);
654
655         g_object_unref (G_OBJECT (reply));
656         reply = NULL;
657
658         notmuch_message_destroy (message);
659     }
660     return 0;
661 }
662
663 enum {
664     FORMAT_DEFAULT,
665     FORMAT_JSON,
666     FORMAT_HEADERS_ONLY,
667 };
668
669 int
670 notmuch_reply_command (void *ctx, int argc, char *argv[])
671 {
672     notmuch_config_t *config;
673     notmuch_database_t *notmuch;
674     notmuch_query_t *query;
675     char *query_string;
676     int opt_index, ret = 0;
677     int (*reply_format_func)(void *ctx, notmuch_config_t *config, notmuch_query_t *query, notmuch_show_params_t *params, notmuch_bool_t reply_all);
678     notmuch_show_params_t params = { .part = -1 };
679     int format = FORMAT_DEFAULT;
680     int reply_all = TRUE;
681
682     notmuch_opt_desc_t options[] = {
683         { NOTMUCH_OPT_KEYWORD, &format, "format", 'f',
684           (notmuch_keyword_t []){ { "default", FORMAT_DEFAULT },
685                                   { "json", FORMAT_JSON },
686                                   { "headers-only", FORMAT_HEADERS_ONLY },
687                                   { 0, 0 } } },
688         { NOTMUCH_OPT_KEYWORD, &reply_all, "reply-to", 'r',
689           (notmuch_keyword_t []){ { "all", TRUE },
690                                   { "sender", FALSE },
691                                   { 0, 0 } } },
692         { NOTMUCH_OPT_BOOLEAN, &params.decrypt, "decrypt", 'd', 0 },
693         { 0, 0, 0, 0, 0 }
694     };
695
696     opt_index = parse_arguments (argc, argv, options, 1);
697     if (opt_index < 0) {
698         /* diagnostics already printed */
699         return 1;
700     }
701
702     if (format == FORMAT_HEADERS_ONLY)
703         reply_format_func = notmuch_reply_format_headers_only;
704     else if (format == FORMAT_JSON)
705         reply_format_func = notmuch_reply_format_json;
706     else
707         reply_format_func = notmuch_reply_format_default;
708
709     if (params.decrypt) {
710 #ifdef GMIME_ATLEAST_26
711         /* TODO: GMimePasswordRequestFunc */
712         params.cryptoctx = g_mime_gpg_context_new (NULL, "gpg");
713 #else
714         GMimeSession* session = g_object_new (g_mime_session_get_type(), NULL);
715         params.cryptoctx = g_mime_gpg_context_new (session, "gpg");
716 #endif
717         if (params.cryptoctx) {
718             g_mime_gpg_context_set_always_trust ((GMimeGpgContext*) params.cryptoctx, FALSE);
719         } else {
720             params.decrypt = FALSE;
721             fprintf (stderr, "Failed to construct gpg context.\n");
722         }
723 #ifndef GMIME_ATLEAST_26
724         g_object_unref (session);
725 #endif
726     }
727
728     config = notmuch_config_open (ctx, NULL, NULL);
729     if (config == NULL)
730         return 1;
731
732     query_string = query_string_from_args (ctx, argc-opt_index, argv+opt_index);
733     if (query_string == NULL) {
734         fprintf (stderr, "Out of memory\n");
735         return 1;
736     }
737
738     if (*query_string == '\0') {
739         fprintf (stderr, "Error: notmuch reply requires at least one search term.\n");
740         return 1;
741     }
742
743     notmuch = notmuch_database_open (notmuch_config_get_database_path (config),
744                                      NOTMUCH_DATABASE_MODE_READ_ONLY);
745     if (notmuch == NULL)
746         return 1;
747
748     query = notmuch_query_create (notmuch, query_string);
749     if (query == NULL) {
750         fprintf (stderr, "Out of memory\n");
751         return 1;
752     }
753
754     if (reply_format_func (ctx, config, query, &params, reply_all) != 0)
755         return 1;
756
757     notmuch_query_destroy (query);
758     notmuch_database_close (notmuch);
759
760     if (params.cryptoctx)
761         g_object_unref(params.cryptoctx);
762
763     return ret;
764 }