cli: add standard option processing to config, help and setup
[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 http://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 http://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 /* Open the named notmuch configuration file. If the filename is NULL,
206  * the value of the environment variable $NOTMUCH_CONFIG will be used.
207  * If $NOTMUCH_CONFIG is unset, the default configuration file
208  * ($HOME/.notmuch-config) will be used.
209  *
210  * If any error occurs, (out of memory, or a permission-denied error,
211  * etc.), this function will print a message to stderr and return
212  * NULL.
213  *
214  * FILE NOT FOUND: When the specified configuration file (whether from
215  * 'filename' or the $NOTMUCH_CONFIG environment variable) does not
216  * exist, the behavior of this function depends on the 'is_new_ret'
217  * variable.
218  *
219  *      If is_new_ret is NULL, then a "file not found" message will be
220  *      printed to stderr and NULL will be returned.
221
222  *      If is_new_ret is non-NULL then a default configuration will be
223  *      returned and *is_new_ret will be set to 1 on return so that
224  *      the caller can recognize this case.
225  *
226  *      These default configuration settings are determined as
227  *      follows:
228  *
229  *              database_path:          $MAILDIR, otherwise $HOME/mail
230  *
231  *              user_name:              $NAME variable if set, otherwise
232  *                                      read from /etc/passwd
233  *
234  *              user_primary_mail:      $EMAIL variable if set, otherwise
235  *                                      constructed from the username and
236  *                                      hostname of the current machine.
237  *
238  *              user_other_email:       Not set.
239  *
240  *      The default configuration also contains comments to guide the
241  *      user in editing the file directly.
242  */
243 notmuch_config_t *
244 notmuch_config_open (void *ctx,
245                      const char *filename,
246                      notmuch_bool_t create_new)
247 {
248     GError *error = NULL;
249     size_t tmp;
250     char *notmuch_config_env = NULL;
251     int file_had_database_group;
252     int file_had_new_group;
253     int file_had_user_group;
254     int file_had_maildir_group;
255     int file_had_search_group;
256     int file_had_crypto_group;
257
258     notmuch_config_t *config = talloc (ctx, notmuch_config_t);
259     if (config == NULL) {
260         fprintf (stderr, "Out of memory.\n");
261         return NULL;
262     }
263     
264     talloc_set_destructor (config, notmuch_config_destructor);
265
266     if (filename) {
267         config->filename = talloc_strdup (config, filename);
268     } else if ((notmuch_config_env = getenv ("NOTMUCH_CONFIG"))) {
269         config->filename = talloc_strdup (config, notmuch_config_env);
270     } else {
271         config->filename = talloc_asprintf (config, "%s/.notmuch-config",
272                                             getenv ("HOME"));
273     }
274
275     config->key_file = g_key_file_new ();
276
277     config->is_new = FALSE;
278     config->database_path = NULL;
279     config->user_name = NULL;
280     config->user_primary_email = NULL;
281     config->user_other_email = NULL;
282     config->user_other_email_length = 0;
283     config->new_tags = NULL;
284     config->new_tags_length = 0;
285     config->new_ignore = NULL;
286     config->new_ignore_length = 0;
287     config->maildir_synchronize_flags = TRUE;
288     config->search_exclude_tags = NULL;
289     config->search_exclude_tags_length = 0;
290     config->crypto_gpg_path = NULL;
291
292     if (! g_key_file_load_from_file (config->key_file,
293                                      config->filename,
294                                      G_KEY_FILE_KEEP_COMMENTS,
295                                      &error))
296     {
297         if (error->domain == G_FILE_ERROR && error->code == G_FILE_ERROR_NOENT) {
298             /* If create_new is true, then the caller is prepared for a
299              * default configuration file in the case of FILE NOT
300              * FOUND.
301              */
302             if (create_new) {
303                 g_error_free (error);
304                 config->is_new = TRUE;
305             } else {
306                 fprintf (stderr, "Configuration file %s not found.\n"
307                          "Try running 'notmuch setup' to create a configuration.\n",
308                          config->filename);
309                 talloc_free (config);
310                 g_error_free (error);
311                 return NULL;
312             }
313         }
314         else
315         {
316             fprintf (stderr, "Error reading configuration file %s: %s\n",
317                      config->filename, error->message);
318             talloc_free (config);
319             g_error_free (error);
320             return NULL;
321         }
322     }
323
324     /* Whenever we know of configuration sections that don't appear in
325      * the configuration file, we add some comments to help the user
326      * understand what can be done.
327      *
328      * It would be convenient to just add those comments now, but
329      * apparently g_key_file will clear any comments when keys are
330      * added later that create the groups. So we have to check for the
331      * groups now, but add the comments only after setting all of our
332      * values.
333      */
334     file_had_database_group = g_key_file_has_group (config->key_file,
335                                                     "database");
336     file_had_new_group = g_key_file_has_group (config->key_file, "new");
337     file_had_user_group = g_key_file_has_group (config->key_file, "user");
338     file_had_maildir_group = g_key_file_has_group (config->key_file, "maildir");
339     file_had_search_group = g_key_file_has_group (config->key_file, "search");
340     file_had_crypto_group = g_key_file_has_group (config->key_file, "crypto");
341
342     if (notmuch_config_get_database_path (config) == NULL) {
343         char *path = getenv ("MAILDIR");
344         if (path)
345             path = talloc_strdup (config, path);
346         else
347             path = talloc_asprintf (config, "%s/mail",
348                                     getenv ("HOME"));
349         notmuch_config_set_database_path (config, path);
350         talloc_free (path);
351     }
352
353     if (notmuch_config_get_user_name (config) == NULL) {
354         char *name = getenv ("NAME");
355         if (name)
356             name = talloc_strdup (config, name);
357         else
358             name = get_name_from_passwd_file (config);
359         notmuch_config_set_user_name (config, name);
360         talloc_free (name);
361     }
362
363     if (notmuch_config_get_user_primary_email (config) == NULL) {
364         char *email = getenv ("EMAIL");
365         if (email) {
366             notmuch_config_set_user_primary_email (config, email);
367         } else {
368             char hostname[256];
369             struct hostent *hostent;
370             const char *domainname;
371
372             char *username = get_username_from_passwd_file (config);
373
374             gethostname (hostname, 256);
375             hostname[255] = '\0';
376
377             hostent = gethostbyname (hostname);
378             if (hostent && (domainname = strchr (hostent->h_name, '.')))
379                 domainname += 1;
380             else
381                 domainname = "(none)";
382
383             email = talloc_asprintf (config, "%s@%s.%s",
384                                      username, hostname, domainname);
385
386             notmuch_config_set_user_primary_email (config, email);
387
388             talloc_free (username);
389             talloc_free (email);
390         }
391     }
392
393     if (notmuch_config_get_new_tags (config, &tmp) == NULL) {
394         const char *tags[] = { "unread", "inbox" };
395         notmuch_config_set_new_tags (config, tags, 2);
396     }
397
398     if (notmuch_config_get_new_ignore (config, &tmp) == NULL) {
399         notmuch_config_set_new_ignore (config, NULL, 0);
400     }
401
402     if (notmuch_config_get_search_exclude_tags (config, &tmp) == NULL) {
403         if (config->is_new) {
404             const char *tags[] = { "deleted", "spam" };
405             notmuch_config_set_search_exclude_tags (config, tags, 2);
406         } else {
407             notmuch_config_set_search_exclude_tags (config, NULL, 0);
408         }
409     }
410
411     error = NULL;
412     config->maildir_synchronize_flags =
413         g_key_file_get_boolean (config->key_file,
414                                 "maildir", "synchronize_flags", &error);
415     if (error) {
416         notmuch_config_set_maildir_synchronize_flags (config, TRUE);
417         g_error_free (error);
418     }
419
420     if (notmuch_config_get_crypto_gpg_path (config) == NULL) {
421         notmuch_config_set_crypto_gpg_path (config, "gpg");
422     }
423     
424     /* Whenever we know of configuration sections that don't appear in
425      * the configuration file, we add some comments to help the user
426      * understand what can be done. */
427     if (config->is_new)
428         g_key_file_set_comment (config->key_file, NULL, NULL,
429                                 toplevel_config_comment, NULL);
430
431     if (! file_had_database_group)
432         g_key_file_set_comment (config->key_file, "database", NULL,
433                                 database_config_comment, NULL);
434
435     if (! file_had_new_group)
436         g_key_file_set_comment (config->key_file, "new", NULL,
437                                 new_config_comment, NULL);
438
439     if (! file_had_user_group)
440         g_key_file_set_comment (config->key_file, "user", NULL,
441                                 user_config_comment, NULL);
442
443     if (! file_had_maildir_group)
444         g_key_file_set_comment (config->key_file, "maildir", NULL,
445                                 maildir_config_comment, NULL);
446
447     if (! file_had_search_group)
448         g_key_file_set_comment (config->key_file, "search", NULL,
449                                 search_config_comment, NULL);
450
451     if (! file_had_crypto_group)
452         g_key_file_set_comment (config->key_file, "crypto", NULL,
453                                 crypto_config_comment, NULL);
454
455     return config;
456 }
457
458 /* Close the given notmuch_config_t object, freeing all resources.
459  * 
460  * Note: Any changes made to the configuration are *not* saved by this
461  * function. To save changes, call notmuch_config_save before
462  * notmuch_config_close.
463 */
464 void
465 notmuch_config_close (notmuch_config_t *config)
466 {
467     talloc_free (config);
468 }
469
470 /* Save any changes made to the notmuch configuration.
471  *
472  * Any comments originally in the file will be preserved.
473  *
474  * Returns 0 if successful, and 1 in case of any error, (after
475  * printing a description of the error to stderr).
476  */
477 int
478 notmuch_config_save (notmuch_config_t *config)
479 {
480     size_t length;
481     char *data, *filename;
482     GError *error = NULL;
483
484     data = g_key_file_to_data (config->key_file, &length, NULL);
485     if (data == NULL) {
486         fprintf (stderr, "Out of memory.\n");
487         return 1;
488     }
489
490     /* Try not to overwrite symlinks. */
491     filename = canonicalize_file_name (config->filename);
492     if (! filename) {
493         if (errno == ENOENT) {
494             filename = strdup (config->filename);
495             if (! filename) {
496                 fprintf (stderr, "Out of memory.\n");
497                 g_free (data);
498                 return 1;
499             }
500         } else {
501             fprintf (stderr, "Error canonicalizing %s: %s\n", config->filename,
502                      strerror (errno));
503             g_free (data);
504             return 1;
505         }
506     }
507
508     if (! g_file_set_contents (filename, data, length, &error)) {
509         if (strcmp (filename, config->filename) != 0) {
510             fprintf (stderr, "Error saving configuration to %s (-> %s): %s\n",
511                      config->filename, filename, error->message);
512         } else {
513             fprintf (stderr, "Error saving configuration to %s: %s\n",
514                      filename, error->message);
515         }
516         g_error_free (error);
517         free (filename);
518         g_free (data);
519         return 1;
520     }
521
522     free (filename);
523     g_free (data);
524     return 0;
525 }
526
527 notmuch_bool_t
528 notmuch_config_is_new (notmuch_config_t *config)
529 {
530     return config->is_new;
531 }
532
533 static const char *
534 _config_get (notmuch_config_t *config, char **field,
535              const char *group, const char *key)
536 {
537     /* read from config file and cache value, if not cached already */
538     if (*field == NULL) {
539         char *value;
540         value = g_key_file_get_string (config->key_file, group, key, NULL);
541         if (value) {
542             *field = talloc_strdup (config, value);
543             free (value);
544         }
545     }
546     return *field;
547 }
548
549 static void
550 _config_set (notmuch_config_t *config, char **field,
551              const char *group, const char *key, const char *value)
552 {
553     g_key_file_set_string (config->key_file, group, key, value);
554
555     /* drop the cached value */
556     talloc_free (*field);
557     *field = NULL;
558 }
559
560 static const char **
561 _config_get_list (notmuch_config_t *config,
562                   const char *section, const char *key,
563                   const char ***outlist, size_t *list_length, size_t *ret_length)
564 {
565     assert(outlist);
566
567     /* read from config file and cache value, if not cached already */
568     if (*outlist == NULL) {
569
570         char **inlist = g_key_file_get_string_list (config->key_file,
571                                              section, key, list_length, NULL);
572         if (inlist) {
573             unsigned int i;
574
575             *outlist = talloc_size (config, sizeof (char *) * (*list_length + 1));
576
577             for (i = 0; i < *list_length; i++)
578                 (*outlist)[i] = talloc_strdup (*outlist, inlist[i]);
579
580             (*outlist)[i] = NULL;
581
582             g_strfreev (inlist);
583         }
584     }
585
586     if (ret_length)
587         *ret_length = *list_length;
588
589     return *outlist;
590 }
591
592 static void
593 _config_set_list (notmuch_config_t *config,
594                   const char *group, const char *name,
595                   const char *list[],
596                   size_t length, const char ***config_var )
597 {
598     g_key_file_set_string_list (config->key_file, group, name, list, length);
599
600     /* drop the cached value */
601     talloc_free (*config_var);
602     *config_var = NULL;
603 }
604
605 const char *
606 notmuch_config_get_database_path (notmuch_config_t *config)
607 {
608     return _config_get (config, &config->database_path, "database", "path");
609 }
610
611 void
612 notmuch_config_set_database_path (notmuch_config_t *config,
613                                   const char *database_path)
614 {
615     _config_set (config, &config->database_path, "database", "path", database_path);
616 }
617
618 const char *
619 notmuch_config_get_user_name (notmuch_config_t *config)
620 {
621     return _config_get (config, &config->user_name, "user", "name");
622 }
623
624 void
625 notmuch_config_set_user_name (notmuch_config_t *config,
626                               const char *user_name)
627 {
628     _config_set (config, &config->user_name, "user", "name", user_name);
629 }
630
631 const char *
632 notmuch_config_get_user_primary_email (notmuch_config_t *config)
633 {
634     return _config_get (config, &config->user_primary_email, "user", "primary_email");
635 }
636
637 void
638 notmuch_config_set_user_primary_email (notmuch_config_t *config,
639                                        const char *primary_email)
640 {
641     _config_set (config, &config->user_primary_email, "user", "primary_email", primary_email);
642 }
643
644 const char **
645 notmuch_config_get_user_other_email (notmuch_config_t *config,   size_t *length)
646 {
647     return _config_get_list (config, "user", "other_email",
648                              &(config->user_other_email),
649                              &(config->user_other_email_length), length);
650 }
651
652 const char **
653 notmuch_config_get_new_tags (notmuch_config_t *config,   size_t *length)
654 {
655     return _config_get_list (config, "new", "tags",
656                              &(config->new_tags),
657                              &(config->new_tags_length), length);
658 }
659
660 const char **
661 notmuch_config_get_new_ignore (notmuch_config_t *config, size_t *length)
662 {
663     return _config_get_list (config, "new", "ignore",
664                              &(config->new_ignore),
665                              &(config->new_ignore_length), length);
666 }
667
668 void
669 notmuch_config_set_user_other_email (notmuch_config_t *config,
670                                      const char *list[],
671                                      size_t length)
672 {
673     _config_set_list (config, "user", "other_email", list, length,
674                      &(config->user_other_email));
675 }
676
677 void
678 notmuch_config_set_new_tags (notmuch_config_t *config,
679                                      const char *list[],
680                                      size_t length)
681 {
682     _config_set_list (config, "new", "tags", list, length,
683                      &(config->new_tags));
684 }
685
686 void
687 notmuch_config_set_new_ignore (notmuch_config_t *config,
688                                const char *list[],
689                                size_t length)
690 {
691     _config_set_list (config, "new", "ignore", list, length,
692                      &(config->new_ignore));
693 }
694
695 const char **
696 notmuch_config_get_search_exclude_tags (notmuch_config_t *config, size_t *length)
697 {
698     return _config_get_list (config, "search", "exclude_tags",
699                              &(config->search_exclude_tags),
700                              &(config->search_exclude_tags_length), length);
701 }
702
703 void
704 notmuch_config_set_search_exclude_tags (notmuch_config_t *config,
705                                       const char *list[],
706                                       size_t length)
707 {
708     _config_set_list (config, "search", "exclude_tags", list, length,
709                       &(config->search_exclude_tags));
710 }
711
712 const char *
713 notmuch_config_get_crypto_gpg_path (notmuch_config_t *config)
714 {
715     return _config_get (config, &config->crypto_gpg_path, "crypto", "gpg_path");
716 }
717
718 void
719 notmuch_config_set_crypto_gpg_path (notmuch_config_t *config,
720                               const char *gpg_path)
721 {
722     _config_set (config, &config->crypto_gpg_path, "crypto", "gpg_path", gpg_path);
723 }
724
725
726 /* Given a configuration item of the form <group>.<key> return the
727  * component group and key. If any error occurs, print a message on
728  * stderr and return 1. Otherwise, return 0.
729  *
730  * Note: This function modifies the original 'item' string.
731  */
732 static int
733 _item_split (char *item, char **group, char **key)
734 {
735     char *period;
736
737     *group = item;
738
739     period = strchr (item, '.');
740     if (period == NULL || *(period+1) == '\0') {
741         fprintf (stderr,
742                  "Invalid configuration name: %s\n"
743                  "(Should be of the form <section>.<item>)\n", item);
744         return 1;
745     }
746
747     *period = '\0';
748     *key = period + 1;
749
750     return 0;
751 }
752
753 static int
754 notmuch_config_command_get (notmuch_config_t *config, char *item)
755 {
756     if (strcmp(item, "database.path") == 0) {
757         printf ("%s\n", notmuch_config_get_database_path (config));
758     } else if (strcmp(item, "user.name") == 0) {
759         printf ("%s\n", notmuch_config_get_user_name (config));
760     } else if (strcmp(item, "user.primary_email") == 0) {
761         printf ("%s\n", notmuch_config_get_user_primary_email (config));
762     } else if (strcmp(item, "user.other_email") == 0) {
763         const char **other_email;
764         size_t i, length;
765         
766         other_email = notmuch_config_get_user_other_email (config, &length);
767         for (i = 0; i < length; i++)
768             printf ("%s\n", other_email[i]);
769     } else if (strcmp(item, "new.tags") == 0) {
770         const char **tags;
771         size_t i, length;
772
773         tags = notmuch_config_get_new_tags (config, &length);
774         for (i = 0; i < length; i++)
775             printf ("%s\n", tags[i]);
776     } else {
777         char **value;
778         size_t i, length;
779         char *group, *key;
780
781         if (_item_split (item, &group, &key))
782             return 1;
783
784         value = g_key_file_get_string_list (config->key_file,
785                                             group, key,
786                                             &length, NULL);
787         if (value == NULL) {
788             fprintf (stderr, "Unknown configuration item: %s.%s\n",
789                      group, key);
790             return 1;
791         }
792
793         for (i = 0; i < length; i++)
794             printf ("%s\n", value[i]);
795
796         g_strfreev (value);
797     }
798
799     return 0;
800 }
801
802 static int
803 notmuch_config_command_set (notmuch_config_t *config, char *item, int argc, char *argv[])
804 {
805     char *group, *key;
806
807     if (_item_split (item, &group, &key))
808         return 1;
809
810     /* With only the name of an item, we clear it from the
811      * configuration file.
812      *
813      * With a single value, we set it as a string.
814      *
815      * With multiple values, we set them as a string list.
816      */
817     switch (argc) {
818     case 0:
819         g_key_file_remove_key (config->key_file, group, key, NULL);
820         break;
821     case 1:
822         g_key_file_set_string (config->key_file, group, key, argv[0]);
823         break;
824     default:
825         g_key_file_set_string_list (config->key_file, group, key,
826                                     (const gchar **) argv, argc);
827         break;
828     }
829
830     return notmuch_config_save (config);
831 }
832
833 static int
834 notmuch_config_command_list (notmuch_config_t *config)
835 {
836     char **groups;
837     size_t g, groups_length;
838
839     groups = g_key_file_get_groups (config->key_file, &groups_length);
840     if (groups == NULL)
841         return 1;
842
843     for (g = 0; g < groups_length; g++) {
844         char **keys;
845         size_t k, keys_length;
846
847         keys = g_key_file_get_keys (config->key_file,
848                                     groups[g], &keys_length, NULL);
849         if (keys == NULL)
850             continue;
851
852         for (k = 0; k < keys_length; k++) {
853             char *value;
854
855             value = g_key_file_get_string (config->key_file,
856                                            groups[g], keys[k], NULL);
857             if (value != NULL) {
858                 printf ("%s.%s=%s\n", groups[g], keys[k], value);
859                 free (value);
860             }
861         }
862
863         g_strfreev (keys);
864     }
865
866     g_strfreev (groups);
867
868     return 0;
869 }
870
871 int
872 notmuch_config_command (notmuch_config_t *config, int argc, char *argv[])
873 {
874     int ret;
875     int opt_index;
876
877     opt_index = notmuch_minimal_options ("config", argc, argv);
878     if (opt_index < 0)
879         return EXIT_FAILURE;
880
881     /* skip at least subcommand argument */
882     argc-= opt_index;
883     argv+= opt_index;
884
885     if (argc < 1) {
886         fprintf (stderr, "Error: notmuch config requires at least one argument.\n");
887         return EXIT_FAILURE;
888     }
889
890     if (strcmp (argv[0], "get") == 0) {
891         if (argc != 2) {
892             fprintf (stderr, "Error: notmuch config get requires exactly "
893                      "one argument.\n");
894             return EXIT_FAILURE;
895         }
896         ret = notmuch_config_command_get (config, argv[1]);
897     } else if (strcmp (argv[0], "set") == 0) {
898         if (argc < 2) {
899             fprintf (stderr, "Error: notmuch config set requires at least "
900                      "one argument.\n");
901             return EXIT_FAILURE;
902         }
903         ret = notmuch_config_command_set (config, argv[1], argc - 2, argv + 2);
904     } else if (strcmp (argv[0], "list") == 0) {
905         ret = notmuch_config_command_list (config);
906     } else {
907         fprintf (stderr, "Unrecognized argument for notmuch config: %s\n",
908                  argv[0]);
909         return EXIT_FAILURE;
910     }
911
912     return ret ? EXIT_FAILURE : EXIT_SUCCESS;
913
914 }
915
916 notmuch_bool_t
917 notmuch_config_get_maildir_synchronize_flags (notmuch_config_t *config)
918 {
919     return config->maildir_synchronize_flags;
920 }
921
922 void
923 notmuch_config_set_maildir_synchronize_flags (notmuch_config_t *config,
924                                               notmuch_bool_t synchronize_flags)
925 {
926     g_key_file_set_boolean (config->key_file,
927                             "maildir", "synchronize_flags", synchronize_flags);
928     config->maildir_synchronize_flags = synchronize_flags;
929 }