]> git.notmuchmail.org Git - notmuch/blob - notmuch-config.c
config: test whether an item is stored in the database by name
[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     if (STRNCMP_LITERAL (item, "query.") == 0)
816         return true;
817     return false;
818 }
819
820 static int
821 _print_db_config(notmuch_config_t *config, const char *name)
822 {
823     notmuch_database_t *notmuch;
824     char *val;
825
826     if (notmuch_database_open (notmuch_config_get_database_path (config),
827                                NOTMUCH_DATABASE_MODE_READ_ONLY, &notmuch))
828         return EXIT_FAILURE;
829
830     /* XXX Handle UUID mismatch? */
831
832     if (print_status_database ("notmuch config", notmuch,
833                                notmuch_database_get_config (notmuch, name, &val)))
834         return EXIT_FAILURE;
835
836      puts (val);
837
838     return EXIT_SUCCESS;
839 }
840
841 static int
842 notmuch_config_command_get (notmuch_config_t *config, char *item)
843 {
844     if (strcmp(item, "database.path") == 0) {
845         printf ("%s\n", notmuch_config_get_database_path (config));
846     } else if (strcmp(item, "user.name") == 0) {
847         printf ("%s\n", notmuch_config_get_user_name (config));
848     } else if (strcmp(item, "user.primary_email") == 0) {
849         printf ("%s\n", notmuch_config_get_user_primary_email (config));
850     } else if (strcmp(item, "user.other_email") == 0) {
851         const char **other_email;
852         size_t i, length;
853         
854         other_email = notmuch_config_get_user_other_email (config, &length);
855         for (i = 0; i < length; i++)
856             printf ("%s\n", other_email[i]);
857     } else if (strcmp(item, "new.tags") == 0) {
858         const char **tags;
859         size_t i, length;
860
861         tags = notmuch_config_get_new_tags (config, &length);
862         for (i = 0; i < length; i++)
863             printf ("%s\n", tags[i]);
864     } else if (STRNCMP_LITERAL (item, BUILT_WITH_PREFIX) == 0) {
865         printf ("%s\n",
866                 notmuch_built_with (item + strlen (BUILT_WITH_PREFIX)) ? "true" : "false");
867     } else if (_stored_in_db (item)) {
868         return _print_db_config (config, item);
869     } else {
870         char **value;
871         size_t i, length;
872         char *group, *key;
873
874         if (_item_split (item, &group, &key))
875             return 1;
876
877         value = g_key_file_get_string_list (config->key_file,
878                                             group, key,
879                                             &length, NULL);
880         if (value == NULL) {
881             fprintf (stderr, "Unknown configuration item: %s.%s\n",
882                      group, key);
883             return 1;
884         }
885
886         for (i = 0; i < length; i++)
887             printf ("%s\n", value[i]);
888
889         g_strfreev (value);
890     }
891
892     return 0;
893 }
894
895 static int
896 _set_db_config(notmuch_config_t *config, const char *key, int argc, char **argv)
897 {
898     notmuch_database_t *notmuch;
899     const char *val = "";
900
901     if (argc > 1) {
902         /* XXX handle lists? */
903         fprintf (stderr, "notmuch config set: at most one value expected for %s\n", key);
904         return EXIT_FAILURE;
905     }
906
907     if (argc > 0) {
908         val = argv[0];
909     }
910
911     if (notmuch_database_open (notmuch_config_get_database_path (config),
912                                NOTMUCH_DATABASE_MODE_READ_WRITE, &notmuch))
913         return EXIT_FAILURE;
914
915     /* XXX Handle UUID mismatch? */
916
917     if (print_status_database ("notmuch config", notmuch,
918                                notmuch_database_set_config (notmuch, key, val)))
919         return EXIT_FAILURE;
920
921     if (print_status_database ("notmuch config", notmuch,
922                                notmuch_database_close (notmuch)))
923         return EXIT_FAILURE;
924
925     return EXIT_SUCCESS;
926 }
927
928 static int
929 notmuch_config_command_set (notmuch_config_t *config, char *item, int argc, char *argv[])
930 {
931     char *group, *key;
932
933     if (STRNCMP_LITERAL (item, BUILT_WITH_PREFIX) == 0) {
934         fprintf (stderr, "Error: read only option: %s\n", item);
935         return 1;
936     }
937
938     if (_stored_in_db (item)) {
939         return _set_db_config (config, item, argc, argv);
940     }
941
942     if (_item_split (item, &group, &key))
943         return 1;
944
945     /* With only the name of an item, we clear it from the
946      * configuration file.
947      *
948      * With a single value, we set it as a string.
949      *
950      * With multiple values, we set them as a string list.
951      */
952     switch (argc) {
953     case 0:
954         g_key_file_remove_key (config->key_file, group, key, NULL);
955         break;
956     case 1:
957         g_key_file_set_string (config->key_file, group, key, argv[0]);
958         break;
959     default:
960         g_key_file_set_string_list (config->key_file, group, key,
961                                     (const gchar **) argv, argc);
962         break;
963     }
964
965     return notmuch_config_save (config);
966 }
967
968 static
969 void
970 _notmuch_config_list_built_with ()
971 {
972     printf("%scompact=%s\n",
973            BUILT_WITH_PREFIX,
974            notmuch_built_with ("compact") ? "true" : "false");
975     printf("%sfield_processor=%s\n",
976            BUILT_WITH_PREFIX,
977            notmuch_built_with ("field_processor") ? "true" : "false");
978     printf("%sretry_lock=%s\n",
979            BUILT_WITH_PREFIX,
980            notmuch_built_with ("retry_lock") ? "true" : "false");
981 }
982
983 static int
984 _list_db_config (notmuch_config_t *config)
985 {
986     notmuch_database_t *notmuch;
987     notmuch_config_list_t *list;
988
989     if (notmuch_database_open (notmuch_config_get_database_path (config),
990                                NOTMUCH_DATABASE_MODE_READ_ONLY, &notmuch))
991         return EXIT_FAILURE;
992
993     /* XXX Handle UUID mismatch? */
994
995
996     if (print_status_database ("notmuch config", notmuch,
997                                notmuch_database_get_config_list (notmuch, "", &list)))
998         return EXIT_FAILURE;
999
1000     for (; notmuch_config_list_valid (list); notmuch_config_list_move_to_next (list)) {
1001         printf("%s=%s\n", notmuch_config_list_key (list), notmuch_config_list_value(list));
1002     }
1003     notmuch_config_list_destroy (list);
1004
1005    return EXIT_SUCCESS;
1006 }
1007
1008 static int
1009 notmuch_config_command_list (notmuch_config_t *config)
1010 {
1011     char **groups;
1012     size_t g, groups_length;
1013
1014     groups = g_key_file_get_groups (config->key_file, &groups_length);
1015     if (groups == NULL)
1016         return 1;
1017
1018     for (g = 0; g < groups_length; g++) {
1019         char **keys;
1020         size_t k, keys_length;
1021
1022         keys = g_key_file_get_keys (config->key_file,
1023                                     groups[g], &keys_length, NULL);
1024         if (keys == NULL)
1025             continue;
1026
1027         for (k = 0; k < keys_length; k++) {
1028             char *value;
1029
1030             value = g_key_file_get_string (config->key_file,
1031                                            groups[g], keys[k], NULL);
1032             if (value != NULL) {
1033                 printf ("%s.%s=%s\n", groups[g], keys[k], value);
1034                 free (value);
1035             }
1036         }
1037
1038         g_strfreev (keys);
1039     }
1040
1041     g_strfreev (groups);
1042
1043     _notmuch_config_list_built_with ();
1044     return _list_db_config (config);
1045 }
1046
1047 int
1048 notmuch_config_command (notmuch_config_t *config, int argc, char *argv[])
1049 {
1050     int ret;
1051     int opt_index;
1052
1053     opt_index = notmuch_minimal_options ("config", argc, argv);
1054     if (opt_index < 0)
1055         return EXIT_FAILURE;
1056
1057     if (notmuch_requested_db_uuid)
1058         fprintf (stderr, "Warning: ignoring --uuid=%s\n",
1059                  notmuch_requested_db_uuid);
1060
1061     /* skip at least subcommand argument */
1062     argc-= opt_index;
1063     argv+= opt_index;
1064
1065     if (argc < 1) {
1066         fprintf (stderr, "Error: notmuch config requires at least one argument.\n");
1067         return EXIT_FAILURE;
1068     }
1069
1070     if (strcmp (argv[0], "get") == 0) {
1071         if (argc != 2) {
1072             fprintf (stderr, "Error: notmuch config get requires exactly "
1073                      "one argument.\n");
1074             return EXIT_FAILURE;
1075         }
1076         ret = notmuch_config_command_get (config, argv[1]);
1077     } else if (strcmp (argv[0], "set") == 0) {
1078         if (argc < 2) {
1079             fprintf (stderr, "Error: notmuch config set requires at least "
1080                      "one argument.\n");
1081             return EXIT_FAILURE;
1082         }
1083         ret = notmuch_config_command_set (config, argv[1], argc - 2, argv + 2);
1084     } else if (strcmp (argv[0], "list") == 0) {
1085         ret = notmuch_config_command_list (config);
1086     } else {
1087         fprintf (stderr, "Unrecognized argument for notmuch config: %s\n",
1088                  argv[0]);
1089         return EXIT_FAILURE;
1090     }
1091
1092     return ret ? EXIT_FAILURE : EXIT_SUCCESS;
1093
1094 }
1095
1096 bool
1097 notmuch_config_get_maildir_synchronize_flags (notmuch_config_t *config)
1098 {
1099     return config->maildir_synchronize_flags;
1100 }
1101
1102 void
1103 notmuch_config_set_maildir_synchronize_flags (notmuch_config_t *config,
1104                                               bool synchronize_flags)
1105 {
1106     g_key_file_set_boolean (config->key_file,
1107                             "maildir", "synchronize_flags", synchronize_flags);
1108     config->maildir_synchronize_flags = synchronize_flags;
1109 }