From: Timo Sirainen Date: Fri, 1 Oct 2010 14:37:19 +0000 (+0100) Subject: acl: Fixed the logic of merging multiple ACLs. X-Git-Tag: 2.0.5~13 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=27a7dee37444438522e04273ce17e6c48775b35c;p=thirdparty%2Fdovecot%2Fcore.git acl: Fixed the logic of merging multiple ACLs. --- diff --git a/src/plugins/acl/acl-api.h b/src/plugins/acl/acl-api.h index 2144ebc64f..8f2cac8726 100644 --- a/src/plugins/acl/acl-api.h +++ b/src/plugins/acl/acl-api.h @@ -110,6 +110,9 @@ bool acl_backend_user_is_in_group(struct acl_backend *backend, /* Returns index for the right name. If it doesn't exist, it's created. */ unsigned int acl_backend_lookup_right(struct acl_backend *backend, const char *right); +/* Returns TRUE if acl_rights matches backend user. */ +bool acl_backend_rights_match_me(struct acl_backend *backend, + const struct acl_rights *rights); /* List mailboxes that have lookup right to some non-owners. */ struct acl_mailbox_list_context * diff --git a/src/plugins/acl/acl-backend-vfile.c b/src/plugins/acl/acl-backend-vfile.c index 9c5b2cd560..74d01166be 100644 --- a/src/plugins/acl/acl-backend-vfile.c +++ b/src/plugins/acl/acl-backend-vfile.c @@ -756,7 +756,7 @@ static void acl_backend_vfile_rights_sort(struct acl_object_vfile *aclobj) array_delete(&aclobj->rights, dest, count - dest); } -static void apply_owner_rights(struct acl_object *_aclobj) +static void apply_owner_default_rights(struct acl_object *_aclobj) { struct acl_rights_update ru; const char *null = NULL; @@ -776,9 +776,9 @@ static void acl_backend_vfile_cache_rebuild(struct acl_object_vfile *aclobj) struct acl_object *_aclobj = &aclobj->aclobj; struct acl_rights_update ru; enum acl_modify_mode add_mode; - const struct acl_rights *rights; + const struct acl_rights *rights, *prev_match = NULL; unsigned int i, count; - bool owner_applied, first_global = TRUE; + bool first_global = TRUE; acl_cache_flush(_aclobj->backend->cache, _aclobj->name); @@ -786,26 +786,54 @@ static void acl_backend_vfile_cache_rebuild(struct acl_object_vfile *aclobj) return; ns = mailbox_list_get_namespace(_aclobj->backend->list); - owner_applied = ns->type != NAMESPACE_PRIVATE; + /* Rights are sorted by their 1) locals first, globals next, + 2) acl_id_type. We'll apply only the rights matching ourself. + + Every time acl_id_type or local/global changes, the new ACLs will + replace all of the existing ACLs. Basically this means that if + user belongs to multiple matching groups or group-overrides, their + ACLs are merged. In all other situations the ACLs are replaced + (because there aren't duplicate rights entries and a user can't + match multiple usernames). */ memset(&ru, 0, sizeof(ru)); rights = array_get(&aclobj->rights, &count); - for (i = 0; i < count; i++) { - if (!owner_applied && - (rights[i].id_type >= ACL_ID_OWNER || rights[i].global)) { - owner_applied = TRUE; - if (rights[i].id_type != ACL_ID_OWNER) { - /* owner rights weren't explicitly specified. - replace all the current rights */ - apply_owner_rights(_aclobj); - } + if (!acl_backend_user_is_owner(_aclobj->backend)) + i = 0; + else { + /* we're the owner. skip over all rights entries until we + reach ACL_ID_OWNER or higher, or alternatively when we + reach a global ACL (even ACL_ID_ANYONE overrides owner's + rights if it's global) */ + for (i = 0; i < count; i++) { + if (rights[i].id_type >= ACL_ID_OWNER || + rights[i].global) + break; } + apply_owner_default_rights(_aclobj); + /* now continue applying the rest of the rights, + if there are any */ + } + for (; i < count; i++) { + if (!acl_backend_rights_match_me(_aclobj->backend, &rights[i])) + continue; + + if (prev_match == NULL || + prev_match->id_type != rights[i].id_type || + prev_match->global != rights[i].global) { + /* replace old ACLs */ + add_mode = ACL_MODIFY_MODE_REPLACE; + } else { + /* merging to existing ACLs */ + i_assert(rights[i].id_type == ACL_ID_GROUP || + rights[i].id_type == ACL_ID_GROUP_OVERRIDE); + add_mode = ACL_MODIFY_MODE_ADD; + } + prev_match = &rights[i]; + /* If [neg_]rights is NULL it needs to be ignored. The easiest way to do that is to just mark it with REMOVE mode */ - add_mode = i > 0 && rights[i-1].id_type == rights[i].id_type && - rights[i-1].global == rights[i].global ? - ACL_MODIFY_MODE_ADD : ACL_MODIFY_MODE_REPLACE; ru.modify_mode = rights[i].rights == NULL ? ACL_MODIFY_MODE_REMOVE : add_mode; ru.neg_modify_mode = rights[i].neg_rights == NULL ? @@ -819,8 +847,6 @@ static void acl_backend_vfile_cache_rebuild(struct acl_object_vfile *aclobj) } acl_cache_update(_aclobj->backend->cache, _aclobj->name, &ru); } - if (!owner_applied && count > 0) - apply_owner_rights(_aclobj); } static int acl_backend_vfile_object_refresh_cache(struct acl_object *_aclobj) diff --git a/src/plugins/acl/acl-backend.c b/src/plugins/acl/acl-backend.c index fa58c41ee9..99e6ed21d9 100644 --- a/src/plugins/acl/acl-backend.c +++ b/src/plugins/acl/acl-backend.c @@ -130,6 +130,27 @@ bool acl_backend_user_is_in_group(struct acl_backend *backend, sizeof(const char *), bsearch_strcmp) != NULL; } +bool acl_backend_rights_match_me(struct acl_backend *backend, + const struct acl_rights *rights) +{ + switch (rights->id_type) { + case ACL_ID_ANYONE: + return TRUE; + case ACL_ID_AUTHENTICATED: + return acl_backend_user_is_authenticated(backend); + case ACL_ID_GROUP: + case ACL_ID_GROUP_OVERRIDE: + return acl_backend_user_is_in_group(backend, rights->identifier); + case ACL_ID_USER: + return acl_backend_user_name_equals(backend, rights->identifier); + case ACL_ID_OWNER: + return acl_backend_user_is_owner(backend); + case ACL_ID_TYPE_COUNT: + break; + } + i_unreached(); +} + unsigned int acl_backend_lookup_right(struct acl_backend *backend, const char *right) { diff --git a/src/plugins/acl/acl-cache.c b/src/plugins/acl/acl-cache.c index 1d55abf6ea..dabe80914b 100644 --- a/src/plugins/acl/acl-cache.c +++ b/src/plugins/acl/acl-cache.c @@ -284,9 +284,8 @@ acl_cache_object_get(struct acl_cache *cache, const char *objname, return obj_cache; } -static void -acl_cache_update_rights(struct acl_cache *cache, const char *objname, - const struct acl_rights_update *update) +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; @@ -310,37 +309,6 @@ acl_cache_update_rights(struct acl_cache *cache, const char *objname, &obj_cache->my_neg_rights); } -void acl_cache_update(struct acl_cache *cache, const char *objname, - const struct acl_rights_update *update) -{ - switch (update->rights.id_type) { - case ACL_ID_ANYONE: - acl_cache_update_rights(cache, objname, update); - break; - case ACL_ID_AUTHENTICATED: - if (acl_backend_user_is_authenticated(cache->backend)) - acl_cache_update_rights(cache, objname, update); - break; - case ACL_ID_GROUP: - case ACL_ID_GROUP_OVERRIDE: - if (acl_backend_user_is_in_group(cache->backend, - update->rights.identifier)) - acl_cache_update_rights(cache, objname, update); - break; - case ACL_ID_USER: - if (acl_backend_user_name_equals(cache->backend, - update->rights.identifier)) - acl_cache_update_rights(cache, objname, update); - break; - case ACL_ID_OWNER: - if (acl_backend_user_is_owner(cache->backend)) - acl_cache_update_rights(cache, objname, update); - break; - case ACL_ID_TYPE_COUNT: - i_unreached(); - } -} - void acl_cache_set_validity(struct acl_cache *cache, const char *objname, const void *validity) {