diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 09:51:24 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 09:51:24 +0000 |
commit | f7548d6d28c313cf80e6f3ef89aed16a19815df1 (patch) | |
tree | a3f6f2a3f247293bee59ecd28e8cd8ceb6ca064a /src/lib-storage/mail-storage.c | |
parent | Initial commit. (diff) | |
download | dovecot-f7548d6d28c313cf80e6f3ef89aed16a19815df1.tar.xz dovecot-f7548d6d28c313cf80e6f3ef89aed16a19815df1.zip |
Adding upstream version 1:2.3.19.1+dfsg1.upstream/1%2.3.19.1+dfsg1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/lib-storage/mail-storage.c')
-rw-r--r-- | src/lib-storage/mail-storage.c | 3288 |
1 files changed, 3288 insertions, 0 deletions
diff --git a/src/lib-storage/mail-storage.c b/src/lib-storage/mail-storage.c new file mode 100644 index 0000000..0a71426 --- /dev/null +++ b/src/lib-storage/mail-storage.c @@ -0,0 +1,3288 @@ +/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "ioloop.h" +#include "array.h" +#include "llist.h" +#include "mail-storage.h" +#include "str.h" +#include "str-sanitize.h" +#include "sha1.h" +#include "unichar.h" +#include "hex-binary.h" +#include "fs-api.h" +#include "iostream-ssl.h" +#include "file-dotlock.h" +#include "file-create-locked.h" +#include "istream.h" +#include "eacces-error.h" +#include "mkdir-parents.h" +#include "time-util.h" +#include "var-expand.h" +#include "dsasl-client.h" +#include "imap-date.h" +#include "settings-parser.h" +#include "mail-index-private.h" +#include "mail-index-alloc-cache.h" +#include "mailbox-tree.h" +#include "mailbox-list-private.h" +#include "mail-storage-private.h" +#include "mail-storage-settings.h" +#include "mail-namespace.h" +#include "mail-search.h" +#include "mail-search-register.h" +#include "mail-search-mime-register.h" +#include "mailbox-search-result-private.h" +#include "mailbox-guid-cache.h" +#include "mail-cache.h" + +#include <ctype.h> + +#define MAILBOX_DELETE_RETRY_SECS 30 +#define MAILBOX_MAX_HIERARCHY_NAME_LENGTH 255 + +extern struct mail_search_register *mail_search_register_imap; +extern struct mail_search_register *mail_search_register_human; + +struct event_category event_category_storage = { + .name = "storage", +}; +struct event_category event_category_mailbox = { + .parent = &event_category_storage, + .name = "mailbox", +}; +struct event_category event_category_mail = { + .parent = &event_category_mailbox, + .name = "mail", +}; + +struct mail_storage_module_register mail_storage_module_register = { 0 }; +struct mail_module_register mail_module_register = { 0 }; + +struct mail_storage_mail_index_module mail_storage_mail_index_module = + MODULE_CONTEXT_INIT(&mail_index_module_register); +ARRAY_TYPE(mail_storage) mail_storage_classes; + +static int mail_storage_init_refcount = 0; + +void mail_storage_init(void) +{ + if (mail_storage_init_refcount++ > 0) + return; + dsasl_clients_init(); + mailbox_attributes_init(); + mailbox_lists_init(); + mail_storage_hooks_init(); + i_array_init(&mail_storage_classes, 8); + mail_storage_register_all(); + mailbox_list_register_all(); +} + +void mail_storage_deinit(void) +{ + i_assert(mail_storage_init_refcount > 0); + if (--mail_storage_init_refcount > 0) + return; + if (mail_search_register_human != NULL) + mail_search_register_deinit(&mail_search_register_human); + if (mail_search_register_imap != NULL) + mail_search_register_deinit(&mail_search_register_imap); + mail_search_mime_register_deinit(); + if (array_is_created(&mail_storage_classes)) + array_free(&mail_storage_classes); + mail_storage_hooks_deinit(); + mailbox_lists_deinit(); + mailbox_attributes_deinit(); + dsasl_clients_deinit(); +} + +void mail_storage_class_register(struct mail_storage *storage_class) +{ + i_assert(mail_storage_find_class(storage_class->name) == NULL); + + /* append it after the list, so the autodetection order is correct */ + array_push_back(&mail_storage_classes, &storage_class); +} + +void mail_storage_class_unregister(struct mail_storage *storage_class) +{ + struct mail_storage *const *classes; + unsigned int i, count; + + classes = array_get(&mail_storage_classes, &count); + for (i = 0; i < count; i++) { + if (classes[i] == storage_class) { + array_delete(&mail_storage_classes, i, 1); + break; + } + } +} + +struct mail_storage *mail_storage_find_class(const char *name) +{ + struct mail_storage *const *classes; + unsigned int i, count; + + i_assert(name != NULL); + + classes = array_get(&mail_storage_classes, &count); + for (i = 0; i < count; i++) { + if (strcasecmp(classes[i]->name, name) == 0) + return classes[i]; + } + return NULL; +} + +static struct mail_storage * +mail_storage_autodetect(const struct mail_namespace *ns, + struct mailbox_list_settings *set) +{ + struct mail_storage *const *classes; + unsigned int i, count; + + classes = array_get(&mail_storage_classes, &count); + for (i = 0; i < count; i++) { + if (classes[i]->v.autodetect != NULL) { + if (classes[i]->v.autodetect(ns, set)) + return classes[i]; + } + } + return NULL; +} + +static void +mail_storage_set_autodetection(const char **data, const char **driver) +{ + const char *p; + + /* check if data is in driver:data format (eg. mbox:~/mail) */ + p = *data; + while (i_isalnum(*p) || *p == '_') p++; + + if (*p == ':' && p != *data) { + /* no autodetection if the storage driver is given. */ + *driver = t_strdup_until(*data, p); + *data = p + 1; + } +} + +static struct mail_storage * +mail_storage_get_class(struct mail_namespace *ns, const char *driver, + struct mailbox_list_settings *list_set, + enum mail_storage_flags flags, const char **error_r) +{ + struct mail_storage *storage_class = NULL; + const char *home; + + if (driver == NULL) { + /* no mail_location, autodetect */ + } else if (strcmp(driver, "auto") == 0) { + /* explicit autodetection with "auto" driver. */ + if (list_set->root_dir != NULL && + *list_set->root_dir == '\0') { + /* handle the same as with driver=NULL */ + list_set->root_dir = NULL; + } + } else { + storage_class = mail_user_get_storage_class(ns->user, driver); + if (storage_class == NULL) { + *error_r = t_strdup_printf( + "Unknown mail storage driver %s", driver); + return NULL; + } + } + + if (list_set->root_dir == NULL || *list_set->root_dir == '\0') { + /* no root directory given. is this allowed? */ + const struct mailbox_list *list; + + list = list_set->layout == NULL ? NULL : + mailbox_list_find_class(list_set->layout); + if (storage_class == NULL && + (flags & MAIL_STORAGE_FLAG_NO_AUTODETECTION) == 0) { + /* autodetection should take care of this */ + } else if (storage_class != NULL && + (storage_class->class_flags & MAIL_STORAGE_CLASS_FLAG_NO_ROOT) != 0) { + /* root not required for this storage */ + } else if (list != NULL && + (list->props & MAILBOX_LIST_PROP_NO_ROOT) != 0) { + /* root not required for this layout */ + } else { + *error_r = "Root mail directory not given"; + return NULL; + } + } + + if (storage_class != NULL) { + storage_class->v.get_list_settings(ns, list_set); + return storage_class; + } + + storage_class = mail_storage_autodetect(ns, list_set); + if (storage_class != NULL) + return storage_class; + + (void)mail_user_get_home(ns->user, &home); + if (home == NULL || *home == '\0') home = "(not set)"; + + if (ns->set->location == NULL || *ns->set->location == '\0') { + *error_r = t_strdup_printf( + "Mail storage autodetection failed with home=%s", home); + } else if (str_begins(ns->set->location, "auto:")) { + *error_r = t_strdup_printf( + "Autodetection failed for %s (home=%s)", + ns->set->location, home); + } else { + *error_r = t_strdup_printf( + "Ambiguous mail location setting, " + "don't know what to do with it: %s " + "(try prefixing it with mbox: or maildir:)", + ns->set->location); + } + return NULL; +} + +static int +mail_storage_verify_root(const char *root_dir, const char *dir_type, + const char **error_r) +{ + struct stat st; + + if (stat(root_dir, &st) == 0) { + /* exists */ + if (S_ISDIR(st.st_mode)) + return 0; + *error_r = t_strdup_printf( + "Root mail directory is a file: %s", root_dir); + return -1; + } else if (errno == EACCES) { + *error_r = mail_error_eacces_msg("stat", root_dir); + return -1; + } else if (errno != ENOENT) { + *error_r = t_strdup_printf("stat(%s) failed: %m", root_dir); + return -1; + } else { + *error_r = t_strdup_printf( + "Root %s directory doesn't exist: %s", dir_type, root_dir); + return -1; + } +} + +static int +mail_storage_create_root(struct mailbox_list *list, + enum mail_storage_flags flags, const char **error_r) +{ + const char *root_dir, *type_name, *error; + enum mailbox_list_path_type type; + + if (list->set.iter_from_index_dir) { + type = MAILBOX_LIST_PATH_TYPE_INDEX; + type_name = "index"; + } else { + type = MAILBOX_LIST_PATH_TYPE_MAILBOX; + type_name = "mail"; + } + if (!mailbox_list_get_root_path(list, type, &root_dir)) { + /* storage doesn't use directories (e.g. shared root) */ + return 0; + } + + if ((flags & MAIL_STORAGE_FLAG_NO_AUTOVERIFY) != 0) { + if (!event_want_debug_log(list->ns->user->event)) + return 0; + + /* we don't need to verify, but since debugging is + enabled, check and log if the root doesn't exist */ + if (mail_storage_verify_root(root_dir, type_name, &error) < 0) { + e_debug(list->ns->user->event, + "Namespace %s: Creating storage despite: %s", + list->ns->prefix, error); + } + return 0; + } + + if ((flags & MAIL_STORAGE_FLAG_NO_AUTOCREATE) == 0) { + /* If the directories don't exist, we'll just autocreate them + later. */ + return 0; + } + return mail_storage_verify_root(root_dir, type_name, error_r); +} + +static bool +mail_storage_match_class(struct mail_storage *storage, + const struct mail_storage *storage_class, + const struct mailbox_list_settings *set) +{ + if (strcmp(storage->name, storage_class->name) != 0) + return FALSE; + + if ((storage->class_flags & MAIL_STORAGE_CLASS_FLAG_UNIQUE_ROOT) != 0 && + strcmp(storage->unique_root_dir, + (set->root_dir != NULL ? set->root_dir : "")) != 0) + return FALSE; + + if (strcmp(storage->name, "shared") == 0) { + /* allow multiple independent shared namespaces */ + return FALSE; + } + return TRUE; +} + +static struct mail_storage * +mail_storage_find(struct mail_user *user, + const struct mail_storage *storage_class, + const struct mailbox_list_settings *set) +{ + struct mail_storage *storage = user->storages; + + for (; storage != NULL; storage = storage->next) { + if (mail_storage_match_class(storage, storage_class, set)) + return storage; + } + return NULL; +} + +int mail_storage_create_full(struct mail_namespace *ns, const char *driver, + const char *data, enum mail_storage_flags flags, + struct mail_storage **storage_r, + const char **error_r) +{ + struct mail_storage *storage_class, *storage = NULL; + struct mailbox_list *list; + struct mailbox_list_settings list_set; + enum mailbox_list_flags list_flags = 0; + const char *p; + + if ((flags & MAIL_STORAGE_FLAG_KEEP_HEADER_MD5) == 0 && + ns->mail_set->pop3_uidl_format != NULL) { + /* if pop3_uidl_format contains %m, we want to keep the + header MD5 sums stored even if we're not running POP3 + right now. */ + p = ns->mail_set->pop3_uidl_format; + while ((p = strchr(p, '%')) != NULL) { + if (p[1] == '%') + p += 2; + else if (var_get_key(++p) == 'm') { + flags |= MAIL_STORAGE_FLAG_KEEP_HEADER_MD5; + break; + } + } + } + + mailbox_list_settings_init_defaults(&list_set); + if (data == NULL) { + /* autodetect */ + } else if (driver != NULL && strcmp(driver, "shared") == 0) { + /* internal shared namespace */ + list_set.root_dir = ns->user->set->base_dir; + } else { + if (driver == NULL) + mail_storage_set_autodetection(&data, &driver); + if (mailbox_list_settings_parse(ns->user, data, &list_set, + error_r) < 0) + return -1; + } + + storage_class = mail_storage_get_class(ns, driver, &list_set, flags, + error_r); + if (storage_class == NULL) + return -1; + i_assert(list_set.layout != NULL); + + if (ns->list == NULL) { + /* first storage for namespace */ + if (mail_storage_is_mailbox_file(storage_class)) + list_flags |= MAILBOX_LIST_FLAG_MAILBOX_FILES; + if ((storage_class->class_flags & MAIL_STORAGE_CLASS_FLAG_NO_ROOT) != 0) + list_flags |= MAILBOX_LIST_FLAG_NO_MAIL_FILES; + if ((storage_class->class_flags & MAIL_STORAGE_CLASS_FLAG_NO_LIST_DELETES) != 0) + list_flags |= MAILBOX_LIST_FLAG_NO_DELETES; + if (mailbox_list_create(list_set.layout, ns, &list_set, + list_flags, &list, error_r) < 0) { + *error_r = t_strdup_printf("Mailbox list driver %s: %s", + list_set.layout, *error_r); + return -1; + } + if ((storage_class->class_flags & MAIL_STORAGE_CLASS_FLAG_NO_ROOT) == 0) { + if (mail_storage_create_root(ns->list, flags, error_r) < 0) + return -1; + } + } + + storage = mail_storage_find(ns->user, storage_class, &list_set); + if (storage != NULL) { + /* using an existing storage */ + storage->refcount++; + mail_namespace_add_storage(ns, storage); + *storage_r = storage; + return 0; + } + + storage = storage_class->v.alloc(); + if (storage->lost_mailbox_prefix == NULL) + storage->lost_mailbox_prefix = MAIL_STORAGE_LOST_MAILBOX_PREFIX; + storage->refcount = 1; + storage->storage_class = storage_class; + storage->user = ns->user; + storage->set = ns->mail_set; + storage->flags = flags; + storage->event = event_create(ns->user->event); + if (storage_class->event_category != NULL) + event_add_category(storage->event, storage_class->event_category); + p_array_init(&storage->module_contexts, storage->pool, 5); + + if (storage->v.create != NULL && + storage->v.create(storage, ns, error_r) < 0) { + *error_r = t_strdup_printf("%s: %s", storage->name, *error_r); + event_unref(&storage->event); + pool_unref(&storage->pool); + return -1; + } + + /* If storage supports list index rebuild, + provide default mailboxes_fs unless storage + wants to use its own. */ + if (storage->v.list_index_rebuild != NULL && + storage->mailboxes_fs == NULL) { + struct fs_settings fs_set; + struct ssl_iostream_settings ssl_set; + const char *error; + i_zero(&fs_set); + + mail_user_init_fs_settings(storage->user, &fs_set, &ssl_set); + if (fs_init("posix", "", &fs_set, &storage->mailboxes_fs, + &error) < 0) { + *error_r = t_strdup_printf("fs_init(posix) failed: %s", error); + storage->v.destroy(storage); + return -1; + } + } + + T_BEGIN { + hook_mail_storage_created(storage); + } T_END; + + i_assert(storage->unique_root_dir != NULL || + (storage->class_flags & MAIL_STORAGE_CLASS_FLAG_UNIQUE_ROOT) == 0); + DLLIST_PREPEND(&ns->user->storages, storage); + mail_namespace_add_storage(ns, storage); + *storage_r = storage; + return 0; +} + +int mail_storage_create(struct mail_namespace *ns, const char *driver, + enum mail_storage_flags flags, const char **error_r) +{ + struct mail_storage *storage; + + return mail_storage_create_full(ns, driver, ns->set->location, + flags, &storage, error_r); +} + +void mail_storage_unref(struct mail_storage **_storage) +{ + struct mail_storage *storage = *_storage; + + i_assert(storage->refcount > 0); + + /* set *_storage=NULL only after calling destroy() callback. + for example mdbox wants to access ns->storage */ + if (--storage->refcount > 0) { + *_storage = NULL; + return; + } + + if (storage->mailboxes != NULL) { + i_panic("Trying to deinit storage without freeing mailbox %s", + storage->mailboxes->vname); + } + if (storage->obj_refcount != 0) + i_panic("Trying to deinit storage before freeing its objects"); + + DLLIST_REMOVE(&storage->user->storages, storage); + + storage->v.destroy(storage); + i_free(storage->last_internal_error); + i_free(storage->error_string); + if (array_is_created(&storage->error_stack)) { + i_assert(array_count(&storage->error_stack) == 0); + array_free(&storage->error_stack); + } + fs_unref(&storage->mailboxes_fs); + event_unref(&storage->event); + + *_storage = NULL; + pool_unref(&storage->pool); + + mail_index_alloc_cache_destroy_unrefed(); +} + +void mail_storage_obj_ref(struct mail_storage *storage) +{ + i_assert(storage->refcount > 0); + + if (storage->obj_refcount++ == 0) + mail_user_ref(storage->user); +} + +void mail_storage_obj_unref(struct mail_storage *storage) +{ + i_assert(storage->refcount > 0); + i_assert(storage->obj_refcount > 0); + + if (--storage->obj_refcount == 0) { + struct mail_user *user = storage->user; + mail_user_unref(&user); + } +} + +void mail_storage_clear_error(struct mail_storage *storage) +{ + i_free_and_null(storage->error_string); + + i_free(storage->last_internal_error); + storage->last_error_is_internal = FALSE; + storage->error = MAIL_ERROR_NONE; +} + +void mail_storage_set_error(struct mail_storage *storage, + enum mail_error error, const char *string) +{ + if (storage->error_string != string) { + i_free(storage->error_string); + storage->error_string = i_strdup(string); + } + storage->last_error_is_internal = FALSE; + storage->error = error; +} + +void mail_storage_set_internal_error(struct mail_storage *storage) +{ + const char *str; + + str = t_strflocaltime(MAIL_ERRSTR_CRITICAL_MSG_STAMP, ioloop_time); + + i_free(storage->error_string); + storage->error_string = i_strdup(str); + storage->error = MAIL_ERROR_TEMP; + + /* this function doesn't set last_internal_error, so + last_error_is_internal can't be TRUE. */ + storage->last_error_is_internal = FALSE; + i_free(storage->last_internal_error); +} + +void mail_storage_set_critical(struct mail_storage *storage, + const char *fmt, ...) +{ + char *old_error = storage->error_string; + char *old_internal_error = storage->last_internal_error; + va_list va; + + storage->error_string = NULL; + storage->last_internal_error = NULL; + /* 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. */ + mail_storage_set_internal_error(storage); + + va_start(va, fmt); + storage->last_internal_error = i_strdup_vprintf(fmt, va); + va_end(va); + storage->last_error_is_internal = TRUE; + e_error(storage->event, "%s", storage->last_internal_error); + + /* free the old_error and old_internal_error only after the new error + is generated, because they may be one of the parameters. */ + i_free(old_error); + i_free(old_internal_error); +} + +void mailbox_set_critical(struct mailbox *box, const char *fmt, ...) +{ + va_list va; + + va_start(va, fmt); + T_BEGIN { + mail_storage_set_critical(box->storage, "Mailbox %s: %s", + box->vname, t_strdup_vprintf(fmt, va)); + } T_END; + va_end(va); +} + +void mail_set_critical(struct mail *mail, const char *fmt, ...) +{ + va_list va; + + va_start(va, fmt); + T_BEGIN { + if (mail->saving) { + mailbox_set_critical(mail->box, "Saving mail: %s", + t_strdup_vprintf(fmt, va)); + } else { + mailbox_set_critical(mail->box, "UID=%u: %s", + mail->uid, t_strdup_vprintf(fmt, va)); + } + } T_END; + va_end(va); +} + +const char *mail_storage_get_last_internal_error(struct mail_storage *storage, + enum mail_error *error_r) +{ + if (error_r != NULL) + *error_r = storage->error; + if (storage->last_error_is_internal) { + i_assert(storage->last_internal_error != NULL); + return storage->last_internal_error; + } + return mail_storage_get_last_error(storage, error_r); +} + +const char *mailbox_get_last_internal_error(struct mailbox *box, + enum mail_error *error_r) +{ + return mail_storage_get_last_internal_error(mailbox_get_storage(box), + error_r); +} + +void mail_storage_copy_error(struct mail_storage *dest, + struct mail_storage *src) +{ + const char *str; + enum mail_error error; + + if (src == dest) + return; + + str = mail_storage_get_last_error(src, &error); + mail_storage_set_error(dest, error, str); +} + +void mail_storage_copy_list_error(struct mail_storage *storage, + struct mailbox_list *list) +{ + const char *str; + enum mail_error error; + + str = mailbox_list_get_last_error(list, &error); + mail_storage_set_error(storage, error, str); +} + +void mailbox_set_index_error(struct mailbox *box) +{ + if (mail_index_is_deleted(box->index)) { + mailbox_set_deleted(box); + mail_index_reset_error(box->index); + } else { + mail_storage_set_index_error(box->storage, box->index); + } +} + +void mail_storage_set_index_error(struct mail_storage *storage, + struct mail_index *index) +{ + const char *index_error; + + mail_storage_set_internal_error(storage); + /* use the lib-index's error as our internal error string */ + index_error = mail_index_get_error_message(index); + if (index_error == NULL) + index_error = "BUG: Unknown internal index error"; + storage->last_internal_error = i_strdup(index_error); + storage->last_error_is_internal = TRUE; + mail_index_reset_error(index); +} + +const struct mail_storage_settings * +mail_storage_get_settings(struct mail_storage *storage) +{ + return storage->set; +} + +struct mail_user *mail_storage_get_user(struct mail_storage *storage) +{ + return storage->user; +} + +void mail_storage_set_callbacks(struct mail_storage *storage, + struct mail_storage_callbacks *callbacks, + void *context) +{ + storage->callbacks = *callbacks; + storage->callback_context = context; +} + +int mail_storage_purge(struct mail_storage *storage) +{ + return storage->v.purge == NULL ? 0 : + storage->v.purge(storage); +} + +const char *mail_storage_get_last_error(struct mail_storage *storage, + enum mail_error *error_r) +{ + /* We get here only in error situations, so we have to return some + error. If storage->error is NONE, it means we forgot to set it at + some point.. */ + if (storage->error == MAIL_ERROR_NONE) { + if (error_r != NULL) + *error_r = MAIL_ERROR_TEMP; + return storage->error_string != NULL ? storage->error_string : + "BUG: Unknown internal error"; + } + + if (storage->error_string == NULL) { + /* This shouldn't happen.. */ + storage->error_string = + i_strdup_printf("BUG: Unknown 0x%x error", + storage->error); + } + + if (error_r != NULL) + *error_r = storage->error; + return storage->error_string; +} + +const char *mailbox_get_last_error(struct mailbox *box, + enum mail_error *error_r) +{ + return mail_storage_get_last_error(box->storage, error_r); +} + +enum mail_error mailbox_get_last_mail_error(struct mailbox *box) +{ + enum mail_error error; + + mail_storage_get_last_error(box->storage, &error); + return error; +} + +void mail_storage_last_error_push(struct mail_storage *storage) +{ + struct mail_storage_error *err; + + if (!array_is_created(&storage->error_stack)) + i_array_init(&storage->error_stack, 2); + err = array_append_space(&storage->error_stack); + err->error_string = i_strdup(storage->error_string); + err->error = storage->error; + err->last_error_is_internal = storage->last_error_is_internal; + if (err->last_error_is_internal) + err->last_internal_error = i_strdup(storage->last_internal_error); +} + +void mail_storage_last_error_pop(struct mail_storage *storage) +{ + unsigned int count = array_count(&storage->error_stack); + const struct mail_storage_error *err = + array_idx(&storage->error_stack, count-1); + + i_free(storage->error_string); + i_free(storage->last_internal_error); + storage->error_string = err->error_string; + storage->error = err->error; + storage->last_error_is_internal = err->last_error_is_internal; + storage->last_internal_error = err->last_internal_error; + array_delete(&storage->error_stack, count-1, 1); +} + +bool mail_storage_is_mailbox_file(struct mail_storage *storage) +{ + return (storage->class_flags & + MAIL_STORAGE_CLASS_FLAG_MAILBOX_IS_FILE) != 0; +} + +bool mail_storage_set_error_from_errno(struct mail_storage *storage) +{ + const char *error_string; + enum mail_error error; + + if (!mail_error_from_errno(&error, &error_string)) + return FALSE; + if (event_want_debug_log(storage->event) && error != MAIL_ERROR_NOTFOUND) { + /* debugging is enabled - admin may be debugging a + (permission) problem, so return FALSE to get the caller to + log the full error message. */ + return FALSE; + } + + mail_storage_set_error(storage, error, error_string); + return TRUE; +} + +const struct mailbox_settings * +mailbox_settings_find(struct mail_namespace *ns, const char *vname) +{ + struct mailbox_settings *box_set; + + if (!array_is_created(&ns->set->mailboxes)) + return NULL; + + if (ns->prefix_len > 0 && + strncmp(ns->prefix, vname, ns->prefix_len-1) == 0) { + if (vname[ns->prefix_len-1] == mail_namespace_get_sep(ns)) + vname += ns->prefix_len; + else if (vname[ns->prefix_len-1] == '\0') { + /* namespace prefix itself */ + vname = ""; + } + } + array_foreach_elem(&ns->set->mailboxes, box_set) { + if (strcmp(box_set->name, vname) == 0) + return box_set; + } + return NULL; +} + +struct mailbox *mailbox_alloc(struct mailbox_list *list, const char *vname, + enum mailbox_flags flags) +{ + struct mailbox_list *new_list = list; + struct mail_storage *storage; + struct mailbox *box; + enum mail_error open_error = 0; + const char *errstr = NULL; + + i_assert(uni_utf8_str_is_valid(vname)); + + if (strncasecmp(vname, "INBOX", 5) == 0 && + !str_begins(vname, "INBOX")) { + /* make sure INBOX shows up in uppercase everywhere. do this + regardless of whether we're in inbox=yes namespace, because + clients expect INBOX to be case insensitive regardless of + server's internal configuration. */ + if (vname[5] == '\0') + vname = "INBOX"; + else if (vname[5] != mail_namespace_get_sep(list->ns)) + /* not INBOX prefix */ ; + else if (strncasecmp(list->ns->prefix, vname, 6) == 0 && + !str_begins(list->ns->prefix, "INBOX")) { + mailbox_list_set_critical(list, + "Invalid server configuration: " + "Namespace prefix=%s must be uppercase INBOX", + list->ns->prefix); + open_error = MAIL_ERROR_TEMP; + } else { + vname = t_strconcat("INBOX", vname + 5, NULL); + } + } + + T_BEGIN { + if (mailbox_list_get_storage(&new_list, vname, &storage) < 0) { + /* do a delayed failure at mailbox_open() */ + storage = mail_namespace_get_default_storage(list->ns); + errstr = mailbox_list_get_last_error(new_list, &open_error); + errstr = t_strdup(errstr); + } + + box = storage->v.mailbox_alloc(storage, new_list, vname, flags); + box->set = mailbox_settings_find(new_list->ns, vname); + box->open_error = open_error; + if (open_error != 0) + mail_storage_set_error(storage, open_error, errstr); + hook_mailbox_allocated(box); + } T_END; + + DLLIST_PREPEND(&box->storage->mailboxes, box); + mail_storage_obj_ref(box->storage); + return box; +} + +struct mailbox *mailbox_alloc_guid(struct mailbox_list *list, + const guid_128_t guid, + enum mailbox_flags flags) +{ + struct mailbox *box = NULL; + struct mailbox_metadata metadata; + enum mail_error open_error = MAIL_ERROR_TEMP; + const char *vname; + + if (mailbox_guid_cache_find(list, guid, &vname) < 0) { + vname = NULL; + } else if (vname != NULL) { + box = mailbox_alloc(list, vname, flags); + if (mailbox_get_metadata(box, MAILBOX_METADATA_GUID, + &metadata) < 0) { + } else if (memcmp(metadata.guid, guid, + sizeof(metadata.guid)) != 0) { + /* GUID mismatch, refresh cache and try again */ + mailbox_free(&box); + mailbox_guid_cache_refresh(list); + return mailbox_alloc_guid(list, guid, flags); + } else { + /* successfully opened the correct mailbox */ + return box; + } + e_error(list->ns->user->event, "mailbox_alloc_guid(%s): " + "Couldn't verify mailbox GUID: %s", + guid_128_to_string(guid), + mailbox_get_last_internal_error(box, NULL)); + vname = NULL; + mailbox_free(&box); + } else { + vname = t_strdup_printf("(nonexistent mailbox with GUID=%s)", + guid_128_to_string(guid)); + open_error = MAIL_ERROR_NOTFOUND; + } + + if (vname == NULL) { + vname = t_strdup_printf("(error in mailbox with GUID=%s)", + guid_128_to_string(guid)); + } + box = mailbox_alloc(list, vname, flags); + box->open_error = open_error; + return box; +} + +static bool +str_contains_special_use(const char *str, const char *special_use) +{ + const char *const *uses; + + i_assert(special_use != NULL); + if (*special_use != '\\') + return FALSE; + + bool ret; + T_BEGIN { + uses = t_strsplit_spaces(str, " "); + ret = str_array_icase_find(uses, special_use); + } T_END; + return ret; +} + +static int +namespace_find_special_use(struct mail_namespace *ns, const char *special_use, + const char **vname_r, enum mail_error *error_code_r) +{ + struct mailbox_list *list = ns->list; + struct mailbox_list_iterate_context *ctx; + const struct mailbox_info *info; + int ret = 0; + + *vname_r = NULL; + *error_code_r = MAIL_ERROR_NONE; + + if (!ns->special_use_mailboxes) + return 0; + if (!HAS_ALL_BITS(ns->type, MAIL_NAMESPACE_TYPE_PRIVATE)) + return 0; + + ctx = mailbox_list_iter_init(list, "*", + MAILBOX_LIST_ITER_SELECT_SPECIALUSE | + MAILBOX_LIST_ITER_RETURN_SPECIALUSE); + while ((info = mailbox_list_iter_next(ctx)) != NULL) { + if ((info->flags & + (MAILBOX_NOSELECT | MAILBOX_NONEXISTENT)) != 0) + continue; + /* iter can only return mailboxes that have non-empty + special-use */ + i_assert(info->special_use != NULL && + *info->special_use != '\0'); + + if (str_contains_special_use(info->special_use, special_use)) { + *vname_r = t_strdup(info->vname); + ret = 1; + break; + } + } + if (mailbox_list_iter_deinit(&ctx) < 0) { + const char *error; + + error = mailbox_list_get_last_error(ns->list, error_code_r); + e_error(ns->user->event, + "Failed to find mailbox with SPECIAL-USE flag '%s' " + "in namespace '%s': %s", + special_use, ns->prefix, error); + return -1; + } + return ret; +} + +static int +namespaces_find_special_use(struct mail_namespace *namespaces, + const char *special_use, + struct mail_namespace **ns_r, + const char **vname_r, enum mail_error *error_code_r) +{ + struct mail_namespace *ns_inbox; + int ret; + + *error_code_r = MAIL_ERROR_NONE; + *vname_r = NULL; + + /* check user's INBOX namespace first */ + *ns_r = ns_inbox = mail_namespace_find_inbox(namespaces); + ret = namespace_find_special_use(*ns_r, special_use, + vname_r, error_code_r); + if (ret != 0) + return ret; + + /* check other namespaces */ + for (*ns_r = namespaces; *ns_r != NULL; *ns_r = (*ns_r)->next) { + if (*ns_r == ns_inbox) { + /* already checked */ + continue; + } + ret = namespace_find_special_use(*ns_r, special_use, + vname_r, error_code_r); + if (ret != 0) + return ret; + } + + *ns_r = ns_inbox; + return 0; +} + +struct mailbox * +mailbox_alloc_for_user(struct mail_user *user, const char *mname, + enum mailbox_flags flags) +{ + struct mail_namespace *ns; + struct mailbox *box; + const char *vname; + enum mail_error open_error = MAIL_ERROR_NONE; + int ret; + + if (HAS_ALL_BITS(flags, MAILBOX_FLAG_SPECIAL_USE)) { + ret = namespaces_find_special_use(user->namespaces, mname, + &ns, &vname, &open_error); + if (ret < 0) { + i_assert(open_error != MAIL_ERROR_NONE); + vname = t_strdup_printf( + "(error finding mailbox with SPECIAL-USE=%s)", + mname); + } else if (ret == 0) { + i_assert(open_error == MAIL_ERROR_NONE); + vname = t_strdup_printf( + "(nonexistent mailbox with SPECIAL-USE=%s)", + mname); + open_error = MAIL_ERROR_NOTFOUND; + } + } else { + vname = mname; + ns = mail_namespace_find(user->namespaces, mname); + } + + if (HAS_ALL_BITS(flags, MAILBOX_FLAG_POST_SESSION)) { + flags |= MAILBOX_FLAG_SAVEONLY; + + if (strcmp(vname, ns->prefix) == 0 && + (ns->flags & NAMESPACE_FLAG_INBOX_USER) != 0) { + /* delivering to a namespace prefix means we actually + want to deliver to the INBOX instead */ + vname = "INBOX"; + ns = mail_namespace_find_inbox(user->namespaces); + } + + if (strcasecmp(vname, "INBOX") == 0) { + /* deliveries to INBOX must always succeed, + regardless of ACLs */ + flags |= MAILBOX_FLAG_IGNORE_ACLS; + } + } + + i_assert(ns != NULL); + box = mailbox_alloc(ns->list, vname, flags); + if (open_error != MAIL_ERROR_NONE) + box->open_error = open_error; + return box; +} + +bool mailbox_is_autocreated(struct mailbox *box) +{ + if (box->inbox_user) + return TRUE; + if ((box->flags & MAILBOX_FLAG_AUTO_CREATE) != 0) + return TRUE; + return box->set != NULL && + strcmp(box->set->autocreate, MAILBOX_SET_AUTO_NO) != 0; +} + +bool mailbox_is_autosubscribed(struct mailbox *box) +{ + if ((box->flags & MAILBOX_FLAG_AUTO_SUBSCRIBE) != 0) + return TRUE; + return box->set != NULL && + strcmp(box->set->autocreate, MAILBOX_SET_AUTO_SUBSCRIBE) == 0; +} + +static int mailbox_autocreate(struct mailbox *box) +{ + const char *errstr; + enum mail_error error; + + if (mailbox_create(box, NULL, FALSE) < 0) { + errstr = mailbox_get_last_internal_error(box, &error); + if (error == MAIL_ERROR_NOTFOUND && box->acl_no_lookup_right) { + /* ACL prevents creating this mailbox */ + return -1; + } + if (error != MAIL_ERROR_EXISTS) { + mailbox_set_critical(box, + "Failed to autocreate mailbox: %s", + errstr); + return -1; + } + } else if (mailbox_is_autosubscribed(box)) { + if (mailbox_set_subscribed(box, TRUE) < 0) { + mailbox_set_critical(box, + "Failed to autosubscribe to mailbox: %s", + mailbox_get_last_internal_error(box, NULL)); + return -1; + } + } + return 0; +} + +static int mailbox_autocreate_and_reopen(struct mailbox *box) +{ + int ret; + + if (mailbox_autocreate(box) < 0) + return -1; + mailbox_close(box); + + ret = box->v.open(box); + if (ret < 0 && box->inbox_user && !box->acl_no_lookup_right && + !box->storage->user->inbox_open_error_logged) { + box->storage->user->inbox_open_error_logged = TRUE; + mailbox_set_critical(box, + "Opening INBOX failed: %s", + mailbox_get_last_internal_error(box, NULL)); + } + return ret; +} + +static bool +mailbox_name_verify_extra_separators(const char *vname, char sep, + const char **error_r) +{ + unsigned int i; + bool prev_sep = FALSE; + + /* Make sure the vname doesn't have extra separators: + + 1) Must not have adjacent separators. If we allow these, these could + end up pointing to existing mailboxes due to kernel ignoring + duplicate '/' in paths. However, this might cause us to handle some + of our own checks wrong, such as skipping ACLs. + + 2) Must not end with separator. Similar reasoning as above. + */ + for (i = 0; vname[i] != '\0'; i++) { + if (vname[i] == sep) { + if (prev_sep) { + *error_r = "Has adjacent hierarchy separators"; + return FALSE; + } + prev_sep = TRUE; + } else { + prev_sep = FALSE; + } + } + if (prev_sep && i > 0) { + *error_r = "Ends with hierarchy separator"; + return FALSE; + } + return TRUE; +} + +static bool +mailbox_verify_name_prefix(struct mail_namespace *ns, const char **vnamep, + const char **error_r) +{ + const char *vname = *vnamep; + + if (ns->prefix_len == 0) + return TRUE; + + /* vname is either "namespace/box" or "namespace" */ + if (strncmp(vname, ns->prefix, ns->prefix_len-1) != 0 || + (vname[ns->prefix_len-1] != '\0' && + vname[ns->prefix_len-1] != ns->prefix[ns->prefix_len-1])) { + /* User input shouldn't normally be able to get us in + here. The main reason this isn't an assert is to + allow any input at all to mailbox_verify_*_name() + without crashing. */ + *error_r = t_strdup_printf("Missing namespace prefix '%s'", + ns->prefix); + return FALSE; + } + vname += ns->prefix_len - 1; + if (vname[0] != '\0') { + i_assert(vname[0] == ns->prefix[ns->prefix_len-1]); + vname++; + + if (vname[0] == '\0') { + /* "namespace/" isn't a valid mailbox name. */ + *error_r = "Ends with hierarchy separator"; + return FALSE; + } + } + *vnamep = vname; + return TRUE; +} + +static int mailbox_verify_name_int(struct mailbox *box) +{ + struct mail_namespace *ns = box->list->ns; + const char *error, *vname = box->vname; + char list_sep, ns_sep; + + if (box->inbox_user) { + /* this is INBOX - don't bother with further checks */ + return 0; + } + + /* Verify the namespace prefix here. Change vname to skip the prefix + for the following checks. */ + if (!mailbox_verify_name_prefix(box->list->ns, &vname, &error)) { + mail_storage_set_error(box->storage, MAIL_ERROR_PARAMS, + t_strdup_printf("Invalid mailbox name '%s': %s", + str_sanitize(vname, 80), error)); + return -1; + } + + list_sep = mailbox_list_get_hierarchy_sep(box->list); + ns_sep = mail_namespace_get_sep(ns); + + /* If namespace { separator } differs from the mailbox_list separator, + the list separator can't actually be used in the mailbox name + unless it's escaped with storage_name_escape_char. For example if + namespace separator is '/' and LAYOUT=Maildir++ has '.' as the + separator, there's no way to use '.' in the mailbox name (without + escaping) because it would end up becoming a hierarchy separator. */ + if (ns_sep != list_sep && + box->list->set.storage_name_escape_char == '\0' && + strchr(vname, list_sep) != NULL) { + mail_storage_set_error(box->storage, MAIL_ERROR_PARAMS, t_strdup_printf( + "Character not allowed in mailbox name: '%c'", list_sep)); + return -1; + } + /* vname must not begin with the hierarchy separator normally. + For example we don't want to allow accessing /etc/passwd. However, + if mail_full_filesystem_access=yes, we do actually want to allow + that. */ + if (vname[0] == ns_sep && + !box->storage->set->mail_full_filesystem_access) { + mail_storage_set_error(box->storage, MAIL_ERROR_PARAMS, + "Invalid mailbox name: Begins with hierarchy separator"); + return -1; + } + + if (!mailbox_name_verify_extra_separators(vname, ns_sep, &error) || + !mailbox_list_is_valid_name(box->list, box->name, &error)) { + mail_storage_set_error(box->storage, MAIL_ERROR_PARAMS, + t_strdup_printf("Invalid mailbox name: %s", error)); + return -1; + } + return 0; +} + +int mailbox_verify_name(struct mailbox *box) +{ + int ret; + T_BEGIN { + ret = mailbox_verify_name_int(box); + } T_END; + return ret; +} + +static int mailbox_verify_existing_name_int(struct mailbox *box) +{ + const char *path; + + if (box->opened) + return 0; + + if (mailbox_verify_name(box) < 0) + return -1; + + /* Make sure box->_path is set, so mailbox_get_path() works from + now on. Note that this may also fail with some backends if the + mailbox doesn't exist. */ + if (mailbox_get_path_to(box, MAILBOX_LIST_PATH_TYPE_MAILBOX, &path) < 0) { + if (box->storage->error != MAIL_ERROR_NOTFOUND || + !mailbox_is_autocreated(box)) + return -1; + /* if this is an autocreated mailbox, create it now */ + if (mailbox_autocreate(box) < 0) + return -1; + mailbox_close(box); + if (mailbox_get_path_to(box, MAILBOX_LIST_PATH_TYPE_MAILBOX, + &path) < 0) + return -1; + } + return 0; +} + +static int mailbox_verify_existing_name(struct mailbox *box) +{ + int ret; + T_BEGIN { + ret = mailbox_verify_existing_name_int(box); + } T_END; + return ret; +} + +static bool mailbox_name_has_control_chars(const char *name) +{ + const char *p; + + for (p = name; *p != '\0'; p++) { + if ((unsigned char)*p < ' ') + return TRUE; + } + return FALSE; +} + +void mailbox_skip_create_name_restrictions(struct mailbox *box, bool set) +{ + box->skip_create_name_restrictions = set; +} + +int mailbox_verify_create_name(struct mailbox *box) +{ + /* mailbox_alloc() already checks that vname is valid UTF8, + so we don't need to verify that. + + check vname instead of storage name, because vname is what is + visible to users, while storage name may be a fixed length GUID. */ + if (mailbox_verify_name(box) < 0) + return -1; + if (box->skip_create_name_restrictions) + return 0; + if (mailbox_name_has_control_chars(box->vname)) { + mail_storage_set_error(box->storage, MAIL_ERROR_PARAMS, + "Control characters not allowed in new mailbox names"); + return -1; + } + if (strlen(box->vname) > MAILBOX_LIST_NAME_MAX_LENGTH) { + mail_storage_set_error(box->storage, MAIL_ERROR_PARAMS, + "Mailbox name too long"); + return -1; + } + /* check individual component names, too */ + const char *old_name = box->name; + const char *name; + const char sep = mailbox_list_get_hierarchy_sep(box->list); + while((name = strchr(old_name, sep)) != NULL) { + if (name - old_name > MAILBOX_MAX_HIERARCHY_NAME_LENGTH) { + mail_storage_set_error(box->storage, MAIL_ERROR_PARAMS, + "Mailbox name too long"); + return -1; + } + name++; + old_name = name; + } + if (strlen(old_name) > MAILBOX_MAX_HIERARCHY_NAME_LENGTH) { + mail_storage_set_error(box->storage, MAIL_ERROR_PARAMS, + "Mailbox name too long"); + return -1; + } + return 0; +} + +static bool have_listable_namespace_prefix(struct mail_namespace *ns, + const char *name) +{ + size_t name_len = strlen(name); + + for (; ns != NULL; ns = ns->next) { + if ((ns->flags & (NAMESPACE_FLAG_LIST_PREFIX | + NAMESPACE_FLAG_LIST_CHILDREN)) == 0) + continue; + + if (ns->prefix_len <= name_len) + continue; + + /* if prefix has multiple hierarchies, match + any of the hierarchies */ + if (strncmp(ns->prefix, name, name_len) == 0 && + ns->prefix[name_len] == mail_namespace_get_sep(ns)) + return TRUE; + } + return FALSE; +} + +int mailbox_exists(struct mailbox *box, bool auto_boxes, + enum mailbox_existence *existence_r) +{ + switch (box->open_error) { + case 0: + break; + case MAIL_ERROR_NOTFOUND: + *existence_r = MAILBOX_EXISTENCE_NONE; + return 0; + default: + /* unsure if this exists or not */ + return -1; + } + if (mailbox_verify_name(box) < 0) { + /* the mailbox name is invalid. we don't know if it currently + exists or not, but since it can never be accessed in any way + report it as if it didn't exist. */ + *existence_r = MAILBOX_EXISTENCE_NONE; + return 0; + } + + if (box->v.exists(box, auto_boxes, existence_r) < 0) + return -1; + + if (!box->inbox_user && *existence_r == MAILBOX_EXISTENCE_NOSELECT && + have_listable_namespace_prefix(box->storage->user->namespaces, + box->vname)) { + /* listable namespace prefix always exists. */ + *existence_r = MAILBOX_EXISTENCE_NOSELECT; + return 0; + } + + /* if this is a shared namespace with only INBOX and + mail_shared_explicit_inbox=no, we'll need to mark the namespace as + usable here since nothing else will. */ + box->list->ns->flags |= NAMESPACE_FLAG_USABLE; + return 0; +} + +static int ATTR_NULL(2) +mailbox_open_full(struct mailbox *box, struct istream *input) +{ + int ret; + + if (box->opened) + return 0; + + switch (box->open_error) { + case 0: + e_debug(box->event, "Mailbox opened"); + break; + case MAIL_ERROR_NOTFOUND: + mail_storage_set_error(box->storage, MAIL_ERROR_NOTFOUND, + T_MAIL_ERR_MAILBOX_NOT_FOUND(box->vname)); + return -1; + default: + mail_storage_set_internal_error(box->storage); + box->storage->error = box->open_error; + return -1; + } + + if (mailbox_verify_existing_name(box) < 0) + return -1; + + if (input != NULL) { + if ((box->storage->class_flags & + MAIL_STORAGE_CLASS_FLAG_OPEN_STREAMS) == 0) { + mailbox_set_critical(box, + "Storage doesn't support streamed mailboxes"); + return -1; + } + box->input = input; + box->flags |= MAILBOX_FLAG_READONLY; + i_stream_ref(box->input); + } + + T_BEGIN { + ret = box->v.open(box); + } T_END; + + if (ret < 0 && box->storage->error == MAIL_ERROR_NOTFOUND && + !box->deleting && !box->creating && + box->input == NULL && mailbox_is_autocreated(box)) T_BEGIN { + ret = mailbox_autocreate_and_reopen(box); + } T_END; + + if (ret < 0) { + if (box->input != NULL) + i_stream_unref(&box->input); + return -1; + } + + box->list->ns->flags |= NAMESPACE_FLAG_USABLE; + return 0; +} + +static bool mailbox_try_undelete(struct mailbox *box) +{ + time_t mtime; + + i_assert(!box->mailbox_undeleting); + + if ((box->flags & MAILBOX_FLAG_READONLY) != 0) { + /* most importantly we don't do this because we want to avoid + a loop: mdbox storage rebuild -> mailbox_open() -> + mailbox_mark_index_deleted() -> mailbox_sync() -> + mdbox storage rebuild. */ + return FALSE; + } + if (mail_index_get_modification_time(box->index, &mtime) < 0) + return FALSE; + if (mtime + MAILBOX_DELETE_RETRY_SECS > time(NULL)) + return FALSE; + + box->mailbox_undeleting = TRUE; + int ret = mailbox_mark_index_deleted(box, FALSE); + box->mailbox_undeleting = FALSE; + if (ret < 0) + return FALSE; + box->mailbox_deleted = FALSE; + return TRUE; +} + +int mailbox_open(struct mailbox *box) +{ + if (mailbox_open_full(box, NULL) < 0) { + if (!box->mailbox_deleted || box->mailbox_undeleting) + return -1; + + /* mailbox has been marked as deleted. if this deletion + started (and crashed) a long time ago, it can be confusing + to user that the mailbox can't be opened. so we'll just + undelete it and reopen. */ + if(!mailbox_try_undelete(box)) + return -1; + + /* make sure we close the mailbox in the middle. some backends + may not have fully opened the mailbox while it was being + undeleted. */ + mailbox_close(box); + if (mailbox_open_full(box, NULL) < 0) + return -1; + } + return 0; +} + +static int mailbox_alloc_index_pvt(struct mailbox *box) +{ + const char *index_dir; + int ret; + + if (box->index_pvt != NULL) + return 1; + + ret = mailbox_get_path_to(box, MAILBOX_LIST_PATH_TYPE_INDEX_PRIVATE, + &index_dir); + if (ret <= 0) + return ret; /* error / no private indexes */ + + if (mailbox_create_missing_dir(box, MAILBOX_LIST_PATH_TYPE_INDEX_PRIVATE) < 0) + return -1; + + /* Note that this may cause box->event to live longer than box */ + box->index_pvt = mail_index_alloc_cache_get(box->event, + NULL, index_dir, t_strconcat(box->index_prefix, ".pvt", NULL)); + mail_index_set_fsync_mode(box->index_pvt, + box->storage->set->parsed_fsync_mode, 0); + mail_index_set_lock_method(box->index_pvt, + box->storage->set->parsed_lock_method, + mail_storage_get_lock_timeout(box->storage, UINT_MAX)); + return 1; +} + +int mailbox_open_index_pvt(struct mailbox *box) +{ + enum mail_index_open_flags index_flags; + int ret; + + if (box->view_pvt != NULL) + return 1; + if (mailbox_get_private_flags_mask(box) == 0) + return 0; + + if ((ret = mailbox_alloc_index_pvt(box)) <= 0) + return ret; + index_flags = MAIL_INDEX_OPEN_FLAG_CREATE | + mail_storage_settings_to_index_flags(box->storage->set); + if ((box->flags & MAILBOX_FLAG_SAVEONLY) != 0) + index_flags |= MAIL_INDEX_OPEN_FLAG_SAVEONLY; + if (mail_index_open(box->index_pvt, index_flags) < 0) + return -1; + box->view_pvt = mail_index_view_open(box->index_pvt); + return 1; +} + +int mailbox_open_stream(struct mailbox *box, struct istream *input) +{ + return mailbox_open_full(box, input); +} + +int mailbox_enable(struct mailbox *box, enum mailbox_feature features) +{ + if (mailbox_verify_name(box) < 0) + return -1; + return box->v.enable(box, features); +} + +enum mailbox_feature mailbox_get_enabled_features(struct mailbox *box) +{ + return box->enabled_features; +} + +void mail_storage_free_binary_cache(struct mail_storage *storage) +{ + if (storage->binary_cache.box == NULL) + return; + + timeout_remove(&storage->binary_cache.to); + i_stream_destroy(&storage->binary_cache.input); + i_zero(&storage->binary_cache); +} + +void mailbox_close(struct mailbox *box) +{ + if (!box->opened) + return; + + if (box->transaction_count != 0) { + i_panic("Trying to close mailbox %s with open transactions", + box->name); + } + box->v.close(box); + + if (box->storage->binary_cache.box == box) + mail_storage_free_binary_cache(box->storage); + box->opened = FALSE; + box->mailbox_deleted = FALSE; + array_clear(&box->search_results); + + if (array_is_created(&box->recent_flags)) + array_free(&box->recent_flags); + box->recent_flags_prev_uid = 0; + box->recent_flags_count = 0; +} + +void mailbox_free(struct mailbox **_box) +{ + struct mailbox *box = *_box; + + *_box = NULL; + + mailbox_close(box); + box->v.free(box); + + if (box->attribute_iter_count != 0) { + i_panic("Trying to free mailbox %s with %u open attribute iterators", + box->name, box->attribute_iter_count); + } + + DLLIST_REMOVE(&box->storage->mailboxes, box); + mail_storage_obj_unref(box->storage); + pool_unref(&box->pool); +} + +bool mailbox_equals(const struct mailbox *box1, + const struct mail_namespace *ns2, const char *vname2) +{ + struct mail_namespace *ns1 = mailbox_get_namespace(box1); + const char *name1; + + if (ns1 != ns2) + return FALSE; + + name1 = mailbox_get_vname(box1); + if (strcmp(name1, vname2) == 0) + return TRUE; + + return strcasecmp(name1, "INBOX") == 0 && + strcasecmp(vname2, "INBOX") == 0; +} + +bool mailbox_is_any_inbox(struct mailbox *box) +{ + return box->inbox_any; +} + +bool mailbox_has_special_use(struct mailbox *box, const char *special_use) +{ + if (box->set == NULL) + return FALSE; + return str_contains_special_use(box->set->special_use, special_use); +} + +static void mailbox_copy_cache_decisions_from_inbox(struct mailbox *box) +{ + struct mail_namespace *ns = + mail_namespace_find_inbox(box->storage->user->namespaces); + struct mailbox *inbox = + mailbox_alloc(ns->list, "INBOX", MAILBOX_FLAG_READONLY); + enum mailbox_existence existence; + + /* this should be NoSelect but since inbox can never be + NoSelect we use EXISTENCE_NONE to avoid creating inbox by accident */ + if (mailbox_exists(inbox, FALSE, &existence) == 0 && + existence != MAILBOX_EXISTENCE_NONE && + mailbox_open(inbox) == 0 && + mailbox_open(box) == 0) { + /* we can't do much about errors here */ + (void)mail_cache_decisions_copy(inbox->cache, box->cache); + } + + mailbox_free(&inbox); +} + +int mailbox_create(struct mailbox *box, const struct mailbox_update *update, + bool directory) +{ + int ret; + + if (mailbox_verify_create_name(box) < 0) + return -1; + + struct event_reason *reason = event_reason_begin("mailbox:create"); + + /* Avoid race conditions by keeping mailbox list locked during changes. + This especially fixes a race during INBOX creation with LAYOUT=index + because it scans for missing mailboxes if INBOX doesn't exist. The + second process's scan can find a half-created INBOX and add it, + causing the first process to become confused. */ + if (mailbox_list_lock(box->list) < 0) { + mail_storage_copy_list_error(box->storage, box->list); + event_reason_end(&reason); + return -1; + } + box->creating = TRUE; + T_BEGIN { + ret = box->v.create_box(box, update, directory); + } T_END; + box->creating = FALSE; + mailbox_list_unlock(box->list); + + if (ret == 0) { + box->list->guid_cache_updated = TRUE; + if (!box->inbox_any) T_BEGIN { + mailbox_copy_cache_decisions_from_inbox(box); + } T_END; + } else if (box->opened) { + /* Creation failed after (partially) opening the mailbox. + It may not be in a valid state, so close it. */ + mail_storage_last_error_push(box->storage); + mailbox_close(box); + mail_storage_last_error_pop(box->storage); + } + event_reason_end(&reason); + return ret; +} + +int mailbox_update(struct mailbox *box, const struct mailbox_update *update) +{ + int ret; + + i_assert(update->min_next_uid == 0 || + update->min_first_recent_uid == 0 || + update->min_first_recent_uid <= update->min_next_uid); + + if (mailbox_verify_existing_name(box) < 0) + return -1; + + struct event_reason *reason = event_reason_begin("mailbox:update"); + ret = box->v.update_box(box, update); + if (!guid_128_is_empty(update->mailbox_guid)) + box->list->guid_cache_invalidated = TRUE; + event_reason_end(&reason); + return ret; +} + +int mailbox_mark_index_deleted(struct mailbox *box, bool del) +{ + struct mail_index_transaction *trans; + enum mail_index_transaction_flags trans_flags = 0; + enum mailbox_flags old_flag; + int ret; + + e_debug(box->event, "Attempting to %s mailbox", del ? + "delete" : "undelete"); + + if (box->marked_deleted && del) { + /* we already marked it deleted. this allows plugins to + "lock" the deletion earlier. */ + return 0; + } + + old_flag = box->flags & MAILBOX_FLAG_OPEN_DELETED; + box->flags |= MAILBOX_FLAG_OPEN_DELETED; + ret = mailbox_open(box); + box->flags = (box->flags & ENUM_NEGATE(MAILBOX_FLAG_OPEN_DELETED)) | old_flag; + if (ret < 0) + return -1; + + trans_flags = del ? 0 : MAIL_INDEX_TRANSACTION_FLAG_EXTERNAL; + trans = mail_index_transaction_begin(box->view, trans_flags); + if (del) + mail_index_set_deleted(trans); + else + mail_index_set_undeleted(trans); + if (mail_index_transaction_commit(&trans) < 0) { + mailbox_set_index_error(box); + return -1; + } + + if (del) { + /* sync the mailbox. this finishes the index deletion and it + can succeed only for a single session. we do it here, so the + rest of the deletion code doesn't have to worry about race + conditions. */ + box->delete_sync_check = TRUE; + ret = mailbox_sync(box, MAILBOX_SYNC_FLAG_FULL_READ); + box->delete_sync_check = FALSE; + if (ret < 0) + return -1; + } + + box->marked_deleted = del; + return 0; +} + +static void mailbox_close_reset_path(struct mailbox *box) +{ + i_zero(&box->_perm); + box->_path = NULL; + box->_index_path = NULL; +} + +static int mailbox_delete_real(struct mailbox *box) +{ + bool list_locked; + int ret; + + if (*box->name == '\0') { + mail_storage_set_error(box->storage, MAIL_ERROR_PARAMS, + "Storage root can't be deleted"); + return -1; + } + + struct event_reason *reason = event_reason_begin("mailbox:delete"); + + box->deleting = TRUE; + if (mailbox_open(box) < 0) { + if (mailbox_get_last_mail_error(box) != MAIL_ERROR_NOTFOUND && + !box->mailbox_deleted) { + event_reason_end(&reason); + return -1; + } + /* might be a \noselect mailbox, so continue deletion */ + } + + if (mailbox_list_lock(box->list) < 0) { + mail_storage_copy_list_error(box->storage, box->list); + list_locked = FALSE; + ret = -1; + } else { + list_locked = TRUE; + ret = box->v.delete_box(box); + } + if (ret < 0 && box->marked_deleted) { + /* deletion failed. revert the mark so it can maybe be + tried again later. */ + if (mailbox_mark_index_deleted(box, FALSE) < 0) + ret = -1; + } + if (list_locked) + mailbox_list_unlock(box->list); + + box->deleting = FALSE; + mailbox_close(box); + + /* if mailbox is reopened, its path may be different with + LAYOUT=index */ + mailbox_close_reset_path(box); + event_reason_end(&reason); + return ret; +} + +int mailbox_delete(struct mailbox *box) +{ + int ret; + T_BEGIN { + ret = mailbox_delete_real(box); + } T_END; + return ret; +} + +int mailbox_delete_empty(struct mailbox *box) +{ + int ret; + + /* FIXME: should be a parameter to delete(), but since it changes API + don't do it for now */ + box->deleting_must_be_empty = TRUE; + ret = mailbox_delete(box); + box->deleting_must_be_empty = FALSE; + return ret; +} + +static bool +mail_storages_rename_compatible(struct mail_storage *storage1, + struct mail_storage *storage2, + const char **error_r) +{ + if (storage1 == storage2) + return TRUE; + + if (strcmp(storage1->name, storage2->name) != 0) { + *error_r = t_strdup_printf("storage %s != %s", + storage1->name, storage2->name); + return FALSE; + } + if ((storage1->class_flags & MAIL_STORAGE_CLASS_FLAG_UNIQUE_ROOT) != 0) { + /* e.g. mdbox where all mails are in storage/ directory and + they can't be easily moved from there. */ + *error_r = t_strdup_printf("storage %s uses unique root", + storage1->name); + return FALSE; + } + return TRUE; +} + +static bool nullequals(const void *p1, const void *p2) +{ + return (p1 == NULL && p2 == NULL) || (p1 != NULL && p2 != NULL); +} + +static bool +mailbox_lists_rename_compatible(struct mailbox_list *list1, + struct mailbox_list *list2, + const char **error_r) +{ + if (!nullequals(list1->set.alt_dir, list2->set.alt_dir)) { + *error_r = t_strdup_printf("Namespace %s has alt dir, %s doesn't", + list1->ns->prefix, list2->ns->prefix); + return FALSE; + } + if (!nullequals(list1->set.index_dir, list2->set.index_dir)) { + *error_r = t_strdup_printf("Namespace %s has index dir, %s doesn't", + list1->ns->prefix, list2->ns->prefix); + return FALSE; + } + if (!nullequals(list1->set.index_cache_dir, list2->set.index_cache_dir)) { + *error_r = t_strdup_printf("Namespace %s has index cache dir, %s doesn't", + list1->ns->prefix, list2->ns->prefix); + return FALSE; + } + if (!nullequals(list1->set.control_dir, list2->set.control_dir)) { + *error_r = t_strdup_printf("Namespace %s has control dir, %s doesn't", + list1->ns->prefix, list2->ns->prefix); + return FALSE; + } + return TRUE; +} + +static +int mailbox_rename_check_children(struct mailbox *src, struct mailbox *dest) +{ + int ret = 0; + size_t src_prefix_len = strlen(src->vname)+1; /* include separator */ + size_t dest_prefix_len = strlen(dest->vname)+1; + /* this can return folders with * in their name, that are not + actually our children */ + char ns_sep = mail_namespace_get_sep(src->list->ns); + const char *pattern = t_strdup_printf("%s%c*", src->vname, ns_sep); + + struct mailbox_list_iterate_context *iter = mailbox_list_iter_init(src->list, pattern, + MAILBOX_LIST_ITER_RETURN_NO_FLAGS); + + const struct mailbox_info *child; + while((child = mailbox_list_iter_next(iter)) != NULL) { + if (strncmp(child->vname, src->vname, src_prefix_len-1) != 0 || + child->vname[src_prefix_len-1] != ns_sep) + continue; /* not our child */ + /* if total length of new name exceeds the limit, fail */ + if (strlen(child->vname + src_prefix_len)+dest_prefix_len > MAILBOX_LIST_NAME_MAX_LENGTH) { + mail_storage_set_error(src->storage, MAIL_ERROR_PARAMS, + "Mailbox or child name too long"); + ret = -1; + break; + } + } + + /* something went bad */ + if (mailbox_list_iter_deinit(&iter) < 0) { + mail_storage_copy_list_error(src->storage, src->list); + ret = -1; + } + return ret; +} + +static int mailbox_rename_real(struct mailbox *src, struct mailbox *dest) +{ + const char *error = NULL; + + /* Check only name validity, \Noselect don't necessarily exist. */ + if (mailbox_verify_name(src) < 0) + return -1; + if (*src->name == '\0') { + mail_storage_set_error(src->storage, MAIL_ERROR_PARAMS, + "Can't rename mailbox root"); + return -1; + } + if (mailbox_verify_create_name(dest) < 0) { + mail_storage_copy_error(src->storage, dest->storage); + return -1; + } + if (mailbox_rename_check_children(src, dest) != 0) { + return -1; + } + + if (!mail_storages_rename_compatible(src->storage, + dest->storage, &error) || + !mailbox_lists_rename_compatible(src->list, + dest->list, &error)) { + e_debug(src->event, + "Can't rename '%s' to '%s': %s", + src->vname, dest->vname, error); + mail_storage_set_error(src->storage, MAIL_ERROR_NOTPOSSIBLE, + "Can't rename mailboxes across specified storages."); + return -1; + } + if (src->list != dest->list && + (src->list->ns->type != MAIL_NAMESPACE_TYPE_PRIVATE || + dest->list->ns->type != MAIL_NAMESPACE_TYPE_PRIVATE)) { + mail_storage_set_error(src->storage, MAIL_ERROR_NOTPOSSIBLE, + "Renaming not supported across non-private namespaces."); + return -1; + } + if (src->list == dest->list && strcmp(src->name, dest->name) == 0) { + mail_storage_set_error(src->storage, MAIL_ERROR_EXISTS, + "Can't rename mailbox to itself."); + return -1; + } + + /* It would be safer to lock both source and destination, but that + could lead to deadlocks. So at least for now lets just lock only the + destination list. */ + if (mailbox_list_lock(dest->list) < 0) { + mail_storage_copy_list_error(src->storage, dest->list); + return -1; + } + int ret = src->v.rename_box(src, dest); + mailbox_list_unlock(dest->list); + if (ret < 0) + return -1; + src->list->guid_cache_invalidated = TRUE; + dest->list->guid_cache_invalidated = TRUE; + return 0; +} + +int mailbox_rename(struct mailbox *src, struct mailbox *dest) +{ + int ret; + T_BEGIN { + struct event_reason *reason = + event_reason_begin("mailbox:rename"); + ret = mailbox_rename_real(src, dest); + event_reason_end(&reason); + } T_END; + return ret; +} + +int mailbox_set_subscribed(struct mailbox *box, bool set) +{ + int ret; + + if (mailbox_verify_name(box) < 0) + return -1; + + struct event_reason *reason = + event_reason_begin(set ? "mailbox:subscribe" : + "mailbox:unsubscribe"); + if (mailbox_list_iter_subscriptions_refresh(box->list) < 0) { + mail_storage_copy_list_error(box->storage, box->list); + ret = -1; + } else if (mailbox_is_subscribed(box) == set) + ret = 0; + else + ret = box->v.set_subscribed(box, set); + event_reason_end(&reason); + return ret; +} + +bool mailbox_is_subscribed(struct mailbox *box) +{ + struct mailbox_node *node; + + i_assert(box->list->subscriptions != NULL); + + node = mailbox_tree_lookup(box->list->subscriptions, box->vname); + return node != NULL && (node->flags & MAILBOX_SUBSCRIBED) != 0; +} + +struct mail_storage *mailbox_get_storage(const struct mailbox *box) +{ + return box->storage; +} + +struct mail_namespace * +mailbox_get_namespace(const struct mailbox *box) +{ + return box->list->ns; +} + +const struct mail_storage_settings *mailbox_get_settings(struct mailbox *box) +{ + return box->storage->set; +} + +const char *mailbox_get_name(const struct mailbox *box) +{ + return box->name; +} + +const char *mailbox_get_vname(const struct mailbox *box) +{ + return box->vname; +} + +bool mailbox_is_readonly(struct mailbox *box) +{ + i_assert(box->opened); + + return box->v.is_readonly(box); +} + +bool mailbox_backends_equal(const struct mailbox *box1, + const struct mailbox *box2) +{ + struct mail_namespace *ns1 = box1->list->ns, *ns2 = box2->list->ns; + + if (strcmp(box1->name, box2->name) != 0) + return FALSE; + + while (ns1->alias_for != NULL) + ns1 = ns1->alias_for; + while (ns2->alias_for != NULL) + ns2 = ns2->alias_for; + return ns1 == ns2; +} + +static void +mailbox_get_status_set_defaults(struct mailbox *box, + struct mailbox_status *status_r) +{ + i_zero(status_r); + if ((box->storage->class_flags & MAIL_STORAGE_CLASS_FLAG_HAVE_MAIL_GUIDS) != 0) + status_r->have_guids = TRUE; + if ((box->storage->class_flags & MAIL_STORAGE_CLASS_FLAG_HAVE_MAIL_SAVE_GUIDS) != 0) + status_r->have_save_guids = TRUE; + if ((box->storage->class_flags & MAIL_STORAGE_CLASS_FLAG_HAVE_MAIL_GUID128) != 0) + status_r->have_only_guid128 = TRUE; +} + +int mailbox_get_status(struct mailbox *box, + enum mailbox_status_items items, + struct mailbox_status *status_r) +{ + mailbox_get_status_set_defaults(box, status_r); + if (mailbox_verify_existing_name(box) < 0) + return -1; + + if (box->v.get_status(box, items, status_r) < 0) + return -1; + i_assert(status_r->have_guids || !status_r->have_save_guids); + return 0; +} + +void mailbox_get_open_status(struct mailbox *box, + enum mailbox_status_items items, + struct mailbox_status *status_r) +{ + i_assert(box->opened); + i_assert((items & MAILBOX_STATUS_FAILING_ITEMS) == 0); + + mailbox_get_status_set_defaults(box, status_r); + if (box->v.get_status(box, items, status_r) < 0) + i_unreached(); +} + +int mailbox_get_metadata(struct mailbox *box, enum mailbox_metadata_items items, + struct mailbox_metadata *metadata_r) +{ + i_zero(metadata_r); + if (mailbox_verify_existing_name(box) < 0) + return -1; + + if (box->v.get_metadata(box, items, metadata_r) < 0) + return -1; + + i_assert((items & MAILBOX_METADATA_GUID) == 0 || + !guid_128_is_empty(metadata_r->guid)); + return 0; +} + +enum mail_flags mailbox_get_private_flags_mask(struct mailbox *box) +{ + if (box->v.get_private_flags_mask != NULL) + return box->v.get_private_flags_mask(box); + else if (box->list->set.index_pvt_dir != NULL) + return MAIL_SEEN; /* FIXME */ + else + return 0; +} + +struct mailbox_sync_context * +mailbox_sync_init(struct mailbox *box, enum mailbox_sync_flags flags) +{ + struct mailbox_sync_context *ctx; + + if (box->transaction_count != 0) { + i_panic("Trying to sync mailbox %s with open transactions", + box->name); + } + if (!box->opened) { + if (mailbox_open(box) < 0) { + ctx = i_new(struct mailbox_sync_context, 1); + ctx->box = box; + ctx->flags = flags; + ctx->open_failed = TRUE; + return ctx; + } + } + T_BEGIN { + ctx = box->v.sync_init(box, flags); + } T_END; + return ctx; +} + +bool mailbox_sync_next(struct mailbox_sync_context *ctx, + struct mailbox_sync_rec *sync_rec_r) +{ + if (ctx->open_failed) + return FALSE; + return ctx->box->v.sync_next(ctx, sync_rec_r); +} + +int mailbox_sync_deinit(struct mailbox_sync_context **_ctx, + struct mailbox_sync_status *status_r) +{ + struct mailbox_sync_context *ctx = *_ctx; + struct mailbox *box = ctx->box; + const char *errormsg; + enum mail_error error; + int ret; + + *_ctx = NULL; + + i_zero(status_r); + + if (!ctx->open_failed) + ret = box->v.sync_deinit(ctx, status_r); + else { + i_free(ctx); + ret = -1; + } + if (ret < 0 && box->inbox_user && + !box->storage->user->inbox_open_error_logged) { + errormsg = mailbox_get_last_internal_error(box, &error); + if (error == MAIL_ERROR_NOTPOSSIBLE) { + box->storage->user->inbox_open_error_logged = TRUE; + e_error(box->event, "Syncing INBOX failed: %s", errormsg); + } + } + if (ret == 0) + box->synced = TRUE; + return ret; +} + +int mailbox_sync(struct mailbox *box, enum mailbox_sync_flags flags) +{ + struct mailbox_sync_context *ctx; + struct mailbox_sync_status status; + + if (array_count(&box->search_results) == 0) { + /* we don't care about mailbox's current state, so we might + as well fix inconsistency state */ + flags |= MAILBOX_SYNC_FLAG_FIX_INCONSISTENT; + } + + ctx = mailbox_sync_init(box, flags); + return mailbox_sync_deinit(&ctx, &status); +} + +#undef mailbox_notify_changes +void mailbox_notify_changes(struct mailbox *box, + mailbox_notify_callback_t *callback, void *context) +{ + i_assert(box->opened); + + box->notify_callback = callback; + box->notify_context = context; + + box->v.notify_changes(box); +} + +void mailbox_notify_changes_stop(struct mailbox *box) +{ + i_assert(box->opened); + + box->notify_callback = NULL; + box->notify_context = NULL; + + box->v.notify_changes(box); +} + +struct mail_search_context * +mailbox_search_init(struct mailbox_transaction_context *t, + struct mail_search_args *args, + const enum mail_sort_type *sort_program, + enum mail_fetch_field wanted_fields, + struct mailbox_header_lookup_ctx *wanted_headers) +{ + i_assert(wanted_headers == NULL || wanted_headers->box == t->box); + + mail_search_args_ref(args); + if (!args->simplified) + mail_search_args_simplify(args); + return t->box->v.search_init(t, args, sort_program, + wanted_fields, wanted_headers); +} + +int mailbox_search_deinit(struct mail_search_context **_ctx) +{ + struct mail_search_context *ctx = *_ctx; + struct mail_search_args *args = ctx->args; + int ret; + + *_ctx = NULL; + mailbox_search_results_initial_done(ctx); + ret = ctx->transaction->box->v.search_deinit(ctx); + mail_search_args_unref(&args); + return ret; +} + +bool mailbox_search_next(struct mail_search_context *ctx, struct mail **mail_r) +{ + bool tryagain; + + while (!mailbox_search_next_nonblock(ctx, mail_r, &tryagain)) { + if (!tryagain) + return FALSE; + } + return TRUE; +} + +bool mailbox_search_next_nonblock(struct mail_search_context *ctx, + struct mail **mail_r, bool *tryagain_r) +{ + struct mailbox *box = ctx->transaction->box; + + *mail_r = NULL; + *tryagain_r = FALSE; + + if (!box->v.search_next_nonblock(ctx, mail_r, tryagain_r)) + return FALSE; + else { + mailbox_search_results_add(ctx, (*mail_r)->uid); + return TRUE; + } +} + +bool mailbox_search_seen_lost_data(struct mail_search_context *ctx) +{ + return ctx->seen_lost_data; +} + +void mailbox_search_mail_detach(struct mail_search_context *ctx, + struct mail *mail) +{ + struct mail_private *pmail = + container_of(mail, struct mail_private, mail); + struct mail *const *mailp; + + array_foreach(&ctx->mails, mailp) { + if (*mailp == mail) { + pmail->search_mail = FALSE; + array_delete(&ctx->mails, + array_foreach_idx(&ctx->mails, mailp), 1); + return; + } + } + i_unreached(); +} + +int mailbox_search_result_build(struct mailbox_transaction_context *t, + struct mail_search_args *args, + enum mailbox_search_result_flags flags, + struct mail_search_result **result_r) +{ + struct mail_search_context *ctx; + struct mail *mail; + int ret; + + ctx = mailbox_search_init(t, args, NULL, 0, NULL); + *result_r = mailbox_search_result_save(ctx, flags); + while (mailbox_search_next(ctx, &mail)) ; + + ret = mailbox_search_deinit(&ctx); + if (ret < 0) + mailbox_search_result_free(result_r); + return ret; +} + +struct mailbox_transaction_context * +mailbox_transaction_begin(struct mailbox *box, + enum mailbox_transaction_flags flags, + const char *reason) +{ + struct mailbox_transaction_context *trans; + + i_assert(box->opened); + + box->transaction_count++; + trans = box->v.transaction_begin(box, flags, reason); + i_assert(trans->reason != NULL); + return trans; +} + +int mailbox_transaction_commit(struct mailbox_transaction_context **t) +{ + struct mail_transaction_commit_changes changes; + int ret; + + /* Store changes temporarily so that plugins overriding + transaction_commit() can look at them. */ + ret = mailbox_transaction_commit_get_changes(t, &changes); + pool_unref(&changes.pool); + return ret; +} + +int mailbox_transaction_commit_get_changes( + struct mailbox_transaction_context **_t, + struct mail_transaction_commit_changes *changes_r) +{ + struct mailbox_transaction_context *t = *_t; + struct mailbox *box = t->box; + unsigned int save_count = t->save_count; + struct event_reason *reason = NULL; + int ret; + + changes_r->pool = NULL; + + *_t = NULL; + + if (t->itrans->attribute_updates != NULL && + t->itrans->attribute_updates->used > 0) { + /* attribute changes are also done directly via lib-index + by ACL and Sieve */ + reason = event_reason_begin("mailbox:attributes_changed"); + } + T_BEGIN { + ret = box->v.transaction_commit(t, changes_r); + } T_END; + /* either all the saved messages get UIDs or none, because a) we + failed, b) MAILBOX_TRANSACTION_FLAG_ASSIGN_UIDS not set, + c) backend doesn't support it (e.g. virtual plugin) */ + i_assert(ret < 0 || + seq_range_count(&changes_r->saved_uids) == save_count || + array_count(&changes_r->saved_uids) == 0); + /* decrease the transaction count only after transaction_commit(). + that way if it creates and destroys transactions internally, we + don't see transaction_count=0 until the parent transaction is fully + finished */ + box->transaction_count--; + event_reason_end(&reason); + if (ret < 0 && changes_r->pool != NULL) + pool_unref(&changes_r->pool); + return ret; +} + +void mailbox_transaction_rollback(struct mailbox_transaction_context **_t) +{ + struct mailbox_transaction_context *t = *_t; + struct mailbox *box = t->box; + + *_t = NULL; + box->v.transaction_rollback(t); + box->transaction_count--; +} + +unsigned int mailbox_transaction_get_count(const struct mailbox *box) +{ + return box->transaction_count; +} + +void mailbox_transaction_set_max_modseq(struct mailbox_transaction_context *t, + uint64_t max_modseq, + ARRAY_TYPE(seq_range) *seqs) +{ + mail_index_transaction_set_max_modseq(t->itrans, max_modseq, seqs); +} + +struct mailbox * +mailbox_transaction_get_mailbox(const struct mailbox_transaction_context *t) +{ + return t->box; +} + +static void mailbox_save_dest_mail_close(struct mail_save_context *ctx) +{ + struct mail_private *mail = (struct mail_private *)ctx->dest_mail; + + mail->v.close(&mail->mail); +} + +struct mail_save_context * +mailbox_save_alloc(struct mailbox_transaction_context *t) +{ + struct mail_save_context *ctx; + T_BEGIN { + ctx = t->box->v.save_alloc(t); + } T_END; + i_assert(!ctx->unfinished); + ctx->unfinished = TRUE; + ctx->data.received_date = (time_t)-1; + ctx->data.save_date = (time_t)-1; + + /* Always have a dest_mail available. A lot of plugins make use + of this. */ + if (ctx->dest_mail == NULL) + ctx->dest_mail = mail_alloc(t, 0, NULL); + else { + /* make sure the mail isn't used before mail_set_seq_saving() */ + mailbox_save_dest_mail_close(ctx); + } + + return ctx; +} + +void mailbox_save_context_deinit(struct mail_save_context *ctx) +{ + i_assert(ctx->dest_mail != NULL); + + mail_free(&ctx->dest_mail); +} + +void mailbox_save_set_flags(struct mail_save_context *ctx, + enum mail_flags flags, + struct mail_keywords *keywords) +{ + struct mailbox *box = ctx->transaction->box; + + if (ctx->data.keywords != NULL) + mailbox_keywords_unref(&ctx->data.keywords); + + ctx->data.flags = flags & ENUM_NEGATE(mailbox_get_private_flags_mask(box)); + ctx->data.pvt_flags = flags & mailbox_get_private_flags_mask(box); + ctx->data.keywords = keywords; + if (keywords != NULL) + mailbox_keywords_ref(keywords); +} + +void mailbox_save_copy_flags(struct mail_save_context *ctx, struct mail *mail) +{ + const char *const *keywords_list; + struct mail_keywords *keywords; + + keywords_list = mail_get_keywords(mail); + keywords = str_array_length(keywords_list) == 0 ? NULL : + mailbox_keywords_create_valid(ctx->transaction->box, + keywords_list); + mailbox_save_set_flags(ctx, mail_get_flags(mail), keywords); + if (keywords != NULL) + mailbox_keywords_unref(&keywords); +} + +void mailbox_save_set_min_modseq(struct mail_save_context *ctx, + uint64_t min_modseq) +{ + ctx->data.min_modseq = min_modseq; +} + +void mailbox_save_set_received_date(struct mail_save_context *ctx, + time_t received_date, int timezone_offset) +{ + ctx->data.received_date = received_date; + ctx->data.received_tz_offset = timezone_offset; +} + +void mailbox_save_set_save_date(struct mail_save_context *ctx, + time_t save_date) +{ + ctx->data.save_date = save_date; +} + +void mailbox_save_set_from_envelope(struct mail_save_context *ctx, + const char *envelope) +{ + i_free(ctx->data.from_envelope); + ctx->data.from_envelope = i_strdup(envelope); +} + +void mailbox_save_set_uid(struct mail_save_context *ctx, uint32_t uid) +{ + ctx->data.uid = uid; +} + +void mailbox_save_set_guid(struct mail_save_context *ctx, const char *guid) +{ + i_assert(guid == NULL || *guid != '\0'); + + i_free(ctx->data.guid); + ctx->data.guid = i_strdup(guid); +} + +void mailbox_save_set_pop3_uidl(struct mail_save_context *ctx, const char *uidl) +{ + i_assert(*uidl != '\0'); + i_assert(strchr(uidl, '\n') == NULL); + + i_free(ctx->data.pop3_uidl); + ctx->data.pop3_uidl = i_strdup(uidl); +} + +void mailbox_save_set_pop3_order(struct mail_save_context *ctx, + unsigned int order) +{ + i_assert(order > 0); + + ctx->data.pop3_order = order; +} + +struct mail *mailbox_save_get_dest_mail(struct mail_save_context *ctx) +{ + return ctx->dest_mail; +} + +int mailbox_save_begin(struct mail_save_context **ctx, struct istream *input) +{ + struct mailbox *box = (*ctx)->transaction->box; + int ret; + + if (mail_index_is_deleted(box->index)) { + mailbox_set_deleted(box); + mailbox_save_cancel(ctx); + return -1; + } + + /* make sure parts get parsed early on */ + const struct mail_storage_settings *mail_set = + mailbox_get_settings(box); + if (mail_set->parsed_mail_attachment_detection_add_flags) + mail_add_temp_wanted_fields((*ctx)->dest_mail, + MAIL_FETCH_MESSAGE_PARTS, NULL); + + if (!(*ctx)->copying_or_moving) { + /* We're actually saving the mail. We're not being called by + mail_storage_copy() because backend didn't support fast + copying. */ + i_assert(!(*ctx)->copying_via_save); + (*ctx)->saving = TRUE; + } else { + i_assert((*ctx)->copying_via_save); + } + if (box->v.save_begin == NULL) { + mail_storage_set_error(box->storage, MAIL_ERROR_NOTPOSSIBLE, + "Saving messages not supported"); + ret = -1; + } else T_BEGIN { + ret = box->v.save_begin(*ctx, input); + } T_END; + + if (ret < 0) { + mailbox_save_cancel(ctx); + return -1; + } + return 0; +} + +int mailbox_save_continue(struct mail_save_context *ctx) +{ + int ret; + + T_BEGIN { + ret = ctx->transaction->box->v.save_continue(ctx); + } T_END; + return ret; +} + +static void +mailbox_save_add_pvt_flags(struct mailbox_transaction_context *t, + enum mail_flags pvt_flags) +{ + struct mail_save_private_changes *save; + + if (!array_is_created(&t->pvt_saves)) + i_array_init(&t->pvt_saves, 8); + save = array_append_space(&t->pvt_saves); + save->mailnum = t->save_count; + save->flags = pvt_flags; +} + +static void +mailbox_save_context_reset(struct mail_save_context *ctx, bool success) +{ + i_assert(!ctx->unfinished); + if (!ctx->copying_or_moving) { + /* we're finishing a save (not copy/move). Note that we could + have come here also from mailbox_save_cancel(), in which + case ctx->saving may be FALSE. */ + i_assert(!ctx->copying_via_save); + i_assert(ctx->saving || !success); + ctx->saving = FALSE; + } else { + i_assert(ctx->copying_via_save || !success); + /* We came from mailbox_copy(). saving==TRUE is possible here + if we also came from mailbox_save_using_mail(). Don't set + saving=FALSE yet in that case, because copy() is still + running. */ + } +} + +int mailbox_save_finish(struct mail_save_context **_ctx) +{ + struct mail_save_context *ctx = *_ctx; + struct mailbox_transaction_context *t = ctx->transaction; + /* we need to keep a copy of this because save_finish implementations + will likely zero the data structure during cleanup */ + enum mail_flags pvt_flags = ctx->data.pvt_flags; + bool copying_via_save = ctx->copying_via_save; + int ret; + + /* Do one final continue. The caller may not have done it if the + input stream's offset already matched the number of bytes that + were wanted to be saved. But due to nested istreams some of the + underlying ones may not have seen the EOF yet, and haven't flushed + out the pending data. */ + if (mailbox_save_continue(ctx) < 0) { + mailbox_save_cancel(_ctx); + return -1; + } + *_ctx = NULL; + + ctx->finishing = TRUE; + T_BEGIN { + ret = t->box->v.save_finish(ctx); + } T_END; + ctx->finishing = FALSE; + + if (ret == 0 && !copying_via_save) { + if (pvt_flags != 0) + mailbox_save_add_pvt_flags(t, pvt_flags); + t->save_count++; + } + + mailbox_save_context_reset(ctx, TRUE); + return ret; +} + +void mailbox_save_cancel(struct mail_save_context **_ctx) +{ + struct mail_save_context *ctx = *_ctx; + + *_ctx = NULL; + T_BEGIN { + ctx->transaction->box->v.save_cancel(ctx); + } T_END; + + /* the dest_mail is no longer valid. if we're still saving + more mails, the mail sequence may get reused. make sure + the mail gets reset in between */ + mailbox_save_dest_mail_close(ctx); + + mailbox_save_context_reset(ctx, FALSE); +} + +struct mailbox_transaction_context * +mailbox_save_get_transaction(struct mail_save_context *ctx) +{ + return ctx->transaction; +} + +static int mailbox_copy_int(struct mail_save_context **_ctx, struct mail *mail) +{ + struct mail_save_context *ctx = *_ctx; + struct mailbox_transaction_context *t = ctx->transaction; + enum mail_flags pvt_flags = ctx->data.pvt_flags; + struct mail *backend_mail; + int ret; + + *_ctx = NULL; + + if (mail_index_is_deleted(t->box->index)) { + mailbox_set_deleted(t->box); + mailbox_save_cancel(&ctx); + return -1; + } + + /* bypass virtual storage, so hard linking can be used whenever + possible */ + if (mail_get_backend_mail(mail, &backend_mail) < 0) { + mailbox_save_cancel(&ctx); + return -1; + } + + i_assert(!ctx->copying_or_moving); + i_assert(ctx->copy_src_mail == NULL); + ctx->copying_or_moving = TRUE; + ctx->copy_src_mail = mail; + ctx->finishing = TRUE; + T_BEGIN { + ret = t->box->v.copy(ctx, backend_mail); + } T_END; + ctx->finishing = FALSE; + if (ret == 0) { + if (pvt_flags != 0) + mailbox_save_add_pvt_flags(t, pvt_flags); + t->save_count++; + } + i_assert(!ctx->unfinished); + + ctx->copy_src_mail = NULL; + ctx->copying_via_save = FALSE; + ctx->copying_or_moving = FALSE; + ctx->saving = FALSE; /* if we came from mailbox_save_using_mail() */ + return ret; +} + +int mailbox_copy(struct mail_save_context **_ctx, struct mail *mail) +{ + struct mail_save_context *ctx = *_ctx; + + i_assert(!ctx->saving); + i_assert(!ctx->moving); + + int ret; + T_BEGIN { + ret = mailbox_copy_int(_ctx, mail); + } T_END; + + return ret; +} + +int mailbox_move(struct mail_save_context **_ctx, struct mail *mail) +{ + struct mail_save_context *ctx = *_ctx; + int ret; + + i_assert(!ctx->saving); + i_assert(!ctx->moving); + + ctx->moving = TRUE; + T_BEGIN { + if ((ret = mailbox_copy_int(_ctx, mail)) == 0) + mail_expunge(mail); + } T_END; + ctx->moving = FALSE; + return ret; +} + +int mailbox_save_using_mail(struct mail_save_context **_ctx, struct mail *mail) +{ + struct mail_save_context *ctx = *_ctx; + + i_assert(!ctx->saving); + i_assert(!ctx->moving); + + ctx->saving = TRUE; + return mailbox_copy_int(_ctx, mail); +} + +bool mailbox_is_inconsistent(struct mailbox *box) +{ + return box->mailbox_deleted || box->v.is_inconsistent(box); +} + +void mailbox_set_deleted(struct mailbox *box) +{ + mail_storage_set_error(box->storage, MAIL_ERROR_NOTFOUND, + "Mailbox was deleted under us"); + box->mailbox_deleted = TRUE; +} + +static int get_path_to(struct mailbox *box, enum mailbox_list_path_type type, + const char **internal_path, const char **path_r) +{ + int ret; + + if (internal_path != NULL && *internal_path != NULL) { + if ((*internal_path)[0] == '\0') { + *path_r = NULL; + return 0; + } + *path_r = *internal_path; + return 1; + } + ret = mailbox_list_get_path(box->list, box->name, type, path_r); + if (ret < 0) { + mail_storage_copy_list_error(box->storage, box->list); + return -1; + } + if (internal_path != NULL && *internal_path == NULL) + *internal_path = ret == 0 ? "" : p_strdup(box->pool, *path_r); + return ret; +} + +int mailbox_get_path_to(struct mailbox *box, enum mailbox_list_path_type type, + const char **path_r) +{ + if (type == MAILBOX_LIST_PATH_TYPE_MAILBOX) + return get_path_to(box, type, &box->_path, path_r); + if (type == MAILBOX_LIST_PATH_TYPE_INDEX) + return get_path_to(box, type, &box->_index_path, path_r); + return get_path_to(box, type, NULL, path_r); +} + +const char *mailbox_get_path(struct mailbox *box) +{ + i_assert(box->_path != NULL); + i_assert(box->_path[0] != '\0'); + return box->_path; +} + +const char *mailbox_get_index_path(struct mailbox *box) +{ + i_assert(box->_index_path != NULL); + i_assert(box->_index_path[0] != '\0'); + return box->_index_path; +} + +static void mailbox_get_permissions_if_not_set(struct mailbox *box) +{ + if (box->_perm.file_create_mode != 0) + return; + + if (box->input != NULL) { + box->_perm.file_uid = geteuid(); + box->_perm.file_create_mode = 0600; + box->_perm.dir_create_mode = 0700; + box->_perm.file_create_gid = (gid_t)-1; + box->_perm.file_create_gid_origin = "defaults"; + return; + } + + struct mailbox_permissions perm; + mailbox_list_get_permissions(box->list, box->name, &perm); + mailbox_permissions_copy(&box->_perm, &perm, box->pool); +} + +const struct mailbox_permissions *mailbox_get_permissions(struct mailbox *box) +{ + mailbox_get_permissions_if_not_set(box); + + if (!box->_perm.mail_index_permissions_set && box->index != NULL) { + box->_perm.mail_index_permissions_set = TRUE; + mail_index_set_permissions(box->index, + box->_perm.file_create_mode, + box->_perm.file_create_gid, + box->_perm.file_create_gid_origin); + } + return &box->_perm; +} + +void mailbox_refresh_permissions(struct mailbox *box) +{ + i_zero(&box->_perm); + (void)mailbox_get_permissions(box); +} + +int mailbox_create_fd(struct mailbox *box, const char *path, int flags, + int *fd_r) +{ + const struct mailbox_permissions *perm = mailbox_get_permissions(box); + mode_t old_mask; + int fd; + + i_assert((flags & O_CREAT) != 0); + + *fd_r = -1; + + old_mask = umask(0); + fd = open(path, flags, perm->file_create_mode); + umask(old_mask); + + if (fd != -1) { + /* ok */ + } else if (errno == EEXIST) { + /* O_EXCL used, caller will handle this error */ + return 0; + } else if (errno == ENOENT) { + mailbox_set_deleted(box); + return -1; + } else if (errno == ENOTDIR) { + mail_storage_set_error(box->storage, MAIL_ERROR_NOTPOSSIBLE, + "Mailbox doesn't allow inferior mailboxes"); + return -1; + } else if (mail_storage_set_error_from_errno(box->storage)) { + return -1; + } else { + mailbox_set_critical(box, "open(%s, O_CREAT) failed: %m", path); + return -1; + } + + if (perm->file_create_gid != (gid_t)-1) { + if (fchown(fd, (uid_t)-1, perm->file_create_gid) == 0) { + /* ok */ + } else if (errno == EPERM) { + mailbox_set_critical(box, "%s", + eperm_error_get_chgrp("fchown", path, + perm->file_create_gid, + perm->file_create_gid_origin)); + } else { + mailbox_set_critical(box, + "fchown(%s) failed: %m", path); + } + } + *fd_r = fd; + return 1; +} + +int mailbox_mkdir(struct mailbox *box, const char *path, + enum mailbox_list_path_type type) +{ + const struct mailbox_permissions *perm = mailbox_get_permissions(box); + const char *root_dir; + + if (!perm->gid_origin_is_mailbox_path) { + /* mailbox root directory doesn't exist, create it */ + root_dir = mailbox_list_get_root_forced(box->list, type); + if (mailbox_list_mkdir_root(box->list, root_dir, type) < 0) { + mail_storage_copy_list_error(box->storage, box->list); + return -1; + } + } + + if (mkdir_parents_chgrp(path, perm->dir_create_mode, + perm->file_create_gid, + perm->file_create_gid_origin) == 0) + return 1; + else if (errno == EEXIST) + return 0; + else if (errno == ENOTDIR) { + mail_storage_set_error(box->storage, MAIL_ERROR_NOTPOSSIBLE, + "Mailbox doesn't allow inferior mailboxes"); + return -1; + } else if (mail_storage_set_error_from_errno(box->storage)) { + return -1; + } else { + mailbox_set_critical(box, "mkdir_parents(%s) failed: %m", path); + return -1; + } +} + +int mailbox_create_missing_dir(struct mailbox *box, + enum mailbox_list_path_type type) +{ + const char *mail_dir, *dir; + struct stat st; + int ret; + + if ((ret = mailbox_get_path_to(box, type, &dir)) <= 0) + return ret; + if (mailbox_get_path_to(box, MAILBOX_LIST_PATH_TYPE_MAILBOX, + &mail_dir) < 0) + return -1; + if (null_strcmp(dir, mail_dir) != 0) { + /* Mailbox directory is different - create a missing dir */ + } else if ((box->list->props & MAILBOX_LIST_PROP_AUTOCREATE_DIRS) != 0) { + /* This layout (e.g. imapc) wants to autocreate missing mailbox + directories as well. */ + } else { + /* If the mailbox directory doesn't exist, the mailbox + shouldn't exist at all. So just assume that it's already + created and if there's a race condition just fail later. */ + return 0; + } + + /* we call this function even when the directory exists, so first do a + quick check to see if we need to mkdir anything */ + if (stat(dir, &st) == 0) + return 0; + + if ((box->storage->class_flags & MAIL_STORAGE_CLASS_FLAG_NO_ROOT) == 0 && + null_strcmp(dir, mail_dir) != 0 && mail_dir != NULL && + stat(mail_dir, &st) < 0 && (errno == ENOENT || errno == ENOTDIR)) { + /* Race condition - mail root directory doesn't exist + anymore either. We shouldn't create this directory + anymore. */ + mailbox_set_deleted(box); + return -1; + } + + return mailbox_mkdir(box, dir, type); +} + +unsigned int mail_storage_get_lock_timeout(struct mail_storage *storage, + unsigned int secs) +{ + return storage->set->mail_max_lock_timeout == 0 ? secs : + I_MIN(secs, storage->set->mail_max_lock_timeout); +} + +enum mail_index_open_flags +mail_storage_settings_to_index_flags(const struct mail_storage_settings *set) +{ + enum mail_index_open_flags index_flags = 0; + +#ifndef MMAP_CONFLICTS_WRITE + if (set->mmap_disable) +#endif + index_flags |= MAIL_INDEX_OPEN_FLAG_MMAP_DISABLE; + if (set->dotlock_use_excl) + index_flags |= MAIL_INDEX_OPEN_FLAG_DOTLOCK_USE_EXCL; + if (set->mail_nfs_index) + index_flags |= MAIL_INDEX_OPEN_FLAG_NFS_FLUSH; + return index_flags; +} + +int mail_parse_human_timestamp(const char *str, time_t *timestamp_r, + bool *utc_r) +{ + struct tm tm; + unsigned int secs; + const char *error; + + if (i_isdigit(str[0]) && i_isdigit(str[1]) && + i_isdigit(str[2]) && i_isdigit(str[3]) && str[4] == '-' && + i_isdigit(str[5]) && i_isdigit(str[6]) && str[7] == '-' && + i_isdigit(str[8]) && i_isdigit(str[9]) && str[10] == '\0') { + /* yyyy-mm-dd */ + i_zero(&tm); + tm.tm_year = (str[0]-'0') * 1000 + (str[1]-'0') * 100 + + (str[2]-'0') * 10 + (str[3]-'0') - 1900; + tm.tm_mon = (str[5]-'0') * 10 + (str[6]-'0') - 1; + tm.tm_mday = (str[8]-'0') * 10 + (str[9]-'0'); + *timestamp_r = mktime(&tm); + *utc_r = FALSE; + return 0; + } else if (imap_parse_date(str, timestamp_r)) { + /* imap date */ + *utc_r = FALSE; + return 0; + } else if (str_to_time(str, timestamp_r) == 0) { + /* unix timestamp */ + *utc_r = TRUE; + return 0; + } else if (settings_get_time(str, &secs, &error) == 0) { + *timestamp_r = ioloop_time - secs; + *utc_r = TRUE; + return 0; + } else { + return -1; + } +} + +void mail_set_mail_cache_corrupted(struct mail *mail, const char *fmt, ...) +{ + struct mail_cache_view *cache_view = + mail->transaction->cache_view; + + i_assert(cache_view != NULL); + + va_list va; + va_start(va, fmt); + + T_BEGIN { + mail_cache_set_seq_corrupted_reason(cache_view, mail->seq, + t_strdup_printf("UID %u: %s", + mail->uid, + t_strdup_vprintf(fmt, va))); + } T_END; + + /* update also the storage's internal error */ + mailbox_set_index_error(mail->box); + + va_end(va); +} + +static int +mail_storage_dotlock_create(const char *lock_path, + const struct file_create_settings *lock_set, + const struct mail_storage_settings *mail_set, + struct file_lock **lock_r, const char **error_r) +{ + const struct dotlock_settings dotlock_set = { + .timeout = lock_set->lock_timeout_secs, + .stale_timeout = I_MAX(60*5, lock_set->lock_timeout_secs), + .lock_suffix = "", + + .use_excl_lock = mail_set->dotlock_use_excl, + .nfs_flush = mail_set->mail_nfs_storage, + .use_io_notify = TRUE, + }; + struct dotlock *dotlock; + int ret = file_dotlock_create(&dotlock_set, lock_path, 0, &dotlock); + if (ret <= 0) { + *error_r = t_strdup_printf("file_dotlock_create(%s) failed: %m", + lock_path); + return ret; + } + *lock_r = file_lock_from_dotlock(&dotlock); + return 1; +} + +int mail_storage_lock_create(const char *lock_path, + const struct file_create_settings *lock_set, + const struct mail_storage_settings *mail_set, + struct file_lock **lock_r, const char **error_r) +{ + struct file_create_settings lock_set_new = *lock_set; + bool created; + + if (lock_set->lock_settings.lock_method == FILE_LOCK_METHOD_DOTLOCK) + return mail_storage_dotlock_create(lock_path, lock_set, mail_set, lock_r, error_r); + + lock_set_new.lock_settings.close_on_free = TRUE; + lock_set_new.lock_settings.unlink_on_free = TRUE; + if (file_create_locked(lock_path, &lock_set_new, lock_r, + &created, error_r) == -1) { + *error_r = t_strdup_printf("file_create_locked(%s) failed: %s", + lock_path, *error_r); + return errno == EAGAIN ? 0 : -1; + } + return 1; +} + +int mailbox_lock_file_create(struct mailbox *box, const char *lock_fname, + unsigned int lock_secs, struct file_lock **lock_r, + const char **error_r) +{ + const struct mailbox_permissions *perm; + struct file_create_settings set; + const char *lock_path; + + perm = mailbox_get_permissions(box); + i_zero(&set); + set.lock_timeout_secs = + mail_storage_get_lock_timeout(box->storage, lock_secs); + set.lock_settings.lock_method = box->storage->set->parsed_lock_method; + set.mode = perm->file_create_mode; + set.gid = perm->file_create_gid; + set.gid_origin = perm->file_create_gid_origin; + + if (box->list->set.volatile_dir == NULL) + lock_path = t_strdup_printf("%s/%s", box->index->dir, lock_fname); + else { + unsigned char box_name_sha1[SHA1_RESULTLEN]; + string_t *str = t_str_new(128); + + /* Keep this simple: Use the lock_fname with a SHA1 of the + mailbox name as the suffix. The mailbox name itself could + be too large as a filename and creating the full directory + structure would be pretty troublesome. It would also make + it more difficult to perform the automated deletion of empty + lock directories. */ + str_printfa(str, "%s/%s.", box->list->set.volatile_dir, + lock_fname); + sha1_get_digest(box->name, strlen(box->name), box_name_sha1); + binary_to_hex_append(str, box_name_sha1, sizeof(box_name_sha1)); + lock_path = str_c(str); + set.mkdir_mode = 0700; + } + + return mail_storage_lock_create(lock_path, &set, + box->storage->set, lock_r, error_r); +} + +void mailbox_sync_notify(struct mailbox *box, uint32_t uid, + enum mailbox_sync_type sync_type) +{ + if (box->v.sync_notify != NULL) + box->v.sync_notify(box, uid, sync_type); + + /* Send an event for expunged mail. */ + if (sync_type == MAILBOX_SYNC_TYPE_EXPUNGE) { + e_debug(event_create_passthrough(box->event)-> + set_name("mail_expunged")-> + add_int("uid", uid)->event(), + "UID %u: Mail expunged", uid); + } +} |