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