]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
plugins: acl - Move rights handling to acl-rights.c
authorAki Tuomi <aki.tuomi@open-xchange.com>
Mon, 27 Nov 2023 13:16:40 +0000 (15:16 +0200)
committerAki Tuomi <aki.tuomi@open-xchange.com>
Wed, 12 Feb 2025 10:34:11 +0000 (12:34 +0200)
src/plugins/acl/Makefile.am
src/plugins/acl/acl-api-private.h
src/plugins/acl/acl-api.c
src/plugins/acl/acl-api.h
src/plugins/acl/acl-backend.c
src/plugins/acl/acl-rights.c [new file with mode: 0644]
src/plugins/acl/acl-rights.h [new file with mode: 0644]

index 041d3dcbd82f8676f3ac876da291994ff2548bb6..0cc0016c056ce96fc12f8a0ea73b18012c4ef296 100644 (file)
@@ -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
 
index bc47473eddd14067019f11800fdc192492ecee4a..c994da94bc565ca5071546fd84997857201720ca 100644 (file)
@@ -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);
index 6fe51560c0cc665e4c08e1466b9fb79e9b8dd2d0..b1157437c485fda7ac527fee05ac12c7287d207e 100644 (file)
 #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;
-
-       /* <id> [<imap acls>] [:<named acls>] */
-       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;
index 7b19a98ec94528c20dd2850f990662cdbcec55e7..6b4bd64bc2a4390d65d286096d411d6b44e11ff4 100644 (file)
@@ -3,95 +3,16 @@
 
 #include <sys/stat.h>
 
+#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
index f5eaa24f7ec76b337d4d769e883d797b89efccb8..d9d59c4953b6c2dced47dce8c771293fceb0029c 100644 (file)
@@ -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 (file)
index 0000000..dcc0d94
--- /dev/null
@@ -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;
+
+       /* <id> [<imap acls>] [:<named acls>] */
+       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 (file)
index 0000000..6153d31
--- /dev/null
@@ -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