* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
- * along with this program. If not, see http://www.gnu.org/licenses/ .
+ * along with this program. If not, see https://www.gnu.org/licenses/ .
*
* Author: Carl Worth <cworth@cworth.org>
*/
#include <netdb.h>
#include <assert.h>
+#include "unicode-util.h"
+
static const char toplevel_config_comment[] =
" .notmuch-config - Configuration file for the notmuch mail system\n"
"\n"
- " For more information about notmuch, see http://notmuchmail.org";
+ " For more information about notmuch, see https://notmuchmail.org";
static const char database_config_comment[] =
" Database configuration\n"
"\t\tsearch results by default. Using an excluded tag in a\n"
"\t\tquery will override that exclusion.\n";
+static const char crypto_config_comment[] =
+ " Cryptography related configuration\n"
+ "\n"
+ " The following old option is now ignored:\n"
+ "\n"
+ "\tgpgpath\n"
+ "\t\tThis option was used by older builds of notmuch to choose\n"
+ "\t\tthe version of gpg to use.\n"
+ "\t\tSetting $PATH is a better approach.\n";
+
struct _notmuch_config {
char *filename;
GKeyFile *key_file;
- notmuch_bool_t is_new;
+ bool is_new;
char *database_path;
char *user_name;
size_t new_tags_length;
const char **new_ignore;
size_t new_ignore_length;
- notmuch_bool_t maildir_synchronize_flags;
+ bool maildir_synchronize_flags;
const char **search_exclude_tags;
size_t search_exclude_tags_length;
};
char *name;
int e;
- pw_buf_size = sysconf(_SC_GETPW_R_SIZE_MAX);
+ pw_buf_size = sysconf (_SC_GETPW_R_SIZE_MAX);
if (pw_buf_size == -1) pw_buf_size = 64;
pw_buf = talloc_size (ctx, pw_buf_size);
while ((e = getpwuid_r (getuid (), &passwd, pw_buf,
- pw_buf_size, &ignored)) == ERANGE) {
- pw_buf_size = pw_buf_size * 2;
- pw_buf = talloc_zero_size(ctx, pw_buf_size);
+ pw_buf_size, &ignored)) == ERANGE) {
+ pw_buf_size = pw_buf_size * 2;
+ pw_buf = talloc_zero_size (ctx, pw_buf_size);
}
if (e == 0) {
char *name;
int e;
- pw_buf_size = sysconf(_SC_GETPW_R_SIZE_MAX);
+ pw_buf_size = sysconf (_SC_GETPW_R_SIZE_MAX);
if (pw_buf_size == -1) pw_buf_size = 64;
pw_buf = talloc_zero_size (ctx, pw_buf_size);
while ((e = getpwuid_r (getuid (), &passwd, pw_buf,
- pw_buf_size, &ignored)) == ERANGE) {
- pw_buf_size = pw_buf_size * 2;
- pw_buf = talloc_zero_size(ctx, pw_buf_size);
+ pw_buf_size, &ignored)) == ERANGE) {
+ pw_buf_size = pw_buf_size * 2;
+ pw_buf = talloc_zero_size (ctx, pw_buf_size);
}
if (e == 0)
return name;
}
+static bool
+get_config_from_file (notmuch_config_t *config, bool create_new)
+{
+ #define BUF_SIZE 4096
+ char *config_str = NULL;
+ int config_len = 0;
+ int config_bufsize = BUF_SIZE;
+ size_t len;
+ GError *error = NULL;
+ bool ret = false;
+
+ FILE *fp = fopen (config->filename, "r");
+ if (fp == NULL) {
+ if (errno == ENOENT) {
+ /* If create_new is true, then the caller is prepared for a
+ * default configuration file in the case of FILE NOT FOUND.
+ */
+ if (create_new) {
+ config->is_new = true;
+ ret = true;
+ } else {
+ fprintf (stderr, "Configuration file %s not found.\n"
+ "Try running 'notmuch setup' to create a configuration.\n",
+ config->filename);
+ }
+ } else {
+ fprintf (stderr, "Error opening config file '%s': %s\n",
+ config->filename, strerror (errno));
+ }
+ goto out;
+ }
+
+ config_str = talloc_zero_array (config, char, config_bufsize);
+ if (config_str == NULL) {
+ fprintf (stderr, "Error reading '%s': Out of memory\n", config->filename);
+ goto out;
+ }
+
+ while ((len = fread (config_str + config_len, 1,
+ config_bufsize - config_len, fp)) > 0) {
+ config_len += len;
+ if (config_len == config_bufsize) {
+ config_bufsize += BUF_SIZE;
+ config_str = talloc_realloc (config, config_str, char, config_bufsize);
+ if (config_str == NULL) {
+ fprintf (stderr, "Error reading '%s': Failed to reallocate memory\n",
+ config->filename);
+ goto out;
+ }
+ }
+ }
+
+ if (ferror (fp)) {
+ fprintf (stderr, "Error reading '%s': I/O error\n", config->filename);
+ goto out;
+ }
+
+ if (g_key_file_load_from_data (config->key_file, config_str, config_len,
+ G_KEY_FILE_KEEP_COMMENTS, &error)) {
+ ret = true;
+ goto out;
+ }
+
+ fprintf (stderr, "Error parsing config file '%s': %s\n",
+ config->filename, error->message);
+
+ g_error_free (error);
+
+ out:
+ if (fp)
+ fclose (fp);
+
+ if (config_str)
+ talloc_free (config_str);
+
+ return ret;
+}
+
/* Open the named notmuch configuration file. If the filename is NULL,
* the value of the environment variable $NOTMUCH_CONFIG will be used.
* If $NOTMUCH_CONFIG is unset, the default configuration file
*
* If is_new_ret is NULL, then a "file not found" message will be
* printed to stderr and NULL will be returned.
-
+ *
* If is_new_ret is non-NULL then a default configuration will be
* returned and *is_new_ret will be set to 1 on return so that
* the caller can recognize this case.
*
- * These default configuration settings are determined as
- * follows:
+ * These default configuration settings are determined as
+ * follows:
*
- * database_path: $HOME/mail
+ * database_path: $MAILDIR, otherwise $HOME/mail
*
- * user_name: From /etc/passwd
+ * user_name: $NAME variable if set, otherwise
+ * read from /etc/passwd
*
- * user_primary_mail: $EMAIL variable if set, otherwise
+ * user_primary_mail: $EMAIL variable if set, otherwise
* constructed from the username and
* hostname of the current machine.
*
notmuch_config_t *
notmuch_config_open (void *ctx,
const char *filename,
- notmuch_bool_t create_new)
+ notmuch_command_mode_t config_mode)
{
GError *error = NULL;
size_t tmp;
int file_had_user_group;
int file_had_maildir_group;
int file_had_search_group;
+ int file_had_crypto_group;
+
+ notmuch_config_t *config = talloc_zero (ctx, notmuch_config_t);
- notmuch_config_t *config = talloc (ctx, notmuch_config_t);
if (config == NULL) {
fprintf (stderr, "Out of memory.\n");
return NULL;
}
-
+
talloc_set_destructor (config, notmuch_config_destructor);
+ /* non-zero defaults */
+ config->maildir_synchronize_flags = true;
+
if (filename) {
config->filename = talloc_strdup (config, filename);
} else if ((notmuch_config_env = getenv ("NOTMUCH_CONFIG"))) {
config->key_file = g_key_file_new ();
- config->is_new = FALSE;
- config->database_path = NULL;
- config->user_name = NULL;
- config->user_primary_email = NULL;
- config->user_other_email = NULL;
- config->user_other_email_length = 0;
- config->new_tags = NULL;
- config->new_tags_length = 0;
- config->new_ignore = NULL;
- config->new_ignore_length = 0;
- config->maildir_synchronize_flags = TRUE;
- config->search_exclude_tags = NULL;
- config->search_exclude_tags_length = 0;
-
- if (! g_key_file_load_from_file (config->key_file,
- config->filename,
- G_KEY_FILE_KEEP_COMMENTS,
- &error))
- {
- /* If create_new is true, then the caller is prepared for a
- * default configuration file in the case of FILE NOT
- * FOUND. Otherwise, any read failure is an error.
- */
- if (create_new &&
- error->domain == G_FILE_ERROR &&
- error->code == G_FILE_ERROR_NOENT)
- {
- g_error_free (error);
- config->is_new = TRUE;
- }
- else
- {
- fprintf (stderr, "Error reading configuration file %s: %s\n",
- config->filename, error->message);
+ if (config_mode & NOTMUCH_COMMAND_CONFIG_OPEN) {
+ bool create_new = (config_mode & NOTMUCH_COMMAND_CONFIG_CREATE) != 0;
+
+ if (! get_config_from_file (config, create_new)) {
talloc_free (config);
- g_error_free (error);
return NULL;
}
}
file_had_user_group = g_key_file_has_group (config->key_file, "user");
file_had_maildir_group = g_key_file_has_group (config->key_file, "maildir");
file_had_search_group = g_key_file_has_group (config->key_file, "search");
-
+ file_had_crypto_group = g_key_file_has_group (config->key_file, "crypto");
if (notmuch_config_get_database_path (config) == NULL) {
- char *path = talloc_asprintf (config, "%s/mail",
- getenv ("HOME"));
+ char *path = getenv ("MAILDIR");
+ if (path)
+ path = talloc_strdup (config, path);
+ else
+ path = talloc_asprintf (config, "%s/mail",
+ getenv ("HOME"));
notmuch_config_set_database_path (config, path);
talloc_free (path);
}
if (notmuch_config_get_user_name (config) == NULL) {
- char *name = get_name_from_passwd_file (config);
+ char *name = getenv ("NAME");
+ if (name)
+ name = talloc_strdup (config, name);
+ else
+ name = get_name_from_passwd_file (config);
notmuch_config_set_user_name (config, name);
talloc_free (name);
}
}
if (notmuch_config_get_new_tags (config, &tmp) == NULL) {
- const char *tags[] = { "unread", "inbox" };
+ const char *tags[] = { "unread", "inbox" };
notmuch_config_set_new_tags (config, tags, 2);
}
g_key_file_get_boolean (config->key_file,
"maildir", "synchronize_flags", &error);
if (error) {
- notmuch_config_set_maildir_synchronize_flags (config, TRUE);
+ notmuch_config_set_maildir_synchronize_flags (config, true);
g_error_free (error);
}
g_key_file_set_comment (config->key_file, "search", NULL,
search_config_comment, NULL);
+ if (! file_had_crypto_group)
+ g_key_file_set_comment (config->key_file, "crypto", NULL,
+ crypto_config_comment, NULL);
+
return config;
}
/* Close the given notmuch_config_t object, freeing all resources.
- *
+ *
* Note: Any changes made to the configuration are *not* saved by this
* function. To save changes, call notmuch_config_save before
* notmuch_config_close.
-*/
+ */
void
notmuch_config_close (notmuch_config_t *config)
{
talloc_free (config);
}
+const char *_notmuch_config_get_path (notmuch_config_t *config) {
+ return config->filename;
+}
/* Save any changes made to the notmuch configuration.
*
* Any comments originally in the file will be preserved.
return 0;
}
-notmuch_bool_t
+bool
notmuch_config_is_new (notmuch_config_t *config)
{
return config->is_new;
const char *section, const char *key,
const char ***outlist, size_t *list_length, size_t *ret_length)
{
- assert(outlist);
+ assert (outlist);
/* read from config file and cache value, if not cached already */
if (*outlist == NULL) {
char **inlist = g_key_file_get_string_list (config->key_file,
- section, key, list_length, NULL);
+ section, key, list_length, NULL);
if (inlist) {
unsigned int i;
static void
_config_set_list (notmuch_config_t *config,
- const char *group, const char *name,
+ const char *group, const char *key,
const char *list[],
size_t length, const char ***config_var )
{
- g_key_file_set_string_list (config->key_file, group, name, list, length);
+ g_key_file_set_string_list (config->key_file, group, key, list, length);
/* drop the cached value */
talloc_free (*config_var);
const char *
notmuch_config_get_database_path (notmuch_config_t *config)
{
- return _config_get (config, &config->database_path, "database", "path");
+ char *db_path = (char *) _config_get (config, &config->database_path, "database", "path");
+
+ if (db_path && *db_path != '/') {
+ /* If the path in the configuration file begins with any
+ * character other than /, presume that it is relative to
+ * $HOME and update as appropriate.
+ */
+ char *abs_path = talloc_asprintf (config, "%s/%s", getenv ("HOME"), db_path);
+ talloc_free (db_path);
+ db_path = config->database_path = abs_path;
+ }
+
+ return db_path;
}
void
size_t length)
{
_config_set_list (config, "user", "other_email", list, length,
- &(config->user_other_email));
+ &(config->user_other_email));
}
void
notmuch_config_set_new_tags (notmuch_config_t *config,
- const char *list[],
- size_t length)
+ const char *list[],
+ size_t length)
{
_config_set_list (config, "new", "tags", list, length,
- &(config->new_tags));
+ &(config->new_tags));
}
void
size_t length)
{
_config_set_list (config, "new", "ignore", list, length,
- &(config->new_ignore));
+ &(config->new_ignore));
}
const char **
void
notmuch_config_set_search_exclude_tags (notmuch_config_t *config,
- const char *list[],
- size_t length)
+ const char *list[],
+ size_t length)
{
_config_set_list (config, "search", "exclude_tags", list, length,
&(config->search_exclude_tags));
}
+
/* Given a configuration item of the form <group>.<key> return the
* component group and key. If any error occurs, print a message on
* stderr and return 1. Otherwise, return 0.
*group = item;
period = strchr (item, '.');
- if (period == NULL || *(period+1) == '\0') {
+ if (period == NULL || *(period + 1) == '\0') {
fprintf (stderr,
"Invalid configuration name: %s\n"
"(Should be of the form <section>.<item>)\n", item);
return 0;
}
+/* These are more properly called Xapian fields, but the user facing
+ * docs call them prefixes, so make the error message match */
+static bool
+validate_field_name (const char *str)
+{
+ const char *key;
+
+ if (! g_utf8_validate (str, -1, NULL)) {
+ fprintf (stderr, "Invalid utf8: %s\n", str);
+ return false;
+ }
+
+ key = g_utf8_strrchr (str, -1, '.');
+ if (! key ) {
+ INTERNAL_ERROR ("Impossible code path on input: %s\n", str);
+ }
+
+ key++;
+
+ if (! *key) {
+ fprintf (stderr, "Empty prefix name: %s\n", str);
+ return false;
+ }
+
+ if (! unicode_word_utf8 (key)) {
+ fprintf (stderr, "Non-word character in prefix name: %s\n", key);
+ return false;
+ }
+
+ if (key[0] >= 'a' && key[0] <= 'z') {
+ fprintf (stderr, "Prefix names starting with lower case letters are reserved: %s\n", key);
+ return false;
+ }
+
+ return true;
+}
+
+#define BUILT_WITH_PREFIX "built_with."
+
+typedef struct config_key {
+ const char *name;
+ bool in_db;
+ bool prefix;
+ bool (*validate)(const char *);
+} config_key_info_t;
+
+static struct config_key
+ config_key_table[] = {
+ { "index.decrypt", true, false, NULL },
+ { "index.header.", true, true, validate_field_name },
+ { "query.", true, true, NULL },
+};
+
+static config_key_info_t *
+_config_key_info (const char *item)
+{
+ for (size_t i = 0; i < ARRAY_SIZE (config_key_table); i++) {
+ if (config_key_table[i].prefix &&
+ strncmp (item, config_key_table[i].name,
+ strlen (config_key_table[i].name)) == 0)
+ return config_key_table + i;
+ if (strcmp (item, config_key_table[i].name) == 0)
+ return config_key_table + i;
+ }
+ return NULL;
+}
+
+static bool
+_stored_in_db (const char *item)
+{
+ config_key_info_t *info;
+
+ info = _config_key_info (item);
+
+ return (info && info->in_db);
+}
+
+static int
+_print_db_config (notmuch_config_t *config, const char *name)
+{
+ notmuch_database_t *notmuch;
+ char *val;
+
+ if (notmuch_database_open (notmuch_config_get_database_path (config),
+ NOTMUCH_DATABASE_MODE_READ_ONLY, ¬much))
+ return EXIT_FAILURE;
+
+ /* XXX Handle UUID mismatch? */
+
+ if (print_status_database ("notmuch config", notmuch,
+ notmuch_database_get_config (notmuch, name, &val)))
+ return EXIT_FAILURE;
+
+ puts (val);
+
+ return EXIT_SUCCESS;
+}
+
static int
notmuch_config_command_get (notmuch_config_t *config, char *item)
{
- if (strcmp(item, "database.path") == 0) {
+ if (strcmp (item, "database.path") == 0) {
printf ("%s\n", notmuch_config_get_database_path (config));
- } else if (strcmp(item, "user.name") == 0) {
+ } else if (strcmp (item, "user.name") == 0) {
printf ("%s\n", notmuch_config_get_user_name (config));
- } else if (strcmp(item, "user.primary_email") == 0) {
+ } else if (strcmp (item, "user.primary_email") == 0) {
printf ("%s\n", notmuch_config_get_user_primary_email (config));
- } else if (strcmp(item, "user.other_email") == 0) {
+ } else if (strcmp (item, "user.other_email") == 0) {
const char **other_email;
size_t i, length;
-
+
other_email = notmuch_config_get_user_other_email (config, &length);
for (i = 0; i < length; i++)
printf ("%s\n", other_email[i]);
- } else if (strcmp(item, "new.tags") == 0) {
+ } else if (strcmp (item, "new.tags") == 0) {
const char **tags;
size_t i, length;
tags = notmuch_config_get_new_tags (config, &length);
for (i = 0; i < length; i++)
printf ("%s\n", tags[i]);
+ } else if (STRNCMP_LITERAL (item, BUILT_WITH_PREFIX) == 0) {
+ printf ("%s\n",
+ notmuch_built_with (item + strlen (BUILT_WITH_PREFIX)) ? "true" : "false");
+ } else if (_stored_in_db (item)) {
+ return _print_db_config (config, item);
} else {
char **value;
size_t i, length;
return 0;
}
+static int
+_set_db_config (notmuch_config_t *config, const char *key, int argc, char **argv)
+{
+ notmuch_database_t *notmuch;
+ const char *val = "";
+
+ if (argc > 1) {
+ /* XXX handle lists? */
+ fprintf (stderr, "notmuch config set: at most one value expected for %s\n", key);
+ return EXIT_FAILURE;
+ }
+
+ if (argc > 0) {
+ val = argv[0];
+ }
+
+ if (notmuch_database_open (notmuch_config_get_database_path (config),
+ NOTMUCH_DATABASE_MODE_READ_WRITE, ¬much))
+ return EXIT_FAILURE;
+
+ /* XXX Handle UUID mismatch? */
+
+ if (print_status_database ("notmuch config", notmuch,
+ notmuch_database_set_config (notmuch, key, val)))
+ return EXIT_FAILURE;
+
+ if (print_status_database ("notmuch config", notmuch,
+ notmuch_database_close (notmuch)))
+ return EXIT_FAILURE;
+
+ return EXIT_SUCCESS;
+}
+
static int
notmuch_config_command_set (notmuch_config_t *config, char *item, int argc, char *argv[])
{
char *group, *key;
+ config_key_info_t *key_info;
+
+ if (STRNCMP_LITERAL (item, BUILT_WITH_PREFIX) == 0) {
+ fprintf (stderr, "Error: read only option: %s\n", item);
+ return 1;
+ }
+
+ key_info = _config_key_info (item);
+ if (key_info && key_info->validate && (! key_info->validate (item)))
+ return 1;
+
+ if (key_info && key_info->in_db) {
+ return _set_db_config (config, item, argc, argv);
+ }
if (_item_split (item, &group, &key))
return 1;
return notmuch_config_save (config);
}
+static
+void
+_notmuch_config_list_built_with ()
+{
+ printf ("%scompact=%s\n",
+ BUILT_WITH_PREFIX,
+ notmuch_built_with ("compact") ? "true" : "false");
+ printf ("%sfield_processor=%s\n",
+ BUILT_WITH_PREFIX,
+ notmuch_built_with ("field_processor") ? "true" : "false");
+ printf ("%sretry_lock=%s\n",
+ BUILT_WITH_PREFIX,
+ notmuch_built_with ("retry_lock") ? "true" : "false");
+}
+
+static int
+_list_db_config (notmuch_config_t *config)
+{
+ notmuch_database_t *notmuch;
+ notmuch_config_list_t *list;
+
+ if (notmuch_database_open (notmuch_config_get_database_path (config),
+ NOTMUCH_DATABASE_MODE_READ_ONLY, ¬much))
+ return EXIT_FAILURE;
+
+ /* XXX Handle UUID mismatch? */
+
+
+ if (print_status_database ("notmuch config", notmuch,
+ notmuch_database_get_config_list (notmuch, "", &list)))
+ return EXIT_FAILURE;
+
+ for (; notmuch_config_list_valid (list); notmuch_config_list_move_to_next (list)) {
+ printf ("%s=%s\n", notmuch_config_list_key (list), notmuch_config_list_value (list));
+ }
+ notmuch_config_list_destroy (list);
+
+ return EXIT_SUCCESS;
+}
+
static int
notmuch_config_command_list (notmuch_config_t *config)
{
g_strfreev (groups);
- return 0;
+ _notmuch_config_list_built_with ();
+ return _list_db_config (config);
}
int
-notmuch_config_command (notmuch_config_t *config, int argc, char *argv[])
+notmuch_config_command (notmuch_config_t *config, unused(notmuch_database_t *notmuch), int argc, char *argv[])
{
int ret;
+ int opt_index;
+
+ opt_index = notmuch_minimal_options ("config", argc, argv);
+ if (opt_index < 0)
+ return EXIT_FAILURE;
+
+ if (notmuch_requested_db_uuid)
+ fprintf (stderr, "Warning: ignoring --uuid=%s\n",
+ notmuch_requested_db_uuid);
- argc--; argv++; /* skip subcommand argument */
+ /* skip at least subcommand argument */
+ argc -= opt_index;
+ argv += opt_index;
if (argc < 1) {
fprintf (stderr, "Error: notmuch config requires at least one argument.\n");
}
-notmuch_bool_t
+bool
notmuch_config_get_maildir_synchronize_flags (notmuch_config_t *config)
{
return config->maildir_synchronize_flags;
void
notmuch_config_set_maildir_synchronize_flags (notmuch_config_t *config,
- notmuch_bool_t synchronize_flags)
+ bool synchronize_flags)
{
g_key_file_set_boolean (config->key_file,
"maildir", "synchronize_flags", synchronize_flags);