]> git.notmuchmail.org Git - notmuch/blob - notmuch-config.c
CLI/config: drop obsolete notmuch_config_get_*
[notmuch] / notmuch-config.c
1 /* notmuch - Not much of an email program, (just index and search)
2  *
3  * Copyright © 2009 Carl Worth
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see https://www.gnu.org/licenses/ .
17  *
18  * Author: Carl Worth <cworth@cworth.org>
19  */
20
21 #include "notmuch-client.h"
22
23 #include <pwd.h>
24 #include <netdb.h>
25 #include <assert.h>
26
27 #include "unicode-util.h"
28
29 static const char toplevel_config_comment[] =
30     " .notmuch-config - Configuration file for the notmuch mail system\n"
31     "\n"
32     " For more information about notmuch, see https://notmuchmail.org";
33
34 struct config_group {
35     const char *group_name;
36     const char *comment;
37 } group_comment_table [] = {
38     {
39         "database",
40         " Database configuration\n"
41         "\n"
42         " The only value supported here is 'path' which should be the top-level\n"
43         " directory where your mail currently exists and to where mail will be\n"
44         " delivered in the future. Files should be individual email messages.\n"
45         " Notmuch will store its database within a sub-directory of the path\n"
46         " configured here named \".notmuch\".\n"
47     },
48     {
49         "user",
50         " User configuration\n"
51         "\n"
52         " Here is where you can let notmuch know how you would like to be\n"
53         " addressed. Valid settings are\n"
54         "\n"
55         "\tname         Your full name.\n"
56         "\tprimary_email        Your primary email address.\n"
57         "\tother_email  A list (separated by ';') of other email addresses\n"
58         "\t             at which you receive email.\n"
59         "\n"
60         " Notmuch will use the various email addresses configured here when\n"
61         " formatting replies. It will avoid including your own addresses in the\n"
62         " recipient list of replies, and will set the From address based on the\n"
63         " address to which the original email was addressed.\n"
64     },
65     {
66         "new",
67         " Configuration for \"notmuch new\"\n"
68         "\n"
69         " The following options are supported here:\n"
70         "\n"
71         "\ttags A list (separated by ';') of the tags that will be\n"
72         "\t     added to all messages incorporated by \"notmuch new\".\n"
73         "\n"
74         "\tignore       A list (separated by ';') of file and directory names\n"
75         "\t     that will not be searched for messages by \"notmuch new\".\n"
76         "\n"
77         "\t     NOTE: *Every* file/directory that goes by one of those\n"
78         "\t     names will be ignored, independent of its depth/location\n"
79         "\t     in the mail store.\n"
80     },
81     {
82         "search",
83         " Search configuration\n"
84         "\n"
85         " The following option is supported here:\n"
86         "\n"
87         "\texclude_tags\n"
88         "\t\tA ;-separated list of tags that will be excluded from\n"
89         "\t\tsearch results by default.  Using an excluded tag in a\n"
90         "\t\tquery will override that exclusion.\n"
91     },
92     {
93         "maildir",
94         " Maildir compatibility configuration\n"
95         "\n"
96         " The following option is supported here:\n"
97         "\n"
98         "\tsynchronize_flags      Valid values are true and false.\n"
99         "\n"
100         "\tIf true, then the following maildir flags (in message filenames)\n"
101         "\twill be synchronized with the corresponding notmuch tags:\n"
102         "\n"
103         "\t\tFlag       Tag\n"
104         "\t\t----       -------\n"
105         "\t\tD  draft\n"
106         "\t\tF  flagged\n"
107         "\t\tP  passed\n"
108         "\t\tR  replied\n"
109         "\t\tS  unread (added when 'S' flag is not present)\n"
110         "\n"
111         "\tThe \"notmuch new\" command will notice flag changes in filenames\n"
112         "\tand update tags, while the \"notmuch tag\" and \"notmuch restore\"\n"
113         "\tcommands will notice tag changes and update flags in filenames\n"
114     },
115 };
116
117 struct _notmuch_config {
118     char *filename;
119     GKeyFile *key_file;
120     bool is_new;
121
122     char *database_path;
123     char *user_name;
124     char *user_primary_email;
125     const char **user_other_email;
126     size_t user_other_email_length;
127     const char **new_tags;
128     size_t new_tags_length;
129     const char **new_ignore;
130     size_t new_ignore_length;
131     bool maildir_synchronize_flags;
132     const char **search_exclude_tags;
133     size_t search_exclude_tags_length;
134 };
135
136 static int
137 notmuch_config_destructor (notmuch_config_t *config)
138 {
139     if (config->key_file)
140         g_key_file_free (config->key_file);
141
142     return 0;
143 }
144
145
146 static bool
147 get_config_from_file (notmuch_config_t *config, bool create_new)
148 {
149     #define BUF_SIZE 4096
150     char *config_str = NULL;
151     int config_len = 0;
152     int config_bufsize = BUF_SIZE;
153     size_t len;
154     GError *error = NULL;
155     bool ret = false;
156
157     FILE *fp = fopen (config->filename, "r");
158     if (fp == NULL) {
159         if (errno == ENOENT) {
160             /* If create_new is true, then the caller is prepared for a
161              * default configuration file in the case of FILE NOT FOUND.
162              */
163             if (create_new) {
164                 config->is_new = true;
165                 ret = true;
166             } else {
167                 fprintf (stderr, "Configuration file %s not found.\n"
168                          "Try running 'notmuch setup' to create a configuration.\n",
169                          config->filename);
170             }
171         } else {
172             fprintf (stderr, "Error opening config file '%s': %s\n",
173                      config->filename, strerror (errno));
174         }
175         goto out;
176     }
177
178     config_str = talloc_zero_array (config, char, config_bufsize);
179     if (config_str == NULL) {
180         fprintf (stderr, "Error reading '%s': Out of memory\n", config->filename);
181         goto out;
182     }
183
184     while ((len = fread (config_str + config_len, 1,
185                          config_bufsize - config_len, fp)) > 0) {
186         config_len += len;
187         if (config_len == config_bufsize) {
188             config_bufsize += BUF_SIZE;
189             config_str = talloc_realloc (config, config_str, char, config_bufsize);
190             if (config_str == NULL) {
191                 fprintf (stderr, "Error reading '%s': Failed to reallocate memory\n",
192                          config->filename);
193                 goto out;
194             }
195         }
196     }
197
198     if (ferror (fp)) {
199         fprintf (stderr, "Error reading '%s': I/O error\n", config->filename);
200         goto out;
201     }
202
203     if (g_key_file_load_from_data (config->key_file, config_str, config_len,
204                                    G_KEY_FILE_KEEP_COMMENTS, &error)) {
205         ret = true;
206         goto out;
207     }
208
209     fprintf (stderr, "Error parsing config file '%s': %s\n",
210              config->filename, error->message);
211
212     g_error_free (error);
213
214   out:
215     if (fp)
216         fclose (fp);
217
218     if (config_str)
219         talloc_free (config_str);
220
221     return ret;
222 }
223
224 /* Open the named notmuch configuration file. If the filename is NULL,
225  * the value of the environment variable $NOTMUCH_CONFIG will be used.
226  * If $NOTMUCH_CONFIG is unset, the default configuration file
227  * ($HOME/.notmuch-config) will be used.
228  *
229  * If any error occurs, (out of memory, or a permission-denied error,
230  * etc.), this function will print a message to stderr and return
231  * NULL.
232  *
233  * FILE NOT FOUND: When the specified configuration file (whether from
234  * 'filename' or the $NOTMUCH_CONFIG environment variable) does not
235  * exist, the behavior of this function depends on the 'is_new_ret'
236  * variable.
237  *
238  *      If is_new_ret is NULL, then a "file not found" message will be
239  *      printed to stderr and NULL will be returned.
240  *
241  *      If is_new_ret is non-NULL then a default configuration will be
242  *      returned and *is_new_ret will be set to 1 on return so that
243  *      the caller can recognize this case.
244  *
245  *      These default configuration settings are determined as
246  *      follows:
247  *
248  *              database_path:          $MAILDIR, otherwise $HOME/mail
249  *
250  *              user_name:              $NAME variable if set, otherwise
251  *                                      read from /etc/passwd
252  *
253  *              user_primary_mail:      $EMAIL variable if set, otherwise
254  *                                      constructed from the username and
255  *                                      hostname of the current machine.
256  *
257  *              user_other_email:       Not set.
258  *
259  *      The default configuration also contains comments to guide the
260  *      user in editing the file directly.
261  */
262 notmuch_config_t *
263 notmuch_config_open (notmuch_database_t *notmuch,
264                      const char *filename,
265                      notmuch_command_mode_t config_mode)
266 {
267     char *notmuch_config_env = NULL;
268
269     notmuch_config_t *config = talloc_zero (notmuch, notmuch_config_t);
270
271     if (config == NULL) {
272         fprintf (stderr, "Out of memory.\n");
273         return NULL;
274     }
275
276     talloc_set_destructor (config, notmuch_config_destructor);
277
278     /* non-zero defaults */
279     config->maildir_synchronize_flags = true;
280
281     if (filename) {
282         config->filename = talloc_strdup (config, filename);
283     } else if ((notmuch_config_env = getenv ("NOTMUCH_CONFIG"))) {
284         config->filename = talloc_strdup (config, notmuch_config_env);
285     } else {
286         config->filename = talloc_asprintf (config, "%s/.notmuch-config",
287                                             getenv ("HOME"));
288     }
289
290     config->key_file = g_key_file_new ();
291
292     if (config_mode & NOTMUCH_COMMAND_CONFIG_OPEN) {
293         bool create_new = (config_mode & NOTMUCH_COMMAND_CONFIG_CREATE) != 0;
294
295         if (! get_config_from_file (config, create_new)) {
296             talloc_free (config);
297             return NULL;
298         }
299     }
300
301     if (config->is_new)
302         g_key_file_set_comment (config->key_file, NULL, NULL,
303                                 toplevel_config_comment, NULL);
304
305     for (size_t i = 0; i < ARRAY_SIZE (group_comment_table); i++) {
306         const char *name = group_comment_table[i].group_name;
307         if (! g_key_file_has_group (config->key_file,  name)) {
308             /* Force group to exist before adding comment */
309             g_key_file_set_value (config->key_file, name, "dummy_key", "dummy_val");
310             g_key_file_remove_key (config->key_file, name, "dummy_key", NULL);
311             g_key_file_set_comment (config->key_file, name, NULL,
312                                     group_comment_table[i].comment, NULL);
313         }
314     }
315     return config;
316 }
317
318 /* Close the given notmuch_config_t object, freeing all resources.
319  *
320  * Note: Any changes made to the configuration are *not* saved by this
321  * function. To save changes, call notmuch_config_save before
322  * notmuch_config_close.
323  */
324 void
325 notmuch_config_close (notmuch_config_t *config)
326 {
327     talloc_free (config);
328 }
329
330 /* Save any changes made to the notmuch configuration.
331  *
332  * Any comments originally in the file will be preserved.
333  *
334  * Returns 0 if successful, and 1 in case of any error, (after
335  * printing a description of the error to stderr).
336  */
337 int
338 notmuch_config_save (notmuch_config_t *config)
339 {
340     size_t length;
341     char *data, *filename;
342     GError *error = NULL;
343
344     data = g_key_file_to_data (config->key_file, &length, NULL);
345     if (data == NULL) {
346         fprintf (stderr, "Out of memory.\n");
347         return 1;
348     }
349
350     /* Try not to overwrite symlinks. */
351     filename = canonicalize_file_name (config->filename);
352     if (! filename) {
353         if (errno == ENOENT) {
354             filename = strdup (config->filename);
355             if (! filename) {
356                 fprintf (stderr, "Out of memory.\n");
357                 g_free (data);
358                 return 1;
359             }
360         } else {
361             fprintf (stderr, "Error canonicalizing %s: %s\n", config->filename,
362                      strerror (errno));
363             g_free (data);
364             return 1;
365         }
366     }
367
368     if (! g_file_set_contents (filename, data, length, &error)) {
369         if (strcmp (filename, config->filename) != 0) {
370             fprintf (stderr, "Error saving configuration to %s (-> %s): %s\n",
371                      config->filename, filename, error->message);
372         } else {
373             fprintf (stderr, "Error saving configuration to %s: %s\n",
374                      filename, error->message);
375         }
376         g_error_free (error);
377         free (filename);
378         g_free (data);
379         return 1;
380     }
381
382     free (filename);
383     g_free (data);
384     return 0;
385 }
386
387 bool
388 notmuch_config_is_new (notmuch_config_t *config)
389 {
390     return config->is_new;
391 }
392
393 static void
394 _config_set (notmuch_config_t *config, char **field,
395              const char *group, const char *key, const char *value)
396 {
397     g_key_file_set_string (config->key_file, group, key, value);
398
399     /* drop the cached value */
400     talloc_free (*field);
401     *field = NULL;
402 }
403
404 static void
405 _config_set_list (notmuch_config_t *config,
406                   const char *group, const char *key,
407                   const char *list[],
408                   size_t length, const char ***config_var )
409 {
410     g_key_file_set_string_list (config->key_file, group, key, list, length);
411
412     /* drop the cached value */
413     talloc_free (*config_var);
414     *config_var = NULL;
415 }
416
417 void
418 notmuch_config_set_database_path (notmuch_config_t *config,
419                                   const char *database_path)
420 {
421     _config_set (config, &config->database_path, "database", "path", database_path);
422 }
423
424 void
425 notmuch_config_set_user_name (notmuch_config_t *config,
426                               const char *user_name)
427 {
428     _config_set (config, &config->user_name, "user", "name", user_name);
429 }
430
431 void
432 notmuch_config_set_user_primary_email (notmuch_config_t *config,
433                                        const char *primary_email)
434 {
435     _config_set (config, &config->user_primary_email, "user", "primary_email", primary_email);
436 }
437
438 void
439 notmuch_config_set_user_other_email (notmuch_config_t *config,
440                                      const char *list[],
441                                      size_t length)
442 {
443     _config_set_list (config, "user", "other_email", list, length,
444                       &(config->user_other_email));
445 }
446
447 void
448 notmuch_config_set_new_tags (notmuch_config_t *config,
449                              const char *list[],
450                              size_t length)
451 {
452     _config_set_list (config, "new", "tags", list, length,
453                       &(config->new_tags));
454 }
455
456 void
457 notmuch_config_set_new_ignore (notmuch_config_t *config,
458                                const char *list[],
459                                size_t length)
460 {
461     _config_set_list (config, "new", "ignore", list, length,
462                       &(config->new_ignore));
463 }
464
465 void
466 notmuch_config_set_search_exclude_tags (notmuch_config_t *config,
467                                         const char *list[],
468                                         size_t length)
469 {
470     _config_set_list (config, "search", "exclude_tags", list, length,
471                       &(config->search_exclude_tags));
472 }
473
474
475 /* Given a configuration item of the form <group>.<key> return the
476  * component group and key. If any error occurs, print a message on
477  * stderr and return 1. Otherwise, return 0.
478  *
479  * Note: This function modifies the original 'item' string.
480  */
481 static int
482 _item_split (char *item, char **group, char **key)
483 {
484     char *period;
485
486     *group = item;
487
488     period = strchr (item, '.');
489     if (period == NULL || *(period + 1) == '\0') {
490         fprintf (stderr,
491                  "Invalid configuration name: %s\n"
492                  "(Should be of the form <section>.<item>)\n", item);
493         return 1;
494     }
495
496     *period = '\0';
497     *key = period + 1;
498
499     return 0;
500 }
501
502 /* These are more properly called Xapian fields, but the user facing
503  * docs call them prefixes, so make the error message match */
504 static bool
505 validate_field_name (const char *str)
506 {
507     const char *key;
508
509     if (! g_utf8_validate (str, -1, NULL)) {
510         fprintf (stderr, "Invalid utf8: %s\n", str);
511         return false;
512     }
513
514     key = g_utf8_strrchr (str, -1, '.');
515     if (! key ) {
516         INTERNAL_ERROR ("Impossible code path on input: %s\n", str);
517     }
518
519     key++;
520
521     if (! *key) {
522         fprintf (stderr, "Empty prefix name: %s\n", str);
523         return false;
524     }
525
526     if (! unicode_word_utf8 (key)) {
527         fprintf (stderr, "Non-word character in prefix name: %s\n", key);
528         return false;
529     }
530
531     if (key[0] >= 'a' && key[0] <= 'z') {
532         fprintf (stderr, "Prefix names starting with lower case letters are reserved: %s\n", key);
533         return false;
534     }
535
536     return true;
537 }
538
539 #define BUILT_WITH_PREFIX "built_with."
540
541 typedef struct config_key {
542     const char *name;
543     bool in_db;
544     bool prefix;
545     bool (*validate)(const char *);
546 } config_key_info_t;
547
548 static struct config_key
549     config_key_table[] = {
550     { "index.decrypt",   true,   false,  NULL },
551     { "index.header.",   true,   true,   validate_field_name },
552     { "query.",          true,   true,   NULL },
553 };
554
555 static config_key_info_t *
556 _config_key_info (const char *item)
557 {
558     for (size_t i = 0; i < ARRAY_SIZE (config_key_table); i++) {
559         if (config_key_table[i].prefix &&
560             strncmp (item, config_key_table[i].name,
561                      strlen (config_key_table[i].name)) == 0)
562             return config_key_table + i;
563         if (strcmp (item, config_key_table[i].name) == 0)
564             return config_key_table + i;
565     }
566     return NULL;
567 }
568
569 static int
570 notmuch_config_command_get (notmuch_database_t *notmuch, char *item)
571 {
572     notmuch_config_values_t *list;
573
574     for (list = notmuch_config_get_values_string (notmuch, item);
575          notmuch_config_values_valid (list);
576          notmuch_config_values_move_to_next (list)) {
577         const char *val = notmuch_config_values_get (list);
578         puts (val);
579     }
580     return EXIT_SUCCESS;
581 }
582
583 static int
584 _set_db_config (notmuch_database_t *notmuch, const char *key, int argc, char **argv)
585 {
586     const char *val = "";
587
588     if (argc > 1) {
589         /* XXX handle lists? */
590         fprintf (stderr, "notmuch config set: at most one value expected for %s\n", key);
591         return EXIT_FAILURE;
592     }
593
594     if (argc > 0) {
595         val = argv[0];
596     }
597
598     if (print_status_database ("notmuch config", notmuch,
599                                notmuch_database_reopen (notmuch,
600                                                         NOTMUCH_DATABASE_MODE_READ_WRITE)))
601         return EXIT_FAILURE;
602
603     if (print_status_database ("notmuch config", notmuch,
604                                notmuch_database_set_config (notmuch, key, val)))
605         return EXIT_FAILURE;
606
607     if (print_status_database ("notmuch config", notmuch,
608                                notmuch_database_close (notmuch)))
609         return EXIT_FAILURE;
610
611     return EXIT_SUCCESS;
612 }
613
614 static int
615 notmuch_config_command_set (notmuch_config_t *config, notmuch_database_t *notmuch, char *item,
616                             int argc, char *argv[])
617 {
618     char *group, *key;
619     config_key_info_t *key_info;
620
621     if (STRNCMP_LITERAL (item, BUILT_WITH_PREFIX) == 0) {
622         fprintf (stderr, "Error: read only option: %s\n", item);
623         return 1;
624     }
625
626     key_info = _config_key_info (item);
627     if (key_info && key_info->validate && (! key_info->validate (item)))
628         return 1;
629
630     if (key_info && key_info->in_db) {
631         return _set_db_config (notmuch, item, argc, argv);
632     }
633
634     if (_item_split (item, &group, &key))
635         return 1;
636
637     /* With only the name of an item, we clear it from the
638      * configuration file.
639      *
640      * With a single value, we set it as a string.
641      *
642      * With multiple values, we set them as a string list.
643      */
644     switch (argc) {
645     case 0:
646         g_key_file_remove_key (config->key_file, group, key, NULL);
647         break;
648     case 1:
649         g_key_file_set_string (config->key_file, group, key, argv[0]);
650         break;
651     default:
652         g_key_file_set_string_list (config->key_file, group, key,
653                                     (const gchar **) argv, argc);
654         break;
655     }
656
657     return notmuch_config_save (config);
658 }
659
660 static
661 void
662 _notmuch_config_list_built_with ()
663 {
664     printf ("%scompact=%s\n",
665             BUILT_WITH_PREFIX,
666             notmuch_built_with ("compact") ? "true" : "false");
667     printf ("%sfield_processor=%s\n",
668             BUILT_WITH_PREFIX,
669             notmuch_built_with ("field_processor") ? "true" : "false");
670     printf ("%sretry_lock=%s\n",
671             BUILT_WITH_PREFIX,
672             notmuch_built_with ("retry_lock") ? "true" : "false");
673 }
674
675 static int
676 notmuch_config_command_list (notmuch_database_t *notmuch)
677 {
678     notmuch_config_pairs_t *list;
679
680     _notmuch_config_list_built_with ();
681     for (list = notmuch_config_get_pairs (notmuch, "");
682          notmuch_config_pairs_valid (list);
683          notmuch_config_pairs_move_to_next (list)) {
684         const char *value = notmuch_config_pairs_value (list);
685         if (value)
686             printf ("%s=%s\n", notmuch_config_pairs_key (list), value);
687     }
688     notmuch_config_pairs_destroy (list);
689     return EXIT_SUCCESS;
690 }
691
692 int
693 notmuch_config_command (notmuch_config_t *config, notmuch_database_t *notmuch,
694                         int argc, char *argv[])
695 {
696     int ret;
697     int opt_index;
698
699     opt_index = notmuch_minimal_options ("config", argc, argv);
700     if (opt_index < 0)
701         return EXIT_FAILURE;
702
703     if (notmuch_requested_db_uuid)
704         fprintf (stderr, "Warning: ignoring --uuid=%s\n",
705                  notmuch_requested_db_uuid);
706
707     /* skip at least subcommand argument */
708     argc -= opt_index;
709     argv += opt_index;
710
711     if (argc < 1) {
712         fprintf (stderr, "Error: notmuch config requires at least one argument.\n");
713         return EXIT_FAILURE;
714     }
715
716     if (strcmp (argv[0], "get") == 0) {
717         if (argc != 2) {
718             fprintf (stderr, "Error: notmuch config get requires exactly "
719                      "one argument.\n");
720             return EXIT_FAILURE;
721         }
722         ret = notmuch_config_command_get (notmuch, argv[1]);
723     } else if (strcmp (argv[0], "set") == 0) {
724         if (argc < 2) {
725             fprintf (stderr, "Error: notmuch config set requires at least "
726                      "one argument.\n");
727             return EXIT_FAILURE;
728         }
729         ret = notmuch_config_command_set (config, notmuch, argv[1], argc - 2, argv + 2);
730     } else if (strcmp (argv[0], "list") == 0) {
731         ret = notmuch_config_command_list (notmuch);
732     } else {
733         fprintf (stderr, "Unrecognized argument for notmuch config: %s\n",
734                  argv[0]);
735         return EXIT_FAILURE;
736     }
737
738     return ret ? EXIT_FAILURE : EXIT_SUCCESS;
739
740 }
741
742 void
743 notmuch_config_set_maildir_synchronize_flags (notmuch_config_t *config,
744                                               bool synchronize_flags)
745 {
746     g_key_file_set_boolean (config->key_file,
747                             "maildir", "synchronize_flags", synchronize_flags);
748     config->maildir_synchronize_flags = synchronize_flags;
749 }