diff options
Diffstat (limited to 'src/plugins/acl/acl-lookup-dict.c')
-rw-r--r-- | src/plugins/acl/acl-lookup-dict.c | 373 |
1 files changed, 373 insertions, 0 deletions
diff --git a/src/plugins/acl/acl-lookup-dict.c b/src/plugins/acl/acl-lookup-dict.c new file mode 100644 index 0000000..638a767 --- /dev/null +++ b/src/plugins/acl/acl-lookup-dict.c @@ -0,0 +1,373 @@ +/* Copyright (c) 2008-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "str.h" +#include "dict.h" +#include "mail-user.h" +#include "mail-namespace.h" +#include "acl-api-private.h" +#include "acl-storage.h" +#include "acl-plugin.h" +#include "acl-lookup-dict.h" + + +#define DICT_SHARED_BOXES_PATH "shared-boxes/" + +struct acl_lookup_dict { + struct mail_user *user; + struct dict *dict; +}; + +struct acl_lookup_dict_iter { + pool_t pool; + struct acl_lookup_dict *dict; + + pool_t iter_value_pool; + ARRAY_TYPE(const_string) iter_ids; + ARRAY_TYPE(const_string) iter_values; + unsigned int iter_idx, iter_value_idx; + + bool failed:1; +}; + +struct acl_lookup_dict *acl_lookup_dict_init(struct mail_user *user) +{ + struct acl_lookup_dict *dict; + const char *uri, *error; + + dict = i_new(struct acl_lookup_dict, 1); + dict->user = user; + + uri = mail_user_plugin_getenv(user, "acl_shared_dict"); + if (uri != NULL) { + struct dict_settings dict_set; + + i_zero(&dict_set); + dict_set.base_dir = user->set->base_dir; + dict_set.event_parent = user->event; + if (dict_init(uri, &dict_set, &dict->dict, &error) < 0) + i_error("acl: dict_init(%s) failed: %s", uri, error); + } else { + e_debug(user->event, "acl: No acl_shared_dict setting - " + "shared mailbox listing is disabled"); + } + return dict; +} + +void acl_lookup_dict_deinit(struct acl_lookup_dict **_dict) +{ + struct acl_lookup_dict *dict = *_dict; + + *_dict = NULL; + if (dict->dict != NULL) + dict_deinit(&dict->dict); + i_free(dict); +} + +bool acl_lookup_dict_is_enabled(struct acl_lookup_dict *dict) +{ + return dict->dict != NULL; +} + +static void +acl_lookup_dict_write_rights_id(string_t *dest, const struct acl_rights *right) +{ + switch (right->id_type) { + case ACL_ID_ANYONE: + case ACL_ID_AUTHENTICATED: + /* don't bother separating these */ + str_append(dest, "anyone"); + break; + case ACL_ID_USER: + str_append(dest, "user/"); + str_append(dest, right->identifier); + break; + case ACL_ID_GROUP: + case ACL_ID_GROUP_OVERRIDE: + str_append(dest, "group/"); + str_append(dest, right->identifier); + break; + case ACL_ID_OWNER: + case ACL_ID_TYPE_COUNT: + i_unreached(); + } +} + +static bool +acl_rights_is_same_user(const struct acl_rights *right, struct mail_user *user) +{ + return right->id_type == ACL_ID_USER && + strcmp(right->identifier, user->username) == 0; +} + +static int acl_lookup_dict_rebuild_add_backend(struct mail_namespace *ns, + ARRAY_TYPE(const_string) *ids) +{ + struct acl_mailbox_list *alist = ACL_LIST_CONTEXT(ns->list); + struct acl_backend *backend; + struct acl_mailbox_list_context *ctx; + struct acl_object *aclobj; + struct acl_object_list_iter *iter; + struct acl_rights rights; + const char *name, *id_dup; + string_t *id; + int ret = 0; + + if ((ns->flags & NAMESPACE_FLAG_NOACL) != 0 || ns->owner == NULL || + alist == NULL || alist->ignore_acls) + return 0; + + id = t_str_new(128); + backend = acl_mailbox_list_get_backend(ns->list); + ctx = acl_backend_nonowner_lookups_iter_init(backend); + while (acl_backend_nonowner_lookups_iter_next(ctx, &name)) { + aclobj = acl_object_init_from_name(backend, name); + + iter = acl_object_list_init(aclobj); + while (acl_object_list_next(iter, &rights)) { + /* avoid pointless user -> user entries, + which some clients do */ + if (acl_rights_has_nonowner_lookup_changes(&rights) && + !acl_rights_is_same_user(&rights, ns->owner)) { + str_truncate(id, 0); + acl_lookup_dict_write_rights_id(id, &rights); + str_append_c(id, '/'); + str_append(id, ns->owner->username); + id_dup = t_strdup(str_c(id)); + array_push_back(ids, &id_dup); + } + } + if (acl_object_list_deinit(&iter) < 0) ret = -1; + acl_object_deinit(&aclobj); + } + if (acl_backend_nonowner_lookups_iter_deinit(&ctx) < 0) ret = -1; + return ret; +} + +static int +acl_lookup_dict_rebuild_update(struct acl_lookup_dict *dict, + const ARRAY_TYPE(const_string) *new_ids_arr, + bool no_removes) +{ + const char *username = dict->user->username; + struct dict_iterate_context *iter; + struct dict_transaction_context *dt = NULL; + const char *prefix, *key, *value, *const *old_ids, *const *new_ids, *p; + const char *error; + ARRAY_TYPE(const_string) old_ids_arr; + unsigned int newi, oldi, old_count, new_count; + string_t *path; + size_t prefix_len; + int ret; + const struct dict_op_settings *set = mail_user_get_dict_op_settings(dict->user); + + /* get all existing identifiers for the user. we might be able to + sync identifiers also for other users whose shared namespaces we + have, but it's possible that the other users have other namespaces + that aren't visible to us, so we don't want to remove anything + that could break them. */ + t_array_init(&old_ids_arr, 128); + prefix = DICT_PATH_SHARED DICT_SHARED_BOXES_PATH; + prefix_len = strlen(prefix); + iter = dict_iterate_init(dict->dict, set, prefix, DICT_ITERATE_FLAG_RECURSE); + while (dict_iterate(iter, &key, &value)) { + /* prefix/$type/$dest/$source */ + key += prefix_len; + p = strrchr(key, '/'); + if (p != NULL && strcmp(p + 1, username) == 0) { + key = t_strdup_until(key, p); + array_push_back(&old_ids_arr, &key); + } + } + if (dict_iterate_deinit(&iter, &error) < 0) { + i_error("acl: dict iteration failed: %s - can't update dict", error); + return -1; + } + + /* sort the existing identifiers */ + array_sort(&old_ids_arr, i_strcmp_p); + + /* sync the identifiers */ + path = t_str_new(256); + str_append(path, prefix); + + old_ids = array_get(&old_ids_arr, &old_count); + new_ids = array_get(new_ids_arr, &new_count); + for (newi = oldi = 0; newi < new_count || oldi < old_count; ) { + ret = newi == new_count ? 1 : + oldi == old_count ? -1 : + strcmp(new_ids[newi], old_ids[oldi]); + if (ret == 0) { + newi++; oldi++; + } else if (ret < 0) { + /* new identifier, add it */ + str_truncate(path, prefix_len); + str_append(path, new_ids[newi]); + dt = dict_transaction_begin(dict->dict, set); + dict_set(dt, str_c(path), "1"); + newi++; + } else if (!no_removes) { + /* old identifier removed */ + str_truncate(path, prefix_len); + str_append(path, old_ids[oldi]); + str_append_c(path, '/'); + str_append(path, username); + dt = dict_transaction_begin(dict->dict, set); + dict_unset(dt, str_c(path)); + oldi++; + } + if (dt != NULL && dict_transaction_commit(&dt, &error) < 0) { + i_error("acl: dict commit failed: %s", error); + return -1; + } + i_assert(dt == NULL); + } + return 0; +} + +int acl_lookup_dict_rebuild(struct acl_lookup_dict *dict) +{ + struct mail_namespace *ns; + ARRAY_TYPE(const_string) ids_arr; + const char **ids; + unsigned int i, dest, count; + int ret = 0; + + if (dict->dict == NULL) + return 0; + + /* get all ACL identifiers with a positive lookup right */ + t_array_init(&ids_arr, 128); + for (ns = dict->user->namespaces; ns != NULL; ns = ns->next) { + if (acl_lookup_dict_rebuild_add_backend(ns, &ids_arr) < 0) + ret = -1; + } + + /* sort identifiers and remove duplicates */ + array_sort(&ids_arr, i_strcmp_p); + + ids = array_get_modifiable(&ids_arr, &count); + for (i = 1, dest = 0; i < count; i++) { + if (strcmp(ids[dest], ids[i]) != 0) { + if (++dest != i) + ids[dest] = ids[i]; + } + } + if (++dest < count) + array_delete(&ids_arr, dest, count-dest); + + /* if lookup failed at some point we can still add new ids, + but we can't remove any existing ones */ + if (acl_lookup_dict_rebuild_update(dict, &ids_arr, ret < 0) < 0) + ret = -1; + return ret; +} + +static void acl_lookup_dict_iterate_read(struct acl_lookup_dict_iter *iter) +{ + struct dict_iterate_context *dict_iter; + const char *id, *prefix, *key, *value, *error; + size_t prefix_len; + + id = array_idx_elem(&iter->iter_ids, iter->iter_idx); + iter->iter_idx++; + iter->iter_value_idx = 0; + + prefix = t_strconcat(DICT_PATH_SHARED DICT_SHARED_BOXES_PATH, + id, "/", NULL); + prefix_len = strlen(prefix); + + /* read all of it to memory. at least currently dict-proxy can support + only one iteration at a time, but the acl code can end up rebuilding + the dict, which opens another iteration. */ + p_clear(iter->iter_value_pool); + array_clear(&iter->iter_values); + const struct dict_op_settings *set = mail_user_get_dict_op_settings(iter->dict->user); + dict_iter = dict_iterate_init(iter->dict->dict, set, prefix, + DICT_ITERATE_FLAG_RECURSE); + while (dict_iterate(dict_iter, &key, &value)) { + i_assert(prefix_len < strlen(key)); + + key = p_strdup(iter->iter_value_pool, key + prefix_len); + array_push_back(&iter->iter_values, &key); + } + if (dict_iterate_deinit(&dict_iter, &error) < 0) { + i_error("%s", error); + iter->failed = TRUE; + } +} + +struct acl_lookup_dict_iter * +acl_lookup_dict_iterate_visible_init(struct acl_lookup_dict *dict) +{ + struct acl_user *auser = ACL_USER_CONTEXT(dict->user); + struct acl_lookup_dict_iter *iter; + const char *id; + unsigned int i; + pool_t pool; + + i_assert(auser != NULL); + + pool = pool_alloconly_create("acl lookup dict iter", 1024); + iter = p_new(pool, struct acl_lookup_dict_iter, 1); + iter->pool = pool; + iter->dict = dict; + + p_array_init(&iter->iter_ids, pool, 16); + id = "anyone"; + array_push_back(&iter->iter_ids, &id); + id = p_strconcat(pool, "user/", dict->user->username, NULL); + array_push_back(&iter->iter_ids, &id); + + i_array_init(&iter->iter_values, 64); + iter->iter_value_pool = + pool_alloconly_create("acl lookup dict iter values", 1024); + + /* get all groups we belong to */ + if (auser->groups != NULL) { + for (i = 0; auser->groups[i] != NULL; i++) { + id = p_strconcat(pool, "group/", auser->groups[i], + NULL); + array_push_back(&iter->iter_ids, &id); + } + } + + /* iterate through all identifiers that match us, start with the + first one */ + if (dict->dict != NULL) + acl_lookup_dict_iterate_read(iter); + else + array_clear(&iter->iter_ids); + return iter; +} + +const char * +acl_lookup_dict_iterate_visible_next(struct acl_lookup_dict_iter *iter) +{ + const char *const *keys; + unsigned int count; + + keys = array_get(&iter->iter_values, &count); + if (iter->iter_value_idx < count) + return keys[iter->iter_value_idx++]; + + if (iter->iter_idx < array_count(&iter->iter_ids)) { + /* get to the next iterator */ + acl_lookup_dict_iterate_read(iter); + return acl_lookup_dict_iterate_visible_next(iter); + } + return NULL; +} + +int acl_lookup_dict_iterate_visible_deinit(struct acl_lookup_dict_iter **_iter) +{ + struct acl_lookup_dict_iter *iter = *_iter; + int ret = iter->failed ? -1 : 0; + + *_iter = NULL; + array_free(&iter->iter_values); + pool_unref(&iter->iter_value_pool); + pool_unref(&iter->pool); + return ret; +} |