/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "ioloop.h" #include "dict.h" #include "index-storage.h" struct index_storage_attribute_iter { struct mailbox_attribute_iter iter; struct dict_iterate_context *diter; char *prefix; size_t prefix_len; bool dict_disabled; }; static struct mail_namespace * mail_user_find_attribute_namespace(struct mail_user *user) { struct mail_namespace *ns; ns = mail_namespace_find_inbox(user->namespaces); if (ns != NULL) return ns; for (ns = user->namespaces; ns != NULL; ns = ns->next) { if (ns->type == MAIL_NAMESPACE_TYPE_PRIVATE) return ns; } return NULL; } static int index_storage_get_user_dict(struct mail_storage *err_storage, struct mail_user *user, struct dict **dict_r) { struct dict_settings dict_set; struct mail_namespace *ns; struct mail_storage *attr_storage; const char *error; if (user->_attr_dict != NULL) { *dict_r = user->_attr_dict; return 0; } if (user->attr_dict_failed) { mail_storage_set_internal_error(err_storage); return -1; } ns = mail_user_find_attribute_namespace(user); if (ns == NULL) { /* probably never happens? */ mail_storage_set_error(err_storage, MAIL_ERROR_NOTPOSSIBLE, "Mailbox attributes not available for this mailbox"); return -1; } attr_storage = mail_namespace_get_default_storage(ns); if (*attr_storage->set->mail_attribute_dict == '\0') { mail_storage_set_error(err_storage, MAIL_ERROR_NOTPOSSIBLE, "Mailbox attributes not enabled"); return -1; } i_zero(&dict_set); dict_set.base_dir = user->set->base_dir; dict_set.event_parent = user->event; if (dict_init(attr_storage->set->mail_attribute_dict, &dict_set, &user->_attr_dict, &error) < 0) { mail_storage_set_critical(err_storage, "mail_attribute_dict: dict_init(%s) failed: %s", attr_storage->set->mail_attribute_dict, error); user->attr_dict_failed = TRUE; return -1; } *dict_r = user->_attr_dict; return 0; } static int index_storage_get_dict(struct mailbox *box, enum mail_attribute_type type_flags, struct dict **dict_r, const char **mailbox_prefix_r) { enum mail_attribute_type type = type_flags & MAIL_ATTRIBUTE_TYPE_MASK; struct mail_storage *storage = box->storage; struct mail_namespace *ns; struct mailbox_metadata metadata; struct dict_settings set; const char *error; if ((type_flags & MAIL_ATTRIBUTE_TYPE_FLAG_VALIDATED) != 0) { /* IMAP METADATA support isn't enabled, so don't allow using mail_attribute_dict. */ mail_storage_set_error(storage, MAIL_ERROR_NOTPOSSIBLE, "Generic mailbox attributes not enabled"); return -1; } if (mailbox_get_metadata(box, MAILBOX_METADATA_GUID, &metadata) < 0) return -1; *mailbox_prefix_r = guid_128_to_string(metadata.guid); ns = mailbox_get_namespace(box); if (type == MAIL_ATTRIBUTE_TYPE_PRIVATE) { /* private attributes are stored in user's own dict */ return index_storage_get_user_dict(storage, storage->user, dict_r); } else if (ns->user == ns->owner) { /* user owns the mailbox. shared attributes are stored in the same dict. */ return index_storage_get_user_dict(storage, storage->user, dict_r); } else if (ns->owner != NULL) { /* accessing shared attribute of a shared mailbox. use the owner's dict. */ return index_storage_get_user_dict(storage, ns->owner, dict_r); } /* accessing shared attributes of a public mailbox. no user owns it, so use the storage's dict. */ if (storage->_shared_attr_dict != NULL) { *dict_r = storage->_shared_attr_dict; return 0; } if (*storage->set->mail_attribute_dict == '\0') { mail_storage_set_error(storage, MAIL_ERROR_NOTPOSSIBLE, "Mailbox attributes not enabled"); return -1; } if (storage->shared_attr_dict_failed) { mail_storage_set_internal_error(storage); return -1; } i_zero(&set); set.base_dir = storage->user->set->base_dir; set.event_parent = storage->user->event; if (dict_init(storage->set->mail_attribute_dict, &set, &storage->_shared_attr_dict, &error) < 0) { mail_storage_set_critical(storage, "mail_attribute_dict: dict_init(%s) failed: %s", storage->set->mail_attribute_dict, error); storage->shared_attr_dict_failed = TRUE; return -1; } *dict_r = storage->_shared_attr_dict; return 0; } static const char * key_get_prefixed(enum mail_attribute_type type_flags, const char *mailbox_prefix, const char *key) { enum mail_attribute_type type = type_flags & MAIL_ATTRIBUTE_TYPE_MASK; switch (type) { case MAIL_ATTRIBUTE_TYPE_PRIVATE: return t_strconcat(DICT_PATH_PRIVATE, mailbox_prefix, "/", key, NULL); case MAIL_ATTRIBUTE_TYPE_SHARED: return t_strconcat(DICT_PATH_SHARED, mailbox_prefix, "/", key, NULL); } i_unreached(); } static int index_storage_attribute_get_dict_trans(struct mailbox_transaction_context *t, enum mail_attribute_type type_flags, struct dict_transaction_context **dtrans_r, const char **mailbox_prefix_r) { enum mail_attribute_type type = type_flags & MAIL_ATTRIBUTE_TYPE_MASK; struct dict_transaction_context **dtransp = NULL; struct dict *dict; struct mailbox_metadata metadata; switch (type) { case MAIL_ATTRIBUTE_TYPE_PRIVATE: dtransp = &t->attr_pvt_trans; break; case MAIL_ATTRIBUTE_TYPE_SHARED: dtransp = &t->attr_shared_trans; break; } i_assert(dtransp != NULL); if (*dtransp != NULL && (type_flags & MAIL_ATTRIBUTE_TYPE_FLAG_VALIDATED) == 0) { /* Transaction already created. Even if it was, don't use it if _FLAG_VALIDATED is being used. It'll be handled below by returning failure. */ if (mailbox_get_metadata(t->box, MAILBOX_METADATA_GUID, &metadata) < 0) return -1; *mailbox_prefix_r = guid_128_to_string(metadata.guid); *dtrans_r = *dtransp; return 0; } if (index_storage_get_dict(t->box, type_flags, &dict, mailbox_prefix_r) < 0) return -1; i_assert(*dtransp == NULL); struct mail_user *user = mailbox_list_get_user(t->box->list); const struct dict_op_settings *set = mail_user_get_dict_op_settings(user); *dtransp = *dtrans_r = dict_transaction_begin(dict, set); return 0; } int index_storage_attribute_set(struct mailbox_transaction_context *t, enum mail_attribute_type type_flags, const char *key, const struct mail_attribute_value *value) { enum mail_attribute_type type = type_flags & MAIL_ATTRIBUTE_TYPE_MASK; struct dict_transaction_context *dtrans; const char *mailbox_prefix; bool pvt = type == MAIL_ATTRIBUTE_TYPE_PRIVATE; time_t ts = value->last_change != 0 ? value->last_change : ioloop_time; int ret = 0; if (index_storage_attribute_get_dict_trans(t, type_flags, &dtrans, &mailbox_prefix) < 0) return -1; T_BEGIN { const char *prefixed_key = key_get_prefixed(type_flags, mailbox_prefix, key); const char *value_str; if (mailbox_attribute_value_to_string(t->box->storage, value, &value_str) < 0) { ret = -1; } else if (value_str != NULL) { dict_set(dtrans, prefixed_key, value_str); mail_index_attribute_set(t->itrans, pvt, key, ts, strlen(value_str)); } else { dict_unset(dtrans, prefixed_key); mail_index_attribute_unset(t->itrans, pvt, key, ts); } } T_END; return ret; } int index_storage_attribute_get(struct mailbox *box, enum mail_attribute_type type_flags, const char *key, struct mail_attribute_value *value_r) { struct dict *dict; const char *mailbox_prefix, *error; int ret; i_zero(value_r); if (index_storage_get_dict(box, type_flags, &dict, &mailbox_prefix) < 0) return -1; struct mail_user *user = mailbox_list_get_user(box->list); const struct dict_op_settings *set = mail_user_get_dict_op_settings(user); ret = dict_lookup(dict, set, pool_datastack_create(), key_get_prefixed(type_flags, mailbox_prefix, key), &value_r->value, &error); if (ret < 0) { mailbox_set_critical(box, "Failed to get attribute %s: %s", key, error); return -1; } return ret; } struct mailbox_attribute_iter * index_storage_attribute_iter_init(struct mailbox *box, enum mail_attribute_type type_flags, const char *prefix) { struct index_storage_attribute_iter *iter; struct dict *dict; const char *mailbox_prefix; iter = i_new(struct index_storage_attribute_iter, 1); iter->iter.box = box; if (index_storage_get_dict(box, type_flags, &dict, &mailbox_prefix) < 0) { if (mailbox_get_last_mail_error(box) == MAIL_ERROR_NOTPOSSIBLE) iter->dict_disabled = TRUE; } else { iter->prefix = i_strdup(key_get_prefixed(type_flags, mailbox_prefix, prefix)); iter->prefix_len = strlen(iter->prefix); struct mail_user *user = mailbox_list_get_user(box->list); const struct dict_op_settings *set = mail_user_get_dict_op_settings(user); iter->diter = dict_iterate_init(dict, set, iter->prefix, DICT_ITERATE_FLAG_RECURSE | DICT_ITERATE_FLAG_NO_VALUE); } return &iter->iter; } const char * index_storage_attribute_iter_next(struct mailbox_attribute_iter *_iter) { struct index_storage_attribute_iter *iter = (struct index_storage_attribute_iter *)_iter; const char *key, *value; if (iter->diter == NULL || !dict_iterate(iter->diter, &key, &value)) return NULL; i_assert(strncmp(key, iter->prefix, iter->prefix_len) == 0); key += iter->prefix_len; return key; } int index_storage_attribute_iter_deinit(struct mailbox_attribute_iter *_iter) { struct index_storage_attribute_iter *iter = (struct index_storage_attribute_iter *)_iter; const char *error; int ret; if (iter->diter == NULL) { ret = iter->dict_disabled ? 0 : -1; } else { if ((ret = dict_iterate_deinit(&iter->diter, &error)) < 0) { mailbox_set_critical(_iter->box, "dict_iterate(%s) failed: %s", iter->prefix, error); } } i_free(iter->prefix); i_free(iter); return ret; }