Mercurial > dovecot > core-2.2
view src/lib-storage/mail-storage.c @ 14133:ba770cba5598
Updated copyright notices to include year 2012.
author | Timo Sirainen <tss@iki.fi> |
---|---|
date | Sun, 12 Feb 2012 18:55:28 +0200 |
parents | ba48de993c8e |
children | 5e4e6c57c142 55586f4a86f1 |
line wrap: on
line source
/* Copyright (c) 2002-2012 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "ioloop.h" #include "array.h" #include "llist.h" #include "unichar.h" #include "istream.h" #include "eacces-error.h" #include "mkdir-parents.h" #include "time-util.h" #include "var-expand.h" #include "mail-index-private.h" #include "mail-index-alloc-cache.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 "mailbox-search-result-private.h" #include "mailbox-guid-cache.h" #include <stdlib.h> #include <ctype.h> #define MAILBOX_DELETE_RETRY_SECS (60*5) extern struct mail_search_register *mail_search_register_imap; extern struct mail_search_register *mail_search_register_human; 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; void mail_storage_init(void) { mailbox_lists_init(); mail_storage_hooks_init(); i_array_init(&mail_storage_classes, 8); } void mail_storage_deinit(void) { 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); if (array_is_created(&mail_storage_classes)) array_free(&mail_storage_classes); mail_storage_hooks_deinit(); mailbox_lists_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_append(&mail_storage_classes, &storage_class, 1); } 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++; 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_storage_find_class(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 (strncmp(ns->set->location, "auto:", 5) == 0) { *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, bool autocreate, const char **error_r) { struct stat st; if (stat(root_dir, &st) == 0) { /* exists */ return 1; } else if (errno == EACCES) { *error_r = mail_error_eacces_msg("stat", root_dir); return -1; } else if (errno != ENOENT && errno != ENOTDIR) { *error_r = t_strdup_printf("stat(%s) failed: %m", root_dir); return -1; } else if (!autocreate) { *error_r = t_strdup_printf( "Root mail directory doesn't exist: %s", root_dir); return -1; } else { /* doesn't exist */ return 0; } } static int mail_storage_create_root(struct mailbox_list *list, enum mail_storage_flags flags, const char **error_r) { const char *root_dir, *error; bool autocreate; int ret; root_dir = mailbox_list_get_path(list, NULL, MAILBOX_LIST_PATH_TYPE_MAILBOX); if (root_dir == NULL) { /* storage doesn't use directories (e.g. shared root) */ return 0; } if ((flags & MAIL_STORAGE_FLAG_NO_AUTOVERIFY) != 0) { if (!list->mail_set->mail_debug) 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, FALSE, &error) < 0) { i_debug("Namespace %s: Creating storage despite: %s", list->ns->prefix, error); } return 0; } autocreate = (flags & MAIL_STORAGE_FLAG_NO_AUTOCREATE) == 0; ret = mail_storage_verify_root(root_dir, autocreate, error_r); if (ret == 0) { ret = mailbox_list_mkdir_root(list, root_dir, MAILBOX_LIST_PATH_TYPE_MAILBOX, error_r); } return ret < 0 ? -1 : 0; } 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) != 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(struct mail_namespace *ns, const char *driver, enum mail_storage_flags flags, 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 *data = ns->set->location; 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; } } } memset(&list_set, 0, sizeof(list_set)); list_set.mailbox_dir_name = ""; list_set.maildir_name = ""; 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 (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 (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); return 0; } storage = storage_class->v.alloc(); storage->refcount = 1; storage->storage_class = storage_class; storage->user = ns->user; storage->set = ns->mail_set; storage->flags = flags; 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); pool_unref(&storage->pool); return -1; } T_BEGIN { hook_mail_storage_created(storage); } T_END; DLLIST_PREPEND(&ns->user->storages, storage); mail_namespace_add_storage(ns, storage); return 0; } 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); if (storage->v.destroy != NULL) storage->v.destroy(storage); i_free(storage->error_string); *_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); storage->error = MAIL_ERROR_NONE; } void mail_storage_set_error(struct mail_storage *storage, enum mail_error error, const char *string) { i_free(storage->error_string); storage->error_string = i_strdup(string); 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; } void mail_storage_set_critical(struct mail_storage *storage, const char *fmt, ...) { va_list va; va_start(va, fmt); i_error("%s", t_strdup_vprintf(fmt, va)); va_end(va); /* 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); } 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 mail_storage_set_index_error(struct mailbox *box) { if (mail_index_is_deleted(box->index)) mailbox_set_deleted(box); else mail_storage_set_internal_error(box->storage); mail_index_reset_error(box->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; (void)mail_storage_get_last_error(box->storage, &error); return error; } 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 (storage->set->mail_debug && 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_user *user, const char *vname) { struct mailbox_settings *const *box_set; struct mail_namespace *ns; ns = mail_namespace_find(user->namespaces, vname); if (ns == NULL) return NULL; 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(&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; i_assert(uni_utf8_str_is_valid(vname)); if ((list->ns->flags & NAMESPACE_FLAG_INBOX_USER) != 0 && strncasecmp(vname, "INBOX", 5) == 0 && strncmp(vname, "INBOX", 5) != 0) { /* make sure INBOX shows up in uppercase everywhere */ if (vname[5] == '\0') vname = "INBOX"; else if (vname[5] == mail_namespace_get_sep(list->ns)) vname = t_strconcat("INBOX", vname + 5, NULL); } if (mailbox_list_get_storage(&new_list, vname, &storage) < 0) { /* just use the first storage. FIXME: does this break? */ storage = list->ns->storage; } T_BEGIN { box = storage->v.mailbox_alloc(storage, new_list, vname, flags); box->set = mailbox_settings_find(storage->user, vname); 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; } i_error("mailbox_alloc_guid(%s): " "Couldn't verify mailbox GUID: %s", guid_128_to_string(guid), mailbox_get_last_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 have_listable_namespace_prefix(struct mail_namespace *ns, const char *name) { unsigned int 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; } static bool mailbox_is_autocreated(struct mailbox *box) { if (box->inbox_user) return TRUE; return box->set != NULL && strcmp(box->set->autocreate, MAILBOX_SET_AUTO_NO) != 0; } 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_list_is_valid_existing_name(box->list, box->name)) { /* report it as not selectable, since it exists but we won't let it be opened. */ *existence_r = MAILBOX_EXISTENCE_NOSELECT; return 0; } if (!box->inbox_user && have_listable_namespace_prefix(box->storage->user->namespaces, box->vname)) { /* listable namespace prefix always exists */ *existence_r = MAILBOX_EXISTENCE_NOSELECT; return 0; } if (auto_boxes && box->set != NULL && mailbox_is_autocreated(box)) { *existence_r = MAILBOX_EXISTENCE_SELECT; return 0; } return box->v.exists(box, auto_boxes, existence_r); } static int mailbox_check_mismatching_separators(struct mailbox *box) { struct mail_namespace *ns = box->list->ns; const char *p, *vname = box->vname; char list_sep, ns_sep; if (box->inbox_user) { /* this is INBOX - don't bother with further checks */ return 0; } list_sep = mailbox_list_get_hierarchy_sep(box->list); ns_sep = mail_namespace_get_sep(ns); if (ns->prefix_len > 0) { /* vname is either "namespace/box" or "namespace" */ i_assert(strncmp(vname, ns->prefix, ns->prefix_len-1) == 0); 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. */ mail_storage_set_error(box->storage, MAIL_ERROR_PARAMS, "Invalid mailbox name"); return -1; } } } if (ns_sep == list_sep || box->list->set.escape_char != '\0') return 0; for (p = vname; *p != '\0'; p++) { if (*p == list_sep) { mail_storage_set_error(box->storage, MAIL_ERROR_PARAMS, t_strdup_printf("Character not allowed " "in mailbox name: '%c'", list_sep)); return -1; } } return 0; } static void mailbox_autocreate(struct mailbox *box) { const char *errstr; enum mail_error error; if (mailbox_create(box, NULL, FALSE) < 0) { errstr = mailbox_get_last_error(box, &error); if (error != MAIL_ERROR_NOTFOUND && !box->inbox_user) { mail_storage_set_critical(box->storage, "Failed to autocreate mailbox %s: %s", box->vname, errstr); } } else if (box->set != NULL && strcmp(box->set->autocreate, MAILBOX_SET_AUTO_SUBSCRIBE) == 0) { if (mailbox_set_subscribed(box, TRUE) < 0) { mail_storage_set_critical(box->storage, "Failed to autosubscribe to mailbox %s: %s", box->vname, mailbox_get_last_error(box, NULL)); } } } static int mailbox_autocreate_and_reopen(struct mailbox *box) { int ret; mailbox_autocreate(box); mailbox_close(box); ret = box->v.open(box); if (ret < 0 && box->inbox_user && !box->storage->user->inbox_open_error_logged) { box->storage->user->inbox_open_error_logged = TRUE; mail_storage_set_critical(box->storage, "Opening INBOX failed: %s", mailbox_get_last_error(box, NULL)); } return ret; } static int mailbox_open_full(struct mailbox *box, struct istream *input) { int ret; if (box->opened) return 0; switch (box->open_error) { case 0: 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_check_mismatching_separators(box) < 0) return -1; if (!mailbox_list_is_valid_existing_name(box->list, box->name)) { mail_storage_set_error(box->storage, MAIL_ERROR_PARAMS, "Invalid mailbox name"); return -1; } if (input != NULL) { if ((box->storage->class_flags & MAIL_STORAGE_CLASS_FLAG_OPEN_STREAMS) == 0) { mail_storage_set_critical(box->storage, "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->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; } int mailbox_open(struct mailbox *box) { return mailbox_open_full(box, NULL); } 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) { return box->v.enable(box, features); } enum mailbox_feature mailbox_get_enabled_features(struct mailbox *box) { return box->enabled_features; } 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); box->opened = FALSE; box->mailbox_deleted = FALSE; array_clear(&box->search_results); } void mailbox_free(struct mailbox **_box) { struct mailbox *box = *_box; *_box = NULL; mailbox_close(box); box->v.free(box); DLLIST_REMOVE(&box->storage->mailboxes, box); mail_storage_obj_unref(box->storage); pool_unref(&box->pool); } int mailbox_create(struct mailbox *box, const struct mailbox_update *update, bool directory) { enum mailbox_dir_create_type type; int ret; if (!mailbox_list_is_valid_create_name(box->list, box->name)) { mail_storage_set_error(box->storage, MAIL_ERROR_PARAMS, "Invalid mailbox name"); return -1; } type = directory ? MAILBOX_DIR_CREATE_TYPE_TRY_NOSELECT : MAILBOX_DIR_CREATE_TYPE_MAILBOX; if (box->list->v.create_mailbox_dir(box->list, box->name, type) < 0) { mail_storage_copy_list_error(box->storage, box->list); if (directory || mailbox_get_last_mail_error(box) != MAIL_ERROR_EXISTS) return -1; /* the directory already exists, but the mailbox might not */ } mailbox_refresh_permissions(box); box->creating = TRUE; ret = box->v.create(box, update, directory); box->creating = FALSE; return ret; } int mailbox_update(struct mailbox *box, const struct mailbox_update *update) { i_assert(update->min_next_uid == 0 || update->min_first_recent_uid == 0 || update->min_first_recent_uid <= update->min_next_uid); return box->v.update(box, update); } 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; 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 & ~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) { mail_storage_set_index_error(box); return -1; } /* 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. */ if (mailbox_sync(box, MAILBOX_SYNC_FLAG_FULL_READ) < 0) return -1; box->marked_deleted = del; return 0; } static bool mailbox_try_undelete(struct mailbox *box) { time_t mtime; if (mail_index_get_modification_time(box->index, &mtime) < 0) return FALSE; if (mtime + MAILBOX_DELETE_RETRY_SECS > time(NULL)) return FALSE; if (mailbox_mark_index_deleted(box, FALSE) < 0) return FALSE; box->mailbox_deleted = FALSE; return TRUE; } int mailbox_delete(struct mailbox *box) { int ret; if (*box->name == '\0') { mail_storage_set_error(box->storage, MAIL_ERROR_PARAMS, "Storage root can't be deleted"); return -1; } if (box->inbox_any) { mail_storage_set_error(box->storage, MAIL_ERROR_NOTPOSSIBLE, "INBOX can't be deleted."); return -1; } box->deleting = TRUE; if (mailbox_open(box) < 0) { if (mailbox_get_last_mail_error(box) != MAIL_ERROR_NOTFOUND) return -1; if (!box->mailbox_deleted) { /* \noselect mailbox */ } else { /* if deletion happened a long time ago, it means it crashed while doing it. undelete the mailbox in that case. */ if (!mailbox_try_undelete(box)) return -1; /* retry */ if (mailbox_open(box) < 0) return -1; } } ret = box->v.delete(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) return -1; } box->deleting = FALSE; mailbox_close(box); 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 = "one namespace has alt dir and another doesn't"; return FALSE; } if (!nullequals(list1->set.index_dir, list2->set.index_dir)) { *error_r = "one namespace has index dir and another doesn't"; return FALSE; } if (!nullequals(list1->set.control_dir, list2->set.control_dir)) { *error_r = "one namespace has control dir and another doesn't"; return FALSE; } return TRUE; } int mailbox_rename(struct mailbox *src, struct mailbox *dest, bool rename_children) { const char *error = NULL; if (!mailbox_list_is_valid_existing_name(src->list, src->name) || *src->name == '\0' || !mailbox_list_is_valid_create_name(dest->list, dest->name)) { mail_storage_set_error(src->storage, MAIL_ERROR_PARAMS, "Invalid mailbox name"); return -1; } if (!mail_storages_rename_compatible(src->storage, dest->storage, &error) || !mailbox_lists_rename_compatible(src->list, dest->list, &error)) { if (src->storage->set->mail_debug) { i_debug("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 != NAMESPACE_PRIVATE || dest->list->ns->type != NAMESPACE_PRIVATE)) { mail_storage_set_error(src->storage, MAIL_ERROR_NOTPOSSIBLE, "Renaming not supported across non-private namespaces."); return -1; } return src->v.rename(src, dest, rename_children); } int mailbox_set_subscribed(struct mailbox *box, bool set) { if (!mailbox_list_is_valid_existing_name(box->list, box->name)) { mail_storage_set_error(box->storage, MAIL_ERROR_PARAMS, "Invalid mailbox name"); return -1; } return box->v.set_subscribed(box, set); } 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) { 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; } int mailbox_get_status(struct mailbox *box, enum mailbox_status_items items, struct mailbox_status *status_r) { memset(status_r, 0, sizeof(*status_r)); return box->v.get_status(box, items, status_r); } void mailbox_get_open_status(struct mailbox *box, enum mailbox_status_items items, struct mailbox_status *status_r) { i_assert(box->opened); memset(status_r, 0, sizeof(*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) { memset(metadata_r, 0, sizeof(*metadata_r)); 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 0; else return box->v.get_private_flags_mask(box); } 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); } 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) { 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; memset(status_r, 0, sizeof(*status_r)); ret = box->v.sync_deinit(ctx, status_r); if (ret < 0 && box->inbox_user && !box->storage->user->inbox_open_error_logged) { errormsg = mailbox_get_last_error(box, &error); if (error == MAIL_ERROR_NOTPOSSIBLE) { box->storage->user->inbox_open_error_logged = TRUE; i_error("Syncing INBOX failed: %s", errormsg); } } 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, unsigned int min_interval, mailbox_notify_callback_t *callback, void *context) { box->notify_min_interval = min_interval; box->notify_callback = callback; box->notify_context = context; box->v.notify_changes(box); } void mailbox_notify_changes_stop(struct mailbox *box) { mailbox_notify_changes(box, 0, NULL, NULL); } 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) { 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; 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; } 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) { struct mailbox_transaction_context *trans; box->transaction_count++; trans = box->v.transaction_begin(box, flags); trans->flags = flags; 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. */ changes.pool = NULL; ret = mailbox_transaction_commit_get_changes(t, &changes); if (changes.pool != NULL) 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; int ret; t->box->transaction_count--; *_t = NULL; T_BEGIN { ret = t->box->v.transaction_commit(t, changes_r); } T_END; return ret; } void mailbox_transaction_rollback(struct mailbox_transaction_context **_t) { struct mailbox_transaction_context *t = *_t; t->box->transaction_count--; *_t = NULL; t->box->v.transaction_rollback(t); } 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; } struct mail_save_context * mailbox_save_alloc(struct mailbox_transaction_context *t) { struct mail_save_context *ctx; ctx = t->box->v.save_alloc(t); ctx->received_date = (time_t)-1; ctx->save_date = (time_t)-1; return ctx; } void mailbox_save_set_flags(struct mail_save_context *ctx, enum mail_flags flags, struct mail_keywords *keywords) { ctx->flags = flags; ctx->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; keywords_list = mail_get_keywords(mail); ctx->keywords = str_array_length(keywords_list) == 0 ? NULL : mailbox_keywords_create_valid(ctx->transaction->box, keywords_list); ctx->flags = mail_get_flags(mail); } void mailbox_save_set_min_modseq(struct mail_save_context *ctx, uint64_t min_modseq) { ctx->min_modseq = min_modseq; } void mailbox_save_set_received_date(struct mail_save_context *ctx, time_t received_date, int timezone_offset) { ctx->received_date = received_date; ctx->received_tz_offset = timezone_offset; } void mailbox_save_set_save_date(struct mail_save_context *ctx, time_t save_date) { ctx->save_date = save_date; } void mailbox_save_set_from_envelope(struct mail_save_context *ctx, const char *envelope) { i_free(ctx->from_envelope); ctx->from_envelope = i_strdup(envelope); } void mailbox_save_set_uid(struct mail_save_context *ctx, uint32_t uid) { ctx->uid = uid; } void mailbox_save_set_guid(struct mail_save_context *ctx, const char *guid) { i_assert(guid == NULL || *guid != '\0'); i_free(ctx->guid); ctx->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->pop3_uidl); ctx->pop3_uidl = i_strdup(uidl); } void mailbox_save_set_dest_mail(struct mail_save_context *ctx, struct mail *mail) { ctx->dest_mail = 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); return -1; } (*ctx)->saving = TRUE; if (box->v.save_begin == NULL) { mail_storage_set_error(box->storage, MAIL_ERROR_NOTPOSSIBLE, "Saving messages not supported"); ret = -1; } else { ret = box->v.save_begin(*ctx, input); } if (ret < 0) { mailbox_save_cancel(ctx); return -1; } return 0; } int mailbox_save_continue(struct mail_save_context *ctx) { return ctx->transaction->box->v.save_continue(ctx); } int mailbox_save_finish(struct mail_save_context **_ctx) { struct mail_save_context *ctx = *_ctx; struct mailbox *box = ctx->transaction->box; struct mail_keywords *keywords = ctx->keywords; int ret; *_ctx = NULL; ret = box->v.save_finish(ctx); if (keywords != NULL) mailbox_keywords_unref(&keywords); return ret; } void mailbox_save_cancel(struct mail_save_context **_ctx) { struct mail_save_context *ctx = *_ctx; struct mail_keywords *keywords = ctx->keywords; *_ctx = NULL; ctx->transaction->box->v.save_cancel(ctx); if (keywords != NULL) mailbox_keywords_unref(&keywords); } struct mailbox_transaction_context * mailbox_save_get_transaction(struct mail_save_context *ctx) { return ctx->transaction; } int mailbox_copy(struct mail_save_context **_ctx, struct mail *mail) { struct mail_save_context *ctx = *_ctx; struct mailbox *box = ctx->transaction->box; struct mail_keywords *keywords = ctx->keywords; int ret; *_ctx = NULL; if (mail_index_is_deleted(box->index)) { mailbox_set_deleted(box); mailbox_save_cancel(_ctx); return -1; } ret = ctx->transaction->box->v.copy(ctx, mail); if (keywords != NULL) mailbox_keywords_unref(&keywords); return ret; } int mailbox_save_using_mail(struct mail_save_context **ctx, struct mail *mail) { (*ctx)->saving = TRUE; return mailbox_copy(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; } const char *mailbox_get_path(struct mailbox *box) { const char *path; if (box->_path == NULL) { path = mailbox_list_get_path(box->list, box->name, MAILBOX_LIST_PATH_TYPE_MAILBOX); box->_path = p_strdup(box->pool, path); } return box->_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; } mailbox_list_get_permissions(box->list, box->name, &box->_perm); box->_perm.file_create_gid_origin = p_strdup(box->pool, box->_perm.file_create_gid_origin); } 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) { memset(&box->_perm, 0, sizeof(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 { mail_storage_set_critical(box->storage, "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) { mail_storage_set_critical(box->storage, "%s", eperm_error_get_chgrp("fchown", path, perm->file_create_gid, perm->file_create_gid_origin)); } else { mail_storage_set_critical(box->storage, "fchown(%s) failed: %m", path); } } *fd_r = fd; return 1; } 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); }