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