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