From: Aki Tuomi Date: Mon, 27 Nov 2023 13:16:40 +0000 (+0200) Subject: plugins: acl - Move rights handling to acl-rights.c X-Git-Tag: 2.4.1~1167 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=6dbba5462f689b95debd92c47640e8e449f36e58;p=thirdparty%2Fdovecot%2Fcore.git plugins: acl - Move rights handling to acl-rights.c --- diff --git a/src/plugins/acl/Makefile.am b/src/plugins/acl/Makefile.am index 041d3dcbd8..0cc0016c05 100644 --- a/src/plugins/acl/Makefile.am +++ b/src/plugins/acl/Makefile.am @@ -33,6 +33,7 @@ lib01_acl_plugin_la_SOURCES = \ acl-mailbox.c \ acl-mailbox-list.c \ acl-plugin.c \ + acl-rights.c \ acl-settings.c \ acl-shared-storage.c \ acl-storage.c @@ -49,6 +50,7 @@ pkginc_lib_HEADERS = \ acl-global-file.h \ acl-lookup-dict.h \ acl-plugin.h \ + acl-rights.h \ acl-settings.h \ acl-storage.h diff --git a/src/plugins/acl/acl-api-private.h b/src/plugins/acl/acl-api-private.h index bc47473edd..c994da94bc 100644 --- a/src/plugins/acl/acl-api-private.h +++ b/src/plugins/acl/acl-api-private.h @@ -3,13 +3,6 @@ #include "acl-api.h" -#define ACL_ID_NAME_ANYONE "anyone" -#define ACL_ID_NAME_AUTHENTICATED "authenticated" -#define ACL_ID_NAME_OWNER "owner" -#define ACL_ID_NAME_USER_PREFIX "user=" -#define ACL_ID_NAME_GROUP_PREFIX "group=" -#define ACL_ID_NAME_GROUP_OVERRIDE_PREFIX "group-override=" - struct acl_backend_vfuncs { const char *name; struct acl_backend *(*alloc)(void); @@ -105,30 +98,7 @@ acl_backend_mask_get_names(struct acl_backend *backend, struct acl_object *acl_backend_get_default_object(struct acl_backend *backend); int acl_backend_get_default_rights(struct acl_backend *backend, const struct acl_mask **mask_r); -void acl_rights_write_id(string_t *dest, const struct acl_rights *right); -bool acl_rights_has_nonowner_lookup_changes(const struct acl_rights *rights); - -int acl_identifier_parse(const char *line, struct acl_rights *rights); -int acl_rights_update_import(struct acl_rights_update *update, - const char *id, const char *const *rights, - const char **error_r); -const char *acl_rights_export(const struct acl_rights *rights); -int acl_rights_parse_line(const char *line, pool_t pool, - struct acl_rights *rights_r, const char **error_r); -void acl_rights_dup(const struct acl_rights *src, - pool_t pool, struct acl_rights *dest_r); -int acl_rights_cmp(const struct acl_rights *r1, const struct acl_rights *r2); void acl_rights_sort(struct acl_object *aclobj); - -const char *const * -acl_right_names_parse(pool_t pool, const char *acl, const char **error_r); -void acl_right_names_write(string_t *dest, const char *const *rights); -void acl_right_names_merge(pool_t pool, const char *const **destp, - const char *const *src, bool dup_strings); -bool acl_right_names_modify(pool_t pool, - const char *const **rightsp, - const char *const *modify_rights, - enum acl_modify_mode modify_mode); void acl_object_rebuild_cache(struct acl_object *aclobj); void acl_object_remove_all_access(struct acl_object *aclobj); void acl_object_add_global_acls(struct acl_object *aclobj); diff --git a/src/plugins/acl/acl-api.c b/src/plugins/acl/acl-api.c index 6fe51560c0..b1157437c4 100644 --- a/src/plugins/acl/acl-api.c +++ b/src/plugins/acl/acl-api.c @@ -11,26 +11,6 @@ #include "acl-cache.h" #include "acl-api-private.h" -struct acl_letter_map { - char letter; - const char *name; -}; - -static const struct acl_letter_map acl_letter_map[] = { - { 'l', MAIL_ACL_LOOKUP }, - { 'r', MAIL_ACL_READ }, - { 'w', MAIL_ACL_WRITE }, - { 's', MAIL_ACL_WRITE_SEEN }, - { 't', MAIL_ACL_WRITE_DELETED }, - { 'i', MAIL_ACL_INSERT }, - { 'p', MAIL_ACL_POST }, - { 'e', MAIL_ACL_EXPUNGE }, - { 'k', MAIL_ACL_CREATE }, - { 'x', MAIL_ACL_DELETE }, - { 'a', MAIL_ACL_ADMIN }, - { '\0', NULL } -}; - struct acl_object *acl_object_init_from_name(struct acl_backend *backend, const char *name) { @@ -271,204 +251,6 @@ int acl_backend_nonowner_lookups_rebuild(struct acl_backend *backend) return backend->v->nonowner_lookups_rebuild(backend); } -void acl_rights_write_id(string_t *dest, const struct acl_rights *right) -{ - switch (right->id_type) { - case ACL_ID_ANYONE: - str_append(dest, ACL_ID_NAME_ANYONE); - break; - case ACL_ID_AUTHENTICATED: - str_append(dest, ACL_ID_NAME_AUTHENTICATED); - break; - case ACL_ID_OWNER: - str_append(dest, ACL_ID_NAME_OWNER); - break; - case ACL_ID_USER: - str_append(dest, ACL_ID_NAME_USER_PREFIX); - str_append(dest, right->identifier); - break; - case ACL_ID_GROUP: - str_append(dest, ACL_ID_NAME_GROUP_PREFIX); - str_append(dest, right->identifier); - break; - case ACL_ID_GROUP_OVERRIDE: - str_append(dest, ACL_ID_NAME_GROUP_OVERRIDE_PREFIX); - str_append(dest, right->identifier); - break; - case ACL_ID_TYPE_COUNT: - i_unreached(); - } -} - -const char *acl_rights_get_id(const struct acl_rights *right) -{ - string_t *str = t_str_new(32); - - acl_rights_write_id(str, right); - return str_c(str); -} - -static bool is_standard_right(const char *name) -{ - unsigned int i; - - for (i = 0; all_mailbox_rights[i] != NULL; i++) { - if (strcmp(all_mailbox_rights[i], name) == 0) - return TRUE; - } - return FALSE; -} - -int acl_rights_update_import(struct acl_rights_update *update, - const char *id, const char *const *rights, - const char **error_r) -{ - ARRAY_TYPE(const_string) dest_rights, dest_neg_rights, *dest; - unsigned int i, j; - - if (acl_identifier_parse(id, &update->rights) < 0) { - *error_r = t_strdup_printf("Invalid ID: %s", id); - return -1; - } - if (rights == NULL) { - update->modify_mode = ACL_MODIFY_MODE_CLEAR; - update->neg_modify_mode = ACL_MODIFY_MODE_CLEAR; - return 0; - } - - t_array_init(&dest_rights, 8); - t_array_init(&dest_neg_rights, 8); - for (i = 0; rights[i] != NULL; i++) { - const char *right = rights[i]; - - if (right[0] != '-') - dest = &dest_rights; - else { - right++; - dest = &dest_neg_rights; - } - if (strcmp(right, "all") != 0) { - if (*right == ':') { - /* non-standard right */ - right++; - array_push_back(dest, &right); - } else if (is_standard_right(right)) { - array_push_back(dest, &right); - } else { - *error_r = t_strdup_printf("Invalid right '%s'", - right); - return -1; - } - } else { - for (j = 0; all_mailbox_rights[j] != NULL; j++) - array_push_back(dest, &all_mailbox_rights[j]); - } - } - if (array_count(&dest_rights) > 0) { - array_append_zero(&dest_rights); - update->rights.rights = array_front(&dest_rights); - } else if (update->modify_mode == ACL_MODIFY_MODE_REPLACE) { - update->modify_mode = ACL_MODIFY_MODE_CLEAR; - } - if (array_count(&dest_neg_rights) > 0) { - array_append_zero(&dest_neg_rights); - update->rights.neg_rights = array_front(&dest_neg_rights); - } else if (update->neg_modify_mode == ACL_MODIFY_MODE_REPLACE) { - update->neg_modify_mode = ACL_MODIFY_MODE_CLEAR; - } - return 0; -} - -const char *acl_rights_export(const struct acl_rights *rights) -{ - string_t *str = t_str_new(128); - - if (rights->rights != NULL) - str_append(str, t_strarray_join(rights->rights, " ")); - if (rights->neg_rights != NULL && rights->neg_rights[0] != NULL) { - if (str_len(str) > 0) - str_append_c(str, ' '); - str_append_c(str, '-'); - str_append(str, t_strarray_join(rights->neg_rights, " -")); - } - return str_c(str); -} - -int acl_rights_parse_line(const char *line, pool_t pool, - struct acl_rights *rights_r, const char **error_r) -{ - const char *id_str, *const *right_names, *error = NULL; - - /* [] [:] */ - if (*line == '"') { - line++; - if (str_unescape_next(&line, &id_str) < 0 || - (line[0] != ' ' && line[0] != '\0')) { - *error_r = "Invalid quoted ID"; - return -1; - } - if (line[0] == ' ') - line++; - } else { - id_str = line; - line = strchr(id_str, ' '); - if (line == NULL) - line = ""; - else - id_str = t_strdup_until(id_str, line++); - } - - i_zero(rights_r); - - right_names = acl_right_names_parse(pool, line, &error); - if (*id_str != '-') - rights_r->rights = right_names; - else { - id_str++; - rights_r->neg_rights = right_names; - } - - if (acl_identifier_parse(id_str, rights_r) < 0) - error = t_strdup_printf("Unknown ID '%s'", id_str); - - if (error != NULL) { - *error_r = error; - return -1; - } - - rights_r->identifier = p_strdup(pool, rights_r->identifier); - return 0; -} - -void acl_rights_dup(const struct acl_rights *src, - pool_t pool, struct acl_rights *dest_r) -{ - i_zero(dest_r); - dest_r->id_type = src->id_type; - dest_r->identifier = p_strdup(pool, src->identifier); - dest_r->rights = src->rights == NULL ? NULL : - p_strarray_dup(pool, src->rights); - dest_r->neg_rights = src->neg_rights == NULL ? NULL : - p_strarray_dup(pool, src->neg_rights); - dest_r->global = src->global; -} - -int acl_rights_cmp(const struct acl_rights *r1, const struct acl_rights *r2) -{ - int ret; - - if (r1->global != r2->global) { - /* globals have higher priority than locals */ - return r1->global ? 1 : -1; - } - - ret = (int)r1->id_type - (int)r2->id_type; - if (ret != 0) - return ret; - - return null_strcmp(r1->identifier, r2->identifier); -} - void acl_rights_sort(struct acl_object *aclobj) { struct acl_rights *rights; @@ -499,235 +281,6 @@ void acl_rights_sort(struct acl_object *aclobj) array_delete(&aclobj->rights, dest, count - dest); } -bool acl_rights_has_nonowner_lookup_changes(const struct acl_rights *rights) -{ - const char *const *p; - - if (rights->id_type == ACL_ID_OWNER) { - /* ignore owner rights */ - return FALSE; - } - - if (rights->rights == NULL) - return FALSE; - - for (p = rights->rights; *p != NULL; p++) { - if (strcmp(*p, MAIL_ACL_LOOKUP) == 0) - return TRUE; - } - return FALSE; -} - -int acl_identifier_parse(const char *line, struct acl_rights *rights) -{ - if (str_begins(line, ACL_ID_NAME_USER_PREFIX, &rights->identifier)) { - rights->id_type = ACL_ID_USER; - } else if (strcmp(line, ACL_ID_NAME_OWNER) == 0) { - rights->id_type = ACL_ID_OWNER; - } else if (str_begins(line, ACL_ID_NAME_GROUP_PREFIX, - &rights->identifier)) { - rights->id_type = ACL_ID_GROUP; - } else if (str_begins(line, ACL_ID_NAME_GROUP_OVERRIDE_PREFIX, - &rights->identifier)) { - rights->id_type = ACL_ID_GROUP_OVERRIDE; - } else if (strcmp(line, ACL_ID_NAME_AUTHENTICATED) == 0) { - rights->id_type = ACL_ID_AUTHENTICATED; - } else if (strcmp(line, ACL_ID_NAME_ANYONE) == 0 || - strcmp(line, "anonymous") == 0) { - rights->id_type = ACL_ID_ANYONE; - } else { - return -1; - } - return 0; -} - -static const char *const * -acl_right_names_alloc(pool_t pool, ARRAY_TYPE(const_string) *rights_arr, - bool dup_strings) -{ - const char **ret, *const *rights; - unsigned int i, dest, count; - - /* sort the rights first so we can easily drop duplicates */ - array_sort(rights_arr, i_strcmp_p); - - /* @UNSAFE */ - rights = array_get(rights_arr, &count); - ret = p_new(pool, const char *, count + 1); - if (count > 0) { - ret[0] = rights[0]; - for (i = dest = 1; i < count; i++) { - if (strcmp(rights[i-1], rights[i]) != 0) - ret[dest++] = rights[i]; - } - ret[dest] = NULL; - if (dup_strings) { - for (i = 0; i < dest; i++) - ret[i] = p_strdup(pool, ret[i]); - } - } - return ret; -} - -const char *const * -acl_right_names_parse(pool_t pool, const char *acl, const char **error_r) -{ - ARRAY_TYPE(const_string) rights; - const char *const *names; - unsigned int i; - - /* parse IMAP ACL list */ - while (*acl == ' ' || *acl == '\t') - acl++; - - t_array_init(&rights, 64); - while (*acl != '\0' && *acl != ' ' && *acl != '\t' && *acl != ':') { - for (i = 0; acl_letter_map[i].letter != '\0'; i++) { - if (acl_letter_map[i].letter == *acl) - break; - } - - if (acl_letter_map[i].letter == '\0') { - *error_r = t_strdup_printf("Unknown ACL '%c'", *acl); - return NULL; - } - - array_push_back(&rights, &acl_letter_map[i].name); - acl++; - } - while (*acl == ' ' || *acl == '\t') acl++; - - if (*acl != '\0') { - /* parse our own extended ACLs */ - if (*acl != ':') { - *error_r = "Missing ':' prefix in ACL extensions"; - return NULL; - } - - names = t_strsplit_spaces(acl + 1, ", \t"); - for (; *names != NULL; names++) { - const char *name = p_strdup(pool, *names); - array_push_back(&rights, &name); - } - } - - return acl_right_names_alloc(pool, &rights, FALSE); -} - -void acl_right_names_write(string_t *dest, const char *const *rights) -{ - char c2[2]; - unsigned int i, j, pos; - - c2[1] = '\0'; - pos = str_len(dest); - for (i = 0; rights[i] != NULL; i++) { - /* use letters if possible */ - for (j = 0; acl_letter_map[j].name != NULL; j++) { - if (strcmp(rights[i], acl_letter_map[j].name) == 0) { - c2[0] = acl_letter_map[j].letter; - str_insert(dest, pos, c2); - pos++; - break; - } - } - if (acl_letter_map[j].name == NULL) { - /* fallback to full name */ - str_append_c(dest, ' '); - str_append(dest, rights[i]); - } - } - if (pos + 1 < str_len(dest)) { - c2[0] = ':'; - str_insert(dest, pos + 1, c2); - } -} - -void acl_right_names_merge(pool_t pool, const char *const **destp, - const char *const *src, bool dup_strings) -{ - const char *const *dest = *destp; - ARRAY_TYPE(const_string) rights; - unsigned int i; - - t_array_init(&rights, 64); - if (dest != NULL) { - for (i = 0; dest[i] != NULL; i++) - array_push_back(&rights, &dest[i]); - } - if (src != NULL) { - for (i = 0; src[i] != NULL; i++) - array_push_back(&rights, &src[i]); - } - - *destp = acl_right_names_alloc(pool, &rights, dup_strings); -} - -bool acl_right_names_modify(pool_t pool, - const char *const **rightsp, - const char *const *modify_rights, - enum acl_modify_mode modify_mode) -{ - const char *const *old_rights = *rightsp; - const char *const *new_rights = NULL; - ARRAY_TYPE(const_string) rights; - unsigned int i, j; - - if (modify_rights == NULL && modify_mode != ACL_MODIFY_MODE_CLEAR) { - /* nothing to do here */ - return FALSE; - } - - switch (modify_mode) { - case ACL_MODIFY_MODE_REMOVE: - if (old_rights == NULL || *old_rights == NULL) { - /* nothing to do */ - return FALSE; - } - t_array_init(&rights, 64); - for (i = 0; old_rights[i] != NULL; i++) { - for (j = 0; modify_rights[j] != NULL; j++) { - if (strcmp(old_rights[i], modify_rights[j]) == 0) - break; - } - if (modify_rights[j] == NULL) - array_push_back(&rights, &old_rights[i]); - } - new_rights = empty_str_array; - modify_rights = array_count(&rights) == 0 ? NULL : - array_front(&rights); - acl_right_names_merge(pool, &new_rights, modify_rights, TRUE); - break; - case ACL_MODIFY_MODE_ADD: - new_rights = old_rights; - acl_right_names_merge(pool, &new_rights, modify_rights, TRUE); - break; - case ACL_MODIFY_MODE_REPLACE: - new_rights = empty_str_array; - acl_right_names_merge(pool, &new_rights, modify_rights, TRUE); - break; - case ACL_MODIFY_MODE_CLEAR: - if (*rightsp == NULL) { - /* ACL didn't exist before either */ - return FALSE; - } - *rightsp = NULL; - return TRUE; - } - i_assert(new_rights != NULL); - *rightsp = new_rights; - - if (old_rights == NULL) - return new_rights[0] != NULL; - - /* see if anything changed */ - for (i = 0; old_rights[i] != NULL && new_rights[i] != NULL; i++) { - if (strcmp(old_rights[i], new_rights[i]) != 0) - return TRUE; - } - return old_rights[i] != NULL || new_rights[i] != NULL; -} - static void apply_owner_default_rights(struct acl_object *aclobj) { struct acl_rights_update ru; diff --git a/src/plugins/acl/acl-api.h b/src/plugins/acl/acl-api.h index 7b19a98ec9..6b4bd64bc2 100644 --- a/src/plugins/acl/acl-api.h +++ b/src/plugins/acl/acl-api.h @@ -3,95 +3,16 @@ #include +#include "acl-rights.h" + struct mailbox_list; struct mail_storage; struct mailbox; struct acl_object; -/* Show mailbox in mailbox list. Allow subscribing to it. */ -#define MAIL_ACL_LOOKUP "lookup" -/* Allow opening mailbox for reading */ -#define MAIL_ACL_READ "read" -/* Allow permanent flag changes (except for seen/deleted). - If not set, doesn't allow save/copy to set any flags either. */ -#define MAIL_ACL_WRITE "write" -/* Allow permanent seen-flag changes */ -#define MAIL_ACL_WRITE_SEEN "write-seen" -/* Allow permanent deleted-flag changes */ -#define MAIL_ACL_WRITE_DELETED "write-deleted" -/* Allow saving and copying mails into the mailbox */ -#define MAIL_ACL_INSERT "insert" -/* Allow posting mails to the mailbox (e.g. Sieve fileinto) */ -#define MAIL_ACL_POST "post" -/* Allow expunging mails */ -#define MAIL_ACL_EXPUNGE "expunge" -/* Allow creating child mailboxes */ -#define MAIL_ACL_CREATE "create" -/* Allow deleting this mailbox */ -#define MAIL_ACL_DELETE "delete" -/* Allow changing ACL state in this mailbox */ -#define MAIL_ACL_ADMIN "admin" - #define MAILBOX_ATTRIBUTE_PREFIX_ACL \ MAILBOX_ATTRIBUTE_PREFIX_DOVECOT_PVT"acl/" -/* ACL identifiers in override order */ -enum acl_id_type { - /* Anyone's rights, including anonymous's. - identifier name is ignored. */ - ACL_ID_ANYONE, - /* Authenticate users' rights. identifier name is ignored. */ - ACL_ID_AUTHENTICATED, - /* Group's rights */ - ACL_ID_GROUP, - /* Owner's rights, used when user is the storage's owner. - identifier name is ignored. */ - ACL_ID_OWNER, - /* User's rights */ - ACL_ID_USER, - /* Same as group's rights, but also overrides user's rights */ - ACL_ID_GROUP_OVERRIDE, - - ACL_ID_TYPE_COUNT -}; - -enum acl_modify_mode { - /* Remove rights from existing ACL */ - ACL_MODIFY_MODE_REMOVE = 0, - /* Add rights to existing ACL (or create a new one) */ - ACL_MODIFY_MODE_ADD, - /* Replace existing ACL with given rights */ - ACL_MODIFY_MODE_REPLACE, - /* Clear all the rights from an existing ACL */ - ACL_MODIFY_MODE_CLEAR -}; - -struct acl_rights { - /* Type of the identifier, user/group */ - enum acl_id_type id_type; - /* Identifier, eg. username / group name */ - const char *identifier; - - /* Rights assigned. NULL entry can be ignored, but { NULL } means user - has no rights. */ - const char *const *rights; - /* Negative rights assigned */ - const char *const *neg_rights; - - /* These rights are global for all users */ - bool global:1; -}; -ARRAY_DEFINE_TYPE(acl_rights, struct acl_rights); - -struct acl_rights_update { - struct acl_rights rights; - - enum acl_modify_mode modify_mode; - enum acl_modify_mode neg_modify_mode; - /* These changes' "last changed" timestamp */ - time_t last_change; -}; - /* data contains the information needed to initialize ACL backend. If username is NULL, it means the user is anonymous. Username and groups are matched case-sensitively. */ @@ -161,7 +82,4 @@ bool acl_object_list_next(struct acl_object_list_iter *iter, struct acl_rights *rights_r); int acl_object_list_deinit(struct acl_object_list_iter **iter); -/* Returns the canonical ID for the right. */ -const char *acl_rights_get_id(const struct acl_rights *right); - #endif diff --git a/src/plugins/acl/acl-backend.c b/src/plugins/acl/acl-backend.c index f5eaa24f7e..d9d59c4953 100644 --- a/src/plugins/acl/acl-backend.c +++ b/src/plugins/acl/acl-backend.c @@ -22,20 +22,6 @@ struct event_category event_category_acl = { .name = "acl", }; -const char *const all_mailbox_rights[] = { - MAIL_ACL_LOOKUP, - MAIL_ACL_READ, - MAIL_ACL_WRITE, - MAIL_ACL_WRITE_SEEN, - MAIL_ACL_WRITE_DELETED, - MAIL_ACL_INSERT, - MAIL_ACL_POST, - MAIL_ACL_EXPUNGE, - MAIL_ACL_CREATE, - MAIL_ACL_DELETE, - MAIL_ACL_ADMIN, - NULL -}; static const char *const *owner_mailbox_rights = all_mailbox_rights; static const char *const non_owner_mailbox_rights[] = { NULL }; diff --git a/src/plugins/acl/acl-rights.c b/src/plugins/acl/acl-rights.c new file mode 100644 index 0000000000..dcc0d94c85 --- /dev/null +++ b/src/plugins/acl/acl-rights.c @@ -0,0 +1,468 @@ +/* Copyright (c) 2023 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "str.h" +#include "strescape.h" +#include "acl-api-private.h" + +const struct acl_letter_map acl_letter_map[] = { + { 'l', MAIL_ACL_LOOKUP }, + { 'r', MAIL_ACL_READ }, + { 'w', MAIL_ACL_WRITE }, + { 's', MAIL_ACL_WRITE_SEEN }, + { 't', MAIL_ACL_WRITE_DELETED }, + { 'i', MAIL_ACL_INSERT }, + { 'p', MAIL_ACL_POST }, + { 'e', MAIL_ACL_EXPUNGE }, + { 'k', MAIL_ACL_CREATE }, + { 'x', MAIL_ACL_DELETE }, + { 'a', MAIL_ACL_ADMIN }, + { '\0', NULL } +}; + +const char *const all_mailbox_rights[] = { + MAIL_ACL_LOOKUP, + MAIL_ACL_READ, + MAIL_ACL_WRITE, + MAIL_ACL_WRITE_SEEN, + MAIL_ACL_WRITE_DELETED, + MAIL_ACL_INSERT, + MAIL_ACL_POST, + MAIL_ACL_EXPUNGE, + MAIL_ACL_CREATE, + MAIL_ACL_DELETE, + MAIL_ACL_ADMIN, + NULL +}; + +static_assert(N_ELEMENTS(acl_letter_map) == N_ELEMENTS(all_mailbox_rights), + "acl_letter_map size differs from all_mailbox_rights"); + +void acl_rights_write_id(string_t *dest, const struct acl_rights *right) +{ + switch (right->id_type) { + case ACL_ID_ANYONE: + str_append(dest, ACL_ID_NAME_ANYONE); + break; + case ACL_ID_AUTHENTICATED: + str_append(dest, ACL_ID_NAME_AUTHENTICATED); + break; + case ACL_ID_OWNER: + str_append(dest, ACL_ID_NAME_OWNER); + break; + case ACL_ID_USER: + str_append(dest, ACL_ID_NAME_USER_PREFIX); + str_append(dest, right->identifier); + break; + case ACL_ID_GROUP: + str_append(dest, ACL_ID_NAME_GROUP_PREFIX); + str_append(dest, right->identifier); + break; + case ACL_ID_GROUP_OVERRIDE: + str_append(dest, ACL_ID_NAME_GROUP_OVERRIDE_PREFIX); + str_append(dest, right->identifier); + break; + case ACL_ID_TYPE_COUNT: + i_unreached(); + } +} + +const char *acl_rights_get_id(const struct acl_rights *right) +{ + string_t *str = t_str_new(32); + + acl_rights_write_id(str, right); + return str_c(str); +} + +static bool is_standard_right(const char *name) +{ + unsigned int i; + + for (i = 0; all_mailbox_rights[i] != NULL; i++) { + if (strcmp(all_mailbox_rights[i], name) == 0) + return TRUE; + } + return FALSE; +} + +int acl_rights_update_import(struct acl_rights_update *update, + const char *id, const char *const *rights, + const char **error_r) +{ + ARRAY_TYPE(const_string) dest_rights, dest_neg_rights, *dest; + unsigned int i, j; + + if (acl_identifier_parse(id, &update->rights) < 0) { + *error_r = t_strdup_printf("Invalid ID: %s", id); + return -1; + } + if (rights == NULL) { + update->modify_mode = ACL_MODIFY_MODE_CLEAR; + update->neg_modify_mode = ACL_MODIFY_MODE_CLEAR; + return 0; + } + + t_array_init(&dest_rights, 8); + t_array_init(&dest_neg_rights, 8); + for (i = 0; rights[i] != NULL; i++) { + const char *right = rights[i]; + + if (right[0] != '-') + dest = &dest_rights; + else { + right++; + dest = &dest_neg_rights; + } + if (strcmp(right, "all") != 0) { + if (*right == ':') { + /* non-standard right */ + right++; + array_push_back(dest, &right); + } else if (is_standard_right(right)) { + array_push_back(dest, &right); + } else { + *error_r = t_strdup_printf("Invalid right '%s'", + right); + return -1; + } + } else { + for (j = 0; all_mailbox_rights[j] != NULL; j++) + array_push_back(dest, &all_mailbox_rights[j]); + } + } + if (array_count(&dest_rights) > 0) { + array_append_zero(&dest_rights); + update->rights.rights = array_front(&dest_rights); + } else if (update->modify_mode == ACL_MODIFY_MODE_REPLACE) { + update->modify_mode = ACL_MODIFY_MODE_CLEAR; + } + if (array_count(&dest_neg_rights) > 0) { + array_append_zero(&dest_neg_rights); + update->rights.neg_rights = array_front(&dest_neg_rights); + } else if (update->neg_modify_mode == ACL_MODIFY_MODE_REPLACE) { + update->neg_modify_mode = ACL_MODIFY_MODE_CLEAR; + } + return 0; +} + +const char *acl_rights_export(const struct acl_rights *rights) +{ + string_t *str = t_str_new(128); + + if (rights->rights != NULL) + str_append(str, t_strarray_join(rights->rights, " ")); + if (rights->neg_rights != NULL && rights->neg_rights[0] != NULL) { + if (str_len(str) > 0) + str_append_c(str, ' '); + str_append_c(str, '-'); + str_append(str, t_strarray_join(rights->neg_rights, " -")); + } + return str_c(str); +} + +int acl_rights_parse_line(const char *line, pool_t pool, + struct acl_rights *rights_r, const char **error_r) +{ + const char *id_str, *const *right_names, *error = NULL; + + /* [] [:] */ + if (*line == '"') { + line++; + if (str_unescape_next(&line, &id_str) < 0 || + (line[0] != ' ' && line[0] != '\0')) { + *error_r = "Invalid quoted ID"; + return -1; + } + if (line[0] == ' ') + line++; + } else { + id_str = line; + line = strchr(id_str, ' '); + if (line == NULL) + line = ""; + else + id_str = t_strdup_until(id_str, line++); + } + + i_zero(rights_r); + + right_names = acl_right_names_parse(pool, line, &error); + if (*id_str != '-') + rights_r->rights = right_names; + else { + id_str++; + rights_r->neg_rights = right_names; + } + + if (acl_identifier_parse(id_str, rights_r) < 0) + error = t_strdup_printf("Unknown ID '%s'", id_str); + + if (error != NULL) { + *error_r = error; + return -1; + } + + rights_r->identifier = p_strdup(pool, rights_r->identifier); + return 0; +} + +void acl_rights_dup(const struct acl_rights *src, + pool_t pool, struct acl_rights *dest_r) +{ + i_zero(dest_r); + dest_r->id_type = src->id_type; + dest_r->identifier = p_strdup(pool, src->identifier); + dest_r->rights = src->rights == NULL ? NULL : + p_strarray_dup(pool, src->rights); + dest_r->neg_rights = src->neg_rights == NULL ? NULL : + p_strarray_dup(pool, src->neg_rights); + dest_r->global = src->global; +} + +int acl_rights_cmp(const struct acl_rights *r1, const struct acl_rights *r2) +{ + int ret; + + if (r1->global != r2->global) { + /* globals have higher priority than locals */ + return r1->global ? 1 : -1; + } + + ret = (int)r1->id_type - (int)r2->id_type; + if (ret != 0) + return ret; + + return null_strcmp(r1->identifier, r2->identifier); +} + +bool acl_rights_has_nonowner_lookup_changes(const struct acl_rights *rights) +{ + const char *const *p; + + if (rights->id_type == ACL_ID_OWNER) { + /* ignore owner rights */ + return FALSE; + } + + if (rights->rights == NULL) + return FALSE; + + for (p = rights->rights; *p != NULL; p++) { + if (strcmp(*p, MAIL_ACL_LOOKUP) == 0) + return TRUE; + } + return FALSE; +} + +int acl_identifier_parse(const char *line, struct acl_rights *rights) +{ + if (str_begins(line, ACL_ID_NAME_USER_PREFIX, &rights->identifier)) { + rights->id_type = ACL_ID_USER; + } else if (strcmp(line, ACL_ID_NAME_OWNER) == 0) { + rights->id_type = ACL_ID_OWNER; + } else if (str_begins(line, ACL_ID_NAME_GROUP_PREFIX, + &rights->identifier)) { + rights->id_type = ACL_ID_GROUP; + } else if (str_begins(line, ACL_ID_NAME_GROUP_OVERRIDE_PREFIX, + &rights->identifier)) { + rights->id_type = ACL_ID_GROUP_OVERRIDE; + } else if (strcmp(line, ACL_ID_NAME_AUTHENTICATED) == 0) { + rights->id_type = ACL_ID_AUTHENTICATED; + } else if (strcmp(line, ACL_ID_NAME_ANYONE) == 0 || + strcmp(line, "anonymous") == 0) { + rights->id_type = ACL_ID_ANYONE; + } else { + return -1; + } + return 0; +} + +static const char *const * +acl_right_names_alloc(pool_t pool, ARRAY_TYPE(const_string) *rights_arr, + bool dup_strings) +{ + const char **ret, *const *rights; + unsigned int i, dest, count; + + /* sort the rights first so we can easily drop duplicates */ + array_sort(rights_arr, i_strcmp_p); + + /* @UNSAFE */ + rights = array_get(rights_arr, &count); + ret = p_new(pool, const char *, count + 1); + if (count > 0) { + ret[0] = rights[0]; + for (i = dest = 1; i < count; i++) { + if (strcmp(rights[i-1], rights[i]) != 0) + ret[dest++] = rights[i]; + } + ret[dest] = NULL; + if (dup_strings) { + for (i = 0; i < dest; i++) + ret[i] = p_strdup(pool, ret[i]); + } + } + return ret; +} + +const char *const * +acl_right_names_parse(pool_t pool, const char *acl, const char **error_r) +{ + ARRAY_TYPE(const_string) rights; + const char *const *names; + unsigned int i; + + /* parse IMAP ACL list */ + while (*acl == ' ' || *acl == '\t') + acl++; + + t_array_init(&rights, 64); + while (*acl != '\0' && *acl != ' ' && *acl != '\t' && *acl != ':') { + for (i = 0; acl_letter_map[i].letter != '\0'; i++) { + if (acl_letter_map[i].letter == *acl) + break; + } + + if (acl_letter_map[i].letter == '\0') { + *error_r = t_strdup_printf("Unknown ACL '%c'", *acl); + return NULL; + } + + array_push_back(&rights, &acl_letter_map[i].name); + acl++; + } + while (*acl == ' ' || *acl == '\t') acl++; + + if (*acl != '\0') { + /* parse our own extended ACLs */ + if (*acl != ':') { + *error_r = "Missing ':' prefix in ACL extensions"; + return NULL; + } + + names = t_strsplit_spaces(acl + 1, ", \t"); + for (; *names != NULL; names++) { + const char *name = p_strdup(pool, *names); + array_push_back(&rights, &name); + } + } + + return acl_right_names_alloc(pool, &rights, FALSE); +} + +void acl_right_names_write(string_t *dest, const char *const *rights) +{ + char c2[2]; + unsigned int i, j, pos; + + c2[1] = '\0'; + pos = str_len(dest); + for (i = 0; rights[i] != NULL; i++) { + /* use letters if possible */ + for (j = 0; acl_letter_map[j].name != NULL; j++) { + if (strcmp(rights[i], acl_letter_map[j].name) == 0) { + c2[0] = acl_letter_map[j].letter; + str_insert(dest, pos, c2); + pos++; + break; + } + } + if (acl_letter_map[j].name == NULL) { + /* fallback to full name */ + str_append_c(dest, ' '); + str_append(dest, rights[i]); + } + } + if (pos + 1 < str_len(dest)) { + c2[0] = ':'; + str_insert(dest, pos + 1, c2); + } +} + +void acl_right_names_merge(pool_t pool, const char *const **destp, + const char *const *src, bool dup_strings) +{ + const char *const *dest = *destp; + ARRAY_TYPE(const_string) rights; + unsigned int i; + + t_array_init(&rights, 64); + if (dest != NULL) { + for (i = 0; dest[i] != NULL; i++) + array_push_back(&rights, &dest[i]); + } + if (src != NULL) { + for (i = 0; src[i] != NULL; i++) + array_push_back(&rights, &src[i]); + } + + *destp = acl_right_names_alloc(pool, &rights, dup_strings); +} + +bool acl_right_names_modify(pool_t pool, + const char *const **rightsp, + const char *const *modify_rights, + enum acl_modify_mode modify_mode) +{ + const char *const *old_rights = *rightsp; + const char *const *new_rights = NULL; + ARRAY_TYPE(const_string) rights; + unsigned int i, j; + + if (modify_rights == NULL && modify_mode != ACL_MODIFY_MODE_CLEAR) { + /* nothing to do here */ + return FALSE; + } + + switch (modify_mode) { + case ACL_MODIFY_MODE_REMOVE: + if (old_rights == NULL || *old_rights == NULL) { + /* nothing to do */ + return FALSE; + } + t_array_init(&rights, 64); + for (i = 0; old_rights[i] != NULL; i++) { + for (j = 0; modify_rights[j] != NULL; j++) { + if (strcmp(old_rights[i], modify_rights[j]) == 0) + break; + } + if (modify_rights[j] == NULL) + array_push_back(&rights, &old_rights[i]); + } + new_rights = empty_str_array; + modify_rights = array_count(&rights) == 0 ? NULL : + array_front(&rights); + acl_right_names_merge(pool, &new_rights, modify_rights, TRUE); + break; + case ACL_MODIFY_MODE_ADD: + new_rights = old_rights; + acl_right_names_merge(pool, &new_rights, modify_rights, TRUE); + break; + case ACL_MODIFY_MODE_REPLACE: + new_rights = empty_str_array; + acl_right_names_merge(pool, &new_rights, modify_rights, TRUE); + break; + case ACL_MODIFY_MODE_CLEAR: + if (*rightsp == NULL) { + /* ACL didn't exist before either */ + return FALSE; + } + *rightsp = NULL; + return TRUE; + } + i_assert(new_rights != NULL); + *rightsp = new_rights; + + if (old_rights == NULL) + return new_rights[0] != NULL; + + /* see if anything changed */ + for (i = 0; old_rights[i] != NULL && new_rights[i] != NULL; i++) { + if (strcmp(old_rights[i], new_rights[i]) != 0) + return TRUE; + } + return old_rights[i] != NULL || new_rights[i] != NULL; +} + diff --git a/src/plugins/acl/acl-rights.h b/src/plugins/acl/acl-rights.h new file mode 100644 index 0000000000..6153d31004 --- /dev/null +++ b/src/plugins/acl/acl-rights.h @@ -0,0 +1,150 @@ +#ifndef ACL_RIGHTS_H +#define ACL_RIGHTS_H + +/* Show mailbox in mailbox list. Allow subscribing to it. */ +#define MAIL_ACL_LOOKUP "lookup" +/* Allow opening mailbox for reading */ +#define MAIL_ACL_READ "read" +/* Allow permanent flag changes (except for seen/deleted). + If not set, doesn't allow save/copy to set any flags either. */ +#define MAIL_ACL_WRITE "write" +/* Allow permanent seen-flag changes */ +#define MAIL_ACL_WRITE_SEEN "write-seen" +/* Allow permanent deleted-flag changes */ +#define MAIL_ACL_WRITE_DELETED "write-deleted" +/* Allow saving and copying mails into the mailbox */ +#define MAIL_ACL_INSERT "insert" +/* Allow posting mails to the mailbox (e.g. Sieve fileinto) */ +#define MAIL_ACL_POST "post" +/* Allow expunging mails */ +#define MAIL_ACL_EXPUNGE "expunge" +/* Allow creating child mailboxes */ +#define MAIL_ACL_CREATE "create" +/* Allow deleting this mailbox */ +#define MAIL_ACL_DELETE "delete" +/* Allow changing ACL state in this mailbox */ +#define MAIL_ACL_ADMIN "admin" + +#define ACL_ID_NAME_ANYONE "anyone" +#define ACL_ID_NAME_AUTHENTICATED "authenticated" +#define ACL_ID_NAME_OWNER "owner" +#define ACL_ID_NAME_USER_PREFIX "user=" +#define ACL_ID_NAME_GROUP_PREFIX "group=" +#define ACL_ID_NAME_GROUP_OVERRIDE_PREFIX "group-override=" + +struct acl_letter_map { + const char letter; + const char *name; +}; + +extern const struct acl_letter_map acl_letter_map[]; +extern const char *const all_mailbox_rights[]; + +/* ACL identifiers in override order */ +enum acl_id_type { + /* Anyone's rights, including anonymous's. + identifier name is ignored. */ + ACL_ID_ANYONE, + /* Authenticate users' rights. identifier name is ignored. */ + ACL_ID_AUTHENTICATED, + /* Group's rights */ + ACL_ID_GROUP, + /* Owner's rights, used when user is the storage's owner. + identifier name is ignored. */ + ACL_ID_OWNER, + /* User's rights */ + ACL_ID_USER, + /* Same as group's rights, but also overrides user's rights */ + ACL_ID_GROUP_OVERRIDE, + + ACL_ID_TYPE_COUNT +}; + +enum acl_modify_mode { + /* Remove rights from existing ACL */ + ACL_MODIFY_MODE_REMOVE = 0, + /* Add rights to existing ACL (or create a new one) */ + ACL_MODIFY_MODE_ADD, + /* Replace existing ACL with given rights */ + ACL_MODIFY_MODE_REPLACE, + /* Clear all the rights from an existing ACL */ + ACL_MODIFY_MODE_CLEAR +}; + +struct acl_rights { + /* Type of the identifier, user/group */ + enum acl_id_type id_type; + /* Identifier, eg. username / group name */ + const char *identifier; + + /* Rights assigned. NULL entry can be ignored, but { NULL } means user + has no rights. */ + const char *const *rights; + /* Negative rights assigned */ + const char *const *neg_rights; + + /* These rights are global for all users */ + bool global:1; +}; +ARRAY_DEFINE_TYPE(acl_rights, struct acl_rights); + +struct acl_rights_update { + /* Holder for rights */ + struct acl_rights rights; + /* Type of modification */ + enum acl_modify_mode modify_mode; + /* Type of modification for negative rights */ + enum acl_modify_mode neg_modify_mode; + + /* These changes' "last changed" timestamp */ + time_t last_change; +}; + +/* Returns the canonical ID for the right. */ +const char *acl_rights_get_id(const struct acl_rights *right); + +/* Append the id name to dest from rights */ +void acl_rights_write_id(string_t *dest, const struct acl_rights *right); + +/* Returns true if the rights are not for owner, and there is MAIL_ACL_LOOKUP + right. */ +bool acl_rights_has_nonowner_lookup_changes(const struct acl_rights *rights); + +/* Parses identifier from line */ +int acl_identifier_parse(const char *line, struct acl_rights *rights); + +int acl_rights_update_import(struct acl_rights_update *update, + const char *id, const char *const *rights, + const char **error_r); + +/* Exports ACL rights to string */ +const char *acl_rights_export(const struct acl_rights *rights); + +/* Parses line containing identifier and rights */ +int acl_rights_parse_line(const char *line, pool_t pool, + struct acl_rights *rights_r, const char **error_r); + +/* Duplicates a right */ +void acl_rights_dup(const struct acl_rights *src, + pool_t pool, struct acl_rights *dest_r); + +/* Comparison for rights */ +int acl_rights_cmp(const struct acl_rights *r1, const struct acl_rights *r2); + +/* Parses acl letter string to names */ +const char *const * +acl_right_names_parse(pool_t pool, const char *acl, const char **error_r); + +/* Writes acl names to destination string as acl letters */ +void acl_right_names_write(string_t *dest, const char *const *rights); + +/* Merges ACL names */ +void acl_right_names_merge(pool_t pool, const char *const **destp, + const char *const *src, bool dup_strings); + +/* Modifies ACL rights */ +bool acl_right_names_modify(pool_t pool, + const char *const **rightsp, + const char *const *modify_rights, + enum acl_modify_mode modify_mode); +#endif