]> git.cworth.org Git - notmuch-old/blob - notmuch-config.c
changelog for backports
[notmuch-old] / 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     char *db_path = (char *)_config_get (config, &config->database_path, "database", "path");
664
665     if (db_path && *db_path != '/') {
666         /* If the path in the configuration file begins with any
667          * character other than /, presume that it is relative to
668          * $HOME and update as appropriate.
669          */
670         char *abs_path = talloc_asprintf (config, "%s/%s", getenv ("HOME"), db_path);
671         talloc_free (db_path);
672         db_path = config->database_path = abs_path;
673     }
674
675     return db_path;
676 }
677
678 void
679 notmuch_config_set_database_path (notmuch_config_t *config,
680                                   const char *database_path)
681 {
682     _config_set (config, &config->database_path, "database", "path", database_path);
683 }
684
685 const char *
686 notmuch_config_get_user_name (notmuch_config_t *config)
687 {
688     return _config_get (config, &config->user_name, "user", "name");
689 }
690
691 void
692 notmuch_config_set_user_name (notmuch_config_t *config,
693                               const char *user_name)
694 {
695     _config_set (config, &config->user_name, "user", "name", user_name);
696 }
697
698 const char *
699 notmuch_config_get_user_primary_email (notmuch_config_t *config)
700 {
701     return _config_get (config, &config->user_primary_email, "user", "primary_email");
702 }
703
704 void
705 notmuch_config_set_user_primary_email (notmuch_config_t *config,
706                                        const char *primary_email)
707 {
708     _config_set (config, &config->user_primary_email, "user", "primary_email", primary_email);
709 }
710
711 const char **
712 notmuch_config_get_user_other_email (notmuch_config_t *config,   size_t *length)
713 {
714     return _config_get_list (config, "user", "other_email",
715                              &(config->user_other_email),
716                              &(config->user_other_email_length), length);
717 }
718
719 const char **
720 notmuch_config_get_new_tags (notmuch_config_t *config,   size_t *length)
721 {
722     return _config_get_list (config, "new", "tags",
723                              &(config->new_tags),
724                              &(config->new_tags_length), length);
725 }
726
727 const char **
728 notmuch_config_get_new_ignore (notmuch_config_t *config, size_t *length)
729 {
730     return _config_get_list (config, "new", "ignore",
731                              &(config->new_ignore),
732                              &(config->new_ignore_length), length);
733 }
734
735 void
736 notmuch_config_set_user_other_email (notmuch_config_t *config,
737                                      const char *list[],
738                                      size_t length)
739 {
740     _config_set_list (config, "user", "other_email", list, length,
741                      &(config->user_other_email));
742 }
743
744 void
745 notmuch_config_set_new_tags (notmuch_config_t *config,
746                                      const char *list[],
747                                      size_t length)
748 {
749     _config_set_list (config, "new", "tags", list, length,
750                      &(config->new_tags));
751 }
752
753 void
754 notmuch_config_set_new_ignore (notmuch_config_t *config,
755                                const char *list[],
756                                size_t length)
757 {
758     _config_set_list (config, "new", "ignore", list, length,
759                      &(config->new_ignore));
760 }
761
762 const char **
763 notmuch_config_get_search_exclude_tags (notmuch_config_t *config, size_t *length)
764 {
765     return _config_get_list (config, "search", "exclude_tags",
766                              &(config->search_exclude_tags),
767                              &(config->search_exclude_tags_length), length);
768 }
769
770 void
771 notmuch_config_set_search_exclude_tags (notmuch_config_t *config,
772                                       const char *list[],
773                                       size_t length)
774 {
775     _config_set_list (config, "search", "exclude_tags", list, length,
776                       &(config->search_exclude_tags));
777 }
778
779 #if (GMIME_MAJOR_VERSION < 3)
780 const char *
781 notmuch_config_get_crypto_gpg_path (notmuch_config_t *config)
782 {
783     return _config_get (config, &config->crypto_gpg_path, "crypto", "gpg_path");
784 }
785
786 void
787 notmuch_config_set_crypto_gpg_path (notmuch_config_t *config,
788                               const char *gpg_path)
789 {
790     _config_set (config, &config->crypto_gpg_path, "crypto", "gpg_path", gpg_path);
791 }
792 #endif
793
794
795 /* Given a configuration item of the form <group>.<key> return the
796  * component group and key. If any error occurs, print a message on
797  * stderr and return 1. Otherwise, return 0.
798  *
799  * Note: This function modifies the original 'item' string.
800  */
801 static int
802 _item_split (char *item, char **group, char **key)
803 {
804     char *period;
805
806     *group = item;
807
808     period = strchr (item, '.');
809     if (period == NULL || *(period+1) == '\0') {
810         fprintf (stderr,
811                  "Invalid configuration name: %s\n"
812                  "(Should be of the form <section>.<item>)\n", item);
813         return 1;
814     }
815
816     *period = '\0';
817     *key = period + 1;
818
819     return 0;
820 }
821
822 #define BUILT_WITH_PREFIX "built_with."
823
824 static bool
825 _stored_in_db (const char *item)
826 {
827     const char * db_configs[] = {
828         "index.decrypt",
829     };
830     if (STRNCMP_LITERAL (item, "query.") == 0)
831         return true;
832     for (size_t i = 0; i < ARRAY_SIZE (db_configs); i++)
833         if (strcmp (item, db_configs[i]) == 0)
834             return true;
835     return false;
836 }
837
838 static int
839 _print_db_config(notmuch_config_t *config, const char *name)
840 {
841     notmuch_database_t *notmuch;
842     char *val;
843
844     if (notmuch_database_open (notmuch_config_get_database_path (config),
845                                NOTMUCH_DATABASE_MODE_READ_ONLY, &notmuch))
846         return EXIT_FAILURE;
847
848     /* XXX Handle UUID mismatch? */
849
850     if (print_status_database ("notmuch config", notmuch,
851                                notmuch_database_get_config (notmuch, name, &val)))
852         return EXIT_FAILURE;
853
854      puts (val);
855
856     return EXIT_SUCCESS;
857 }
858
859 static int
860 notmuch_config_command_get (notmuch_config_t *config, char *item)
861 {
862     if (strcmp(item, "database.path") == 0) {
863         printf ("%s\n", notmuch_config_get_database_path (config));
864     } else if (strcmp(item, "user.name") == 0) {
865         printf ("%s\n", notmuch_config_get_user_name (config));
866     } else if (strcmp(item, "user.primary_email") == 0) {
867         printf ("%s\n", notmuch_config_get_user_primary_email (config));
868     } else if (strcmp(item, "user.other_email") == 0) {
869         const char **other_email;
870         size_t i, length;
871         
872         other_email = notmuch_config_get_user_other_email (config, &length);
873         for (i = 0; i < length; i++)
874             printf ("%s\n", other_email[i]);
875     } else if (strcmp(item, "new.tags") == 0) {
876         const char **tags;
877         size_t i, length;
878
879         tags = notmuch_config_get_new_tags (config, &length);
880         for (i = 0; i < length; i++)
881             printf ("%s\n", tags[i]);
882     } else if (STRNCMP_LITERAL (item, BUILT_WITH_PREFIX) == 0) {
883         printf ("%s\n",
884                 notmuch_built_with (item + strlen (BUILT_WITH_PREFIX)) ? "true" : "false");
885     } else if (_stored_in_db (item)) {
886         return _print_db_config (config, item);
887     } else {
888         char **value;
889         size_t i, length;
890         char *group, *key;
891
892         if (_item_split (item, &group, &key))
893             return 1;
894
895         value = g_key_file_get_string_list (config->key_file,
896                                             group, key,
897                                             &length, NULL);
898         if (value == NULL) {
899             fprintf (stderr, "Unknown configuration item: %s.%s\n",
900                      group, key);
901             return 1;
902         }
903
904         for (i = 0; i < length; i++)
905             printf ("%s\n", value[i]);
906
907         g_strfreev (value);
908     }
909
910     return 0;
911 }
912
913 static int
914 _set_db_config(notmuch_config_t *config, const char *key, int argc, char **argv)
915 {
916     notmuch_database_t *notmuch;
917     const char *val = "";
918
919     if (argc > 1) {
920         /* XXX handle lists? */
921         fprintf (stderr, "notmuch config set: at most one value expected for %s\n", key);
922         return EXIT_FAILURE;
923     }
924
925     if (argc > 0) {
926         val = argv[0];
927     }
928
929     if (notmuch_database_open (notmuch_config_get_database_path (config),
930                                NOTMUCH_DATABASE_MODE_READ_WRITE, &notmuch))
931         return EXIT_FAILURE;
932
933     /* XXX Handle UUID mismatch? */
934
935     if (print_status_database ("notmuch config", notmuch,
936                                notmuch_database_set_config (notmuch, key, val)))
937         return EXIT_FAILURE;
938
939     if (print_status_database ("notmuch config", notmuch,
940                                notmuch_database_close (notmuch)))
941         return EXIT_FAILURE;
942
943     return EXIT_SUCCESS;
944 }
945
946 static int
947 notmuch_config_command_set (notmuch_config_t *config, char *item, int argc, char *argv[])
948 {
949     char *group, *key;
950
951     if (STRNCMP_LITERAL (item, BUILT_WITH_PREFIX) == 0) {
952         fprintf (stderr, "Error: read only option: %s\n", item);
953         return 1;
954     }
955
956     if (_stored_in_db (item)) {
957         return _set_db_config (config, item, argc, argv);
958     }
959
960     if (_item_split (item, &group, &key))
961         return 1;
962
963     /* With only the name of an item, we clear it from the
964      * configuration file.
965      *
966      * With a single value, we set it as a string.
967      *
968      * With multiple values, we set them as a string list.
969      */
970     switch (argc) {
971     case 0:
972         g_key_file_remove_key (config->key_file, group, key, NULL);
973         break;
974     case 1:
975         g_key_file_set_string (config->key_file, group, key, argv[0]);
976         break;
977     default:
978         g_key_file_set_string_list (config->key_file, group, key,
979                                     (const gchar **) argv, argc);
980         break;
981     }
982
983     return notmuch_config_save (config);
984 }
985
986 static
987 void
988 _notmuch_config_list_built_with ()
989 {
990     printf("%scompact=%s\n",
991            BUILT_WITH_PREFIX,
992            notmuch_built_with ("compact") ? "true" : "false");
993     printf("%sfield_processor=%s\n",
994            BUILT_WITH_PREFIX,
995            notmuch_built_with ("field_processor") ? "true" : "false");
996     printf("%sretry_lock=%s\n",
997            BUILT_WITH_PREFIX,
998            notmuch_built_with ("retry_lock") ? "true" : "false");
999 }
1000
1001 static int
1002 _list_db_config (notmuch_config_t *config)
1003 {
1004     notmuch_database_t *notmuch;
1005     notmuch_config_list_t *list;
1006
1007     if (notmuch_database_open (notmuch_config_get_database_path (config),
1008                                NOTMUCH_DATABASE_MODE_READ_ONLY, &notmuch))
1009         return EXIT_FAILURE;
1010
1011     /* XXX Handle UUID mismatch? */
1012
1013
1014     if (print_status_database ("notmuch config", notmuch,
1015                                notmuch_database_get_config_list (notmuch, "", &list)))
1016         return EXIT_FAILURE;
1017
1018     for (; notmuch_config_list_valid (list); notmuch_config_list_move_to_next (list)) {
1019         printf("%s=%s\n", notmuch_config_list_key (list), notmuch_config_list_value(list));
1020     }
1021     notmuch_config_list_destroy (list);
1022
1023    return EXIT_SUCCESS;
1024 }
1025
1026 static int
1027 notmuch_config_command_list (notmuch_config_t *config)
1028 {
1029     char **groups;
1030     size_t g, groups_length;
1031
1032     groups = g_key_file_get_groups (config->key_file, &groups_length);
1033     if (groups == NULL)
1034         return 1;
1035
1036     for (g = 0; g < groups_length; g++) {
1037         char **keys;
1038         size_t k, keys_length;
1039
1040         keys = g_key_file_get_keys (config->key_file,
1041                                     groups[g], &keys_length, NULL);
1042         if (keys == NULL)
1043             continue;
1044
1045         for (k = 0; k < keys_length; k++) {
1046             char *value;
1047
1048             value = g_key_file_get_string (config->key_file,
1049                                            groups[g], keys[k], NULL);
1050             if (value != NULL) {
1051                 printf ("%s.%s=%s\n", groups[g], keys[k], value);
1052                 free (value);
1053             }
1054         }
1055
1056         g_strfreev (keys);
1057     }
1058
1059     g_strfreev (groups);
1060
1061     _notmuch_config_list_built_with ();
1062     return _list_db_config (config);
1063 }
1064
1065 int
1066 notmuch_config_command (notmuch_config_t *config, int argc, char *argv[])
1067 {
1068     int ret;
1069     int opt_index;
1070
1071     opt_index = notmuch_minimal_options ("config", argc, argv);
1072     if (opt_index < 0)
1073         return EXIT_FAILURE;
1074
1075     if (notmuch_requested_db_uuid)
1076         fprintf (stderr, "Warning: ignoring --uuid=%s\n",
1077                  notmuch_requested_db_uuid);
1078
1079     /* skip at least subcommand argument */
1080     argc-= opt_index;
1081     argv+= opt_index;
1082
1083     if (argc < 1) {
1084         fprintf (stderr, "Error: notmuch config requires at least one argument.\n");
1085         return EXIT_FAILURE;
1086     }
1087
1088     if (strcmp (argv[0], "get") == 0) {
1089         if (argc != 2) {
1090             fprintf (stderr, "Error: notmuch config get requires exactly "
1091                      "one argument.\n");
1092             return EXIT_FAILURE;
1093         }
1094         ret = notmuch_config_command_get (config, argv[1]);
1095     } else if (strcmp (argv[0], "set") == 0) {
1096         if (argc < 2) {
1097             fprintf (stderr, "Error: notmuch config set requires at least "
1098                      "one argument.\n");
1099             return EXIT_FAILURE;
1100         }
1101         ret = notmuch_config_command_set (config, argv[1], argc - 2, argv + 2);
1102     } else if (strcmp (argv[0], "list") == 0) {
1103         ret = notmuch_config_command_list (config);
1104     } else {
1105         fprintf (stderr, "Unrecognized argument for notmuch config: %s\n",
1106                  argv[0]);
1107         return EXIT_FAILURE;
1108     }
1109
1110     return ret ? EXIT_FAILURE : EXIT_SUCCESS;
1111
1112 }
1113
1114 bool
1115 notmuch_config_get_maildir_synchronize_flags (notmuch_config_t *config)
1116 {
1117     return config->maildir_synchronize_flags;
1118 }
1119
1120 void
1121 notmuch_config_set_maildir_synchronize_flags (notmuch_config_t *config,
1122                                               bool synchronize_flags)
1123 {
1124     g_key_file_set_boolean (config->key_file,
1125                             "maildir", "synchronize_flags", synchronize_flags);
1126     config->maildir_synchronize_flags = synchronize_flags;
1127 }