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