]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
Added IMAP ACL commands with ability to modify ACLs.
authorTimo Sirainen <tss@iki.fi>
Sun, 16 Nov 2008 02:46:14 +0000 (04:46 +0200)
committerTimo Sirainen <tss@iki.fi>
Sun, 16 Nov 2008 02:46:14 +0000 (04:46 +0200)
Based on patch by Bernhard Herzog and Sascha Wilde.

--HG--
branch : HEAD

14 files changed:
configure.in
src/plugins/Makefile.am
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-vfile.c
src/plugins/acl/acl-cache.c
src/plugins/acl/acl-mailbox.c
src/plugins/acl/acl-plugin.h
src/plugins/acl/acl-storage.h [new file with mode: 0644]
src/plugins/imap-acl/Makefile.am [new file with mode: 0644]
src/plugins/imap-acl/imap-acl-plugin.c [new file with mode: 0644]
src/plugins/imap-acl/imap-acl-plugin.h [new file with mode: 0644]

index bf5d2943ed8a8e42e73f91386f491ef17e5e1e13..a69a4a3466381d400d480e08c623968b9f43fee5 100644 (file)
@@ -2453,6 +2453,7 @@ src/tests/Makefile
 src/util/Makefile
 src/plugins/Makefile
 src/plugins/acl/Makefile
+src/plugins/imap-acl/Makefile
 src/plugins/autocreate/Makefile
 src/plugins/convert/Makefile
 src/plugins/expire/Makefile
index 03964af8a6ded920f4f1c2932b970d919c440620..a6dcc92bf0f452b6e9723e179b0a81e27749048e 100644 (file)
@@ -12,6 +12,7 @@ endif
 
 SUBDIRS = \
        acl \
+       imap-acl \
        autocreate \
        convert \
        expire \
index afeea0099899c6b409b9871090833931abeb31e8..4bb8e702d6fb9c9016ff02e90b5789017aa347bd 100644 (file)
@@ -26,7 +26,8 @@ noinst_HEADERS = \
        acl-api-private.h \
        acl-backend-vfile.h \
        acl-cache.h \
-       acl-plugin.h
+       acl-plugin.h \
+       acl-storage.h
 
 install-exec-local:
        for d in imap lda; do \
index 6dd737210b1ad1adb8d8353179d805a04b35fdc1..54d24ba19639b4f97f311010f4ecef22ab2b315a 100644 (file)
@@ -3,6 +3,13 @@
 
 #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 {
        struct acl_backend *(*alloc)(void);
        int (*init)(struct acl_backend *backend, const char *data);
@@ -25,7 +32,7 @@ struct acl_backend_vfuncs {
 
        int (*object_refresh_cache)(struct acl_object *aclobj);
        int (*object_update)(struct acl_object *aclobj,
-                            const struct acl_rights_update *rights);
+                            const struct acl_rights_update *update);
 
        struct acl_object_list_iter *
                (*object_list_init)(struct acl_object *aclobj);
@@ -65,9 +72,13 @@ struct acl_object_list_iter {
        struct acl_object *aclobj;
 
        unsigned int idx;
+       unsigned int returned_owner:1;
        unsigned int failed:1;
 };
 
+const char *const *
+acl_backend_mask_get_names(struct acl_backend *backend,
+                          const struct acl_mask *mask, pool_t pool);
 int acl_backend_get_default_rights(struct acl_backend *backend,
                                   const struct acl_mask **mask_r);
 
index d81315f56a5a63a41d07da21d3cffb7b875ca07f..b70d2ad248a8c659babea27c8195810d18143c52 100644 (file)
@@ -52,31 +52,14 @@ int acl_object_have_right(struct acl_object *aclobj, unsigned int right_idx)
        return acl_cache_mask_isset(have_mask, right_idx);
 }
 
-static int acl_object_get_my_rights_real(struct acl_object *aclobj, pool_t pool,
-                                        const char *const **rights_r)
+const char *const *
+acl_backend_mask_get_names(struct acl_backend *backend,
+                          const struct acl_mask *mask, pool_t pool)
 {
-       struct acl_backend *backend = aclobj->backend;
-       const struct acl_mask *mask;
        const char *const *names;
        const char **buf, **rights;
        unsigned int names_count, count, i, j, name_idx;
 
-       if (*aclobj->name == '\0') {
-               /* we want to look up default rights */
-               if (acl_backend_get_default_rights(backend, &mask) < 0)
-                       return -1;
-       } else {
-               if (backend->v.object_refresh_cache(aclobj) < 0)
-                       return -1;
-
-               mask = acl_cache_get_my_rights(backend->cache,
-                                              aclobj->name);
-               if (mask == NULL) {
-                       if (acl_backend_get_default_rights(backend, &mask) < 0)
-                               return -1;
-               }
-       }
-
        names = acl_cache_get_names(backend->cache, &names_count);
        buf = t_new(const char *, (mask->size * CHAR_BIT) + 1);
        count = 0;
@@ -98,7 +81,32 @@ static int acl_object_get_my_rights_real(struct acl_object *aclobj, pool_t pool,
        /* @UNSAFE */
        rights = p_new(pool, const char *, count + 1);
        memcpy(rights, buf, count * sizeof(const char *));
-       *rights_r = rights;
+       return rights;
+}
+
+static int acl_object_get_my_rights_real(struct acl_object *aclobj, pool_t pool,
+                                        const char *const **rights_r)
+{
+       struct acl_backend *backend = aclobj->backend;
+       const struct acl_mask *mask;
+
+       if (*aclobj->name == '\0') {
+               /* we want to look up default rights */
+               if (acl_backend_get_default_rights(backend, &mask) < 0)
+                       return -1;
+       } else {
+               if (backend->v.object_refresh_cache(aclobj) < 0)
+                       return -1;
+
+               mask = acl_cache_get_my_rights(backend->cache,
+                                              aclobj->name);
+               if (mask == NULL) {
+                       if (acl_backend_get_default_rights(backend, &mask) < 0)
+                               return -1;
+               }
+       }
+
+       *rights_r = acl_backend_mask_get_names(backend, mask, pool);
        return 0;
 }
 
@@ -116,9 +124,9 @@ int acl_object_get_my_rights(struct acl_object *aclobj, pool_t pool,
 }
 
 int acl_object_update(struct acl_object *aclobj,
-                     const struct acl_rights_update *rights)
+                     const struct acl_rights_update *update)
 {
-        return aclobj->backend->v.object_update(aclobj, rights);
+        return aclobj->backend->v.object_update(aclobj, update);
 }
 
 struct acl_object_list_iter *acl_object_list_init(struct acl_object *aclobj)
index f254cb3e9a5bd68fd37e26d1fcd6b66f31aa40ae..b56e75b50b13b759dcc72d45aeb5c973d4bceb93 100644 (file)
@@ -3,6 +3,7 @@
 
 struct mailbox_list;
 struct mail_storage;
+struct mailbox;
 struct acl_object;
 
 /* Show mailbox in mailbox list. Allow subscribing to it. */
@@ -48,12 +49,14 @@ enum acl_id_type {
 };
 
 enum acl_modify_mode {
-       /* Add rights to existing ACL (or create a new one) */
-       ACL_MODIFY_MODE_ADD = 0,
        /* Remove rights from existing ACL */
-       ACL_MODIFY_MODE_REMOVE,
+       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
+       ACL_MODIFY_MODE_REPLACE,
+       /* Clear all the rights from an existing ACL */
+       ACL_MODIFY_MODE_CLEAR
 };
 
 struct acl_rights {
@@ -126,7 +129,7 @@ int acl_object_get_my_rights(struct acl_object *aclobj, pool_t pool,
 
 /* Update ACL of given object. */
 int acl_object_update(struct acl_object *aclobj,
-                     const struct acl_rights_update *rights);
+                     const struct acl_rights_update *update);
 
 /* List all identifiers. */
 struct acl_object_list_iter *acl_object_list_init(struct acl_object *aclobj);
index 0f5dfb0d17e92b8f0b6a4828ff44325337fa9542..9b3720d1db59779facf7f5dba338c1478f015b2a 100644 (file)
@@ -3,9 +3,14 @@
 #include "lib.h"
 #include "ioloop.h"
 #include "array.h"
+#include "bsearch-insert-pos.h"
+#include "str.h"
 #include "istream.h"
+#include "ostream.h"
+#include "file-dotlock.h"
 #include "nfs-workarounds.h"
 #include "mail-storage-private.h"
+#include "mail-namespace.h"
 #include "acl-cache.h"
 #include "acl-backend-vfile.h"
 
@@ -52,6 +57,14 @@ static const struct acl_letter_map acl_letter_map[] = {
        { '\0', NULL }
 };
 
+static struct dotlock_settings dotlock_set = {
+       MEMBER(temp_prefix) NULL,
+       MEMBER(lock_suffix) NULL,
+
+       MEMBER(timeout) 30,
+       MEMBER(stale_timeout) 120
+};
+
 static struct acl_backend *acl_backend_vfile_alloc(void)
 {
        struct acl_backend_vfile *backend;
@@ -266,7 +279,8 @@ static void acl_backend_vfile_object_deinit(struct acl_object *_aclobj)
 }
 
 static const char *const *
-acl_rights_alloc(pool_t pool, ARRAY_TYPE(const_string) *rights_arr)
+acl_rights_alloc(pool_t pool, ARRAY_TYPE(const_string) *rights_arr,
+                bool dup_strings)
 {
        const char **ret, **rights;
        unsigned int i, dest, count;
@@ -283,6 +297,11 @@ acl_rights_alloc(pool_t pool, ARRAY_TYPE(const_string) *rights_arr)
                        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;
 }
@@ -326,7 +345,7 @@ acl_parse_rights(pool_t pool, const char *acl, const char **error_r)
                }
        }
 
-       return acl_rights_alloc(pool, &rights);
+       return acl_rights_alloc(pool, &rights, FALSE);
 }
 
 static int
@@ -362,31 +381,34 @@ acl_object_vfile_parse_line(struct acl_object_vfile *aclobj, bool global,
 
        switch (*line) {
        case 'u':
-               if (strncmp(line, "user=", 5) == 0) {
+               if (strncmp(line, ACL_ID_NAME_USER_PREFIX,
+                           strlen(ACL_ID_NAME_USER_PREFIX)) == 0) {
                        rights.id_type = ACL_ID_USER;
                        rights.identifier = line + 5;
                        break;
                }
        case 'o':
-               if (strcmp(line, "owner") == 0) {
+               if (strcmp(line, ACL_ID_NAME_OWNER) == 0) {
                        rights.id_type = ACL_ID_OWNER;
                        break;
                }
        case 'g':
-               if (strncmp(line, "group=", 6) == 0) {
+               if (strncmp(line, ACL_ID_NAME_GROUP_PREFIX,
+                           strlen(ACL_ID_NAME_GROUP_PREFIX)) == 0) {
                        rights.id_type = ACL_ID_GROUP;
                        rights.identifier = line + 6;
                        break;
-               } else if (strncmp(line, "group-override=", 15) == 0) {
+               } else if (strncmp(line, ACL_ID_NAME_GROUP_OVERRIDE_PREFIX,
+                                  strlen(ACL_ID_NAME_GROUP_OVERRIDE_PREFIX)) == 0) {
                        rights.id_type = ACL_ID_GROUP_OVERRIDE;
                        rights.identifier = line + 15;
                        break;
                }
        case 'a':
-               if (strcmp(line, "authenticated") == 0) {
+               if (strcmp(line, ACL_ID_NAME_AUTHENTICATED) == 0) {
                        rights.id_type = ACL_ID_AUTHENTICATED;
                        break;
-               } else if (strcmp(line, "anyone") == 0 ||
+               } else if (strcmp(line, ACL_ID_NAME_ANYONE) == 0 ||
                           strcmp(line, "anonymous") == 0) {
                        rights.id_type = ACL_ID_ANYONE;
                        break;
@@ -615,17 +637,23 @@ int acl_backend_vfile_object_get_mtime(struct acl_object *aclobj,
 static int acl_rights_cmp(const void *p1, const void *p2)
 {
        const struct acl_rights *r1 = p1, *r2 = p2;
+       int ret;
 
        if (r1->global != r2->global) {
                /* globals have higher priority than locals */
                return r1->global ? 1 : -1;
        }
 
-       return r1->id_type - r2->id_type;
+       ret = r1->id_type - r2->id_type;
+       if (ret != 0)
+               return ret;
+
+       return null_strcmp(r1->identifier, r2->identifier);
 }
 
 static void
-acl_rights_merge(pool_t pool, const char *const **destp, const char *const *src)
+acl_rights_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;
@@ -641,7 +669,7 @@ acl_rights_merge(pool_t pool, const char *const **destp, const char *const *src)
                        array_append(&rights, &src[i], 1);
        }
 
-       *destp = acl_rights_alloc(pool, &rights);
+       *destp = acl_rights_alloc(pool, &rights, dup_strings);
 }
 
 static void acl_backend_vfile_rights_sort(struct acl_object_vfile *aclobj)
@@ -657,17 +685,14 @@ static void acl_backend_vfile_rights_sort(struct acl_object_vfile *aclobj)
 
        /* merge identical identifiers */
        for (dest = 0, i = 1; i < count; i++) {
-               if (rights[i].global == rights[dest].global &&
-                   rights[i].id_type == rights[dest].id_type &&
-                   null_strcmp(rights[i].identifier,
-                               rights[dest].identifier) == 0) {
+               if (acl_rights_cmp(&rights[i], &rights[dest]) == 0) {
                        /* add i's rights to dest and delete i */
                        acl_rights_merge(aclobj->rights_pool,
                                         &rights[dest].rights,
-                                        rights[i].rights);
+                                        rights[i].rights, FALSE);
                        acl_rights_merge(aclobj->rights_pool,
                                         &rights[dest].neg_rights,
-                                        rights[i].neg_rights);
+                                        rights[i].neg_rights, FALSE);
                } else {
                        if (++dest != i)
                                rights[dest] = rights[i];
@@ -763,24 +788,306 @@ static int acl_backend_vfile_object_refresh_cache(struct acl_object *_aclobj)
        return 0;
 }
 
+static int acl_backend_vfile_update_begin(struct acl_object_vfile *aclobj,
+                                         struct dotlock **dotlock_r)
+{
+       struct acl_object *_aclobj = &aclobj->aclobj;
+       mode_t mode;
+       gid_t gid;
+       int fd;
+
+       /* first lock the ACL file */
+       mailbox_list_get_permissions(_aclobj->backend->list, &mode, &gid);
+       fd = file_dotlock_open_mode(&dotlock_set, aclobj->local_path, 0,
+                                   mode, (uid_t)-1, gid, dotlock_r);
+       if (fd == -1) {
+               i_error("file_dotlock_open_mode(%s) failed: %m",
+                       aclobj->local_path);
+               return -1;
+       }
+
+       /* locked successfully, re-read the existing file to make sure we
+          don't lose any changes. */
+       acl_cache_flush(_aclobj->backend->cache, _aclobj->name);
+       if (acl_backend_vfile_object_refresh_cache(_aclobj) < 0) {
+               file_dotlock_delete(dotlock_r);
+               return -1;
+       }
+       return fd;
+}
+
+static bool modify_right_list(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;
+       const char *null = 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;
+       }
+
+       if (old_rights == NULL)
+               old_rights = &null;
+
+       switch (modify_mode) {
+       case ACL_MODIFY_MODE_REMOVE:
+               if (*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_append(&rights, &old_rights[i], 1);
+               }
+               new_rights = &null;
+               modify_rights = array_idx(&rights, 0);
+               acl_rights_merge(pool, &new_rights, modify_rights, TRUE);
+               break;
+       case ACL_MODIFY_MODE_ADD:
+               new_rights = old_rights;
+               acl_rights_merge(pool, &new_rights, modify_rights, TRUE);
+               break;
+       case ACL_MODIFY_MODE_REPLACE:
+               new_rights = &null;
+               acl_rights_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;
+       }
+       *rightsp = new_rights;
+
+       /* 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 bool
+vfile_object_modify_right(struct acl_object_vfile *aclobj, unsigned int idx,
+                         const struct acl_rights_update *update)
+{
+       struct acl_rights *right;
+       bool c1, c2;
+
+       right = array_idx_modifiable(&aclobj->rights, idx);
+       c1 = modify_right_list(aclobj->rights_pool, &right->rights,
+                              update->rights.rights, update->modify_mode);
+       c2 = modify_right_list(aclobj->rights_pool, &right->neg_rights,
+                              update->rights.neg_rights,
+                              update->neg_modify_mode);
+
+       if (right->rights == NULL && right->neg_rights == NULL) {
+               /* this identifier no longer exists */
+               array_delete(&aclobj->rights, idx, 1);
+               c1 = TRUE;
+       }
+       return c1 || c2;
+}
+
+static bool
+vfile_object_add_right(struct acl_object_vfile *aclobj, unsigned int idx,
+                      const struct acl_rights_update *update)
+{
+       struct acl_rights right;
+
+       if (update->modify_mode == ACL_MODIFY_MODE_REMOVE &&
+           update->neg_modify_mode == ACL_MODIFY_MODE_REMOVE) {
+               /* nothing to do */
+               return FALSE;
+       }
+
+       memset(&right, 0, sizeof(right));
+       right.id_type = update->rights.id_type;
+       right.identifier = p_strdup(aclobj->rights_pool,
+                                   update->rights.identifier);
+       array_insert(&aclobj->rights, idx, &right, 1);
+       return vfile_object_modify_right(aclobj, idx, update);
+}
+
+static void vfile_write_rights_list(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[j]);
+               }
+       }
+}
+
+static void
+vfile_write_right(string_t *dest, const struct acl_rights *right,
+                 bool neg)
+{
+       const char *const *rights = neg ? right->neg_rights : right->rights;
+
+       if (neg) str_append_c(dest,'-');
+
+       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();
+       }
+       str_append_c(dest, ' ');
+       vfile_write_rights_list(dest, rights);
+       str_append_c(dest, '\n');
+}
+
+static int
+acl_backend_vfile_update_write(struct acl_object_vfile *aclobj,
+                              int fd, const char *path)
+{
+       struct ostream *output;
+       string_t *str;
+       const struct acl_rights *rights;
+       unsigned int i, count;
+       int ret = 0;
+
+       output = o_stream_create_fd_file(fd, 0, FALSE);
+       o_stream_cork(output);
+
+       str = t_str_new(256);
+       /* rights are sorted with globals at the end, so we can stop at the
+          first global */
+       rights = array_get(&aclobj->rights, &count);
+       for (i = 0; i < count && !rights[i].global; i++) {
+               if (rights[i].rights != NULL)
+                       vfile_write_right(str, &rights[i], FALSE);
+               if (rights[i].neg_rights != NULL)
+                       vfile_write_right(str, &rights[i], TRUE);
+               o_stream_send(output, str_data(str), str_len(str));
+               str_truncate(str, 0);
+       }
+       if (o_stream_flush(output) < 0) {
+               i_error("write(%s) failed: %m", path);
+               ret = -1;
+       }
+       o_stream_destroy(&output);
+       /* we really don't want to lose ACL files' contents, so fsync() always
+          before renaming */
+       if (fsync(fd) < 0) {
+               i_error("fsync(%s) failed: %m", path);
+               ret = -1;
+       }
+       return ret;
+}
+
 static int
-acl_backend_vfile_object_update(struct acl_object *aclobj ATTR_UNUSED,
-                               const struct acl_rights_update *rights
-                                       ATTR_UNUSED)
+acl_backend_vfile_object_update(struct acl_object *_aclobj,
+                               const struct acl_rights_update *update)
 {
-       /* FIXME */
-       return -1;
+       struct acl_object_vfile *aclobj = (struct acl_object_vfile *)_aclobj;
+       const struct acl_rights *rights;
+       struct dotlock *dotlock;
+       const char *path;
+       unsigned int i, count;
+       int fd;
+       bool changed;
+
+       /* global ACLs can't be updated here */
+       i_assert(!update->rights.global);
+
+       fd = acl_backend_vfile_update_begin(aclobj, &dotlock);
+       if (fd == -1)
+               return -1;
+
+       rights = array_get(&aclobj->rights, &count);
+       if (!bsearch_insert_pos(&update->rights, rights, count, sizeof(*rights),
+                               acl_rights_cmp, &i))
+               changed = vfile_object_add_right(aclobj, i, update);
+       else
+               changed = vfile_object_modify_right(aclobj, i, update);
+       if (!changed) {
+               file_dotlock_delete(&dotlock);
+               return 0;
+       } else {
+               acl_cache_flush(_aclobj->backend->cache, _aclobj->name);
+
+               path = file_dotlock_get_lock_path(dotlock);
+               if (acl_backend_vfile_update_write(aclobj, fd, path) < 0) {
+                       file_dotlock_delete(&dotlock);
+                       return -1;
+               }
+               return file_dotlock_replace(&dotlock, 0);
+       }
 }
 
 static struct acl_object_list_iter *
-acl_backend_vfile_object_list_init(struct acl_object *aclobj)
+acl_backend_vfile_object_list_init(struct acl_object *_aclobj)
 {
+       struct acl_object_vfile *aclobj =
+               (struct acl_object_vfile *)_aclobj;
        struct acl_object_list_iter *iter;
+       struct mail_namespace *ns;
 
        iter = i_new(struct acl_object_list_iter, 1);
-       iter->aclobj = aclobj;
+       iter->aclobj = _aclobj;
+
+       if (!array_is_created(&aclobj->rights)) {
+               /* we may have the object cached, but we don't have all the
+                  rights read into memory */
+               acl_cache_flush(_aclobj->backend->cache, _aclobj->name);
+       }
+
+       /* be sure to return owner for private namespaces.
+          (other namespaces don't have an owner) */
+       ns = mailbox_list_get_namespace(_aclobj->backend->list);
+       if (ns->type != NAMESPACE_PRIVATE)
+               iter->returned_owner = TRUE;
 
-       if (aclobj->backend->v.object_refresh_cache(aclobj) < 0)
+       if (_aclobj->backend->v.object_refresh_cache(_aclobj) < 0)
                iter->failed = TRUE;
        return iter;
 }
@@ -793,11 +1100,26 @@ acl_backend_vfile_object_list_next(struct acl_object_list_iter *iter,
                (struct acl_object_vfile *)iter->aclobj;
        const struct acl_rights *rights;
 
-       if (!array_is_created(&aclobj->rights) ||
-           iter->idx == array_count(&aclobj->rights))
-               return 0;
+       if (iter->idx == array_count(&aclobj->rights)) {
+               struct acl_backend *backend = iter->aclobj->backend;
+
+               if (iter->returned_owner)
+                       return 0;
+
+               /* return missing owner based on the default ACLs */
+               iter->returned_owner = TRUE;
+               memset(rights_r, 0, sizeof(*rights_r));
+               rights_r->id_type = ACL_ID_OWNER;
+               rights_r->rights =
+                       acl_backend_mask_get_names(backend,
+                                                  backend->default_aclmask,
+                                                  pool_datastack_create());
+               return 1;
+       }
 
        rights = array_idx(&aclobj->rights, iter->idx++);
+       if (rights->id_type == ACL_ID_OWNER && rights->rights != NULL)
+               iter->returned_owner = TRUE;
        *rights_r = *rights;
        return 1;
 }
index 9a8cd434e30155844b4f9b942887c2cab6b1e7bf..fedc80a36f8587e23ea26de0f9c2d61d1fc50211 100644 (file)
@@ -244,6 +244,8 @@ acl_cache_update_rights_mask(struct acl_cache *cache,
                        changed = FALSE;
                new_mask = change_mask;
                break;
+       case ACL_MODIFY_MODE_CLEAR:
+               i_unreached();
        }
 
        if (new_mask != old_mask) {
index a0729257927dbea674f847993debd46150cb63f2..b994c39735930a397a3cbef9050820900e2d72b3 100644 (file)
@@ -30,7 +30,21 @@ struct acl_transaction_context {
 static MODULE_CONTEXT_DEFINE_INIT(acl_mail_module, &mail_module_register);
 static struct acl_transaction_context acl_transaction_failure;
 
-static int mailbox_acl_right_lookup(struct mailbox *box, unsigned int right_idx)
+struct acl_object *acl_storage_get_default_aclobj(struct mail_storage *storage)
+{
+       struct acl_mail_storage *astorage = ACL_CONTEXT(storage);
+
+       return astorage->rights.backend->default_aclobj;
+}
+
+struct acl_object *acl_mailbox_get_aclobj(struct mailbox *box)
+{
+       struct acl_mailbox *abox = ACL_CONTEXT(box);
+
+       return abox->aclobj;
+}
+
+int acl_mailbox_right_lookup(struct mailbox *box, unsigned int right_idx)
 {
        struct acl_mailbox *abox = ACL_CONTEXT(box);
        struct acl_mail_storage *astorage = ACL_CONTEXT(box->storage);
@@ -57,19 +71,19 @@ static bool acl_is_readonly(struct mailbox *box)
        if (abox->module_ctx.super.is_readonly(box))
                return TRUE;
 
-       if (mailbox_acl_right_lookup(box, ACL_STORAGE_RIGHT_INSERT) > 0)
+       if (acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_INSERT) > 0)
                return FALSE;
-       if (mailbox_acl_right_lookup(box, ACL_STORAGE_RIGHT_EXPUNGE) > 0)
+       if (acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_EXPUNGE) > 0)
                return FALSE;
 
        /* Next up is the "shared flag rights" */
-       if (mailbox_acl_right_lookup(box, ACL_STORAGE_RIGHT_WRITE) > 0)
+       if (acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_WRITE) > 0)
                return FALSE;
        if ((box->private_flags_mask & MAIL_DELETED) == 0 &&
-           mailbox_acl_right_lookup(box, ACL_STORAGE_RIGHT_WRITE_DELETED) > 0)
+           acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_WRITE_DELETED) > 0)
                return FALSE;
        if ((box->private_flags_mask & MAIL_SEEN) == 0 &&
-           mailbox_acl_right_lookup(box, ACL_STORAGE_RIGHT_WRITE_SEEN) > 0)
+           acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_WRITE_SEEN) > 0)
                return FALSE;
 
        return TRUE;
@@ -82,7 +96,7 @@ static bool acl_allow_new_keywords(struct mailbox *box)
        if (!abox->module_ctx.super.allow_new_keywords(box))
                return FALSE;
 
-       return mailbox_acl_right_lookup(box, ACL_STORAGE_RIGHT_WRITE) > 0;
+       return acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_WRITE) > 0;
 }
 
 static int acl_mailbox_close(struct mailbox *box)
@@ -99,17 +113,17 @@ acl_get_write_rights(struct mailbox *box,
 {
        int ret;
 
-       ret = mailbox_acl_right_lookup(box, ACL_STORAGE_RIGHT_WRITE);
+       ret = acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_WRITE);
        if (ret < 0)
                return -1;
        *flags_r = ret > 0;
 
-       ret = mailbox_acl_right_lookup(box, ACL_STORAGE_RIGHT_WRITE_SEEN);
+       ret = acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_WRITE_SEEN);
        if (ret < 0)
                return -1;
        *flag_seen_r = ret > 0;
 
-       ret = mailbox_acl_right_lookup(box, ACL_STORAGE_RIGHT_WRITE_DELETED);
+       ret = acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_WRITE_DELETED);
        if (ret < 0)
                return -1;
        *flag_del_r = ret > 0;
@@ -171,7 +185,7 @@ acl_mail_update_keywords(struct mail *_mail, enum modify_type modify_type,
        union mail_module_context *amail = ACL_MAIL_CONTEXT(mail);
        int ret;
 
-       ret = mailbox_acl_right_lookup(_mail->box, ACL_STORAGE_RIGHT_WRITE);
+       ret = acl_mailbox_right_lookup(_mail->box, ACL_STORAGE_RIGHT_WRITE);
        if (ret <= 0) {
                /* if we don't have permission, just silently return success. */
                if (ret < 0)
@@ -188,7 +202,7 @@ static void acl_mail_expunge(struct mail *_mail)
        union mail_module_context *amail = ACL_MAIL_CONTEXT(mail);
        int ret;
 
-       ret = mailbox_acl_right_lookup(_mail->box, ACL_STORAGE_RIGHT_EXPUNGE);
+       ret = acl_mailbox_right_lookup(_mail->box, ACL_STORAGE_RIGHT_EXPUNGE);
        if (ret <= 0) {
                /* if we don't have permission, silently return success so
                   users won't see annoying error messages in case their
@@ -251,7 +265,7 @@ acl_save_begin(struct mail_save_context *ctx, struct istream *input)
        struct mailbox *box = ctx->transaction->box;
        struct acl_mailbox *abox = ACL_CONTEXT(box);
 
-       if (mailbox_acl_right_lookup(box, ACL_STORAGE_RIGHT_INSERT) <= 0)
+       if (acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_INSERT) <= 0)
                return -1;
        if (acl_save_get_flags(box, &ctx->flags, &ctx->keywords) < 0)
                return -1;
@@ -266,7 +280,7 @@ acl_copy(struct mailbox_transaction_context *t, struct mail *mail,
 {
        struct acl_mailbox *abox = ACL_CONTEXT(t->box);
 
-       if (mailbox_acl_right_lookup(t->box, ACL_STORAGE_RIGHT_INSERT) <= 0)
+       if (acl_mailbox_right_lookup(t->box, ACL_STORAGE_RIGHT_INSERT) <= 0)
                return -1;
        if (acl_save_get_flags(t->box, &flags, &keywords) < 0)
                return -1;
@@ -299,7 +313,7 @@ acl_keywords_create(struct mailbox *box, const char *const keywords[],
        struct acl_mailbox *abox = ACL_CONTEXT(box);
        int ret;
 
-       ret = mailbox_acl_right_lookup(box, ACL_STORAGE_RIGHT_WRITE);
+       ret = acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_WRITE);
        if (ret < 0) {
                if (!skip_invalid)
                        return -1;
index 287e9936cf329d58426324b31b817a5099820cff..d8f83349f8da88401af4488d51c24f599a2b7aca 100644 (file)
@@ -2,25 +2,11 @@
 #define ACL_PLUGIN_H
 
 #include "mail-storage-private.h"
+#include "acl-storage.h"
 
 #define ACL_CONTEXT(obj) \
        MODULE_CONTEXT(obj, acl_storage_module)
 
-enum acl_storage_rights {
-       ACL_STORAGE_RIGHT_LOOKUP,
-       ACL_STORAGE_RIGHT_READ,
-       ACL_STORAGE_RIGHT_WRITE,
-       ACL_STORAGE_RIGHT_WRITE_SEEN,
-       ACL_STORAGE_RIGHT_WRITE_DELETED,
-       ACL_STORAGE_RIGHT_INSERT,
-       ACL_STORAGE_RIGHT_EXPUNGE,
-       ACL_STORAGE_RIGHT_CREATE,
-       ACL_STORAGE_RIGHT_DELETE,
-       ACL_STORAGE_RIGHT_ADMIN,
-
-       ACL_STORAGE_RIGHT_COUNT
-};
-
 struct acl_storage_rights_context {
        struct acl_backend *backend;
        unsigned int acl_storage_right_idx[ACL_STORAGE_RIGHT_COUNT];
diff --git a/src/plugins/acl/acl-storage.h b/src/plugins/acl/acl-storage.h
new file mode 100644 (file)
index 0000000..761eb86
--- /dev/null
@@ -0,0 +1,28 @@
+#ifndef ACL_STORAGE_H
+#define ACL_STORAGE_H
+
+enum acl_storage_rights {
+       ACL_STORAGE_RIGHT_LOOKUP,
+       ACL_STORAGE_RIGHT_READ,
+       ACL_STORAGE_RIGHT_WRITE,
+       ACL_STORAGE_RIGHT_WRITE_SEEN,
+       ACL_STORAGE_RIGHT_WRITE_DELETED,
+       ACL_STORAGE_RIGHT_INSERT,
+       ACL_STORAGE_RIGHT_EXPUNGE,
+       ACL_STORAGE_RIGHT_CREATE,
+       ACL_STORAGE_RIGHT_DELETE,
+       ACL_STORAGE_RIGHT_ADMIN,
+
+       ACL_STORAGE_RIGHT_COUNT
+};
+
+/* Returns default acl_object for the given mail storage. */
+struct acl_object *acl_storage_get_default_aclobj(struct mail_storage *storage);
+/* Returns acl_object for the given mailbox. */
+struct acl_object *acl_mailbox_get_aclobj(struct mailbox *box);
+/* Returns 1 if we have the requested right. If not, returns 0 and sets storage
+   error to MAIL_ERROR_PERM. Returns -1 if internal error occurred and also
+   sets storage error. */
+int acl_mailbox_right_lookup(struct mailbox *box, unsigned int right_idx);
+
+#endif
diff --git a/src/plugins/imap-acl/Makefile.am b/src/plugins/imap-acl/Makefile.am
new file mode 100644 (file)
index 0000000..bbc8eb9
--- /dev/null
@@ -0,0 +1,20 @@
+AM_CPPFLAGS = \
+       -I$(top_srcdir)/src/lib \
+       -I$(top_srcdir)/src/lib-mail \
+       -I$(top_srcdir)/src/lib-imap \
+       -I$(top_srcdir)/src/lib-storage \
+       -I$(top_srcdir)/src/imap \
+       -I$(top_srcdir)/src/plugins/acl
+
+imap_moduledir = $(moduledir)/imap
+
+lib02_imap_acl_plugin_la_LDFLAGS = -module -avoid-version
+
+imap_module_LTLIBRARIES = \
+       lib02_imap_acl_plugin.la
+
+lib02_imap_acl_plugin_la_SOURCES = \
+       imap-acl-plugin.c
+
+noinst_HEADERS = \
+       imap-acl-plugin.h
diff --git a/src/plugins/imap-acl/imap-acl-plugin.c b/src/plugins/imap-acl/imap-acl-plugin.c
new file mode 100644 (file)
index 0000000..28079c4
--- /dev/null
@@ -0,0 +1,455 @@
+/* Copyright (c) 2008 Dovecot authors, see the included COPYING file */
+
+#include "common.h"
+#include "str.h"
+#include "imap-quote.h"
+#include "imap-resp-code.h"
+#include "commands.h"
+#include "mail-storage.h"
+#include "mail-namespace.h"
+#include "acl-api.h"
+#include "acl-storage.h"
+#include "imap-acl-plugin.h"
+
+#include <stdlib.h>
+
+#define ERROR_NOT_ADMIN "["IMAP_RESP_CODE_ACL"] " \
+       "You lack administrator privileges on this mailbox."
+
+#define ACL_MAILBOX_OPEN_FLAGS \
+       (MAILBOX_OPEN_READONLY | MAILBOX_OPEN_FAST | MAILBOX_OPEN_KEEP_RECENT)
+
+#define IMAP_ACL_ANYONE "anyone"
+#define IMAP_ACL_AUTHENTICATED "authenticated"
+#define IMAP_ACL_OWNER "owner"
+#define IMAP_ACL_GROUP_PREFIX "$"
+#define IMAP_ACL_GROUP_OVERRIDE_PREFIX "!$"
+
+struct imap_acl_letter_map {
+       char letter;
+       const char *name;
+};
+
+static const struct imap_acl_letter_map imap_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 },
+       { 'e', MAIL_ACL_EXPUNGE },
+       { 'k', MAIL_ACL_CREATE },
+       { 'x', MAIL_ACL_DELETE },
+       { 'a', MAIL_ACL_ADMIN },
+       { '\0', NULL }
+};
+
+static struct mailbox *
+acl_mailbox_open_as_admin(struct client_command_context *cmd, const char *name)
+{
+       struct mail_storage *storage;
+       struct mailbox *box;
+       int ret;
+
+       storage = client_find_storage(cmd, &name);
+       if (storage == NULL)
+               return NULL;
+
+       /* Force opening the mailbox so that we can give a nicer error message
+          if mailbox isn't selectable but is listable. */
+       box = mailbox_open(storage, name, NULL, ACL_MAILBOX_OPEN_FLAGS |
+                          MAIL_STORAGE_FLAG_IGNORE_ACLS);
+       if (box == NULL) {
+               client_send_storage_error(cmd, storage);
+               return NULL;
+       }
+
+       ret = acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_ADMIN);
+       if (ret > 0)
+               return box;
+
+       /* not an administrator. */
+       if (acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_LOOKUP) <= 0) {
+               client_send_tagline(cmd, t_strdup_printf(
+                       "["IMAP_RESP_CODE_NONEXISTENT"] "
+                       MAIL_ERRSTR_MAILBOX_NOT_FOUND, name));
+       } else {
+               client_send_tagline(cmd, "["IMAP_RESP_CODE_ACL"] "
+                                   ERROR_NOT_ADMIN);
+       }
+       mailbox_close(&box);
+       return NULL;
+}
+
+static const struct imap_acl_letter_map *
+imap_acl_letter_map_find(const char *name)
+{
+       unsigned int i;
+
+       for (i = 0; imap_acl_letter_map[i].name != NULL; i++) {
+               if (strcmp(imap_acl_letter_map[i].name, name) == 0)
+                       return &imap_acl_letter_map[i];
+       }
+       return NULL;
+}
+
+static void
+imap_acl_write_rights_list(string_t *dest, const char *const *rights)
+{
+       const struct imap_acl_letter_map *map;
+       unsigned int i;
+       bool append_c = FALSE, append_d = FALSE;
+
+       for (i = 0; rights[i] != NULL; i++) {
+               /* write only letters */
+               map = imap_acl_letter_map_find(rights[i]);
+               if (map != NULL) {
+                       str_append_c(dest, map->letter);
+                       if (map->letter == 'k' || map->letter == 'x')
+                               append_c = TRUE;
+                       if (map->letter == 't' || map->letter == 'e')
+                               append_d = TRUE;
+               }
+       }
+       if (append_c)
+               str_append_c(dest, 'c');
+       if (append_d)
+               str_append_c(dest, 'd');
+}
+
+static void
+imap_acl_write_right(string_t *dest, string_t *tmp,
+                    const struct acl_rights *right, bool neg)
+{
+       const char *const *rights = neg ? right->neg_rights : right->rights;
+
+       if (neg) str_append_c(dest,'-');
+
+       str_truncate(tmp, 0);
+       switch (right->id_type) {
+       case ACL_ID_ANYONE:
+               str_append(tmp, IMAP_ACL_ANYONE);
+               break;
+       case ACL_ID_AUTHENTICATED:
+               str_append(tmp, IMAP_ACL_AUTHENTICATED);
+               break;
+       case ACL_ID_OWNER:
+               str_append(tmp, IMAP_ACL_OWNER);
+               break;
+       case ACL_ID_USER:
+               str_append(tmp, right->identifier);
+               break;
+       case ACL_ID_GROUP:
+               str_append(tmp, IMAP_ACL_GROUP_PREFIX);
+               str_append(tmp, right->identifier);
+               break;
+       case ACL_ID_GROUP_OVERRIDE:
+               str_append(tmp, IMAP_ACL_GROUP_OVERRIDE_PREFIX);
+               str_append(tmp, right->identifier);
+               break;
+       case ACL_ID_TYPE_COUNT:
+               i_unreached();
+       }
+
+       imap_quote_append(dest, str_data(tmp), str_len(tmp), FALSE);
+       str_append_c(dest, ' ');
+       imap_acl_write_rights_list(dest, rights);
+}
+
+static int imap_acl_write_aclobj(string_t *dest, struct acl_object *aclobj)
+{
+       struct acl_object_list_iter *iter;
+       struct acl_rights rights;
+       string_t *tmp;
+       int ret;
+
+       tmp = t_str_new(128);
+       iter = acl_object_list_init(aclobj);
+       while ((ret = acl_object_list_next(iter, &rights)) > 0) {
+               str_append_c(dest, ' ');
+               if (rights.rights != NULL)
+                       imap_acl_write_right(dest, tmp, &rights, FALSE);
+               if (rights.neg_rights != NULL)
+                       imap_acl_write_right(dest, tmp, &rights, TRUE);
+       }
+       acl_object_list_deinit(&iter);
+       return ret;
+}
+
+static bool cmd_getacl(struct client_command_context *cmd)
+{
+       struct mailbox *box;
+       const char *mailbox;
+       string_t *str;
+       unsigned int len;
+       int ret;
+
+       if (!client_read_string_args(cmd, 1, &mailbox)) {
+               client_send_command_error(cmd, "Invalid arguments.");
+               return TRUE;
+       }
+
+       box = acl_mailbox_open_as_admin(cmd, mailbox);
+       if (box == NULL)
+               return TRUE;
+
+       str = t_str_new(128);
+       str_append(str, "* ACL ");
+       imap_quote_append_string(str, mailbox, FALSE);
+       len = str_len(str);
+
+       ret = imap_acl_write_aclobj(str, acl_mailbox_get_aclobj(box));
+       if (ret == 0) {
+               client_send_line(cmd->client, str_c(str));
+               client_send_tagline(cmd, "OK Getacl completed.");
+       } else {
+               client_send_tagline(cmd, "NO "MAIL_ERRSTR_CRITICAL_MSG);
+       }
+       mailbox_close(&box);
+       return TRUE;
+}
+
+static bool cmd_myrights(struct client_command_context *cmd)
+{
+       struct mail_storage *storage;
+       struct mailbox *box;
+       const char *mailbox, *real_mailbox;
+       const char *const *rights;
+       string_t *str;
+
+       if (!client_read_string_args(cmd, 1, &mailbox)) {
+               client_send_command_error(cmd, "Invalid arguments.");
+               return TRUE;
+       }
+
+       real_mailbox = mailbox;
+       storage = client_find_storage(cmd, &real_mailbox);
+       if (storage == NULL)
+               return TRUE;
+
+       box = mailbox_open(storage, real_mailbox, NULL, ACL_MAILBOX_OPEN_FLAGS);
+       if (box == NULL) {
+               client_send_storage_error(cmd, storage);
+               return TRUE;
+       }
+
+       if (acl_object_get_my_rights(acl_mailbox_get_aclobj(box),
+                                    pool_datastack_create(), &rights) < 0) {
+               client_send_tagline(cmd, "NO "MAIL_ERRSTR_CRITICAL_MSG);
+               mailbox_close(&box);
+               return TRUE;
+       }
+
+       str = t_str_new(128);
+       str_append(str, "* MYRIGHTS ");
+       imap_quote_append_string(str, mailbox, FALSE);
+       str_append_c(str,' ');
+       imap_acl_write_rights_list(str, rights);
+
+       client_send_line(cmd->client, str_c(str));
+       client_send_tagline(cmd, "OK Myrights completed.");
+       return TRUE;
+}
+
+static bool cmd_listrights(struct client_command_context *cmd)
+{
+       struct mailbox *box;
+       const char *mailbox, *identifier;
+       string_t *str;
+
+       if (!client_read_string_args(cmd, 2, &mailbox, &identifier)) {
+               client_send_command_error(cmd, "Invalid arguments.");
+               return TRUE;
+       }
+
+       box = acl_mailbox_open_as_admin(cmd, mailbox);
+       if (box == NULL)
+               return TRUE;
+
+       str = t_str_new(128);
+       str_append(str, "* LISTRIGHTS ");
+       imap_quote_append_string(str, mailbox, FALSE);
+       str_append_c(str, ' ');
+       imap_quote_append_string(str, identifier, FALSE);
+       str_append_c(str, ' ');
+       str_append(str, "\"\" l r w s t p i e k x a c d");
+
+       client_send_line(cmd->client, str_c(str));
+       client_send_tagline(cmd, "OK Listrights completed.");
+       return TRUE;
+}
+
+static int
+imap_acl_letters_parse(const char *letters, const char *const **rights_r,
+                      const char **error_r)
+{
+       ARRAY_TYPE(const_string) rights;
+       unsigned int i;
+
+       t_array_init(&rights, 64);
+       for (; *letters != '\0'; letters++) {
+               for (i = 0; imap_acl_letter_map[i].name != NULL; i++) {
+                       if (imap_acl_letter_map[i].letter == *letters) {
+                               array_append(&rights,
+                                            &imap_acl_letter_map[i].name, 1);
+                               break;
+                       }
+               }
+               if (imap_acl_letter_map[i].name == NULL) {
+                       *error_r = t_strdup_printf("Invalid ACL right: %c",
+                                                  *letters);
+                       return -1;
+               }
+       }
+       (void)array_append_space(&rights);
+       *rights_r = array_idx(&rights, 0);
+       return 0;
+}
+
+static int
+imap_acl_identifier_parse(const char *id, struct acl_rights *rights)
+{
+       if (strcmp(id, IMAP_ACL_ANYONE) == 0)
+               rights->id_type = ACL_ID_ANYONE;
+       else if (strcmp(id, IMAP_ACL_AUTHENTICATED) == 0)
+               rights->id_type = ACL_ID_AUTHENTICATED;
+       else if (strcmp(id, IMAP_ACL_OWNER) == 0)
+               rights->id_type = ACL_ID_OWNER;
+       else if (strncmp(id, IMAP_ACL_GROUP_PREFIX,
+                        strlen(IMAP_ACL_GROUP_PREFIX)) == 0) {
+               rights->id_type = ACL_ID_GROUP;
+               rights->identifier = id + strlen(IMAP_ACL_GROUP_PREFIX);
+       } else if (strncmp(id, IMAP_ACL_GROUP_OVERRIDE_PREFIX,
+                          strlen(IMAP_ACL_GROUP_OVERRIDE_PREFIX)) == 0) {
+               rights->id_type = ACL_ID_GROUP_OVERRIDE;
+               rights->identifier = id +
+                       strlen(IMAP_ACL_GROUP_OVERRIDE_PREFIX);
+       } else {
+               rights->id_type = ACL_ID_USER;
+               rights->identifier = id;
+       }
+       return 0;
+}
+
+static bool cmd_setacl(struct client_command_context *cmd)
+{
+       struct mailbox *box;
+       struct acl_rights_update update;
+       const char *mailbox, *identifier, *rights, *error;
+       bool negative = FALSE;
+
+       if (!client_read_string_args(cmd, 3, &mailbox, &identifier, &rights) ||
+           *identifier == '\0' || *rights == '\0') {
+               client_send_command_error(cmd, "Invalid arguments.");
+               return TRUE;
+       }
+
+       memset(&update, 0, sizeof(update));
+       if (*identifier == '-') {
+               negative = TRUE;
+               identifier++;
+       }
+
+       if (imap_acl_identifier_parse(identifier, &update.rights) < 0) {
+               client_send_command_error(cmd,
+                       t_strdup_printf("Invalid identifier: %s", identifier));
+               return TRUE;
+       }
+       if (imap_acl_letters_parse(rights, &update.rights.rights, &error) < 0) {
+               client_send_command_error(cmd, error);
+               return TRUE;
+       }
+
+       box = acl_mailbox_open_as_admin(cmd, mailbox);
+       if (box == NULL)
+               return TRUE;
+
+       switch (*rights) {
+       case '-':
+               update.modify_mode = ACL_MODIFY_MODE_REMOVE;
+               rights++;
+               break;
+       case '+':
+               update.modify_mode = ACL_MODIFY_MODE_ADD;
+               rights++;
+               break;
+       default:
+               update.modify_mode = ACL_MODIFY_MODE_REPLACE;
+               break;
+       }
+
+       if (negative) {
+               update.neg_modify_mode = update.modify_mode;
+               update.modify_mode = ACL_MODIFY_MODE_REMOVE;
+               update.rights.neg_rights = update.rights.rights;
+               update.rights.rights = NULL;
+       }
+
+       if (acl_object_update(acl_mailbox_get_aclobj(box), &update) < 0)
+               client_send_tagline(cmd, "NO "MAIL_ERRSTR_CRITICAL_MSG);
+       else
+               client_send_tagline(cmd, "OK Setacl complete.");
+       mailbox_close(&box);
+       return TRUE;
+}
+
+static bool cmd_deleteacl(struct client_command_context *cmd)
+{
+       struct mailbox *box;
+       struct acl_rights_update update;
+       const char *mailbox, *identifier;
+
+       if (!client_read_string_args(cmd, 2, &mailbox, &identifier) ||
+           *identifier == '\0') {
+               client_send_command_error(cmd, "Invalid arguments.");
+               return TRUE;
+       }
+
+       memset(&update, 0, sizeof(update));
+       if (*identifier != '-')
+               update.modify_mode = ACL_MODIFY_MODE_CLEAR;
+       else {
+               update.neg_modify_mode = ACL_MODIFY_MODE_CLEAR;
+               identifier++;
+       }
+
+       if (imap_acl_identifier_parse(identifier, &update.rights) < 0) {
+               client_send_command_error(cmd,
+                       t_strdup_printf("Invalid identifier: %s", identifier));
+               return TRUE;
+       }
+
+       box = acl_mailbox_open_as_admin(cmd, mailbox);
+       if (box == NULL)
+               return TRUE;
+
+       if (acl_object_update(acl_mailbox_get_aclobj(box), &update) < 0)
+               client_send_tagline(cmd, "NO "MAIL_ERRSTR_CRITICAL_MSG);
+       else
+               client_send_tagline(cmd, "OK Deleteacl complete.");
+       mailbox_close(&box);
+       return TRUE;
+}
+
+void imap_acl_plugin_init(void)
+{
+       if (getenv("ACL") == NULL)
+               return;
+
+       str_append(capability_string, " ACL RIGHTS=texk");
+
+       command_register("LISTRIGHTS", cmd_listrights, 0);
+       command_register("GETACL", cmd_getacl, 0);
+       command_register("MYRIGHTS", cmd_myrights, 0);
+       command_register("SETACL", cmd_setacl, 0);
+       command_register("DELETEACL", cmd_deleteacl, 0);
+}
+
+void imap_acl_plugin_deinit(void)
+{
+       command_unregister("GETACL");
+       command_unregister("MYRIGHTS");
+       command_unregister("SETACL");
+       command_unregister("DELETEACL");
+       command_unregister("LISTRIGHTS");
+}
diff --git a/src/plugins/imap-acl/imap-acl-plugin.h b/src/plugins/imap-acl/imap-acl-plugin.h
new file mode 100644 (file)
index 0000000..866db5f
--- /dev/null
@@ -0,0 +1,7 @@
+#ifndef IMAP_ACL_PLUGIN_H
+#define IMAP_ACL_PLUGIN_H
+
+void imap_acl_plugin_init(void);
+void imap_acl_plugin_deinit(void);
+
+#endif