summaryrefslogtreecommitdiffstats
path: root/src/lib-storage/mailbox-attribute.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib-storage/mailbox-attribute.c')
-rw-r--r--src/lib-storage/mailbox-attribute.c587
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 = &regs[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;
+}