]> git.notmuchmail.org Git - notmuch/blob - notmuch-config.c
lib/open: pull _load_key_file out of _choose_database_path
[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 static const char database_config_comment[] =
35     " Database configuration\n"
36     "\n"
37     " The only value supported here is 'path' which should be the top-level\n"
38     " directory where your mail currently exists and to where mail will be\n"
39     " delivered in the future. Files should be individual email messages.\n"
40     " Notmuch will store its database within a sub-directory of the path\n"
41     " configured here named \".notmuch\".\n";
42
43 static const char new_config_comment[] =
44     " Configuration for \"notmuch new\"\n"
45     "\n"
46     " The following options are supported here:\n"
47     "\n"
48     "\ttags     A list (separated by ';') of the tags that will be\n"
49     "\t added to all messages incorporated by \"notmuch new\".\n"
50     "\n"
51     "\tignore   A list (separated by ';') of file and directory names\n"
52     "\t that will not be searched for messages by \"notmuch new\".\n"
53     "\n"
54     "\t NOTE: *Every* file/directory that goes by one of those\n"
55     "\t names will be ignored, independent of its depth/location\n"
56     "\t in the mail store.\n";
57
58 static const char user_config_comment[] =
59     " User configuration\n"
60     "\n"
61     " Here is where you can let notmuch know how you would like to be\n"
62     " addressed. Valid settings are\n"
63     "\n"
64     "\tname             Your full name.\n"
65     "\tprimary_email    Your primary email address.\n"
66     "\tother_email      A list (separated by ';') of other email addresses\n"
67     "\t         at which you receive email.\n"
68     "\n"
69     " Notmuch will use the various email addresses configured here when\n"
70     " formatting replies. It will avoid including your own addresses in the\n"
71     " recipient list of replies, and will set the From address based on the\n"
72     " address to which the original email was addressed.\n";
73
74 static const char maildir_config_comment[] =
75     " Maildir compatibility configuration\n"
76     "\n"
77     " The following option is supported here:\n"
78     "\n"
79     "\tsynchronize_flags      Valid values are true and false.\n"
80     "\n"
81     "\tIf true, then the following maildir flags (in message filenames)\n"
82     "\twill be synchronized with the corresponding notmuch tags:\n"
83     "\n"
84     "\t\tFlag   Tag\n"
85     "\t\t----   -------\n"
86     "\t\tD      draft\n"
87     "\t\tF      flagged\n"
88     "\t\tP      passed\n"
89     "\t\tR      replied\n"
90     "\t\tS      unread (added when 'S' flag is not present)\n"
91     "\n"
92     "\tThe \"notmuch new\" command will notice flag changes in filenames\n"
93     "\tand update tags, while the \"notmuch tag\" and \"notmuch restore\"\n"
94     "\tcommands will notice tag changes and update flags in filenames\n";
95
96 static const char search_config_comment[] =
97     " Search configuration\n"
98     "\n"
99     " The following option is supported here:\n"
100     "\n"
101     "\texclude_tags\n"
102     "\t\tA ;-separated list of tags that will be excluded from\n"
103     "\t\tsearch results by default.  Using an excluded tag in a\n"
104     "\t\tquery will override that exclusion.\n";
105
106 static const char crypto_config_comment[] =
107     " Cryptography related configuration\n"
108     "\n"
109     " The following old option is now ignored:\n"
110     "\n"
111     "\tgpgpath\n"
112     "\t\tThis option was used by older builds of notmuch to choose\n"
113     "\t\tthe version of gpg to use.\n"
114     "\t\tSetting $PATH is a better approach.\n";
115
116 struct _notmuch_config {
117     char *filename;
118     GKeyFile *key_file;
119     bool is_new;
120
121     char *database_path;
122     char *user_name;
123     char *user_primary_email;
124     const char **user_other_email;
125     size_t user_other_email_length;
126     const char **new_tags;
127     size_t new_tags_length;
128     const char **new_ignore;
129     size_t new_ignore_length;
130     bool maildir_synchronize_flags;
131     const char **search_exclude_tags;
132     size_t search_exclude_tags_length;
133 };
134
135 static int
136 notmuch_config_destructor (notmuch_config_t *config)
137 {
138     if (config->key_file)
139         g_key_file_free (config->key_file);
140
141     return 0;
142 }
143
144 static char *
145 get_name_from_passwd_file (void *ctx)
146 {
147     long pw_buf_size;
148     char *pw_buf;
149     struct passwd passwd, *ignored;
150     char *name;
151     int e;
152
153     pw_buf_size = sysconf (_SC_GETPW_R_SIZE_MAX);
154     if (pw_buf_size == -1) pw_buf_size = 64;
155     pw_buf = talloc_size (ctx, pw_buf_size);
156
157     while ((e = getpwuid_r (getuid (), &passwd, pw_buf,
158                             pw_buf_size, &ignored)) == ERANGE) {
159         pw_buf_size = pw_buf_size * 2;
160         pw_buf = talloc_zero_size (ctx, pw_buf_size);
161     }
162
163     if (e == 0) {
164         char *comma = strchr (passwd.pw_gecos, ',');
165         if (comma)
166             name = talloc_strndup (ctx, passwd.pw_gecos,
167                                    comma - passwd.pw_gecos);
168         else
169             name = talloc_strdup (ctx, passwd.pw_gecos);
170     } else {
171         name = talloc_strdup (ctx, "");
172     }
173
174     talloc_free (pw_buf);
175
176     return name;
177 }
178
179 static char *
180 get_username_from_passwd_file (void *ctx)
181 {
182     long pw_buf_size;
183     char *pw_buf;
184     struct passwd passwd, *ignored;
185     char *name;
186     int e;
187
188     pw_buf_size = sysconf (_SC_GETPW_R_SIZE_MAX);
189     if (pw_buf_size == -1) pw_buf_size = 64;
190     pw_buf = talloc_zero_size (ctx, pw_buf_size);
191
192     while ((e = getpwuid_r (getuid (), &passwd, pw_buf,
193                             pw_buf_size, &ignored)) == ERANGE) {
194         pw_buf_size = pw_buf_size * 2;
195         pw_buf = talloc_zero_size (ctx, pw_buf_size);
196     }
197
198     if (e == 0)
199         name = talloc_strdup (ctx, passwd.pw_name);
200     else
201         name = talloc_strdup (ctx, "");
202
203     talloc_free (pw_buf);
204
205     return name;
206 }
207
208 static bool
209 get_config_from_file (notmuch_config_t *config, bool create_new)
210 {
211     #define BUF_SIZE 4096
212     char *config_str = NULL;
213     int config_len = 0;
214     int config_bufsize = BUF_SIZE;
215     size_t len;
216     GError *error = NULL;
217     bool ret = false;
218
219     FILE *fp = fopen (config->filename, "r");
220     if (fp == NULL) {
221         if (errno == ENOENT) {
222             /* If create_new is true, then the caller is prepared for a
223              * default configuration file in the case of FILE NOT FOUND.
224              */
225             if (create_new) {
226                 config->is_new = true;
227                 ret = true;
228             } else {
229                 fprintf (stderr, "Configuration file %s not found.\n"
230                          "Try running 'notmuch setup' to create a configuration.\n",
231                          config->filename);
232             }
233         } else {
234             fprintf (stderr, "Error opening config file '%s': %s\n",
235                      config->filename, strerror (errno));
236         }
237         goto out;
238     }
239
240     config_str = talloc_zero_array (config, char, config_bufsize);
241     if (config_str == NULL) {
242         fprintf (stderr, "Error reading '%s': Out of memory\n", config->filename);
243         goto out;
244     }
245
246     while ((len = fread (config_str + config_len, 1,
247                          config_bufsize - config_len, fp)) > 0) {
248         config_len += len;
249         if (config_len == config_bufsize) {
250             config_bufsize += BUF_SIZE;
251             config_str = talloc_realloc (config, config_str, char, config_bufsize);
252             if (config_str == NULL) {
253                 fprintf (stderr, "Error reading '%s': Failed to reallocate memory\n",
254                          config->filename);
255                 goto out;
256             }
257         }
258     }
259
260     if (ferror (fp)) {
261         fprintf (stderr, "Error reading '%s': I/O error\n", config->filename);
262         goto out;
263     }
264
265     if (g_key_file_load_from_data (config->key_file, config_str, config_len,
266                                    G_KEY_FILE_KEEP_COMMENTS, &error)) {
267         ret = true;
268         goto out;
269     }
270
271     fprintf (stderr, "Error parsing config file '%s': %s\n",
272              config->filename, error->message);
273
274     g_error_free (error);
275
276   out:
277     if (fp)
278         fclose (fp);
279
280     if (config_str)
281         talloc_free (config_str);
282
283     return ret;
284 }
285
286 /* Open the named notmuch configuration file. If the filename is NULL,
287  * the value of the environment variable $NOTMUCH_CONFIG will be used.
288  * If $NOTMUCH_CONFIG is unset, the default configuration file
289  * ($HOME/.notmuch-config) will be used.
290  *
291  * If any error occurs, (out of memory, or a permission-denied error,
292  * etc.), this function will print a message to stderr and return
293  * NULL.
294  *
295  * FILE NOT FOUND: When the specified configuration file (whether from
296  * 'filename' or the $NOTMUCH_CONFIG environment variable) does not
297  * exist, the behavior of this function depends on the 'is_new_ret'
298  * variable.
299  *
300  *      If is_new_ret is NULL, then a "file not found" message will be
301  *      printed to stderr and NULL will be returned.
302  *
303  *      If is_new_ret is non-NULL then a default configuration will be
304  *      returned and *is_new_ret will be set to 1 on return so that
305  *      the caller can recognize this case.
306  *
307  *      These default configuration settings are determined as
308  *      follows:
309  *
310  *              database_path:          $MAILDIR, otherwise $HOME/mail
311  *
312  *              user_name:              $NAME variable if set, otherwise
313  *                                      read from /etc/passwd
314  *
315  *              user_primary_mail:      $EMAIL variable if set, otherwise
316  *                                      constructed from the username and
317  *                                      hostname of the current machine.
318  *
319  *              user_other_email:       Not set.
320  *
321  *      The default configuration also contains comments to guide the
322  *      user in editing the file directly.
323  */
324 notmuch_config_t *
325 notmuch_config_open (void *ctx,
326                      const char *filename,
327                      notmuch_command_mode_t config_mode)
328 {
329     GError *error = NULL;
330     size_t tmp;
331     char *notmuch_config_env = NULL;
332     int file_had_database_group;
333     int file_had_new_group;
334     int file_had_user_group;
335     int file_had_maildir_group;
336     int file_had_search_group;
337     int file_had_crypto_group;
338
339     notmuch_config_t *config = talloc_zero (ctx, notmuch_config_t);
340
341     if (config == NULL) {
342         fprintf (stderr, "Out of memory.\n");
343         return NULL;
344     }
345
346     talloc_set_destructor (config, notmuch_config_destructor);
347
348     /* non-zero defaults */
349     config->maildir_synchronize_flags = true;
350
351     if (filename) {
352         config->filename = talloc_strdup (config, filename);
353     } else if ((notmuch_config_env = getenv ("NOTMUCH_CONFIG"))) {
354         config->filename = talloc_strdup (config, notmuch_config_env);
355     } else {
356         config->filename = talloc_asprintf (config, "%s/.notmuch-config",
357                                             getenv ("HOME"));
358     }
359
360     config->key_file = g_key_file_new ();
361
362     if (config_mode & NOTMUCH_COMMAND_CONFIG_OPEN) {
363         bool create_new = (config_mode & NOTMUCH_COMMAND_CONFIG_CREATE) != 0;
364
365         if (! get_config_from_file (config, create_new)) {
366             talloc_free (config);
367             return NULL;
368         }
369     }
370
371     /* Whenever we know of configuration sections that don't appear in
372      * the configuration file, we add some comments to help the user
373      * understand what can be done.
374      *
375      * It would be convenient to just add those comments now, but
376      * apparently g_key_file will clear any comments when keys are
377      * added later that create the groups. So we have to check for the
378      * groups now, but add the comments only after setting all of our
379      * values.
380      */
381     file_had_database_group = g_key_file_has_group (config->key_file,
382                                                     "database");
383     file_had_new_group = g_key_file_has_group (config->key_file, "new");
384     file_had_user_group = g_key_file_has_group (config->key_file, "user");
385     file_had_maildir_group = g_key_file_has_group (config->key_file, "maildir");
386     file_had_search_group = g_key_file_has_group (config->key_file, "search");
387     file_had_crypto_group = g_key_file_has_group (config->key_file, "crypto");
388
389     if (notmuch_config_get_database_path (config) == NULL) {
390         char *path = getenv ("MAILDIR");
391         if (path)
392             path = talloc_strdup (config, path);
393         else
394             path = talloc_asprintf (config, "%s/mail",
395                                     getenv ("HOME"));
396         notmuch_config_set_database_path (config, path);
397         talloc_free (path);
398     }
399
400     if (notmuch_config_get_user_name (config) == NULL) {
401         char *name = getenv ("NAME");
402         if (name)
403             name = talloc_strdup (config, name);
404         else
405             name = get_name_from_passwd_file (config);
406         notmuch_config_set_user_name (config, name);
407         talloc_free (name);
408     }
409
410     if (notmuch_config_get_user_primary_email (config) == NULL) {
411         char *email = getenv ("EMAIL");
412         if (email) {
413             notmuch_config_set_user_primary_email (config, email);
414         } else {
415             char hostname[256];
416             struct hostent *hostent;
417             const char *domainname;
418
419             char *username = get_username_from_passwd_file (config);
420
421             gethostname (hostname, 256);
422             hostname[255] = '\0';
423
424             hostent = gethostbyname (hostname);
425             if (hostent && (domainname = strchr (hostent->h_name, '.')))
426                 domainname += 1;
427             else
428                 domainname = "(none)";
429
430             email = talloc_asprintf (config, "%s@%s.%s",
431                                      username, hostname, domainname);
432
433             notmuch_config_set_user_primary_email (config, email);
434
435             talloc_free (username);
436             talloc_free (email);
437         }
438     }
439
440     if (notmuch_config_get_new_tags (config, &tmp) == NULL) {
441         const char *tags[] = { "unread", "inbox" };
442         notmuch_config_set_new_tags (config, tags, 2);
443     }
444
445     if (notmuch_config_get_new_ignore (config, &tmp) == NULL) {
446         notmuch_config_set_new_ignore (config, NULL, 0);
447     }
448
449     if (notmuch_config_get_search_exclude_tags (config, &tmp) == NULL) {
450         if (config->is_new) {
451             const char *tags[] = { "deleted", "spam" };
452             notmuch_config_set_search_exclude_tags (config, tags, 2);
453         } else {
454             notmuch_config_set_search_exclude_tags (config, NULL, 0);
455         }
456     }
457
458     error = NULL;
459     config->maildir_synchronize_flags =
460         g_key_file_get_boolean (config->key_file,
461                                 "maildir", "synchronize_flags", &error);
462     if (error) {
463         notmuch_config_set_maildir_synchronize_flags (config, true);
464         g_error_free (error);
465     }
466
467     /* Whenever we know of configuration sections that don't appear in
468      * the configuration file, we add some comments to help the user
469      * understand what can be done. */
470     if (config->is_new)
471         g_key_file_set_comment (config->key_file, NULL, NULL,
472                                 toplevel_config_comment, NULL);
473
474     if (! file_had_database_group)
475         g_key_file_set_comment (config->key_file, "database", NULL,
476                                 database_config_comment, NULL);
477
478     if (! file_had_new_group)
479         g_key_file_set_comment (config->key_file, "new", NULL,
480                                 new_config_comment, NULL);
481
482     if (! file_had_user_group)
483         g_key_file_set_comment (config->key_file, "user", NULL,
484                                 user_config_comment, NULL);
485
486     if (! file_had_maildir_group)
487         g_key_file_set_comment (config->key_file, "maildir", NULL,
488                                 maildir_config_comment, NULL);
489
490     if (! file_had_search_group)
491         g_key_file_set_comment (config->key_file, "search", NULL,
492                                 search_config_comment, NULL);
493
494     if (! file_had_crypto_group)
495         g_key_file_set_comment (config->key_file, "crypto", NULL,
496                                 crypto_config_comment, NULL);
497
498     return config;
499 }
500
501 /* Close the given notmuch_config_t object, freeing all resources.
502  *
503  * Note: Any changes made to the configuration are *not* saved by this
504  * function. To save changes, call notmuch_config_save before
505  * notmuch_config_close.
506  */
507 void
508 notmuch_config_close (notmuch_config_t *config)
509 {
510     talloc_free (config);
511 }
512
513 const char *
514 _notmuch_config_get_path (notmuch_config_t *config)
515 {
516     return config->filename;
517 }
518 /* Save any changes made to the notmuch configuration.
519  *
520  * Any comments originally in the file will be preserved.
521  *
522  * Returns 0 if successful, and 1 in case of any error, (after
523  * printing a description of the error to stderr).
524  */
525 int
526 notmuch_config_save (notmuch_config_t *config)
527 {
528     size_t length;
529     char *data, *filename;
530     GError *error = NULL;
531
532     data = g_key_file_to_data (config->key_file, &length, NULL);
533     if (data == NULL) {
534         fprintf (stderr, "Out of memory.\n");
535         return 1;
536     }
537
538     /* Try not to overwrite symlinks. */
539     filename = canonicalize_file_name (config->filename);
540     if (! filename) {
541         if (errno == ENOENT) {
542             filename = strdup (config->filename);
543             if (! filename) {
544                 fprintf (stderr, "Out of memory.\n");
545                 g_free (data);
546                 return 1;
547             }
548         } else {
549             fprintf (stderr, "Error canonicalizing %s: %s\n", config->filename,
550                      strerror (errno));
551             g_free (data);
552             return 1;
553         }
554     }
555
556     if (! g_file_set_contents (filename, data, length, &error)) {
557         if (strcmp (filename, config->filename) != 0) {
558             fprintf (stderr, "Error saving configuration to %s (-> %s): %s\n",
559                      config->filename, filename, error->message);
560         } else {
561             fprintf (stderr, "Error saving configuration to %s: %s\n",
562                      filename, error->message);
563         }
564         g_error_free (error);
565         free (filename);
566         g_free (data);
567         return 1;
568     }
569
570     free (filename);
571     g_free (data);
572     return 0;
573 }
574
575 bool
576 notmuch_config_is_new (notmuch_config_t *config)
577 {
578     return config->is_new;
579 }
580
581 static const char *
582 _config_get (notmuch_config_t *config, char **field,
583              const char *group, const char *key)
584 {
585     /* read from config file and cache value, if not cached already */
586     if (*field == NULL) {
587         char *value;
588         value = g_key_file_get_string (config->key_file, group, key, NULL);
589         if (value) {
590             *field = talloc_strdup (config, value);
591             free (value);
592         }
593     }
594     return *field;
595 }
596
597 static void
598 _config_set (notmuch_config_t *config, char **field,
599              const char *group, const char *key, const char *value)
600 {
601     g_key_file_set_string (config->key_file, group, key, value);
602
603     /* drop the cached value */
604     talloc_free (*field);
605     *field = NULL;
606 }
607
608 static const char **
609 _config_get_list (notmuch_config_t *config,
610                   const char *section, const char *key,
611                   const char ***outlist, size_t *list_length, size_t *ret_length)
612 {
613     assert (outlist);
614
615     /* read from config file and cache value, if not cached already */
616     if (*outlist == NULL) {
617
618         char **inlist = g_key_file_get_string_list (config->key_file,
619                                                     section, key, list_length, NULL);
620         if (inlist) {
621             unsigned int i;
622
623             *outlist = talloc_size (config, sizeof (char *) * (*list_length + 1));
624
625             for (i = 0; i < *list_length; i++)
626                 (*outlist)[i] = talloc_strdup (*outlist, inlist[i]);
627
628             (*outlist)[i] = NULL;
629
630             g_strfreev (inlist);
631         }
632     }
633
634     if (ret_length)
635         *ret_length = *list_length;
636
637     return *outlist;
638 }
639
640 static void
641 _config_set_list (notmuch_config_t *config,
642                   const char *group, const char *key,
643                   const char *list[],
644                   size_t length, const char ***config_var )
645 {
646     g_key_file_set_string_list (config->key_file, group, key, list, length);
647
648     /* drop the cached value */
649     talloc_free (*config_var);
650     *config_var = NULL;
651 }
652
653 const char *
654 notmuch_config_get_database_path (notmuch_config_t *config)
655 {
656     char *db_path = (char *) _config_get (config, &config->database_path, "database", "path");
657
658     if (db_path && *db_path != '/') {
659         /* If the path in the configuration file begins with any
660          * character other than /, presume that it is relative to
661          * $HOME and update as appropriate.
662          */
663         char *abs_path = talloc_asprintf (config, "%s/%s", getenv ("HOME"), db_path);
664         talloc_free (db_path);
665         db_path = config->database_path = abs_path;
666     }
667
668     return db_path;
669 }
670
671 void
672 notmuch_config_set_database_path (notmuch_config_t *config,
673                                   const char *database_path)
674 {
675     _config_set (config, &config->database_path, "database", "path", database_path);
676 }
677
678 const char *
679 notmuch_config_get_user_name (notmuch_config_t *config)
680 {
681     return _config_get (config, &config->user_name, "user", "name");
682 }
683
684 void
685 notmuch_config_set_user_name (notmuch_config_t *config,
686                               const char *user_name)
687 {
688     _config_set (config, &config->user_name, "user", "name", user_name);
689 }
690
691 const char *
692 notmuch_config_get_user_primary_email (notmuch_config_t *config)
693 {
694     return _config_get (config, &config->user_primary_email, "user", "primary_email");
695 }
696
697 void
698 notmuch_config_set_user_primary_email (notmuch_config_t *config,
699                                        const char *primary_email)
700 {
701     _config_set (config, &config->user_primary_email, "user", "primary_email", primary_email);
702 }
703
704 const char **
705 notmuch_config_get_user_other_email (notmuch_config_t *config,   size_t *length)
706 {
707     return _config_get_list (config, "user", "other_email",
708                              &(config->user_other_email),
709                              &(config->user_other_email_length), length);
710 }
711
712 const char **
713 notmuch_config_get_new_tags (notmuch_config_t *config,   size_t *length)
714 {
715     return _config_get_list (config, "new", "tags",
716                              &(config->new_tags),
717                              &(config->new_tags_length), length);
718 }
719
720 const char **
721 notmuch_config_get_new_ignore (notmuch_config_t *config, size_t *length)
722 {
723     return _config_get_list (config, "new", "ignore",
724                              &(config->new_ignore),
725                              &(config->new_ignore_length), length);
726 }
727
728 void
729 notmuch_config_set_user_other_email (notmuch_config_t *config,
730                                      const char *list[],
731                                      size_t length)
732 {
733     _config_set_list (config, "user", "other_email", list, length,
734                       &(config->user_other_email));
735 }
736
737 void
738 notmuch_config_set_new_tags (notmuch_config_t *config,
739                              const char *list[],
740                              size_t length)
741 {
742     _config_set_list (config, "new", "tags", list, length,
743                       &(config->new_tags));
744 }
745
746 void
747 notmuch_config_set_new_ignore (notmuch_config_t *config,
748                                const char *list[],
749                                size_t length)
750 {
751     _config_set_list (config, "new", "ignore", list, length,
752                       &(config->new_ignore));
753 }
754
755 const char **
756 notmuch_config_get_search_exclude_tags (notmuch_config_t *config, size_t *length)
757 {
758     return _config_get_list (config, "search", "exclude_tags",
759                              &(config->search_exclude_tags),
760                              &(config->search_exclude_tags_length), length);
761 }
762
763 void
764 notmuch_config_set_search_exclude_tags (notmuch_config_t *config,
765                                         const char *list[],
766                                         size_t length)
767 {
768     _config_set_list (config, "search", "exclude_tags", list, length,
769                       &(config->search_exclude_tags));
770 }
771
772
773 /* Given a configuration item of the form <group>.<key> return the
774  * component group and key. If any error occurs, print a message on
775  * stderr and return 1. Otherwise, return 0.
776  *
777  * Note: This function modifies the original 'item' string.
778  */
779 static int
780 _item_split (char *item, char **group, char **key)
781 {
782     char *period;
783
784     *group = item;
785
786     period = strchr (item, '.');
787     if (period == NULL || *(period + 1) == '\0') {
788         fprintf (stderr,
789                  "Invalid configuration name: %s\n"
790                  "(Should be of the form <section>.<item>)\n", item);
791         return 1;
792     }
793
794     *period = '\0';
795     *key = period + 1;
796
797     return 0;
798 }
799
800 /* These are more properly called Xapian fields, but the user facing
801  * docs call them prefixes, so make the error message match */
802 static bool
803 validate_field_name (const char *str)
804 {
805     const char *key;
806
807     if (! g_utf8_validate (str, -1, NULL)) {
808         fprintf (stderr, "Invalid utf8: %s\n", str);
809         return false;
810     }
811
812     key = g_utf8_strrchr (str, -1, '.');
813     if (! key ) {
814         INTERNAL_ERROR ("Impossible code path on input: %s\n", str);
815     }
816
817     key++;
818
819     if (! *key) {
820         fprintf (stderr, "Empty prefix name: %s\n", str);
821         return false;
822     }
823
824     if (! unicode_word_utf8 (key)) {
825         fprintf (stderr, "Non-word character in prefix name: %s\n", key);
826         return false;
827     }
828
829     if (key[0] >= 'a' && key[0] <= 'z') {
830         fprintf (stderr, "Prefix names starting with lower case letters are reserved: %s\n", key);
831         return false;
832     }
833
834     return true;
835 }
836
837 #define BUILT_WITH_PREFIX "built_with."
838
839 typedef struct config_key {
840     const char *name;
841     bool in_db;
842     bool prefix;
843     bool (*validate)(const char *);
844 } config_key_info_t;
845
846 static struct config_key
847     config_key_table[] = {
848     { "index.decrypt",   true,   false,  NULL },
849     { "index.header.",   true,   true,   validate_field_name },
850     { "query.",          true,   true,   NULL },
851 };
852
853 static config_key_info_t *
854 _config_key_info (const char *item)
855 {
856     for (size_t i = 0; i < ARRAY_SIZE (config_key_table); i++) {
857         if (config_key_table[i].prefix &&
858             strncmp (item, config_key_table[i].name,
859                      strlen (config_key_table[i].name)) == 0)
860             return config_key_table + i;
861         if (strcmp (item, config_key_table[i].name) == 0)
862             return config_key_table + i;
863     }
864     return NULL;
865 }
866
867 static bool
868 _stored_in_db (const char *item)
869 {
870     config_key_info_t *info;
871
872     info = _config_key_info (item);
873
874     return (info && info->in_db);
875 }
876
877 static int
878 _print_db_config (notmuch_config_t *config, const char *name)
879 {
880     notmuch_database_t *notmuch;
881     char *val;
882
883     if (notmuch_database_open (notmuch_config_get_database_path (config),
884                                NOTMUCH_DATABASE_MODE_READ_ONLY, &notmuch))
885         return EXIT_FAILURE;
886
887     /* XXX Handle UUID mismatch? */
888
889     if (print_status_database ("notmuch config", notmuch,
890                                notmuch_database_get_config (notmuch, name, &val)))
891         return EXIT_FAILURE;
892
893     puts (val);
894
895     return EXIT_SUCCESS;
896 }
897
898 static int
899 notmuch_config_command_get (notmuch_config_t *config, char *item)
900 {
901     if (strcmp (item, "database.path") == 0) {
902         printf ("%s\n", notmuch_config_get_database_path (config));
903     } else if (strcmp (item, "user.name") == 0) {
904         printf ("%s\n", notmuch_config_get_user_name (config));
905     } else if (strcmp (item, "user.primary_email") == 0) {
906         printf ("%s\n", notmuch_config_get_user_primary_email (config));
907     } else if (strcmp (item, "user.other_email") == 0) {
908         const char **other_email;
909         size_t i, length;
910
911         other_email = notmuch_config_get_user_other_email (config, &length);
912         for (i = 0; i < length; i++)
913             printf ("%s\n", other_email[i]);
914     } else if (strcmp (item, "new.tags") == 0) {
915         const char **tags;
916         size_t i, length;
917
918         tags = notmuch_config_get_new_tags (config, &length);
919         for (i = 0; i < length; i++)
920             printf ("%s\n", tags[i]);
921     } else if (STRNCMP_LITERAL (item, BUILT_WITH_PREFIX) == 0) {
922         printf ("%s\n",
923                 notmuch_built_with (item + strlen (BUILT_WITH_PREFIX)) ? "true" : "false");
924     } else if (_stored_in_db (item)) {
925         return _print_db_config (config, item);
926     } else {
927         char **value;
928         size_t i, length;
929         char *group, *key;
930
931         if (_item_split (item, &group, &key))
932             return 1;
933
934         value = g_key_file_get_string_list (config->key_file,
935                                             group, key,
936                                             &length, NULL);
937         if (value == NULL) {
938             fprintf (stderr, "Unknown configuration item: %s.%s\n",
939                      group, key);
940             return 1;
941         }
942
943         for (i = 0; i < length; i++)
944             printf ("%s\n", value[i]);
945
946         g_strfreev (value);
947     }
948
949     return 0;
950 }
951
952 static int
953 _set_db_config (notmuch_config_t *config, const char *key, int argc, char **argv)
954 {
955     notmuch_database_t *notmuch;
956     const char *val = "";
957
958     if (argc > 1) {
959         /* XXX handle lists? */
960         fprintf (stderr, "notmuch config set: at most one value expected for %s\n", key);
961         return EXIT_FAILURE;
962     }
963
964     if (argc > 0) {
965         val = argv[0];
966     }
967
968     if (notmuch_database_open (notmuch_config_get_database_path (config),
969                                NOTMUCH_DATABASE_MODE_READ_WRITE, &notmuch))
970         return EXIT_FAILURE;
971
972     /* XXX Handle UUID mismatch? */
973
974     if (print_status_database ("notmuch config", notmuch,
975                                notmuch_database_set_config (notmuch, key, val)))
976         return EXIT_FAILURE;
977
978     if (print_status_database ("notmuch config", notmuch,
979                                notmuch_database_close (notmuch)))
980         return EXIT_FAILURE;
981
982     return EXIT_SUCCESS;
983 }
984
985 static int
986 notmuch_config_command_set (notmuch_config_t *config, char *item, int argc, char *argv[])
987 {
988     char *group, *key;
989     config_key_info_t *key_info;
990
991     if (STRNCMP_LITERAL (item, BUILT_WITH_PREFIX) == 0) {
992         fprintf (stderr, "Error: read only option: %s\n", item);
993         return 1;
994     }
995
996     key_info = _config_key_info (item);
997     if (key_info && key_info->validate && (! key_info->validate (item)))
998         return 1;
999
1000     if (key_info && key_info->in_db) {
1001         return _set_db_config (config, item, argc, argv);
1002     }
1003
1004     if (_item_split (item, &group, &key))
1005         return 1;
1006
1007     /* With only the name of an item, we clear it from the
1008      * configuration file.
1009      *
1010      * With a single value, we set it as a string.
1011      *
1012      * With multiple values, we set them as a string list.
1013      */
1014     switch (argc) {
1015     case 0:
1016         g_key_file_remove_key (config->key_file, group, key, NULL);
1017         break;
1018     case 1:
1019         g_key_file_set_string (config->key_file, group, key, argv[0]);
1020         break;
1021     default:
1022         g_key_file_set_string_list (config->key_file, group, key,
1023                                     (const gchar **) argv, argc);
1024         break;
1025     }
1026
1027     return notmuch_config_save (config);
1028 }
1029
1030 static
1031 void
1032 _notmuch_config_list_built_with ()
1033 {
1034     printf ("%scompact=%s\n",
1035             BUILT_WITH_PREFIX,
1036             notmuch_built_with ("compact") ? "true" : "false");
1037     printf ("%sfield_processor=%s\n",
1038             BUILT_WITH_PREFIX,
1039             notmuch_built_with ("field_processor") ? "true" : "false");
1040     printf ("%sretry_lock=%s\n",
1041             BUILT_WITH_PREFIX,
1042             notmuch_built_with ("retry_lock") ? "true" : "false");
1043 }
1044
1045 static int
1046 _list_db_config (notmuch_config_t *config)
1047 {
1048     notmuch_database_t *notmuch;
1049     notmuch_config_list_t *list;
1050
1051     if (notmuch_database_open (notmuch_config_get_database_path (config),
1052                                NOTMUCH_DATABASE_MODE_READ_ONLY, &notmuch))
1053         return EXIT_FAILURE;
1054
1055     /* XXX Handle UUID mismatch? */
1056
1057
1058     if (print_status_database ("notmuch config", notmuch,
1059                                notmuch_database_get_config_list (notmuch, "", &list)))
1060         return EXIT_FAILURE;
1061
1062     for (; notmuch_config_list_valid (list); notmuch_config_list_move_to_next (list)) {
1063         printf ("%s=%s\n", notmuch_config_list_key (list), notmuch_config_list_value (list));
1064     }
1065     notmuch_config_list_destroy (list);
1066
1067     return EXIT_SUCCESS;
1068 }
1069
1070 static int
1071 notmuch_config_command_list (notmuch_config_t *config)
1072 {
1073     char **groups;
1074     size_t g, groups_length;
1075
1076     groups = g_key_file_get_groups (config->key_file, &groups_length);
1077     if (groups == NULL)
1078         return 1;
1079
1080     for (g = 0; g < groups_length; g++) {
1081         char **keys;
1082         size_t k, keys_length;
1083
1084         keys = g_key_file_get_keys (config->key_file,
1085                                     groups[g], &keys_length, NULL);
1086         if (keys == NULL)
1087             continue;
1088
1089         for (k = 0; k < keys_length; k++) {
1090             char *value;
1091
1092             value = g_key_file_get_string (config->key_file,
1093                                            groups[g], keys[k], NULL);
1094             if (value != NULL) {
1095                 printf ("%s.%s=%s\n", groups[g], keys[k], value);
1096                 free (value);
1097             }
1098         }
1099
1100         g_strfreev (keys);
1101     }
1102
1103     g_strfreev (groups);
1104
1105     _notmuch_config_list_built_with ();
1106     return _list_db_config (config);
1107 }
1108
1109 int
1110 notmuch_config_command (notmuch_config_t *config, unused(notmuch_database_t *notmuch),
1111                         int argc, char *argv[])
1112 {
1113     int ret;
1114     int opt_index;
1115
1116     opt_index = notmuch_minimal_options ("config", argc, argv);
1117     if (opt_index < 0)
1118         return EXIT_FAILURE;
1119
1120     if (notmuch_requested_db_uuid)
1121         fprintf (stderr, "Warning: ignoring --uuid=%s\n",
1122                  notmuch_requested_db_uuid);
1123
1124     /* skip at least subcommand argument */
1125     argc -= opt_index;
1126     argv += opt_index;
1127
1128     if (argc < 1) {
1129         fprintf (stderr, "Error: notmuch config requires at least one argument.\n");
1130         return EXIT_FAILURE;
1131     }
1132
1133     if (strcmp (argv[0], "get") == 0) {
1134         if (argc != 2) {
1135             fprintf (stderr, "Error: notmuch config get requires exactly "
1136                      "one argument.\n");
1137             return EXIT_FAILURE;
1138         }
1139         ret = notmuch_config_command_get (config, argv[1]);
1140     } else if (strcmp (argv[0], "set") == 0) {
1141         if (argc < 2) {
1142             fprintf (stderr, "Error: notmuch config set requires at least "
1143                      "one argument.\n");
1144             return EXIT_FAILURE;
1145         }
1146         ret = notmuch_config_command_set (config, argv[1], argc - 2, argv + 2);
1147     } else if (strcmp (argv[0], "list") == 0) {
1148         ret = notmuch_config_command_list (config);
1149     } else {
1150         fprintf (stderr, "Unrecognized argument for notmuch config: %s\n",
1151                  argv[0]);
1152         return EXIT_FAILURE;
1153     }
1154
1155     return ret ? EXIT_FAILURE : EXIT_SUCCESS;
1156
1157 }
1158
1159 bool
1160 notmuch_config_get_maildir_synchronize_flags (notmuch_config_t *config)
1161 {
1162     return config->maildir_synchronize_flags;
1163 }
1164
1165 void
1166 notmuch_config_set_maildir_synchronize_flags (notmuch_config_t *config,
1167                                               bool synchronize_flags)
1168 {
1169     g_key_file_set_boolean (config->key_file,
1170                             "maildir", "synchronize_flags", synchronize_flags);
1171     config->maildir_synchronize_flags = synchronize_flags;
1172 }