diff options
Diffstat (limited to 'pigeonhole/src/lib-sieve/sieve-storage.c')
-rw-r--r-- | pigeonhole/src/lib-sieve/sieve-storage.c | 1590 |
1 files changed, 1590 insertions, 0 deletions
diff --git a/pigeonhole/src/lib-sieve/sieve-storage.c b/pigeonhole/src/lib-sieve/sieve-storage.c new file mode 100644 index 0000000..1e2aa34 --- /dev/null +++ b/pigeonhole/src/lib-sieve/sieve-storage.c @@ -0,0 +1,1590 @@ +/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file + */ + +#include "lib.h" +#include "array.h" +#include "str-sanitize.h" +#include "home-expand.h" +#include "eacces-error.h" +#include "mkdir-parents.h" +#include "ioloop.h" + +#include "sieve-common.h" +#include "sieve-settings.h" +#include "sieve-error-private.h" + +#include "sieve-script-private.h" +#include "sieve-storage-private.h" + +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <time.h> +#include <utime.h> + +#define CRITICAL_MSG \ + "Internal error occurred. Refer to server log for more information." +#define CRITICAL_MSG_STAMP CRITICAL_MSG " [%Y-%m-%d %H:%M:%S]" + +struct event_category event_category_sieve_storage = { + .parent = &event_category_sieve, + .name = "sieve-storage", +}; + +/* + * Storage class + */ + +struct sieve_storage_class_registry { + ARRAY_TYPE(sieve_storage_class) storage_classes; +}; + +void sieve_storages_init(struct sieve_instance *svinst) +{ + svinst->storage_reg = p_new(svinst->pool, + struct sieve_storage_class_registry, 1); + p_array_init(&svinst->storage_reg->storage_classes, svinst->pool, 8); + + sieve_storage_class_register(svinst, &sieve_file_storage); + sieve_storage_class_register(svinst, &sieve_dict_storage); + sieve_storage_class_register(svinst, &sieve_ldap_storage); +} + +void sieve_storages_deinit(struct sieve_instance *svinst ATTR_UNUSED) +{ + /* nothing yet */ +} + +void sieve_storage_class_register(struct sieve_instance *svinst, + const struct sieve_storage *storage_class) +{ + struct sieve_storage_class_registry *reg = svinst->storage_reg; + const struct sieve_storage *old_class; + + old_class = sieve_storage_find_class(svinst, + storage_class->driver_name); + if (old_class != NULL) { + if (old_class->v.alloc == NULL) { + /* replacing a "support not compiled in" storage class + */ + sieve_storage_class_unregister(svinst, old_class); + } else { + i_panic("sieve_storage_class_register(%s): " + "Already registered", + storage_class->driver_name); + } + } + + array_append(®->storage_classes, &storage_class, 1); +} + +void sieve_storage_class_unregister(struct sieve_instance *svinst, + const struct sieve_storage *storage_class) +{ + struct sieve_storage_class_registry *reg = svinst->storage_reg; + const struct sieve_storage *const *classes; + unsigned int i, count; + + classes = array_get(®->storage_classes, &count); + for (i = 0; i < count; i++) { + if (classes[i] == storage_class) { + array_delete(®->storage_classes, i, 1); + break; + } + } +} + +const struct sieve_storage * +sieve_storage_find_class(struct sieve_instance *svinst, const char *name) +{ + struct sieve_storage_class_registry *reg = svinst->storage_reg; + const struct sieve_storage *const *classes; + unsigned int i, count; + + i_assert(name != NULL); + + classes = array_get(®->storage_classes, &count); + for (i = 0; i < count; i++) { + if (strcasecmp(classes[i]->driver_name, name) == 0) + return classes[i]; + } + return NULL; +} + +/* + * Storage instance + */ + +static const char *split_next_arg(const char *const **_args) +{ + const char *const *args = *_args; + const char *str = args[0]; + + /* join arguments for escaped ";" separator */ + + args++; + while (*args != NULL && **args == '\0') { + args++; + if (*args == NULL) { + /* string ends with ";", just ignore it. */ + break; + } + str = t_strconcat(str, ";", *args, NULL); + args++; + } + *_args = args; + return str; +} + +static int +sieve_storage_driver_parse(struct sieve_instance *svinst, const char **data, + const struct sieve_storage **driver_r) +{ + const struct sieve_storage *storage_class = NULL; + const char *p; + + p = strchr(*data, ':'); + if (p == NULL) + return 0; + + /* Lookup storage driver */ + T_BEGIN { + const char *driver; + + driver = t_strdup_until(*data, p); + *data = p+1; + + storage_class = sieve_storage_find_class(svinst, driver); + if (storage_class == NULL) { + e_error(svinst->event, + "Unknown storage driver module `%s'", + driver); + } else if (storage_class->v.alloc == NULL) { + e_error(svinst->event, + "Support not compiled in for storage driver `%s'", + driver); + storage_class = NULL; + } + } T_END; + + *driver_r = storage_class; + return (storage_class == NULL ? -1 : 1); +} + +static int +sieve_storage_data_parse(struct sieve_storage *storage, const char *data, + const char **location_r, const char *const **options_r) +{ + ARRAY_TYPE(const_string) options; + const char *const *tmp; + + if (*data == '\0') { + *options_r = NULL; + *location_r = data; + return 0; + } + + /* <location> */ + tmp = t_strsplit(data, ";"); + *location_r = split_next_arg(&tmp); + + if (options_r != NULL) { + t_array_init(&options, 8); + + /* [<option> *(';' <option>)] */ + while (*tmp != NULL) { + const char *option = split_next_arg(&tmp); + + if (strncasecmp(option, "name=", 5) == 0) { + if (option[5] == '\0') { + e_error(storage->event, + "Failed to parse storage location: " + "Empty name not allowed"); + return -1; + } + + if (storage->script_name == NULL) { + if (!sieve_script_name_is_valid(option+5)) { + e_error(storage->event, + "Failed to parse storage location: " + "Invalid script name `%s'.", + str_sanitize(option+5, 80)); + return -1; + } + storage->script_name = p_strdup(storage->pool, option+5); + } + + } else if (strncasecmp(option, "bindir=", 7) == 0) { + const char *bin_dir = option+7; + + if (bin_dir[0] == '\0') { + e_error(storage->event, + "Failed to parse storage location: " + "Empty bindir not allowed"); + return -1; + } + + if (bin_dir[0] == '~') { + /* home-relative path. change to absolute. */ + const char *home = sieve_environment_get_homedir(storage->svinst); + + if (home != NULL) { + bin_dir = home_expand_tilde(bin_dir, home); + } else if (bin_dir[1] == '/' || bin_dir[1] == '\0') { + e_error(storage->event, + "Failed to parse storage location: " + "bindir is relative to home directory (~/), " + "but home directory cannot be determined"); + return -1; + } + } + + storage->bin_dir = p_strdup(storage->pool, bin_dir); + } else { + array_append(&options, &option, 1); + } + } + + (void)array_append_space(&options); + *options_r = array_idx(&options, 0); + } + + return 0; +} + +struct event * +sieve_storage_event_create(struct sieve_instance *svinst, + const struct sieve_storage *storage_class) +{ + struct event *event; + + event = event_create(svinst->event); + event_add_category(event, &event_category_sieve_storage); + event_add_str(event, "driver", storage_class->driver_name); + event_set_append_log_prefix( + event, t_strdup_printf("%s storage: ", + storage_class->driver_name)); + + return event; +} + +struct sieve_storage * +sieve_storage_alloc(struct sieve_instance *svinst, struct event *event, + const struct sieve_storage *storage_class, const char *data, + enum sieve_storage_flags flags, bool main) +{ + struct sieve_storage *storage; + + i_assert(storage_class->v.alloc != NULL); + storage = storage_class->v.alloc(); + + storage->storage_class = storage_class; + storage->refcount = 1; + storage->svinst = svinst; + storage->flags = flags; + storage->data = p_strdup_empty(storage->pool, data); + storage->main_storage = main; + + if (event != NULL) { + storage->event = event; + event_ref(storage->event); + } else { + storage->event = + sieve_storage_event_create(svinst, storage_class); + } + + return storage; +} + +static struct sieve_storage * +sieve_storage_init(struct sieve_instance *svinst, + const struct sieve_storage *storage_class, const char *data, + enum sieve_storage_flags flags, bool main, + enum sieve_error *error_r) +{ + struct sieve_storage *storage; + const char *const *options; + const char *location; + struct event *event; + enum sieve_error error; + + if (error_r != NULL) + *error_r = SIEVE_ERROR_NONE; + else + error_r = &error; + + i_assert(storage_class->v.init != NULL); + + event = sieve_storage_event_create(svinst, storage_class); + + if ((flags & SIEVE_STORAGE_FLAG_SYNCHRONIZING) != 0 && + !storage_class->allows_synchronization) { + e_debug(event, "Storage does not support synchronization"); + *error_r = SIEVE_ERROR_NOT_POSSIBLE; + event_unref(&event); + return NULL; + } + + if ((flags & SIEVE_STORAGE_FLAG_READWRITE) != 0 && + storage_class->v.save_init == NULL) { + e_error(event, "Storage does not support write access"); + *error_r = SIEVE_ERROR_TEMP_FAILURE; + event_unref(&event); + return NULL; + } + + T_BEGIN { + storage = sieve_storage_alloc(svinst, event, storage_class, + data, flags, main); + + if (sieve_storage_data_parse(storage, data, + &location, &options) < 0) { + *error_r = SIEVE_ERROR_TEMP_FAILURE; + sieve_storage_unref(&storage); + storage = NULL; + } else { + storage->location = p_strdup(storage->pool, location); + + event_add_str(event, "script_location", + storage->location); + + if (storage_class->v.init(storage, options, + error_r) < 0) { + sieve_storage_unref(&storage); + storage = NULL; + } + } + } T_END; + + event_unref(&event); + return storage; +} + +struct sieve_storage * +sieve_storage_create(struct sieve_instance *svinst, const char *location, + enum sieve_storage_flags flags, enum sieve_error *error_r) +{ + const struct sieve_storage *storage_class; + enum sieve_error error; + const char *data; + int ret; + + /* Dont use this function for creating a synchronizing storage */ + i_assert((flags & SIEVE_STORAGE_FLAG_SYNCHRONIZING) == 0); + + if (error_r != NULL) + *error_r = SIEVE_ERROR_NONE; + else + error_r = &error; + + data = location; + if ((ret = sieve_storage_driver_parse(svinst, &data, + &storage_class)) < 0) { + *error_r = SIEVE_ERROR_TEMP_FAILURE; + return NULL; + } + + if (ret == 0) + storage_class = &sieve_file_storage; + + return sieve_storage_init(svinst, storage_class, data, flags, + FALSE, error_r); +} + +static struct sieve_storage * +sieve_storage_do_create_main(struct sieve_instance *svinst, + struct mail_user *user, + enum sieve_storage_flags flags, + enum sieve_error *error_r) +{ + struct sieve_storage *storage = NULL; + const struct sieve_storage + *sieve_class = NULL, + *sieve_dir_class = NULL; + const char *set_sieve, *set_sieve_dir; + const char *data, *storage_path; + unsigned long long int uint_setting; + size_t size_setting; + int ret; + + /* Sieve storage location */ + + set_sieve = sieve_setting_get(svinst, "sieve"); + + if (set_sieve != NULL) { + if (*set_sieve == '\0') { + /* disabled */ + e_debug(svinst->event, "storage: " + "Personal storage is disabled (sieve=\"\")"); + *error_r = SIEVE_ERROR_NOT_FOUND; + return NULL; + } + + data = set_sieve; + if ((ret = sieve_storage_driver_parse(svinst, &data, + &sieve_class)) < 0) { + *error_r = SIEVE_ERROR_TEMP_FAILURE; + return NULL; + } + + if (ret > 0) { + /* The normal case: explicit driver name */ + storage = sieve_storage_init(svinst, sieve_class, data, + flags, TRUE, error_r); + if (storage == NULL) + return NULL; + } + + /* No driver name */ + } + + if (storage == NULL) { + /* Script storage directory configuration (deprecated) */ + + set_sieve_dir = sieve_setting_get(svinst, "sieve_dir"); + if (set_sieve_dir == NULL) { + set_sieve_dir = sieve_setting_get(svinst, + "sieve_storage"); + } + + if (set_sieve_dir == NULL || *set_sieve_dir == '\0') { + storage_path = ""; + } else { + const char *p; + + /* Parse and check driver */ + storage_path = set_sieve_dir; + if ((ret = sieve_storage_driver_parse( + svinst, &storage_path, &sieve_dir_class)) < 0) { + *error_r = SIEVE_ERROR_TEMP_FAILURE; + return NULL; + } + + if (ret > 0 && sieve_dir_class != &sieve_file_storage) { + e_error(svinst->event, "storage: " + "Cannot use deprecated sieve_dir= setting " + "with `%s' driver for main Sieve storage", + sieve_dir_class->driver_name); + } + + /* Ignore any options */ + p = strchr(storage_path, ';'); + if (p != NULL) + storage_path = t_strdup_until(storage_path, p); + } + + storage = sieve_file_storage_init_legacy(svinst, set_sieve, + storage_path, flags, + error_r); + } + + if (storage == NULL) + return NULL; + + (void)sieve_storage_sync_init(storage, user); + + /* Get quota settings if storage driver provides none */ + + if (storage->max_storage == 0 && + sieve_setting_get_size_value(svinst, "sieve_quota_max_storage", + &size_setting)) { + storage->max_storage = size_setting; + } + + if (storage->max_scripts == 0 && + sieve_setting_get_uint_value(svinst, "sieve_quota_max_scripts", + &uint_setting)) { + storage->max_scripts = uint_setting; + } + + if (storage->max_storage > 0) { + e_debug(storage->event, "quota: " + "Storage limit: %llu bytes", + (unsigned long long int) storage->max_storage); + } + if (storage->max_scripts > 0) { + e_debug(storage->event, "quota: " + "Script count limit: %llu scripts", + (unsigned long long int) storage->max_scripts); + } + return storage; +} + +struct sieve_storage * +sieve_storage_create_main(struct sieve_instance *svinst, struct mail_user *user, + enum sieve_storage_flags flags, + enum sieve_error *error_r) +{ + struct sieve_storage *storage; + const char *set_enabled, *set_default, *set_default_name; + enum sieve_error error; + + if (error_r != NULL) + *error_r = SIEVE_ERROR_NONE; + else + error_r = &error; + + /* Check whether Sieve is disabled for this user */ + if ((set_enabled = sieve_setting_get(svinst, "sieve_enabled")) != NULL && + strcasecmp(set_enabled, "no") == 0) { + e_debug(svinst->event, + "Sieve is disabled for this user"); + *error_r = SIEVE_ERROR_NOT_POSSIBLE; + return NULL; + } + + /* Determine location for default script */ + set_default = sieve_setting_get(svinst, "sieve_default"); + if (set_default == NULL) { + /* For backwards compatibility */ + set_default = sieve_setting_get(svinst, "sieve_global_path"); + } + + /* Attempt to locate user's main storage */ + storage = sieve_storage_do_create_main(svinst, user, flags, error_r); + if (storage != NULL) { + /* Success; record default script location for later use */ + storage->default_location = + p_strdup_empty(storage->pool, set_default); + + set_default_name = + sieve_setting_get(svinst, "sieve_default_name"); + if (set_default_name != NULL && *set_default_name != '\0' && + !sieve_script_name_is_valid(set_default_name)) { + e_error(storage->event, + "Invalid script name `%s' for `sieve_default_name' setting.", + str_sanitize(set_default_name, 80)); + set_default_name = NULL; + } + storage->default_name = + p_strdup_empty(storage->pool, set_default_name); + + if (storage->default_location != NULL && + storage->default_name != NULL) { + e_debug(storage->event, + "Default script at `%s' is visible by name `%s'", + storage->default_location, storage->default_name); + } + } else if (*error_r != SIEVE_ERROR_TEMP_FAILURE && + (flags & SIEVE_STORAGE_FLAG_SYNCHRONIZING) == 0 && + (flags & SIEVE_STORAGE_FLAG_READWRITE) == 0) { + + /* Failed; try using default script location + (not for temporary failures, read/write access, or dsync) */ + if (set_default == NULL) { + e_debug(svinst->event, "storage: " + "No default script location configured"); + } else { + e_debug(svinst->event, "storage: " + "Trying default script location `%s'", + set_default); + + storage = sieve_storage_create(svinst, set_default, 0, + error_r); + if (storage == NULL) { + switch (*error_r) { + case SIEVE_ERROR_NOT_FOUND: + e_debug(svinst->event, "storage: " + "Default script location `%s' not found", + set_default); + break; + case SIEVE_ERROR_TEMP_FAILURE: + e_error(svinst->event, "storage: " + "Failed to access default script location `%s' " + "(temporary failure)", + set_default); + break; + default: + e_error(svinst->event, "storage: " + "Failed to access default script location `%s'", + set_default); + break; + } + } + } + if (storage != NULL) + storage->is_default = TRUE; + } + return storage; +} + +void sieve_storage_ref(struct sieve_storage *storage) +{ + storage->refcount++; +} + +void sieve_storage_unref(struct sieve_storage **_storage) +{ + struct sieve_storage *storage = *_storage; + + i_assert(storage->refcount > 0); + + if (--storage->refcount != 0) + return; + + if (storage->default_for != NULL) { + i_assert(storage->is_default); + sieve_storage_unref(&storage->default_for); + } + + sieve_storage_sync_deinit(storage); + + if (storage->v.destroy != NULL) + storage->v.destroy(storage); + + i_free(storage->error); + event_unref(&storage->event); + pool_unref(&storage->pool); + *_storage = NULL; +} + +int sieve_storage_setup_bindir(struct sieve_storage *storage, mode_t mode) +{ + const char *bin_dir = storage->bin_dir; + struct stat st; + + if (bin_dir == NULL) + return -1; + + if (stat(bin_dir, &st) == 0) + return 0; + + if (errno == EACCES) { + e_error(storage->event, + "Failed to setup directory for binaries: " + "%s", eacces_error_get("stat", bin_dir)); + return -1; + } else if (errno != ENOENT) { + e_error(storage->event, + "Failed to setup directory for binaries: " + "stat(%s) failed: %m", + bin_dir); + return -1; + } + + if (mkdir_parents(bin_dir, mode) == 0) { + e_debug(storage->event, + "Created directory for binaries: %s", bin_dir); + return 1; + } + + switch (errno) { + case EEXIST: + return 0; + case ENOENT: + e_error(storage->event, + "Directory for binaries was deleted while it was being created"); + break; + case EACCES: + e_error(storage->event, + "%s", eacces_error_get_creating("mkdir_parents_chgrp", bin_dir)); + break; + default: + e_error(storage->event, + "mkdir_parents_chgrp(%s) failed: %m", bin_dir); + break; + } + + return -1; +} + +int sieve_storage_is_singular(struct sieve_storage *storage) +{ + if (storage->v.is_singular == NULL) + return 1; + return storage->v.is_singular(storage); +} + +int sieve_storage_get_last_change(struct sieve_storage *storage, + time_t *last_change_r) +{ + i_assert(storage->v.get_last_change != NULL); + return storage->v.get_last_change(storage, last_change_r); +} + +void sieve_storage_set_modified(struct sieve_storage *storage, time_t mtime) +{ + if (storage->v.set_modified == NULL) + return; + + storage->v.set_modified(storage, mtime); +} + +/* + * Script access + */ + +static struct sieve_script * +sieve_storage_get_script_direct(struct sieve_storage *storage, const char *name, + enum sieve_error *error_r) +{ + struct sieve_script *script; + + if (error_r != NULL) + *error_r = SIEVE_ERROR_NONE; + sieve_storage_clear_error(storage); + + /* Validate script name */ + if (name != NULL && !sieve_script_name_is_valid(name)) { + sieve_storage_set_error(storage, + SIEVE_ERROR_BAD_PARAMS, + "Invalid script name `%s'.", + str_sanitize(name, 80)); + if (error_r != NULL) + *error_r = storage->error_code; + return NULL; + } + + i_assert(storage->v.get_script != NULL); + script = storage->v.get_script(storage, name); + return script; +} + +struct sieve_script * +sieve_storage_get_script(struct sieve_storage *storage, const char *name, + enum sieve_error *error_r) +{ + struct sieve_instance *svinst = storage->svinst; + struct sieve_script *script; + + script = sieve_storage_get_script_direct(storage, name, error_r); + if (script == NULL) { + /* Error */ + if (storage->error_code == SIEVE_ERROR_NOT_FOUND && + (storage->flags & SIEVE_STORAGE_FLAG_SYNCHRONIZING) == 0 && + storage->default_name != NULL && + storage->default_location != NULL && + strcmp(storage->default_name, name) == 0) { + /* Not found; if this name maps to the default script, + try to access that instead */ + i_assert(*storage->default_location != '\0'); + + e_debug(storage->event, + "Trying default script instead"); + + script = sieve_script_create( + svinst, storage->default_location, NULL, + error_r); + if (script != NULL) { + script->storage->is_default = TRUE; + script->storage->default_for = storage; + sieve_storage_ref(storage); + } + + } else if (error_r != NULL) { + *error_r = storage->error_code; + } + } + return script; +} + +struct sieve_script * +sieve_storage_open_script(struct sieve_storage *storage, const char *name, + enum sieve_error *error_r) +{ + struct sieve_instance *svinst = storage->svinst; + struct sieve_script *script; + + script = sieve_storage_get_script(storage, name, error_r); + if (script == NULL) + return NULL; + + if (sieve_script_open(script, error_r) >= 0) + return script; + + /* Error */ + sieve_script_unref(&script); + script = NULL; + + if (storage->error_code == SIEVE_ERROR_NOT_FOUND && + (storage->flags & SIEVE_STORAGE_FLAG_SYNCHRONIZING) == 0 && + storage->default_name != NULL && + storage->default_location != NULL && + strcmp(storage->default_name, name) == 0) { + /* Not found; if this name maps to the default script, + try to open that instead */ + i_assert(*storage->default_location != '\0'); + + e_debug(storage->event, "Trying default script instead"); + + script = sieve_script_create_open( + svinst, storage->default_location, NULL, error_r); + if (script != NULL) { + script->storage->is_default = TRUE; + script->storage->default_for = storage; + sieve_storage_ref(storage); + } + } + return script; +} + +static int +sieve_storage_check_script_direct(struct sieve_storage *storage, + const char *name, enum sieve_error *error_r) + ATTR_NULL(3) +{ + struct sieve_script *script; + enum sieve_error error; + int ret; + + if (error_r == NULL) + error_r = &error; + + script = sieve_storage_get_script_direct(storage, name, error_r); + if (script == NULL) + return (*error_r == SIEVE_ERROR_NOT_FOUND ? 0 : -1); + + ret = sieve_script_open(script, error_r); + sieve_script_unref(&script); + return (ret >= 0 ? 1 : (*error_r == SIEVE_ERROR_NOT_FOUND ? 0 : -1)); +} + +int sieve_storage_check_script(struct sieve_storage *storage, const char *name, + enum sieve_error *error_r) +{ + struct sieve_script *script; + enum sieve_error error; + + if (error_r == NULL) + error_r = &error; + + script = sieve_storage_open_script(storage, name, error_r); + if (script == NULL) + return (*error_r == SIEVE_ERROR_NOT_FOUND ? 0 : -1); + + sieve_script_unref(&script); + return 1; +} + +/* + * Script sequence + */ + +struct sieve_script_sequence * +sieve_storage_get_script_sequence(struct sieve_storage *storage, + enum sieve_error *error_r) +{ + enum sieve_error error; + + if (error_r != NULL) + *error_r = SIEVE_ERROR_NONE; + else + error_r = &error; + + i_assert(storage->v.get_script_sequence != NULL); + return storage->v.get_script_sequence(storage, error_r); +} + +/* + * Active script + */ + +static int +sieve_storage_active_script_do_get_name(struct sieve_storage *storage, + const char **name_r, bool *default_r) + ATTR_NULL(3) +{ + struct sieve_instance *svinst = storage->svinst; + enum sieve_error error; + int ret; + + if (default_r != NULL) + *default_r = FALSE; + + i_assert(storage->v.active_script_get_name != NULL); + ret = storage->v.active_script_get_name(storage, name_r); + + if (ret != 0 || + (storage->flags & SIEVE_STORAGE_FLAG_SYNCHRONIZING) != 0 || + storage->default_location == NULL || + storage->default_name == NULL) { + return ret; + } + + *name_r = storage->default_name; + + ret = sieve_script_check(svinst, storage->default_location, + NULL, &error); + if (ret <= 0) + return ret; + + if (default_r != NULL) + *default_r = TRUE; + return 1; +} + +int sieve_storage_active_script_get_name(struct sieve_storage *storage, + const char **name_r) +{ + return sieve_storage_active_script_do_get_name(storage, name_r, NULL); +} + +int sieve_storage_active_script_is_default(struct sieve_storage *storage) +{ + const char *name; + bool is_default = FALSE; + int ret; + + ret = sieve_storage_active_script_do_get_name(storage, &name, + &is_default); + return (ret < 0 ? -1 : (is_default ? 1 : 0)); +} + +struct sieve_script * +sieve_storage_active_script_open(struct sieve_storage *storage, + enum sieve_error *error_r) +{ + struct sieve_instance *svinst = storage->svinst; + struct sieve_script *script; + + i_assert(storage->v.active_script_open != NULL); + script = storage->v.active_script_open(storage); + + if (script != NULL || + (storage->flags & SIEVE_STORAGE_FLAG_SYNCHRONIZING) != 0 || + storage->default_location == NULL) { + if (error_r != NULL) + *error_r = storage->error_code; + return script; + } + + /* Try default script location */ + script = sieve_script_create_open(svinst, storage->default_location, + NULL, error_r); + if (script != NULL) { + script->storage->is_default = TRUE; + script->storage->default_for = storage; + sieve_storage_ref(storage); + } + return script; +} + +int sieve_storage_deactivate(struct sieve_storage *storage, time_t mtime) +{ + int ret; + + i_assert((storage->flags & SIEVE_STORAGE_FLAG_READWRITE) != 0); + + i_assert(storage->v.deactivate != NULL); + ret = storage->v.deactivate(storage); + + if (ret >= 0) { + struct event_passthrough *e = + event_create_passthrough(storage->event)-> + set_name("sieve_storage_deactivated"); + e_debug(e->event(), "Storage activated"); + + sieve_storage_set_modified(storage, mtime); + (void)sieve_storage_sync_deactivate(storage); + } else { + struct event_passthrough *e = + event_create_passthrough(storage->event)-> + add_str("error", storage->error)-> + set_name("sieve_storage_deactivated"); + e_debug(e->event(), "Failed to deactivate storage: %s", + storage->error); + } + + return ret; +} + +int sieve_storage_active_script_get_last_change(struct sieve_storage *storage, + time_t *last_change_r) +{ + i_assert(storage->v.active_script_get_last_change != NULL); + + return storage->v.active_script_get_last_change(storage, last_change_r); +} + +/* + * Listing scripts + */ + +struct sieve_storage_list_context * +sieve_storage_list_init(struct sieve_storage *storage) +{ + struct sieve_storage_list_context *lctx; + + i_assert(storage->v.list_init != NULL); + lctx = storage->v.list_init(storage); + + if (lctx != NULL) + lctx->storage = storage; + + return lctx; +} + +const char * +sieve_storage_list_next(struct sieve_storage_list_context *lctx, bool *active_r) +{ + struct sieve_storage *storage = lctx->storage; + struct sieve_instance *svinst = storage->svinst; + const char *scriptname; + bool have_default, script_active = FALSE; + + have_default = (storage->default_name != NULL && + storage->default_location != NULL && + (storage->flags & + SIEVE_STORAGE_FLAG_SYNCHRONIZING) == 0); + + i_assert(storage->v.list_next != NULL); + scriptname = storage->v.list_next(lctx, &script_active); + + i_assert(!script_active || !lctx->seen_active); + if (script_active) + lctx->seen_active = TRUE; + + if (scriptname != NULL) { + /* Remember when we see that the storage has its own script for + default */ + if (have_default && + strcmp(scriptname, storage->default_name) == 0) + lctx->seen_default = TRUE; + + } else if (have_default && !lctx->seen_default && + sieve_script_check(svinst, storage->default_location, + NULL, NULL) > 0) { + + /* Return default script at the end if it was not listed + thus far (storage backend has no script under default + name) */ + scriptname = storage->default_name; + lctx->seen_default = TRUE; + + /* Mark default as active if no normal script is active */ + if (!lctx->seen_active) { + script_active = TRUE; + lctx->seen_active = TRUE; + } + } + + if (active_r != NULL) + *active_r = script_active; + return scriptname; +} + +int sieve_storage_list_deinit(struct sieve_storage_list_context **_lctx) +{ + struct sieve_storage_list_context *lctx = *_lctx; + struct sieve_storage *storage = lctx->storage; + int ret; + + i_assert(storage->v.list_deinit != NULL); + ret = storage->v.list_deinit(lctx); + + *_lctx = NULL; + return ret; +} + +/* + * Saving scripts + */ + +static struct event * +sieve_storage_save_create_event(struct sieve_storage *storage, + const char *scriptname) ATTR_NULL(2) +{ + struct event *event; + + event = event_create(storage->event); + event_add_str(event, "script_name", scriptname); + if (scriptname == NULL) { + event_set_append_log_prefix(event, "save: "); + } else { + event_set_append_log_prefix( + event, t_strdup_printf("script `%s': save: ", + scriptname)); + } + + return event; +} + +static void sieve_storage_save_cleanup(struct sieve_storage_save_context *sctx) +{ + if (sctx->scriptobject != NULL) + sieve_script_unref(&sctx->scriptobject); +} + +static void sieve_storage_save_deinit(struct sieve_storage_save_context **_sctx) +{ + struct sieve_storage_save_context *sctx = *_sctx; + + *_sctx = NULL; + if (sctx == NULL) + return; + + sieve_storage_save_cleanup(sctx); + event_unref(&sctx->event); + pool_unref(&sctx->pool); +} + +struct sieve_storage_save_context * +sieve_storage_save_init(struct sieve_storage *storage, const char *scriptname, + struct istream *input) +{ + struct sieve_storage_save_context *sctx; + + if (scriptname != NULL) { + /* Validate script name */ + if (!sieve_script_name_is_valid(scriptname)) { + sieve_storage_set_error(storage, + SIEVE_ERROR_BAD_PARAMS, + "Invalid Sieve script name `%s'.", + str_sanitize(scriptname, 80)); + return NULL; + } + } + + i_assert((storage->flags & SIEVE_STORAGE_FLAG_READWRITE) != 0); + + i_assert(storage->v.save_alloc != NULL); + sctx = storage->v.save_alloc(storage); + sctx->storage = storage; + + sctx->event = sieve_storage_save_create_event(storage, scriptname); + + struct event_passthrough *e = + event_create_passthrough(sctx->event)-> + set_name("sieve_storage_save_started"); + e_debug(e->event(), "Started saving script"); + + i_assert(storage->v.save_init != NULL); + if ((storage->v.save_init(sctx, scriptname, input)) < 0) { + struct event_passthrough *e = + event_create_passthrough(sctx->event)-> + add_str("error", storage->error)-> + set_name("sieve_storage_save_finished"); + e_debug(e->event(), "Failed to save script: %s", + storage->error); + + sieve_storage_save_deinit(&sctx); + return NULL; + } + + sctx->mtime = (time_t)-1; + + i_assert(sctx->input != NULL); + + return sctx; +} + +int sieve_storage_save_continue(struct sieve_storage_save_context *sctx) +{ + struct sieve_storage *storage = sctx->storage; + int ret; + + i_assert(storage->v.save_continue != NULL); + ret = storage->v.save_continue(sctx); + if (ret < 0) + sctx->failed = TRUE; + return ret; +} + +int sieve_storage_save_finish(struct sieve_storage_save_context *sctx) +{ + struct sieve_storage *storage = sctx->storage; + int ret; + + i_assert(!sctx->finished); + sctx->finished = TRUE; + + i_assert(storage->v.save_finish != NULL); + ret = storage->v.save_finish(sctx); + if (ret < 0) { + struct event_passthrough *e = + event_create_passthrough(sctx->event)-> + add_str("error", storage->error)-> + set_name("sieve_storage_save_finished"); + e_debug(e->event(), "Failed to upload script: %s", + storage->error); + + sctx->failed = TRUE; + } + return ret; +} + +void sieve_storage_save_set_mtime(struct sieve_storage_save_context *sctx, + time_t mtime) +{ + sctx->mtime = mtime; +} + +struct sieve_script * +sieve_storage_save_get_tempscript(struct sieve_storage_save_context *sctx) +{ + struct sieve_storage *storage = sctx->storage; + + if (sctx->failed) + return NULL; + + if (sctx->scriptobject != NULL) + return sctx->scriptobject; + + i_assert(storage->v.save_get_tempscript != NULL); + sctx->scriptobject = storage->v.save_get_tempscript(sctx); + + i_assert(sctx->scriptobject != NULL || + storage->error_code != SIEVE_ERROR_NONE); + return sctx->scriptobject; +} + +bool sieve_storage_save_will_activate(struct sieve_storage_save_context *sctx) +{ + if (sctx->scriptname == NULL) + return FALSE; + + if (sctx->active_scriptname == NULL) { + const char *scriptname; + + if (sieve_storage_active_script_get_name(sctx->storage, + &scriptname) > 0) { + sctx->active_scriptname = + p_strdup(sctx->pool, scriptname); + } + } + + /* Is the requested script active? */ + return (sctx->active_scriptname != NULL && + strcmp(sctx->scriptname, sctx->active_scriptname) == 0); +} + +int sieve_storage_save_commit(struct sieve_storage_save_context **_sctx) +{ + struct sieve_storage_save_context *sctx = *_sctx; + struct sieve_storage *storage; + const char *scriptname; + bool default_activate = FALSE; + int ret; + + *_sctx = NULL; + if (sctx == NULL) + return 0; + + storage = sctx->storage; + scriptname = sctx->scriptname; + + i_assert(!sctx->failed); + i_assert(sctx->finished); + i_assert(sctx->scriptname != NULL); + + /* Check whether we're replacing the default active script */ + if (storage->default_name != NULL && + storage->default_location != NULL && + (storage->flags & SIEVE_STORAGE_FLAG_SYNCHRONIZING) == 0 && + strcmp(sctx->scriptname, storage->default_name) == 0 && + sieve_storage_save_will_activate(sctx) && + sieve_storage_check_script_direct(storage, storage->default_name, + NULL) <= 0) + default_activate = TRUE; + + sieve_storage_save_cleanup(sctx); + + i_assert(storage->v.save_commit != NULL); + ret = storage->v.save_commit(sctx); + + /* Implicitly activate it when we're replacing the default + active script */ + if (ret >= 0 && default_activate) { + struct sieve_script *script; + enum sieve_error error; + + script = sieve_storage_open_script(storage, scriptname, &error); + if (script == NULL) { + /* Somehow not actually saved */ + ret = (error == SIEVE_ERROR_NOT_FOUND ? 0 : -1); + } else if (sieve_script_activate(script, (time_t)-1) < 0) { + /* Failed to activate; roll back */ + ret = -1; + (void)sieve_script_delete(script, TRUE); + } + if (script != NULL) + sieve_script_unref(&script); + + if (ret < 0) { + e_error(sctx->event, + "Failed to implicitly activate script `%s' " + "while replacing the default active script", + scriptname); + } + } + + if (ret >= 0) { + struct event_passthrough *e = + event_create_passthrough(sctx->event)-> + set_name("sieve_storage_save_finished"); + e_debug(e->event(), "Finished saving script"); + + /* set INBOX mailbox attribute */ + (void)sieve_storage_sync_script_save(storage, scriptname); + } else { + struct event_passthrough *e = + event_create_passthrough(sctx->event)-> + add_str("error", storage->error)-> + set_name("sieve_storage_save_finished"); + + e_debug(e->event(), "Failed to save script: %s", + storage->error); + } + + sieve_storage_save_deinit(&sctx); + return ret; +} + +void sieve_storage_save_cancel(struct sieve_storage_save_context **_sctx) +{ + struct sieve_storage_save_context *sctx = *_sctx; + struct sieve_storage *storage; + + *_sctx = NULL; + if (sctx == NULL) + return; + + storage = sctx->storage; + + sctx->failed = TRUE; + + sieve_storage_save_cleanup(sctx); + + if (!sctx->finished) + (void)sieve_storage_save_finish(sctx); + + struct event_passthrough *e = + event_create_passthrough(sctx->event)-> + add_str("error", "Canceled")-> + set_name("sieve_storage_save_finished"); + e_debug(e->event(), "Canceled saving script"); + + i_assert(storage->v.save_cancel != NULL); + storage->v.save_cancel(sctx); + + sieve_storage_save_deinit(&sctx); +} + +int sieve_storage_save_as_active(struct sieve_storage *storage, + struct istream *input, time_t mtime) +{ + struct event *event; + int ret; + + event = event_create(storage->event); + event_set_append_log_prefix(event, "active script: save: "); + + struct event_passthrough *e = + event_create_passthrough(event)-> + set_name("sieve_storage_save_started"); + e_debug(e->event(), "Started saving active script"); + + i_assert(storage->v.save_as_active != NULL); + ret = storage->v.save_as_active(storage, input, mtime); + + if (ret >= 0) { + struct event_passthrough *e = + event_create_passthrough(event)-> + set_name("sieve_storage_save_finished"); + e_debug(e->event(), "Finished saving active script"); + } else { + struct event_passthrough *e = + event_create_passthrough(event)-> + add_str("error", storage->error)-> + set_name("sieve_storage_save_finished"); + e_debug(e->event(), "Failed to save active script: %s", + storage->error); + } + + event_unref(&event); + return ret; +} + +int sieve_storage_save_as(struct sieve_storage *storage, struct istream *input, + const char *name) +{ + struct event *event; + int ret; + + event = sieve_storage_save_create_event(storage, name); + + struct event_passthrough *e = + event_create_passthrough(event)-> + set_name("sieve_storage_save_started"); + e_debug(e->event(), "Started saving script"); + + i_assert(storage->v.save_as != NULL); + ret = storage->v.save_as(storage, input, name); + + if (ret >= 0) { + struct event_passthrough *e = + event_create_passthrough(event)-> + set_name("sieve_storage_save_finished"); + e_debug(e->event(), "Finished saving sieve script"); + } else { + struct event_passthrough *e = + event_create_passthrough(event)-> + add_str("error", storage->error)-> + set_name("sieve_storage_save_finished"); + e_debug(e->event(), "Failed to save script: %s", + storage->error); + } + + event_unref(&event); + return ret; +} + +/* + * Checking quota + */ + +bool sieve_storage_quota_validsize(struct sieve_storage *storage, size_t size, + uint64_t *limit_r) +{ + uint64_t max_size; + + max_size = sieve_max_script_size(storage->svinst); + if (max_size > 0 && size > max_size) { + *limit_r = max_size; + return FALSE; + } + + return TRUE; +} + +uint64_t sieve_storage_quota_max_script_size(struct sieve_storage *storage) +{ + return sieve_max_script_size(storage->svinst); +} + +int sieve_storage_quota_havespace(struct sieve_storage *storage, + const char *scriptname, size_t size, + enum sieve_storage_quota *quota_r, + uint64_t *limit_r) +{ + *quota_r = SIEVE_STORAGE_QUOTA_NONE; + *limit_r = 0; + + /* Check the script size */ + if (!sieve_storage_quota_validsize(storage, size, limit_r)) { + *quota_r = SIEVE_STORAGE_QUOTA_MAXSIZE; + return 0; + } + + /* Do we need to scan the storage (quota enabled) ? */ + if (storage->max_scripts == 0 && storage->max_storage == 0) + return 1; + + if (storage->v.quota_havespace == NULL) + return 1; + + return storage->v.quota_havespace(storage, scriptname, size, + quota_r, limit_r); +} + +/* + * Properties + */ + +const char *sieve_storage_location(const struct sieve_storage *storage) +{ + return storage->location; +} + +bool sieve_storage_is_default(const struct sieve_storage *storage) +{ + return storage->is_default; +} + +/* + * Error handling + */ + +void sieve_storage_clear_error(struct sieve_storage *storage) +{ + i_free(storage->error); + storage->error_code = SIEVE_ERROR_NONE; + storage->error = NULL; +} + +void sieve_storage_set_error(struct sieve_storage *storage, + enum sieve_error error, const char *fmt, ...) +{ + va_list va; + + sieve_storage_clear_error(storage); + + if (fmt != NULL) { + va_start(va, fmt); + storage->error = i_strdup_vprintf(fmt, va); + va_end(va); + } + + storage->error_code = error; +} + +void sieve_storage_copy_error(struct sieve_storage *storage, + const struct sieve_storage *source) +{ + sieve_storage_clear_error(storage); + storage->error = i_strdup(source->error); + storage->error_code = source->error_code; +} + +void sieve_storage_set_internal_error(struct sieve_storage *storage) +{ + struct tm *tm; + char str[256]; + + sieve_storage_clear_error(storage); + + /* critical errors may contain sensitive data, so let user + see only "Internal error" with a timestamp to make it + easier to look from log files the actual error message. */ + tm = localtime(&ioloop_time); + + storage->error = + (strftime(str, sizeof(str), CRITICAL_MSG_STAMP, tm) > 0 ? + i_strdup(str) : i_strdup(CRITICAL_MSG)); + + storage->error_code = SIEVE_ERROR_TEMP_FAILURE; +} + +void sieve_storage_set_critical(struct sieve_storage *storage, + const char *fmt, ...) +{ + va_list va; + + if (fmt != NULL) { + if ((storage->flags & SIEVE_STORAGE_FLAG_SYNCHRONIZING) == 0) { + va_start(va, fmt); + e_error(storage->svinst->event, "%s storage: %s", + storage->driver_name, + t_strdup_vprintf(fmt, va)); + va_end(va); + + sieve_storage_set_internal_error(storage); + + } else { + sieve_storage_clear_error(storage); + + /* no user is involved while synchronizing, so do it the + normal way */ + va_start(va, fmt); + storage->error = i_strdup_vprintf(fmt, va); + va_end(va); + + storage->error_code = SIEVE_ERROR_TEMP_FAILURE; + } + } +} + +const char * +sieve_storage_get_last_error(struct sieve_storage *storage, + enum sieve_error *error_r) +{ + /* We get here only in error situations, so we have to return some + error. If storage->error is NULL, it means we forgot to set it at + some point.. + */ + + if (error_r != NULL) + *error_r = storage->error_code; + + return storage->error != NULL ? storage->error : "Unknown error"; +} |