1 /* notmuch - Not much of an email program, (just index and search)
3 * Copyright © 2009 Carl Worth
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.
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.
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/ .
18 * Author: Carl Worth <cworth@cworth.org>
21 #include "notmuch-client.h"
27 static const char toplevel_config_comment[] =
28 " .notmuch-config - Configuration file for the notmuch mail system\n"
30 " For more information about notmuch, see http://notmuchmail.org";
32 static const char database_config_comment[] =
33 " Database configuration\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";
41 static const char new_config_comment[] =
42 " Configuration for \"notmuch new\"\n"
44 " The following options are supported here:\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"
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"
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";
56 static const char user_config_comment[] =
57 " User configuration\n"
59 " Here is where you can let notmuch know how you would like to be\n"
60 " addressed. Valid settings are\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"
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";
72 static const char maildir_config_comment[] =
73 " Maildir compatibility configuration\n"
75 " The following option is supported here:\n"
77 "\tsynchronize_flags Valid values are true and false.\n"
79 "\tIf true, then the following maildir flags (in message filenames)\n"
80 "\twill be synchronized with the corresponding notmuch tags:\n"
88 "\t\tS unread (added when 'S' flag is not present)\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";
94 static const char search_config_comment[] =
95 " Search configuration\n"
97 " The following option is supported here:\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";
104 static const char crypto_config_comment[] =
105 " Cryptography related configuration\n"
107 " The following option is supported here:\n"
110 "\t\tbinary name or full path to invoke gpg.\n";
112 struct _notmuch_config {
115 notmuch_bool_t is_new;
118 char *crypto_gpg_path;
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;
133 notmuch_config_destructor (notmuch_config_t *config)
135 if (config->key_file)
136 g_key_file_free (config->key_file);
142 get_name_from_passwd_file (void *ctx)
146 struct passwd passwd, *ignored;
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);
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);
161 char *comma = strchr (passwd.pw_gecos, ',');
163 name = talloc_strndup (ctx, passwd.pw_gecos,
164 comma - passwd.pw_gecos);
166 name = talloc_strdup (ctx, passwd.pw_gecos);
168 name = talloc_strdup (ctx, "");
171 talloc_free (pw_buf);
177 get_username_from_passwd_file (void *ctx)
181 struct passwd passwd, *ignored;
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);
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);
196 name = talloc_strdup (ctx, passwd.pw_name);
198 name = talloc_strdup (ctx, "");
200 talloc_free (pw_buf);
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.
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
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'
219 * If is_new_ret is NULL, then a "file not found" message will be
220 * printed to stderr and NULL will be returned.
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.
226 * These default configuration settings are determined as
229 * database_path: $MAILDIR, otherwise $HOME/mail
231 * user_name: $NAME variable if set, otherwise
232 * read from /etc/passwd
234 * user_primary_mail: $EMAIL variable if set, otherwise
235 * constructed from the username and
236 * hostname of the current machine.
238 * user_other_email: Not set.
240 * The default configuration also contains comments to guide the
241 * user in editing the file directly.
244 notmuch_config_open (void *ctx,
245 const char *filename,
246 notmuch_bool_t create_new)
248 GError *error = NULL;
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;
258 notmuch_config_t *config = talloc (ctx, notmuch_config_t);
259 if (config == NULL) {
260 fprintf (stderr, "Out of memory.\n");
264 talloc_set_destructor (config, notmuch_config_destructor);
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);
271 config->filename = talloc_asprintf (config, "%s/.notmuch-config",
275 config->key_file = g_key_file_new ();
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;
292 if (! g_key_file_load_from_file (config->key_file,
294 G_KEY_FILE_KEEP_COMMENTS,
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
303 g_error_free (error);
304 config->is_new = TRUE;
306 fprintf (stderr, "Configuration file %s not found.\n"
307 "Try running 'notmuch setup' to create a configuration.\n",
309 talloc_free (config);
310 g_error_free (error);
316 fprintf (stderr, "Error reading configuration file %s: %s\n",
317 config->filename, error->message);
318 talloc_free (config);
319 g_error_free (error);
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.
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
334 file_had_database_group = g_key_file_has_group (config->key_file,
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");
342 if (notmuch_config_get_database_path (config) == NULL) {
343 char *path = getenv ("MAILDIR");
345 path = talloc_strdup (config, path);
347 path = talloc_asprintf (config, "%s/mail",
349 notmuch_config_set_database_path (config, path);
353 if (notmuch_config_get_user_name (config) == NULL) {
354 char *name = getenv ("NAME");
356 name = talloc_strdup (config, name);
358 name = get_name_from_passwd_file (config);
359 notmuch_config_set_user_name (config, name);
363 if (notmuch_config_get_user_primary_email (config) == NULL) {
364 char *email = getenv ("EMAIL");
366 notmuch_config_set_user_primary_email (config, email);
369 struct hostent *hostent;
370 const char *domainname;
372 char *username = get_username_from_passwd_file (config);
374 gethostname (hostname, 256);
375 hostname[255] = '\0';
377 hostent = gethostbyname (hostname);
378 if (hostent && (domainname = strchr (hostent->h_name, '.')))
381 domainname = "(none)";
383 email = talloc_asprintf (config, "%s@%s.%s",
384 username, hostname, domainname);
386 notmuch_config_set_user_primary_email (config, email);
388 talloc_free (username);
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);
398 if (notmuch_config_get_new_ignore (config, &tmp) == NULL) {
399 notmuch_config_set_new_ignore (config, NULL, 0);
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);
407 notmuch_config_set_search_exclude_tags (config, NULL, 0);
412 config->maildir_synchronize_flags =
413 g_key_file_get_boolean (config->key_file,
414 "maildir", "synchronize_flags", &error);
416 notmuch_config_set_maildir_synchronize_flags (config, TRUE);
417 g_error_free (error);
420 if (notmuch_config_get_crypto_gpg_path (config) == NULL) {
421 notmuch_config_set_crypto_gpg_path (config, "gpg");
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. */
428 g_key_file_set_comment (config->key_file, NULL, NULL,
429 toplevel_config_comment, NULL);
431 if (! file_had_database_group)
432 g_key_file_set_comment (config->key_file, "database", NULL,
433 database_config_comment, NULL);
435 if (! file_had_new_group)
436 g_key_file_set_comment (config->key_file, "new", NULL,
437 new_config_comment, NULL);
439 if (! file_had_user_group)
440 g_key_file_set_comment (config->key_file, "user", NULL,
441 user_config_comment, NULL);
443 if (! file_had_maildir_group)
444 g_key_file_set_comment (config->key_file, "maildir", NULL,
445 maildir_config_comment, NULL);
447 if (! file_had_search_group)
448 g_key_file_set_comment (config->key_file, "search", NULL,
449 search_config_comment, NULL);
451 if (! file_had_crypto_group)
452 g_key_file_set_comment (config->key_file, "crypto", NULL,
453 crypto_config_comment, NULL);
458 /* Close the given notmuch_config_t object, freeing all resources.
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.
465 notmuch_config_close (notmuch_config_t *config)
467 talloc_free (config);
470 /* Save any changes made to the notmuch configuration.
472 * Any comments originally in the file will be preserved.
474 * Returns 0 if successful, and 1 in case of any error, (after
475 * printing a description of the error to stderr).
478 notmuch_config_save (notmuch_config_t *config)
481 char *data, *filename;
482 GError *error = NULL;
484 data = g_key_file_to_data (config->key_file, &length, NULL);
486 fprintf (stderr, "Out of memory.\n");
490 /* Try not to overwrite symlinks. */
491 filename = canonicalize_file_name (config->filename);
493 if (errno == ENOENT) {
494 filename = strdup (config->filename);
496 fprintf (stderr, "Out of memory.\n");
501 fprintf (stderr, "Error canonicalizing %s: %s\n", config->filename,
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);
513 fprintf (stderr, "Error saving configuration to %s: %s\n",
514 filename, error->message);
516 g_error_free (error);
528 notmuch_config_is_new (notmuch_config_t *config)
530 return config->is_new;
534 _config_get (notmuch_config_t *config, char **field,
535 const char *group, const char *key)
537 /* read from config file and cache value, if not cached already */
538 if (*field == NULL) {
540 value = g_key_file_get_string (config->key_file, group, key, NULL);
542 *field = talloc_strdup (config, value);
550 _config_set (notmuch_config_t *config, char **field,
551 const char *group, const char *key, const char *value)
553 g_key_file_set_string (config->key_file, group, key, value);
555 /* drop the cached value */
556 talloc_free (*field);
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)
567 /* read from config file and cache value, if not cached already */
568 if (*outlist == NULL) {
570 char **inlist = g_key_file_get_string_list (config->key_file,
571 section, key, list_length, NULL);
575 *outlist = talloc_size (config, sizeof (char *) * (*list_length + 1));
577 for (i = 0; i < *list_length; i++)
578 (*outlist)[i] = talloc_strdup (*outlist, inlist[i]);
580 (*outlist)[i] = NULL;
587 *ret_length = *list_length;
593 _config_set_list (notmuch_config_t *config,
594 const char *group, const char *name,
596 size_t length, const char ***config_var )
598 g_key_file_set_string_list (config->key_file, group, name, list, length);
600 /* drop the cached value */
601 talloc_free (*config_var);
606 notmuch_config_get_database_path (notmuch_config_t *config)
608 return _config_get (config, &config->database_path, "database", "path");
612 notmuch_config_set_database_path (notmuch_config_t *config,
613 const char *database_path)
615 _config_set (config, &config->database_path, "database", "path", database_path);
619 notmuch_config_get_user_name (notmuch_config_t *config)
621 return _config_get (config, &config->user_name, "user", "name");
625 notmuch_config_set_user_name (notmuch_config_t *config,
626 const char *user_name)
628 _config_set (config, &config->user_name, "user", "name", user_name);
632 notmuch_config_get_user_primary_email (notmuch_config_t *config)
634 return _config_get (config, &config->user_primary_email, "user", "primary_email");
638 notmuch_config_set_user_primary_email (notmuch_config_t *config,
639 const char *primary_email)
641 _config_set (config, &config->user_primary_email, "user", "primary_email", primary_email);
645 notmuch_config_get_user_other_email (notmuch_config_t *config, size_t *length)
647 return _config_get_list (config, "user", "other_email",
648 &(config->user_other_email),
649 &(config->user_other_email_length), length);
653 notmuch_config_get_new_tags (notmuch_config_t *config, size_t *length)
655 return _config_get_list (config, "new", "tags",
657 &(config->new_tags_length), length);
661 notmuch_config_get_new_ignore (notmuch_config_t *config, size_t *length)
663 return _config_get_list (config, "new", "ignore",
664 &(config->new_ignore),
665 &(config->new_ignore_length), length);
669 notmuch_config_set_user_other_email (notmuch_config_t *config,
673 _config_set_list (config, "user", "other_email", list, length,
674 &(config->user_other_email));
678 notmuch_config_set_new_tags (notmuch_config_t *config,
682 _config_set_list (config, "new", "tags", list, length,
683 &(config->new_tags));
687 notmuch_config_set_new_ignore (notmuch_config_t *config,
691 _config_set_list (config, "new", "ignore", list, length,
692 &(config->new_ignore));
696 notmuch_config_get_search_exclude_tags (notmuch_config_t *config, size_t *length)
698 return _config_get_list (config, "search", "exclude_tags",
699 &(config->search_exclude_tags),
700 &(config->search_exclude_tags_length), length);
704 notmuch_config_set_search_exclude_tags (notmuch_config_t *config,
708 _config_set_list (config, "search", "exclude_tags", list, length,
709 &(config->search_exclude_tags));
713 notmuch_config_get_crypto_gpg_path (notmuch_config_t *config)
715 return _config_get (config, &config->crypto_gpg_path, "crypto", "gpg_path");
719 notmuch_config_set_crypto_gpg_path (notmuch_config_t *config,
720 const char *gpg_path)
722 _config_set (config, &config->crypto_gpg_path, "crypto", "gpg_path", gpg_path);
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.
730 * Note: This function modifies the original 'item' string.
733 _item_split (char *item, char **group, char **key)
739 period = strchr (item, '.');
740 if (period == NULL || *(period+1) == '\0') {
742 "Invalid configuration name: %s\n"
743 "(Should be of the form <section>.<item>)\n", item);
754 notmuch_config_command_get (notmuch_config_t *config, char *item)
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;
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) {
773 tags = notmuch_config_get_new_tags (config, &length);
774 for (i = 0; i < length; i++)
775 printf ("%s\n", tags[i]);
781 if (_item_split (item, &group, &key))
784 value = g_key_file_get_string_list (config->key_file,
788 fprintf (stderr, "Unknown configuration item: %s.%s\n",
793 for (i = 0; i < length; i++)
794 printf ("%s\n", value[i]);
803 notmuch_config_command_set (notmuch_config_t *config, char *item, int argc, char *argv[])
807 if (_item_split (item, &group, &key))
810 /* With only the name of an item, we clear it from the
811 * configuration file.
813 * With a single value, we set it as a string.
815 * With multiple values, we set them as a string list.
819 g_key_file_remove_key (config->key_file, group, key, NULL);
822 g_key_file_set_string (config->key_file, group, key, argv[0]);
825 g_key_file_set_string_list (config->key_file, group, key,
826 (const gchar **) argv, argc);
830 return notmuch_config_save (config);
834 notmuch_config_command_list (notmuch_config_t *config)
837 size_t g, groups_length;
839 groups = g_key_file_get_groups (config->key_file, &groups_length);
843 for (g = 0; g < groups_length; g++) {
845 size_t k, keys_length;
847 keys = g_key_file_get_keys (config->key_file,
848 groups[g], &keys_length, NULL);
852 for (k = 0; k < keys_length; k++) {
855 value = g_key_file_get_string (config->key_file,
856 groups[g], keys[k], NULL);
858 printf ("%s.%s=%s\n", groups[g], keys[k], value);
872 notmuch_config_command (notmuch_config_t *config, int argc, char *argv[])
876 argc--; argv++; /* skip subcommand argument */
879 fprintf (stderr, "Error: notmuch config requires at least one argument.\n");
883 if (strcmp (argv[0], "get") == 0) {
885 fprintf (stderr, "Error: notmuch config get requires exactly "
889 ret = notmuch_config_command_get (config, argv[1]);
890 } else if (strcmp (argv[0], "set") == 0) {
892 fprintf (stderr, "Error: notmuch config set requires at least "
896 ret = notmuch_config_command_set (config, argv[1], argc - 2, argv + 2);
897 } else if (strcmp (argv[0], "list") == 0) {
898 ret = notmuch_config_command_list (config);
900 fprintf (stderr, "Unrecognized argument for notmuch config: %s\n",
905 return ret ? EXIT_FAILURE : EXIT_SUCCESS;
910 notmuch_config_get_maildir_synchronize_flags (notmuch_config_t *config)
912 return config->maildir_synchronize_flags;
916 notmuch_config_set_maildir_synchronize_flags (notmuch_config_t *config,
917 notmuch_bool_t synchronize_flags)
919 g_key_file_set_boolean (config->key_file,
920 "maildir", "synchronize_flags", synchronize_flags);
921 config->maildir_synchronize_flags = synchronize_flags;