91a24a76567c87dd69864733b125601fd1b1b082
[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 *crypto_gpg_path;
123     char *user_name;
124     char *user_primary_email;
125     const char **user_other_email;
126     size_t user_other_email_length;
127     const char **new_tags;
128     size_t new_tags_length;
129     const char **new_ignore;
130     size_t new_ignore_length;
131     bool maildir_synchronize_flags;
132     const char **search_exclude_tags;
133     size_t search_exclude_tags_length;
134 };
135
136 static int
137 notmuch_config_destructor (notmuch_config_t *config)
138 {
139     if (config->key_file)
140         g_key_file_free (config->key_file);
141
142     return 0;
143 }
144
145 static char *
146 get_name_from_passwd_file (void *ctx)
147 {
148     long pw_buf_size;
149     char *pw_buf;
150     struct passwd passwd, *ignored;
151     char *name;
152     int e;
153
154     pw_buf_size = sysconf(_SC_GETPW_R_SIZE_MAX);
155     if (pw_buf_size == -1) pw_buf_size = 64;
156     pw_buf = talloc_size (ctx, pw_buf_size);
157
158     while ((e = getpwuid_r (getuid (), &passwd, pw_buf,
159                             pw_buf_size, &ignored)) == ERANGE) {
160         pw_buf_size = pw_buf_size * 2;
161         pw_buf = talloc_zero_size(ctx, pw_buf_size);
162     }
163
164     if (e == 0) {
165         char *comma = strchr (passwd.pw_gecos, ',');
166         if (comma)
167             name = talloc_strndup (ctx, passwd.pw_gecos,
168                                    comma - passwd.pw_gecos);
169         else
170             name = talloc_strdup (ctx, passwd.pw_gecos);
171     } else {
172         name = talloc_strdup (ctx, "");
173     }
174
175     talloc_free (pw_buf);
176
177     return name;
178 }
179
180 static char *
181 get_username_from_passwd_file (void *ctx)
182 {
183     long pw_buf_size;
184     char *pw_buf;
185     struct passwd passwd, *ignored;
186     char *name;
187     int e;
188
189     pw_buf_size = sysconf(_SC_GETPW_R_SIZE_MAX);
190     if (pw_buf_size == -1) pw_buf_size = 64;
191     pw_buf = talloc_zero_size (ctx, pw_buf_size);
192
193     while ((e = getpwuid_r (getuid (), &passwd, pw_buf,
194                             pw_buf_size, &ignored)) == ERANGE) {
195         pw_buf_size = pw_buf_size * 2;
196         pw_buf = talloc_zero_size(ctx, pw_buf_size);
197     }
198
199     if (e == 0)
200         name = talloc_strdup (ctx, passwd.pw_name);
201     else
202         name = talloc_strdup (ctx, "");
203
204     talloc_free (pw_buf);
205
206     return name;
207 }
208
209 static bool
210 get_config_from_file (notmuch_config_t *config, bool create_new)
211 {
212     #define BUF_SIZE 4096
213     char *config_str = NULL;
214     int config_len = 0;
215     int config_bufsize = BUF_SIZE;
216     size_t len;
217     GError *error = NULL;
218     bool ret = false;
219
220     FILE *fp = fopen(config->filename, "r");
221     if (fp == NULL) {
222         if (errno == ENOENT) {
223             /* If create_new is true, then the caller is prepared for a
224              * default configuration file in the case of FILE NOT FOUND.
225              */
226             if (create_new) {
227                 config->is_new = true;
228                 ret = true;
229             } else {
230                 fprintf (stderr, "Configuration file %s not found.\n"
231                          "Try running 'notmuch setup' to create a configuration.\n",
232                          config->filename);
233             }
234         } else {
235             fprintf (stderr, "Error opening config file '%s': %s\n",
236                      config->filename, strerror(errno));
237         }
238         goto out;
239     }
240
241     config_str = talloc_zero_array (config, char, config_bufsize);
242     if (config_str == NULL) {
243         fprintf (stderr, "Error reading '%s': Out of memory\n", config->filename);
244         goto out;
245     }
246
247     while ((len = fread (config_str + config_len, 1,
248                          config_bufsize - config_len, fp)) > 0) {
249         config_len += len;
250         if (config_len == config_bufsize) {
251             config_bufsize += BUF_SIZE;
252             config_str = talloc_realloc (config, config_str, char, config_bufsize);
253             if (config_str == NULL) {
254                 fprintf (stderr, "Error reading '%s': Failed to reallocate memory\n",
255                          config->filename);
256                 goto out;
257             }
258         }
259     }
260
261     if (ferror (fp)) {
262         fprintf (stderr, "Error reading '%s': I/O error\n", config->filename);
263         goto out;
264     }
265
266     if (g_key_file_load_from_data (config->key_file, config_str, config_len,
267                                    G_KEY_FILE_KEEP_COMMENTS, &error)) {
268         ret = true;
269         goto out;
270     }
271
272     fprintf (stderr, "Error parsing config file '%s': %s\n",
273              config->filename, error->message);
274
275     g_error_free (error);
276
277 out:
278     if (fp)
279         fclose(fp);
280
281     if (config_str)
282         talloc_free(config_str);
283
284     return ret;
285 }
286
287 /* Open the named notmuch configuration file. If the filename is NULL,
288  * the value of the environment variable $NOTMUCH_CONFIG will be used.
289  * If $NOTMUCH_CONFIG is unset, the default configuration file
290  * ($HOME/.notmuch-config) will be used.
291  *
292  * If any error occurs, (out of memory, or a permission-denied error,
293  * etc.), this function will print a message to stderr and return
294  * NULL.
295  *
296  * FILE NOT FOUND: When the specified configuration file (whether from
297  * 'filename' or the $NOTMUCH_CONFIG environment variable) does not
298  * exist, the behavior of this function depends on the 'is_new_ret'
299  * variable.
300  *
301  *      If is_new_ret is NULL, then a "file not found" message will be
302  *      printed to stderr and NULL will be returned.
303
304  *      If is_new_ret is non-NULL then a default configuration will be
305  *      returned and *is_new_ret will be set to 1 on return so that
306  *      the caller can recognize this case.
307  *
308  *      These default configuration settings are determined as
309  *      follows:
310  *
311  *              database_path:          $MAILDIR, otherwise $HOME/mail
312  *
313  *              user_name:              $NAME variable if set, otherwise
314  *                                      read from /etc/passwd
315  *
316  *              user_primary_mail:      $EMAIL variable if set, otherwise
317  *                                      constructed from the username and
318  *                                      hostname of the current machine.
319  *
320  *              user_other_email:       Not set.
321  *
322  *      The default configuration also contains comments to guide the
323  *      user in editing the file directly.
324  */
325 notmuch_config_t *
326 notmuch_config_open (void *ctx,
327                      const char *filename,
328                      notmuch_config_mode_t config_mode)
329 {
330     GError *error = NULL;
331     size_t tmp;
332     char *notmuch_config_env = NULL;
333     int file_had_database_group;
334     int file_had_new_group;
335     int file_had_user_group;
336     int file_had_maildir_group;
337     int file_had_search_group;
338     int file_had_crypto_group;
339
340     notmuch_config_t *config = talloc_zero (ctx, notmuch_config_t);
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     info = _config_key_info (item);
867
868     return (info && info->in_db);
869 }
870
871 static int
872 _print_db_config(notmuch_config_t *config, const char *name)
873 {
874     notmuch_database_t *notmuch;
875     char *val;
876
877     if (notmuch_database_open (notmuch_config_get_database_path (config),
878                                NOTMUCH_DATABASE_MODE_READ_ONLY, &notmuch))
879         return EXIT_FAILURE;
880
881     /* XXX Handle UUID mismatch? */
882
883     if (print_status_database ("notmuch config", notmuch,
884                                notmuch_database_get_config (notmuch, name, &val)))
885         return EXIT_FAILURE;
886
887      puts (val);
888
889     return EXIT_SUCCESS;
890 }
891
892 static int
893 notmuch_config_command_get (notmuch_config_t *config, char *item)
894 {
895     if (strcmp(item, "database.path") == 0) {
896         printf ("%s\n", notmuch_config_get_database_path (config));
897     } else if (strcmp(item, "user.name") == 0) {
898         printf ("%s\n", notmuch_config_get_user_name (config));
899     } else if (strcmp(item, "user.primary_email") == 0) {
900         printf ("%s\n", notmuch_config_get_user_primary_email (config));
901     } else if (strcmp(item, "user.other_email") == 0) {
902         const char **other_email;
903         size_t i, length;
904         
905         other_email = notmuch_config_get_user_other_email (config, &length);
906         for (i = 0; i < length; i++)
907             printf ("%s\n", other_email[i]);
908     } else if (strcmp(item, "new.tags") == 0) {
909         const char **tags;
910         size_t i, length;
911
912         tags = notmuch_config_get_new_tags (config, &length);
913         for (i = 0; i < length; i++)
914             printf ("%s\n", tags[i]);
915     } else if (STRNCMP_LITERAL (item, BUILT_WITH_PREFIX) == 0) {
916         printf ("%s\n",
917                 notmuch_built_with (item + strlen (BUILT_WITH_PREFIX)) ? "true" : "false");
918     } else if (_stored_in_db (item)) {
919         return _print_db_config (config, item);
920     } else {
921         char **value;
922         size_t i, length;
923         char *group, *key;
924
925         if (_item_split (item, &group, &key))
926             return 1;
927
928         value = g_key_file_get_string_list (config->key_file,
929                                             group, key,
930                                             &length, NULL);
931         if (value == NULL) {
932             fprintf (stderr, "Unknown configuration item: %s.%s\n",
933                      group, key);
934             return 1;
935         }
936
937         for (i = 0; i < length; i++)
938             printf ("%s\n", value[i]);
939
940         g_strfreev (value);
941     }
942
943     return 0;
944 }
945
946 static int
947 _set_db_config(notmuch_config_t *config, const char *key, int argc, char **argv)
948 {
949     notmuch_database_t *notmuch;
950     const char *val = "";
951
952     if (argc > 1) {
953         /* XXX handle lists? */
954         fprintf (stderr, "notmuch config set: at most one value expected for %s\n", key);
955         return EXIT_FAILURE;
956     }
957
958     if (argc > 0) {
959         val = argv[0];
960     }
961
962     if (notmuch_database_open (notmuch_config_get_database_path (config),
963                                NOTMUCH_DATABASE_MODE_READ_WRITE, &notmuch))
964         return EXIT_FAILURE;
965
966     /* XXX Handle UUID mismatch? */
967
968     if (print_status_database ("notmuch config", notmuch,
969                                notmuch_database_set_config (notmuch, key, val)))
970         return EXIT_FAILURE;
971
972     if (print_status_database ("notmuch config", notmuch,
973                                notmuch_database_close (notmuch)))
974         return EXIT_FAILURE;
975
976     return EXIT_SUCCESS;
977 }
978
979 static int
980 notmuch_config_command_set (notmuch_config_t *config, char *item, int argc, char *argv[])
981 {
982     char *group, *key;
983     config_key_info_t *key_info;
984
985     if (STRNCMP_LITERAL (item, BUILT_WITH_PREFIX) == 0) {
986         fprintf (stderr, "Error: read only option: %s\n", item);
987         return 1;
988     }
989
990     key_info = _config_key_info (item);
991     if (key_info && key_info->validate && (! key_info->validate (item)))
992         return 1;
993
994     if (key_info && key_info->in_db) {
995         return _set_db_config (config, item, argc, argv);
996     }
997
998     if (_item_split (item, &group, &key))
999         return 1;
1000
1001     /* With only the name of an item, we clear it from the
1002      * configuration file.
1003      *
1004      * With a single value, we set it as a string.
1005      *
1006      * With multiple values, we set them as a string list.
1007      */
1008     switch (argc) {
1009     case 0:
1010         g_key_file_remove_key (config->key_file, group, key, NULL);
1011         break;
1012     case 1:
1013         g_key_file_set_string (config->key_file, group, key, argv[0]);
1014         break;
1015     default:
1016         g_key_file_set_string_list (config->key_file, group, key,
1017                                     (const gchar **) argv, argc);
1018         break;
1019     }
1020
1021     return notmuch_config_save (config);
1022 }
1023
1024 static
1025 void
1026 _notmuch_config_list_built_with ()
1027 {
1028     printf("%scompact=%s\n",
1029            BUILT_WITH_PREFIX,
1030            notmuch_built_with ("compact") ? "true" : "false");
1031     printf("%sfield_processor=%s\n",
1032            BUILT_WITH_PREFIX,
1033            notmuch_built_with ("field_processor") ? "true" : "false");
1034     printf("%sretry_lock=%s\n",
1035            BUILT_WITH_PREFIX,
1036            notmuch_built_with ("retry_lock") ? "true" : "false");
1037 }
1038
1039 static int
1040 _list_db_config (notmuch_config_t *config)
1041 {
1042     notmuch_database_t *notmuch;
1043     notmuch_config_list_t *list;
1044
1045     if (notmuch_database_open (notmuch_config_get_database_path (config),
1046                                NOTMUCH_DATABASE_MODE_READ_ONLY, &notmuch))
1047         return EXIT_FAILURE;
1048
1049     /* XXX Handle UUID mismatch? */
1050
1051
1052     if (print_status_database ("notmuch config", notmuch,
1053                                notmuch_database_get_config_list (notmuch, "", &list)))
1054         return EXIT_FAILURE;
1055
1056     for (; notmuch_config_list_valid (list); notmuch_config_list_move_to_next (list)) {
1057         printf("%s=%s\n", notmuch_config_list_key (list), notmuch_config_list_value(list));
1058     }
1059     notmuch_config_list_destroy (list);
1060
1061    return EXIT_SUCCESS;
1062 }
1063
1064 static int
1065 notmuch_config_command_list (notmuch_config_t *config)
1066 {
1067     char **groups;
1068     size_t g, groups_length;
1069
1070     groups = g_key_file_get_groups (config->key_file, &groups_length);
1071     if (groups == NULL)
1072         return 1;
1073
1074     for (g = 0; g < groups_length; g++) {
1075         char **keys;
1076         size_t k, keys_length;
1077
1078         keys = g_key_file_get_keys (config->key_file,
1079                                     groups[g], &keys_length, NULL);
1080         if (keys == NULL)
1081             continue;
1082
1083         for (k = 0; k < keys_length; k++) {
1084             char *value;
1085
1086             value = g_key_file_get_string (config->key_file,
1087                                            groups[g], keys[k], NULL);
1088             if (value != NULL) {
1089                 printf ("%s.%s=%s\n", groups[g], keys[k], value);
1090                 free (value);
1091             }
1092         }
1093
1094         g_strfreev (keys);
1095     }
1096
1097     g_strfreev (groups);
1098
1099     _notmuch_config_list_built_with ();
1100     return _list_db_config (config);
1101 }
1102
1103 int
1104 notmuch_config_command (notmuch_config_t *config, int argc, char *argv[])
1105 {
1106     int ret;
1107     int opt_index;
1108
1109     opt_index = notmuch_minimal_options ("config", argc, argv);
1110     if (opt_index < 0)
1111         return EXIT_FAILURE;
1112
1113     if (notmuch_requested_db_uuid)
1114         fprintf (stderr, "Warning: ignoring --uuid=%s\n",
1115                  notmuch_requested_db_uuid);
1116
1117     /* skip at least subcommand argument */
1118     argc-= opt_index;
1119     argv+= opt_index;
1120
1121     if (argc < 1) {
1122         fprintf (stderr, "Error: notmuch config requires at least one argument.\n");
1123         return EXIT_FAILURE;
1124     }
1125
1126     if (strcmp (argv[0], "get") == 0) {
1127         if (argc != 2) {
1128             fprintf (stderr, "Error: notmuch config get requires exactly "
1129                      "one argument.\n");
1130             return EXIT_FAILURE;
1131         }
1132         ret = notmuch_config_command_get (config, argv[1]);
1133     } else if (strcmp (argv[0], "set") == 0) {
1134         if (argc < 2) {
1135             fprintf (stderr, "Error: notmuch config set requires at least "
1136                      "one argument.\n");
1137             return EXIT_FAILURE;
1138         }
1139         ret = notmuch_config_command_set (config, argv[1], argc - 2, argv + 2);
1140     } else if (strcmp (argv[0], "list") == 0) {
1141         ret = notmuch_config_command_list (config);
1142     } else {
1143         fprintf (stderr, "Unrecognized argument for notmuch config: %s\n",
1144                  argv[0]);
1145         return EXIT_FAILURE;
1146     }
1147
1148     return ret ? EXIT_FAILURE : EXIT_SUCCESS;
1149
1150 }
1151
1152 bool
1153 notmuch_config_get_maildir_synchronize_flags (notmuch_config_t *config)
1154 {
1155     return config->maildir_synchronize_flags;
1156 }
1157
1158 void
1159 notmuch_config_set_maildir_synchronize_flags (notmuch_config_t *config,
1160                                               bool synchronize_flags)
1161 {
1162     g_key_file_set_boolean (config->key_file,
1163                             "maildir", "synchronize_flags", synchronize_flags);
1164     config->maildir_synchronize_flags = synchronize_flags;
1165 }