diff options
Diffstat (limited to 'src/plugins/acl/acl-cache.c')
-rw-r--r-- | src/plugins/acl/acl-cache.c | 395 |
1 files changed, 395 insertions, 0 deletions
diff --git a/src/plugins/acl/acl-cache.c b/src/plugins/acl/acl-cache.c new file mode 100644 index 0000000..8f07d56 --- /dev/null +++ b/src/plugins/acl/acl-cache.c @@ -0,0 +1,395 @@ +/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "hash.h" +#include "acl-cache.h" +#include "acl-api-private.h" + +/* Give more than enough so that the arrays should never have to be grown. + IMAP ACLs define only 10 standard rights and 10 user-defined rights. */ +#define DEFAULT_ACL_RIGHTS_COUNT 64 + +#define ACL_GLOBAL_COUNT 2 + +struct acl_object_cache { + char *name; + + struct acl_mask *my_rights, *my_neg_rights; + struct acl_mask *my_current_rights; +}; + +struct acl_cache { + struct acl_backend *backend; + /* name => object */ + HASH_TABLE(char *, struct acl_object_cache *) objects; + + size_t validity_rec_size; + + /* Right names mapping is used for faster rights checking. Note that + acl_mask bitmask relies on the order to never change, so only new + rights can be added to the mapping. */ + pool_t right_names_pool; + /* idx => right name. */ + ARRAY(const char *) right_idx_name_map; + /* name => idx+1 */ + HASH_TABLE(char *, void *) right_name_idx_map; +}; + +static struct acl_mask negative_cache_entry; + +struct acl_cache *acl_cache_init(struct acl_backend *backend, + size_t validity_rec_size) +{ + struct acl_cache *cache; + + cache = i_new(struct acl_cache, 1); + cache->backend = backend; + cache->validity_rec_size = validity_rec_size; + cache->right_names_pool = + pool_alloconly_create("ACL right names", 1024); + hash_table_create(&cache->objects, default_pool, 0, str_hash, strcmp); + hash_table_create(&cache->right_name_idx_map, + cache->right_names_pool, 0, str_hash, strcmp); + i_array_init(&cache->right_idx_name_map, DEFAULT_ACL_RIGHTS_COUNT); + return cache; +} + +void acl_cache_deinit(struct acl_cache **_cache) +{ + struct acl_cache *cache = *_cache; + + *_cache = NULL; + + acl_cache_flush_all(cache); + array_free(&cache->right_idx_name_map); + hash_table_destroy(&cache->right_name_idx_map); + hash_table_destroy(&cache->objects); + pool_unref(&cache->right_names_pool); + i_free(cache); +} + +static void acl_cache_free_object_cache(struct acl_object_cache *obj_cache) +{ + if (obj_cache->my_current_rights != NULL && + obj_cache->my_current_rights != &negative_cache_entry) + acl_cache_mask_deinit(&obj_cache->my_current_rights); + if (obj_cache->my_rights != NULL) + acl_cache_mask_deinit(&obj_cache->my_rights); + if (obj_cache->my_neg_rights != NULL) + acl_cache_mask_deinit(&obj_cache->my_neg_rights); + i_free(obj_cache->name); + i_free(obj_cache); +} + +static struct acl_mask * +acl_cache_mask_init_real(struct acl_cache *cache, pool_t pool, + const char *const *rights) +{ + struct acl_mask *mask; + unsigned int rights_count, i, idx; + unsigned char *p; + buffer_t *bitmask; + + rights_count = str_array_length(rights); + bitmask = t_buffer_create(DEFAULT_ACL_RIGHTS_COUNT / CHAR_BIT); + for (i = 0; i < rights_count; i++) { + idx = acl_cache_right_lookup(cache, rights[i]); + p = buffer_get_space_unsafe(bitmask, idx / CHAR_BIT, 1); + *p |= 1 << (idx % CHAR_BIT); + } + + /* @UNSAFE */ + mask = p_malloc(pool, SIZEOF_ACL_MASK(bitmask->used)); + memcpy(mask->mask, bitmask->data, bitmask->used); + mask->pool = pool; + mask->size = bitmask->used; + return mask; +} + +struct acl_mask *acl_cache_mask_init(struct acl_cache *cache, pool_t pool, + const char *const *rights) +{ + struct acl_mask *mask; + + T_BEGIN { + mask = acl_cache_mask_init_real(cache, pool, rights); + } T_END; + return mask; +} + +static struct acl_mask * +acl_cache_mask_dup(pool_t pool, const struct acl_mask *src) +{ + struct acl_mask *mask; + + mask = p_malloc(pool, SIZEOF_ACL_MASK(src->size)); + memcpy(mask->mask, src->mask, src->size); + mask->pool = pool; + mask->size = src->size; + return mask; +} + +void acl_cache_mask_deinit(struct acl_mask **_mask) +{ + struct acl_mask *mask = *_mask; + + *_mask = NULL; + p_free(mask->pool, mask); +} + +unsigned int acl_cache_right_lookup(struct acl_cache *cache, const char *right) +{ + unsigned int idx; + void *idx_p; + char *name; + const char *const_name; + + /* use +1 for right_name_idx_map values because we can't add NULL + values. */ + idx_p = hash_table_lookup(cache->right_name_idx_map, right); + if (idx_p == NULL) { + /* new right name, add it */ + const_name = name = p_strdup(cache->right_names_pool, right); + + idx = array_count(&cache->right_idx_name_map); + array_push_back(&cache->right_idx_name_map, &const_name); + hash_table_insert(cache->right_name_idx_map, name, + POINTER_CAST(idx + 1)); + } else { + idx = POINTER_CAST_TO(idx_p, unsigned int)-1; + } + return idx; +} + +void acl_cache_flush(struct acl_cache *cache, const char *objname) +{ + struct acl_object_cache *obj_cache; + + obj_cache = hash_table_lookup(cache->objects, objname); + if (obj_cache != NULL) { + hash_table_remove(cache->objects, objname); + acl_cache_free_object_cache(obj_cache); + } +} + +void acl_cache_flush_all(struct acl_cache *cache) +{ + struct hash_iterate_context *iter; + char *key; + struct acl_object_cache *obj_cache; + + iter = hash_table_iterate_init(cache->objects); + while (hash_table_iterate(iter, cache->objects, &key, &obj_cache)) + acl_cache_free_object_cache(obj_cache); + hash_table_iterate_deinit(&iter); + + hash_table_clear(cache->objects, FALSE); +} + +static void +acl_cache_update_rights_mask(struct acl_cache *cache, + struct acl_object_cache *obj_cache, + enum acl_modify_mode modify_mode, + const char *const *rights, + struct acl_mask **mask_p) +{ + struct acl_mask *change_mask, *old_mask, *new_mask; + unsigned int i, size; + bool changed = TRUE; + + change_mask = rights == NULL ? NULL : + acl_cache_mask_init(cache, default_pool, rights); + old_mask = *mask_p; + new_mask = old_mask; + + switch (modify_mode) { + case ACL_MODIFY_MODE_ADD: + if (old_mask == NULL) { + new_mask = change_mask; + break; + } + + if (change_mask == NULL) { + /* no changes */ + changed = FALSE; + break; + } + + /* merge the masks */ + if (old_mask->size >= change_mask->size) { + /* keep using the old mask */ + for (i = 0; i < change_mask->size; i++) + old_mask->mask[i] |= change_mask->mask[i]; + } else { + /* use the new mask, put old changes into it */ + for (i = 0; i < old_mask->size; i++) + change_mask->mask[i] |= old_mask->mask[i]; + new_mask = change_mask; + } + break; + case ACL_MODIFY_MODE_REMOVE: + if (old_mask == NULL || change_mask == NULL) { + changed = FALSE; + break; + } + + /* remove changed bits from old mask */ + size = I_MIN(old_mask->size, change_mask->size); + for (i = 0; i < size; i++) + old_mask->mask[i] &= ENUM_NEGATE(change_mask->mask[i]); + break; + case ACL_MODIFY_MODE_REPLACE: + if (old_mask == NULL && change_mask == NULL) + changed = FALSE; + new_mask = change_mask; + break; + case ACL_MODIFY_MODE_CLEAR: + i_unreached(); + } + + if (new_mask != old_mask) { + *mask_p = new_mask; + if (old_mask != NULL) + acl_cache_mask_deinit(&old_mask); + } + if (new_mask != change_mask && change_mask != NULL) + acl_cache_mask_deinit(&change_mask); + + if (changed && obj_cache->my_current_rights != NULL) { + /* current rights need to be recalculated */ + if (obj_cache->my_current_rights == &negative_cache_entry) + obj_cache->my_current_rights = NULL; + else + acl_cache_mask_deinit(&obj_cache->my_current_rights); + } +} + +static struct acl_object_cache * +acl_cache_object_get(struct acl_cache *cache, const char *objname, + bool *created_r) +{ + struct acl_object_cache *obj_cache; + + obj_cache = hash_table_lookup(cache->objects, objname); + if (obj_cache == NULL) { + obj_cache = i_malloc(MALLOC_ADD(sizeof(struct acl_object_cache), + cache->validity_rec_size)); + obj_cache->name = i_strdup(objname); + hash_table_insert(cache->objects, obj_cache->name, obj_cache); + *created_r = TRUE; + } else { + *created_r = FALSE; + } + return obj_cache; +} + +void acl_cache_update(struct acl_cache *cache, const char *objname, + const struct acl_rights_update *update) +{ + struct acl_object_cache *obj_cache; + bool created; + + obj_cache = acl_cache_object_get(cache, objname, &created); + i_assert(obj_cache->my_current_rights != &negative_cache_entry); + + if (created && update->modify_mode != ACL_MODIFY_MODE_REPLACE) { + /* since the rights aren't being replaced, start with our + default rights */ + obj_cache->my_rights = + acl_cache_mask_dup(default_pool, + cache->backend->default_aclmask); + } + + acl_cache_update_rights_mask(cache, obj_cache, update->modify_mode, + update->rights.rights, + &obj_cache->my_rights); + acl_cache_update_rights_mask(cache, obj_cache, update->neg_modify_mode, + update->rights.neg_rights, + &obj_cache->my_neg_rights); +} + +void acl_cache_set_validity(struct acl_cache *cache, const char *objname, + const void *validity) +{ + struct acl_object_cache *obj_cache; + bool created; + + obj_cache = acl_cache_object_get(cache, objname, &created); + + /* @UNSAFE: validity is stored after the cache record */ + memcpy(obj_cache + 1, validity, cache->validity_rec_size); + + if (created) { + /* negative cache entry */ + obj_cache->my_current_rights = &negative_cache_entry; + } +} + +void *acl_cache_get_validity(struct acl_cache *cache, const char *objname) +{ + struct acl_object_cache *obj_cache; + + obj_cache = hash_table_lookup(cache->objects, objname); + return obj_cache == NULL ? NULL : (obj_cache + 1); +} + +const char *const *acl_cache_get_names(struct acl_cache *cache, + unsigned int *count_r) +{ + *count_r = array_count(&cache->right_idx_name_map); + return array_front(&cache->right_idx_name_map); +} + +static void +acl_cache_my_current_rights_recalculate(struct acl_object_cache *obj_cache) +{ + struct acl_mask *mask; + unsigned int i, size; + + /* @UNSAFE */ + size = obj_cache->my_rights == NULL ? 0 : + obj_cache->my_rights->size; + mask = i_malloc(SIZEOF_ACL_MASK(size)); + mask->pool = default_pool; + mask->size = size; + + /* apply the positive rights */ + if (obj_cache->my_rights != NULL) + memcpy(mask->mask, obj_cache->my_rights->mask, mask->size); + if (obj_cache->my_neg_rights != NULL) { + /* apply the negative rights. they override positive rights. */ + size = I_MIN(mask->size, obj_cache->my_neg_rights->size); + for (i = 0; i < size; i++) + mask->mask[i] &= ENUM_NEGATE(obj_cache->my_neg_rights->mask[i]); + } + + obj_cache->my_current_rights = mask; +} + +const struct acl_mask * +acl_cache_get_my_rights(struct acl_cache *cache, const char *objname) +{ + struct acl_object_cache *obj_cache; + + obj_cache = hash_table_lookup(cache->objects, objname); + if (obj_cache == NULL || + obj_cache->my_current_rights == &negative_cache_entry) + return NULL; + + if (obj_cache->my_current_rights == NULL) { + T_BEGIN { + acl_cache_my_current_rights_recalculate(obj_cache); + } T_END; + } + return obj_cache->my_current_rights; +} + +bool acl_cache_mask_isset(const struct acl_mask *mask, unsigned int right_idx) +{ + unsigned int mask_idx; + + mask_idx = right_idx / CHAR_BIT; + return mask_idx < mask->size && + (mask->mask[mask_idx] & (1 << (right_idx % CHAR_BIT))) != 0; +} |