]> git.cworth.org Git - notmuch/blob - notmuch.c
lib: convert notmuch decryption policy to an enum
[notmuch] / notmuch.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 https://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
25 /*
26  * Notmuch subcommand hook.
27  *
28  * The return value will be used as notmuch exit status code,
29  * preferrably EXIT_SUCCESS or EXIT_FAILURE.
30  */
31 typedef int (*command_function_t) (notmuch_config_t *config, int argc, char *argv[]);
32
33 typedef struct command {
34     const char *name;
35     command_function_t function;
36     notmuch_config_mode_t config_mode;
37     const char *summary;
38 } command_t;
39
40 static int
41 notmuch_help_command (notmuch_config_t *config, int argc, char *argv[]);
42
43 static int
44 notmuch_command (notmuch_config_t *config, int argc, char *argv[]);
45
46 static int
47 _help_for (const char *topic);
48
49 static bool print_version = false, print_help = false;
50 const char *notmuch_requested_db_uuid = NULL;
51
52 const notmuch_opt_desc_t notmuch_shared_options [] = {
53     { .opt_bool = &print_version, .name = "version" },
54     { .opt_bool = &print_help, .name = "help" },
55     { .opt_string = &notmuch_requested_db_uuid, .name = "uuid" },
56     { }
57 };
58
59 /* any subcommand wanting to support these options should call
60  * inherit notmuch_shared_options and call
61  * notmuch_process_shared_options (subcommand_name);
62  */
63 void
64 notmuch_process_shared_options (const char *subcommand_name) {
65     if (print_version) {
66         printf ("notmuch " STRINGIFY(NOTMUCH_VERSION) "\n");
67         exit (EXIT_SUCCESS);
68     }
69
70     if (print_help) {
71         int ret = _help_for (subcommand_name);
72         exit (ret);
73     }
74 }
75
76 /* This is suitable for subcommands that do not actually open the
77  * database.
78  */
79 int notmuch_minimal_options (const char *subcommand_name,
80                                   int argc, char **argv)
81 {
82     int opt_index;
83
84     notmuch_opt_desc_t options[] = {
85         { .opt_inherit = notmuch_shared_options },
86         { }
87     };
88
89     opt_index = parse_arguments (argc, argv, options, 1);
90
91     if (opt_index < 0)
92         return -1;
93
94     /* We can't use argv here as it is sometimes NULL */
95     notmuch_process_shared_options (subcommand_name);
96     return opt_index;
97 }
98
99
100 struct _notmuch_client_indexing_cli_choices indexing_cli_choices = { };
101 const notmuch_opt_desc_t  notmuch_shared_indexing_options [] = {
102     { .opt_keyword = &indexing_cli_choices.decrypt_policy,
103       .present = &indexing_cli_choices.decrypt_policy_set, .keywords =
104       (notmuch_keyword_t []){ { "false", NOTMUCH_DECRYPT_FALSE },
105                               { "true", NOTMUCH_DECRYPT_TRUE },
106                               { 0, 0 } },
107       .name = "decrypt" },
108     { }
109 };
110
111
112 notmuch_status_t
113 notmuch_process_shared_indexing_options (notmuch_database_t *notmuch, g_mime_3_unused(notmuch_config_t *config))
114 {
115     if (indexing_cli_choices.opts == NULL)
116         indexing_cli_choices.opts = notmuch_database_get_default_indexopts (notmuch);
117     if (indexing_cli_choices.decrypt_policy_set) {
118         notmuch_status_t status;
119         if (indexing_cli_choices.opts == NULL)
120             return NOTMUCH_STATUS_OUT_OF_MEMORY;
121         status = notmuch_indexopts_set_decrypt_policy (indexing_cli_choices.opts, indexing_cli_choices.decrypt_policy);
122         if (status != NOTMUCH_STATUS_SUCCESS) {
123             fprintf (stderr, "Error: Failed to set index decryption policy to %d. (%s)\n",
124                      indexing_cli_choices.decrypt_policy, notmuch_status_to_string (status));
125             notmuch_indexopts_destroy (indexing_cli_choices.opts);
126             indexing_cli_choices.opts = NULL;
127             return status;
128         }
129     }
130 #if (GMIME_MAJOR_VERSION < 3)
131     if (indexing_cli_choices.opts && notmuch_indexopts_get_decrypt_policy (indexing_cli_choices.opts) == NOTMUCH_DECRYPT_TRUE) {
132         const char* gpg_path = notmuch_config_get_crypto_gpg_path (config);
133         if (gpg_path && strcmp(gpg_path, "gpg"))
134             fprintf (stderr, "Warning: deprecated crypto.gpg_path is set to '%s'\n"
135                      "\tbut ignoring (use $PATH instead)\n", gpg_path);
136     }
137 #endif
138     return NOTMUCH_STATUS_SUCCESS;
139 }
140
141
142 static command_t commands[] = {
143     { NULL, notmuch_command, NOTMUCH_CONFIG_OPEN | NOTMUCH_CONFIG_CREATE,
144       "Notmuch main command." },
145     { "setup", notmuch_setup_command, NOTMUCH_CONFIG_OPEN | NOTMUCH_CONFIG_CREATE,
146       "Interactively set up notmuch for first use." },
147     { "new", notmuch_new_command, NOTMUCH_CONFIG_OPEN,
148       "Find and import new messages to the notmuch database." },
149     { "insert", notmuch_insert_command, NOTMUCH_CONFIG_OPEN,
150       "Add a new message into the maildir and notmuch database." },
151     { "search", notmuch_search_command, NOTMUCH_CONFIG_OPEN,
152       "Search for messages matching the given search terms." },
153     { "address", notmuch_address_command, NOTMUCH_CONFIG_OPEN,
154       "Get addresses from messages matching the given search terms." },
155     { "show", notmuch_show_command, NOTMUCH_CONFIG_OPEN,
156       "Show all messages matching the search terms." },
157     { "count", notmuch_count_command, NOTMUCH_CONFIG_OPEN,
158       "Count messages matching the search terms." },
159     { "reply", notmuch_reply_command, NOTMUCH_CONFIG_OPEN,
160       "Construct a reply template for a set of messages." },
161     { "tag", notmuch_tag_command, NOTMUCH_CONFIG_OPEN,
162       "Add/remove tags for all messages matching the search terms." },
163     { "dump", notmuch_dump_command, NOTMUCH_CONFIG_OPEN,
164       "Create a plain-text dump of the tags for each message." },
165     { "restore", notmuch_restore_command, NOTMUCH_CONFIG_OPEN,
166       "Restore the tags from the given dump file (see 'dump')." },
167     { "compact", notmuch_compact_command, NOTMUCH_CONFIG_OPEN,
168       "Compact the notmuch database." },
169     { "reindex", notmuch_reindex_command, NOTMUCH_CONFIG_OPEN,
170       "Re-index all messages matching the search terms." },
171     { "config", notmuch_config_command, NOTMUCH_CONFIG_OPEN,
172       "Get or set settings in the notmuch configuration file." },
173 #if WITH_EMACS
174     { "emacs-mua", NULL, 0,
175       "send mail with notmuch and emacs." },
176 #endif
177     { "help", notmuch_help_command, NOTMUCH_CONFIG_CREATE, /* create but don't save config */
178       "This message, or more detailed help for the named command." }
179 };
180
181 typedef struct help_topic {
182     const char *name;
183     const char *summary;
184 } help_topic_t;
185
186 static help_topic_t help_topics[] = {
187     { "search-terms",
188       "Common search term syntax." },
189     { "hooks",
190       "Hooks that will be run before or after certain commands." },
191 };
192
193 static command_t *
194 find_command (const char *name)
195 {
196     size_t i;
197
198     for (i = 0; i < ARRAY_SIZE (commands); i++)
199         if ((!name && !commands[i].name) ||
200             (name && commands[i].name && strcmp (name, commands[i].name) == 0))
201             return &commands[i];
202
203     return NULL;
204 }
205
206 int notmuch_format_version;
207
208 static void
209 usage (FILE *out)
210 {
211     command_t *command;
212     help_topic_t *topic;
213     unsigned int i;
214
215     fprintf (out,
216              "Usage: notmuch --help\n"
217              "       notmuch --version\n"
218              "       notmuch <command> [args...]\n");
219     fprintf (out, "\n");
220     fprintf (out, "The available commands are as follows:\n");
221     fprintf (out, "\n");
222
223     for (i = 0; i < ARRAY_SIZE (commands); i++) {
224         command = &commands[i];
225
226         if (command->name)
227             fprintf (out, "  %-12s  %s\n", command->name, command->summary);
228     }
229
230     fprintf (out, "\n");
231     fprintf (out, "Additional help topics are as follows:\n");
232     fprintf (out, "\n");
233
234     for (i = 0; i < ARRAY_SIZE (help_topics); i++) {
235         topic = &help_topics[i];
236         fprintf (out, "  %-12s  %s\n", topic->name, topic->summary);
237     }
238
239     fprintf (out, "\n");
240     fprintf (out,
241              "Use \"notmuch help <command or topic>\" for more details "
242              "on each command or topic.\n\n");
243 }
244
245 void
246 notmuch_exit_if_unsupported_format (void)
247 {
248     if (notmuch_format_version > NOTMUCH_FORMAT_CUR) {
249         fprintf (stderr, "\
250 A caller requested output format version %d, but the installed notmuch\n\
251 CLI only supports up to format version %d.  You may need to upgrade your\n\
252 notmuch CLI.\n",
253                  notmuch_format_version, NOTMUCH_FORMAT_CUR);
254         exit (NOTMUCH_EXIT_FORMAT_TOO_NEW);
255     } else if (notmuch_format_version < NOTMUCH_FORMAT_MIN) {
256         fprintf (stderr, "\
257 A caller requested output format version %d, which is no longer supported\n\
258 by the notmuch CLI (it requires at least version %d).  You may need to\n\
259 upgrade your notmuch front-end.\n",
260                  notmuch_format_version, NOTMUCH_FORMAT_MIN);
261         exit (NOTMUCH_EXIT_FORMAT_TOO_OLD);
262     } else if (notmuch_format_version < NOTMUCH_FORMAT_MIN_ACTIVE) {
263         /* Warn about old version requests so compatibility issues are
264          * less likely when we drop support for a deprecated format
265          * versions. */
266         fprintf (stderr, "\
267 A caller requested deprecated output format version %d, which may not\n\
268 be supported in the future.\n", notmuch_format_version);
269     }
270 }
271
272 void
273 notmuch_exit_if_unmatched_db_uuid (notmuch_database_t *notmuch)
274 {
275     const char *uuid = NULL;
276
277     if (!notmuch_requested_db_uuid)
278         return;
279     IGNORE_RESULT (notmuch_database_get_revision (notmuch, &uuid));
280
281     if (strcmp (notmuch_requested_db_uuid, uuid) != 0){
282         fprintf (stderr, "Error: requested database revision %s does not match %s\n",
283                  notmuch_requested_db_uuid, uuid);
284         exit (1);
285     }
286 }
287
288 static void
289 exec_man (const char *page)
290 {
291     if (execlp ("man", "man", page, (char *) NULL)) {
292         perror ("exec man");
293         exit (1);
294     }
295 }
296
297 static int
298 _help_for (const char *topic_name)
299 {
300     command_t *command;
301     help_topic_t *topic;
302     unsigned int i;
303
304     if (!topic_name) {
305         printf ("The notmuch mail system.\n\n");
306         usage (stdout);
307         return EXIT_SUCCESS;
308     }
309
310     if (strcmp (topic_name, "help") == 0) {
311         printf ("The notmuch help system.\n\n"
312                 "\tNotmuch uses the man command to display help. In case\n"
313                 "\tof difficulties check that MANPATH includes the pages\n"
314                 "\tinstalled by notmuch.\n\n"
315                 "\tTry \"notmuch help\" for a list of topics.\n");
316         return EXIT_SUCCESS;
317     }
318
319     command = find_command (topic_name);
320     if (command) {
321         char *page = talloc_asprintf (NULL, "notmuch-%s", command->name);
322         exec_man (page);
323     }
324
325     for (i = 0; i < ARRAY_SIZE (help_topics); i++) {
326         topic = &help_topics[i];
327         if (strcmp (topic_name, topic->name) == 0) {
328             char *page = talloc_asprintf (NULL, "notmuch-%s", topic->name);
329             exec_man (page);
330         }
331     }
332
333     fprintf (stderr,
334              "\nSorry, %s is not a known command. There's not much I can do to help.\n\n",
335              topic_name);
336     return EXIT_FAILURE;
337 }
338
339 static int
340 notmuch_help_command (unused (notmuch_config_t * config), int argc, char *argv[])
341 {
342     int opt_index;
343
344     opt_index = notmuch_minimal_options ("help", argc, argv);
345     if (opt_index < 0)
346         return EXIT_FAILURE;
347
348     /* skip at least subcommand argument */
349     argc-= opt_index;
350     argv+= opt_index;
351
352     if (argc == 0) {
353         return _help_for (NULL);
354     }
355
356     return _help_for (argv[0]);
357 }
358
359 /* Handle the case of "notmuch" being invoked with no command
360  * argument. For now we just call notmuch_setup_command, but we plan
361  * to be more clever about this in the future.
362  */
363 static int
364 notmuch_command (notmuch_config_t *config,
365                  unused(int argc), unused(char *argv[]))
366 {
367     char *db_path;
368     struct stat st;
369
370     /* If the user has never configured notmuch, then run
371      * notmuch_setup_command which will give a nice welcome message,
372      * and interactively guide the user through the configuration. */
373     if (notmuch_config_is_new (config))
374         return notmuch_setup_command (config, 0, NULL);
375
376     /* Notmuch is already configured, but is there a database? */
377     db_path = talloc_asprintf (config, "%s/%s",
378                                notmuch_config_get_database_path (config),
379                                ".notmuch");
380     if (stat (db_path, &st)) {
381         if (errno != ENOENT) {
382             fprintf (stderr, "Error looking for notmuch database at %s: %s\n",
383                      db_path, strerror (errno));
384             return EXIT_FAILURE;
385         }
386         printf ("Notmuch is configured, but there's not yet a database at\n\n\t%s\n\n",
387                 db_path);
388         printf ("You probably want to run \"notmuch new\" now to create that database.\n\n"
389                 "Note that the first run of \"notmuch new\" can take a very long time\n"
390                 "and that the resulting database will use roughly the same amount of\n"
391                 "storage space as the email being indexed.\n\n");
392         return EXIT_SUCCESS;
393     }
394
395     printf ("Notmuch is configured and appears to have a database. Excellent!\n\n"
396             "At this point you can start exploring the functionality of notmuch by\n"
397             "using commands such as:\n\n"
398             "\tnotmuch search tag:inbox\n\n"
399             "\tnotmuch search to:\"%s\"\n\n"
400             "\tnotmuch search from:\"%s\"\n\n"
401             "\tnotmuch search subject:\"my favorite things\"\n\n"
402             "See \"notmuch help search\" for more details.\n\n"
403             "You can also use \"notmuch show\" with any of the thread IDs resulting\n"
404             "from a search. Finally, you may want to explore using a more sophisticated\n"
405             "interface to notmuch such as the emacs interface implemented in notmuch.el\n"
406             "or any other interface described at https://notmuchmail.org\n\n"
407             "And don't forget to run \"notmuch new\" whenever new mail arrives.\n\n"
408             "Have fun, and may your inbox never have much mail.\n\n",
409             notmuch_config_get_user_name (config),
410             notmuch_config_get_user_primary_email (config));
411
412     return EXIT_SUCCESS;
413 }
414
415 /*
416  * Try to run subcommand in argv[0] as notmuch- prefixed external
417  * command. argv must be NULL terminated (argv passed to main always
418  * is).
419  *
420  * Does not return if the external command is found and
421  * executed. Return true if external command is not found. Return
422  * false on errors.
423  */
424 static bool try_external_command(char *argv[])
425 {
426     char *old_argv0 = argv[0];
427     bool ret = true;
428
429     argv[0] = talloc_asprintf (NULL, "notmuch-%s", old_argv0);
430
431     /*
432      * This will only return on errors. Not finding an external
433      * command (ENOENT) is not an error from our perspective.
434      */
435     execvp (argv[0], argv);
436     if (errno != ENOENT) {
437         fprintf (stderr, "Error: Running external command '%s' failed: %s\n",
438                  argv[0], strerror(errno));
439         ret = false;
440     }
441
442     talloc_free (argv[0]);
443     argv[0] = old_argv0;
444
445     return ret;
446 }
447
448 int
449 main (int argc, char *argv[])
450 {
451     void *local;
452     char *talloc_report;
453     const char *command_name = NULL;
454     command_t *command;
455     const char *config_file_name = NULL;
456     notmuch_config_t *config = NULL;
457     int opt_index;
458     int ret;
459
460     notmuch_opt_desc_t options[] = {
461         { .opt_string = &config_file_name, .name = "config" },
462         { .opt_inherit = notmuch_shared_options },
463         { }
464     };
465
466     talloc_enable_null_tracking ();
467
468     local = talloc_new (NULL);
469
470     g_mime_init (GMIME_ENABLE_RFC2047_WORKAROUNDS);
471 #if !GLIB_CHECK_VERSION(2, 35, 1)
472     g_type_init ();
473 #endif
474
475     /* Globally default to the current output format version. */
476     notmuch_format_version = NOTMUCH_FORMAT_CUR;
477
478     opt_index = parse_arguments (argc, argv, options, 1);
479     if (opt_index < 0) {
480         ret = EXIT_FAILURE;
481         goto DONE;
482     }
483
484     if (opt_index < argc)
485         command_name = argv[opt_index];
486
487     notmuch_process_shared_options (command_name);
488
489     command = find_command (command_name);
490     /* if command->function is NULL, try external command */
491     if (!command || !command->function) {
492         /* This won't return if the external command is found. */
493         if (try_external_command(argv + opt_index))
494             fprintf (stderr, "Error: Unknown command '%s' (see \"notmuch help\")\n",
495                      command_name);
496         ret = EXIT_FAILURE;
497         goto DONE;
498     }
499
500     config = notmuch_config_open (local, config_file_name, command->config_mode);
501     if (!config) {
502         ret = EXIT_FAILURE;
503         goto DONE;
504     }
505
506     ret = (command->function)(config, argc - opt_index, argv + opt_index);
507
508   DONE:
509     if (config)
510         notmuch_config_close (config);
511
512     talloc_report = getenv ("NOTMUCH_TALLOC_REPORT");
513     if (talloc_report && strcmp (talloc_report, "") != 0) {
514         /* this relies on the previous call to
515          * talloc_enable_null_tracking
516          */
517
518         FILE *report = fopen (talloc_report, "w");
519         if (report) {
520             talloc_report_full (NULL, report);
521         } else {
522             ret = 1;
523             fprintf (stderr, "ERROR: unable to write talloc log. ");
524             perror (talloc_report);
525         }
526     }
527
528     talloc_free (local);
529
530     return ret;
531 }