diff options
Diffstat (limited to 'src/lib-storage/mailbox-attribute.c')
-rw-r--r-- | src/lib-storage/mailbox-attribute.c | 587 |
1 files changed, 587 insertions, 0 deletions
diff --git a/src/lib-storage/mailbox-attribute.c b/src/lib-storage/mailbox-attribute.c new file mode 100644 index 0000000..f27df7f --- /dev/null +++ b/src/lib-storage/mailbox-attribute.c @@ -0,0 +1,587 @@ +/* Copyright (c) 2003-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "str.h" +#include "istream.h" +#include "mail-storage-private.h" +#include "bsearch-insert-pos.h" +#include "mailbox-attribute-internal.h" + +static ARRAY(struct mailbox_attribute_internal) mailbox_internal_attributes; +static pool_t mailbox_attribute_pool; + +void mailbox_attributes_init(void) +{ + mailbox_attribute_pool = + pool_alloconly_create("mailbox attributes", 2048); + i_array_init(&mailbox_internal_attributes, 32); + + /* internal mailbox attributes */ + mailbox_attributes_internal_init(); +} + +void mailbox_attributes_deinit(void) +{ + pool_unref(&mailbox_attribute_pool); + array_free(&mailbox_internal_attributes); +} + +/* + * Internal attributes + */ + +static int +mailbox_attribute_internal_cmp( + const struct mailbox_attribute_internal *reg1, + const struct mailbox_attribute_internal *reg2) +{ + if (reg1->type != reg2->type) + return (int)reg1->type - (int)reg2->type; + return strcmp(reg1->key, reg2->key); +} + +void mailbox_attribute_register_internal( + const struct mailbox_attribute_internal *iattr) +{ + struct mailbox_attribute_internal ireg; + unsigned int insert_idx; + + /* Validated attributes must have a set() callback that validates the + provided values. Also read-only _RANK_AUTHORITY attributes don't + need validation. */ + i_assert((iattr->flags & MAIL_ATTRIBUTE_INTERNAL_FLAG_VALIDATED) == 0 || + iattr->set != NULL || + iattr->rank == MAIL_ATTRIBUTE_INTERNAL_RANK_AUTHORITY); + + (void)array_bsearch_insert_pos(&mailbox_internal_attributes, + iattr, mailbox_attribute_internal_cmp, &insert_idx); + + ireg = *iattr; + ireg.key = p_strdup(mailbox_attribute_pool, iattr->key); + array_insert(&mailbox_internal_attributes, insert_idx, &ireg, 1); +} + +void mailbox_attribute_register_internals( + const struct mailbox_attribute_internal *iattrs, unsigned int count) +{ + unsigned int i; + + for (i = 0; i < count; i++) + mailbox_attribute_register_internal(&iattrs[i]); +} + +void mailbox_attribute_unregister_internal( + const struct mailbox_attribute_internal *iattr) +{ + unsigned int idx; + + if (!array_bsearch_insert_pos(&mailbox_internal_attributes, + iattr, mailbox_attribute_internal_cmp, &idx)) { + i_panic("mailbox_attribute_unregister_internal(%s): " + "key not found", iattr->key); + } + + array_delete(&mailbox_internal_attributes, idx, 1); +} + +void mailbox_attribute_unregister_internals( + const struct mailbox_attribute_internal *iattrs, unsigned int count) +{ + unsigned int i; + + for (i = 0; i < count; i++) + mailbox_attribute_unregister_internal(&iattrs[i]); +} + +static const struct mailbox_attribute_internal * +mailbox_internal_attribute_get_int(enum mail_attribute_type type_flags, + const char *key) +{ + const struct mailbox_attribute_internal *iattr; + struct mailbox_attribute_internal dreg; + unsigned int insert_idx; + + i_zero(&dreg); + dreg.type = type_flags & MAIL_ATTRIBUTE_TYPE_MASK; + dreg.key = key; + + if (array_bsearch_insert_pos(&mailbox_internal_attributes, + &dreg, mailbox_attribute_internal_cmp, + &insert_idx)) { + /* exact match */ + return array_idx(&mailbox_internal_attributes, insert_idx); + } + if (insert_idx == 0) { + /* not found at all */ + return NULL; + } + iattr = array_idx(&mailbox_internal_attributes, insert_idx-1); + if (!str_begins(key, iattr->key)) { + /* iattr isn't a prefix of key */ + return NULL; + } else if ((iattr->flags & MAIL_ATTRIBUTE_INTERNAL_FLAG_CHILDREN) != 0) { + /* iattr is a prefix of key and it wants to handle the key */ + return iattr; + } else { + return NULL; + } +} + +static const struct mailbox_attribute_internal * +mailbox_internal_attribute_get(enum mail_attribute_type type_flags, + const char *key) +{ + const struct mailbox_attribute_internal *iattr; + + iattr = mailbox_internal_attribute_get_int(type_flags, key); + if ((type_flags & MAIL_ATTRIBUTE_TYPE_FLAG_VALIDATED) != 0 && + iattr != NULL && + (iattr->flags & MAIL_ATTRIBUTE_INTERNAL_FLAG_VALIDATED) == 0) { + /* only validated attributes can be accessed */ + iattr = NULL; + } + return iattr; +} + +static void +mailbox_internal_attributes_add_prefixes(ARRAY_TYPE(const_string) *attrs, + pool_t pool, unsigned int old_count, + const char *key) +{ + unsigned int new_count; + + if (key[0] == '\0') + return; + new_count = array_count(attrs); + for (unsigned int i = old_count; i < new_count; i++) { + const char *old_key = array_idx_elem(attrs, i); + const char *prefixed_key; + + if (old_key[0] == '\0') + prefixed_key = p_strndup(pool, key, strlen(key)-1); + else + prefixed_key = p_strconcat(pool, key, old_key, NULL); + array_idx_set(attrs, i, &prefixed_key); + } +} + +static int +mailbox_internal_attributes_get(struct mailbox *box, + enum mail_attribute_type type_flags, const char *prefix, + pool_t attr_pool, bool have_dict, ARRAY_TYPE(const_string) *attrs) +{ + const struct mailbox_attribute_internal *regs; + struct mailbox_attribute_internal dreg; + char *bare_prefix; + size_t plen; + unsigned int count, i, j; + int ret = 0; + + bare_prefix = t_strdup_noconst(prefix); + plen = strlen(bare_prefix); + if (plen > 0 && bare_prefix[plen-1] == '/') { + bare_prefix[plen-1] = '\0'; + plen--; + } + + i_zero(&dreg); + dreg.type = type_flags & MAIL_ATTRIBUTE_TYPE_MASK; + dreg.key = bare_prefix; + + (void)array_bsearch_insert_pos(&mailbox_internal_attributes, + &dreg, mailbox_attribute_internal_cmp, &i); + + /* iterate attributes that might have children whose keys begins with + the prefix */ + regs = array_get(&mailbox_internal_attributes, &count); + for (j = i; j > 0; j--) { + const struct mailbox_attribute_internal *attr = ®s[j-1]; + + if ((attr->flags & MAIL_ATTRIBUTE_INTERNAL_FLAG_CHILDREN) == 0 || + !str_begins(bare_prefix, attr->key)) + break; + + /* For example: bare_prefix="foo/bar" and attr->key="foo/", so + iter() is called with key_prefix="bar". It could add to + attrs: { "", "baz" }, which means with the full prefix: + { "foo/bar", "foo/bar/baz" } */ + if (attr->iter != NULL && + attr->iter(box, bare_prefix + strlen(attr->key), + attr_pool, attrs) < 0) + ret = -1; + } + + /* iterate attributes whose key begins with the prefix */ + for (; i < count; i++) { + const char *key = regs[i].key; + + if (regs[i].type != dreg.type) + return ret; + if ((type_flags & MAIL_ATTRIBUTE_TYPE_FLAG_VALIDATED) != 0 && + (regs[i].flags & MAIL_ATTRIBUTE_INTERNAL_FLAG_VALIDATED) == 0) + continue; + + if (plen > 0) { + if (strncmp(key, bare_prefix, plen) != 0) + return ret; + if (key[plen] == '/') { + /* remove prefix */ + key += plen + 1; + } else if (key[plen] == '\0') { + /* list the key itself, so this becomes an + empty key string. it's the same as how the + dict backend works too. */ + key += plen; + } else { + return ret; + } + } + if (regs[i].iter != NULL) { + /* For example: bare_prefix="foo" and + attr->key="foo/bar/", so key="bar/". iter() is + always called with key_prefix="", so we're also + responsible for adding the "bar/" prefix to the + attrs that iter() returns. */ + unsigned int old_count = array_count(attrs); + if (regs[i].iter(box, "", attr_pool, attrs) < 0) + ret = -1; + mailbox_internal_attributes_add_prefixes(attrs, attr_pool, + old_count, key); + } else if (have_dict || regs[i].rank == MAIL_ATTRIBUTE_INTERNAL_RANK_AUTHORITY) + array_push_back(attrs, &key); + } + return ret; +} + +/* + * Attribute API + */ + +static int +mailbox_attribute_set_common(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; + const struct mailbox_attribute_internal *iattr; + int ret; + + iattr = mailbox_internal_attribute_get(type_flags, key); + + /* allow internal server attribute only for inbox */ + if (iattr != NULL && !t->box->inbox_any && + str_begins(key, MAILBOX_ATTRIBUTE_PREFIX_DOVECOT_PVT_SERVER)) + iattr = NULL; + + /* handle internal attribute */ + if (iattr != NULL) { + switch (iattr->rank) { + case MAIL_ATTRIBUTE_INTERNAL_RANK_DEFAULT: + case MAIL_ATTRIBUTE_INTERNAL_RANK_OVERRIDE: + /* notify about assignment */ + if (iattr->set != NULL && iattr->set(t, key, value) < 0) + return -1; + break; + case MAIL_ATTRIBUTE_INTERNAL_RANK_AUTHORITY: + if (iattr->set == NULL) { + mail_storage_set_error(t->box->storage, MAIL_ERROR_NOTPOSSIBLE, t_strdup_printf( + "The /%s/%s attribute cannot be changed", + (type == MAIL_ATTRIBUTE_TYPE_SHARED ? "shared" : "private"), key)); + return -1; + } + /* assign internal attribute */ + return iattr->set(t, key, value); + default: + i_unreached(); + } + /* the value was validated. */ + type_flags &= ENUM_NEGATE(MAIL_ATTRIBUTE_TYPE_FLAG_VALIDATED); + } + + ret = t->box->v.attribute_set(t, type_flags, key, value); + return ret; +} + +int mailbox_attribute_set(struct mailbox_transaction_context *t, + enum mail_attribute_type type_flags, const char *key, + const struct mail_attribute_value *value) +{ + return mailbox_attribute_set_common(t, type_flags, key, value); +} + +int mailbox_attribute_unset(struct mailbox_transaction_context *t, + enum mail_attribute_type type_flags, const char *key) +{ + struct mail_attribute_value value; + + i_zero(&value); + return mailbox_attribute_set_common(t, type_flags, key, &value); +} + +int mailbox_attribute_value_to_string(struct mail_storage *storage, + const struct mail_attribute_value *value, + const char **str_r) +{ + string_t *str; + const unsigned char *data; + size_t size; + + if (value->value_stream == NULL) { + *str_r = value->value; + return 0; + } + str = t_str_new(128); + i_stream_seek(value->value_stream, 0); + while (i_stream_read_more(value->value_stream, &data, &size) > 0) { + if (memchr(data, '\0', size) != NULL) { + mail_storage_set_error(storage, MAIL_ERROR_PARAMS, + "Attribute string value has NULs"); + return -1; + } + str_append_data(str, data, size); + i_stream_skip(value->value_stream, size); + } + if (value->value_stream->stream_errno != 0) { + mail_storage_set_critical(storage, "read(%s) failed: %s", + i_stream_get_name(value->value_stream), + i_stream_get_error(value->value_stream)); + return -1; + } + i_assert(value->value_stream->eof); + *str_r = str_c(str); + return 0; +} + +static int +mailbox_attribute_get_common(struct mailbox *box, + enum mail_attribute_type type_flags, + const char *key, + struct mail_attribute_value *value_r) +{ + const struct mailbox_attribute_internal *iattr; + int ret; + + if (key[0] == '\0') { + /* never exists, and may cause dict lookup errors */ + return 0; + } + + iattr = mailbox_internal_attribute_get(type_flags, key); + + /* allow internal server attributes only for the inbox */ + if (iattr != NULL && !box->inbox_user && + str_begins(key, MAILBOX_ATTRIBUTE_PREFIX_DOVECOT_PVT_SERVER)) + iattr = NULL; + + /* internal attribute */ + if (iattr != NULL) { + switch (iattr->rank) { + case MAIL_ATTRIBUTE_INTERNAL_RANK_OVERRIDE: + /* we already checked that this attribute has + validated-flag */ + type_flags &= ENUM_NEGATE(MAIL_ATTRIBUTE_TYPE_FLAG_VALIDATED); + + if (iattr->get == NULL) + break; + if ((ret = iattr->get(box, key, value_r)) != 0) { + if (ret < 0) + return -1; + value_r->flags |= MAIL_ATTRIBUTE_VALUE_FLAG_READONLY; + return 1; + } + break; + case MAIL_ATTRIBUTE_INTERNAL_RANK_DEFAULT: + break; + case MAIL_ATTRIBUTE_INTERNAL_RANK_AUTHORITY: + if ((ret = iattr->get(box, key, value_r)) <= 0) + return ret; + value_r->flags |= MAIL_ATTRIBUTE_VALUE_FLAG_READONLY; + return 1; + default: + i_unreached(); + } + } + + ret = box->v.attribute_get(box, type_flags, key, value_r); + if (ret != 0) + return ret; + + /* default entries */ + if (iattr != NULL) { + switch (iattr->rank) { + case MAIL_ATTRIBUTE_INTERNAL_RANK_DEFAULT: + if (iattr->get == NULL) + ret = 0; + else { + if ((ret = iattr->get(box, key, value_r)) < 0) + return ret; + } + if (ret > 0) { + value_r->flags |= MAIL_ATTRIBUTE_VALUE_FLAG_READONLY; + return 1; + } + break; + case MAIL_ATTRIBUTE_INTERNAL_RANK_OVERRIDE: + break; + default: + i_unreached(); + } + } + return 0; +} + +int mailbox_attribute_get(struct mailbox *box, + enum mail_attribute_type type_flags, const char *key, + struct mail_attribute_value *value_r) +{ + int ret; + i_zero(value_r); + if ((ret = mailbox_attribute_get_common(box, type_flags, key, + value_r)) <= 0) + return ret; + i_assert(value_r->value != NULL); + return 1; +} + +int mailbox_attribute_get_stream(struct mailbox *box, + enum mail_attribute_type type_flags, + const char *key, + struct mail_attribute_value *value_r) +{ + int ret; + + i_zero(value_r); + value_r->flags |= MAIL_ATTRIBUTE_VALUE_FLAG_INT_STREAMS; + if ((ret = mailbox_attribute_get_common(box, type_flags, key, + value_r)) <= 0) + return ret; + i_assert(value_r->value != NULL || value_r->value_stream != NULL); + return 1; +} + +struct mailbox_attribute_internal_iter { + struct mailbox_attribute_iter iter; + pool_t pool; + + ARRAY_TYPE(const_string) extra_attrs; + unsigned int extra_attr_idx; + + struct mailbox_attribute_iter *real_iter; + bool iter_failed; +}; + +struct mailbox_attribute_iter * +mailbox_attribute_iter_init(struct mailbox *box, + enum mail_attribute_type type_flags, + const char *prefix) +{ + struct mailbox_attribute_internal_iter *intiter; + struct mailbox_attribute_iter *iter; + ARRAY_TYPE(const_string) extra_attrs; + const char *const *attr; + pool_t pool; + bool have_dict, failed = FALSE; + + iter = box->v.attribute_iter_init(box, type_flags, prefix); + i_assert(iter->box != NULL); + box->attribute_iter_count++; + + /* check which internal attributes may apply */ + t_array_init(&extra_attrs, 4); + have_dict = box->storage->set->mail_attribute_dict[0] != '\0'; + pool = pool_alloconly_create("mailbox internal attribute iter", 128); + if (mailbox_internal_attributes_get(box, type_flags, prefix, pool, + have_dict, &extra_attrs) < 0) + failed = TRUE; + + /* any extra internal attributes to add? */ + if (array_count(&extra_attrs) == 0 && !failed) { + /* no */ + pool_unref(&pool); + return iter; + } + + /* yes */ + intiter = p_new(pool, struct mailbox_attribute_internal_iter, 1); + intiter->pool = pool; + intiter->real_iter = iter; + intiter->iter_failed = failed; + p_array_init(&intiter->extra_attrs, pool, 4); + + /* copy relevant attributes */ + array_foreach(&extra_attrs, attr) { + /* skip internal server attributes unless we're iterating inbox */ + if (!box->inbox_user && + strncmp(*attr, MAILBOX_ATTRIBUTE_PREFIX_DOVECOT_PVT_SERVER, + strlen(MAILBOX_ATTRIBUTE_PREFIX_DOVECOT_PVT_SERVER)) == 0) + continue; + array_push_back(&intiter->extra_attrs, attr); + } + return &intiter->iter; +} + +const char *mailbox_attribute_iter_next(struct mailbox_attribute_iter *iter) +{ + struct mailbox_attribute_internal_iter *intiter; + const char *const *attrs; + unsigned int count, i; + const char *result; + + if (iter->box != NULL) { + /* no internal attributes to add */ + return iter->box->v.attribute_iter_next(iter); + } + + /* filter out duplicate results */ + intiter = (struct mailbox_attribute_internal_iter *)iter; + attrs = array_get(&intiter->extra_attrs, &count); + while ((result = intiter->real_iter->box-> + v.attribute_iter_next(intiter->real_iter)) != NULL) { + for (i = 0; i < count; i++) { + if (strcasecmp(attrs[i], result) == 0) + break; + } + if (i == count) { + /* return normally */ + return result; + } + /* this attribute name is also to be returned as extra; + skip now */ + } + + /* return extra attributes at the end */ + if (intiter->extra_attr_idx < count) + return attrs[intiter->extra_attr_idx++]; + return NULL; +} + +int mailbox_attribute_iter_deinit(struct mailbox_attribute_iter **_iter) +{ + struct mailbox_attribute_iter *iter = *_iter; + struct mailbox_attribute_internal_iter *intiter; + int ret; + + *_iter = NULL; + + if (iter->box != NULL) { + /* not wrapped */ + i_assert(iter->box->attribute_iter_count > 0); + iter->box->attribute_iter_count--; + return iter->box->v.attribute_iter_deinit(iter); + } + + /* wrapped */ + intiter = (struct mailbox_attribute_internal_iter *)iter; + + i_assert(intiter->real_iter->box->attribute_iter_count > 0); + intiter->real_iter->box->attribute_iter_count--; + + ret = intiter->real_iter->box->v.attribute_iter_deinit(intiter->real_iter); + if (intiter->iter_failed) + ret = -1; + pool_unref(&intiter->pool); + return ret; +} |