# HG changeset patch # User Timo Sirainen # Date 1226803574 -7200 # Node ID 29b1ec15880a6286965576ebafd26caf45e7ce63 # Parent 106ad33091ca1aed602c2a025903728ac907bc17 Added IMAP ACL commands with ability to modify ACLs. Based on patch by Bernhard Herzog and Sascha Wilde. diff -r 106ad33091ca -r 29b1ec15880a configure.in --- a/configure.in Sun Nov 16 04:45:06 2008 +0200 +++ b/configure.in Sun Nov 16 04:46:14 2008 +0200 @@ -2453,6 +2453,7 @@ 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 diff -r 106ad33091ca -r 29b1ec15880a src/plugins/Makefile.am --- a/src/plugins/Makefile.am Sun Nov 16 04:45:06 2008 +0200 +++ b/src/plugins/Makefile.am Sun Nov 16 04:46:14 2008 +0200 @@ -12,6 +12,7 @@ SUBDIRS = \ acl \ + imap-acl \ autocreate \ convert \ expire \ diff -r 106ad33091ca -r 29b1ec15880a src/plugins/acl/Makefile.am --- a/src/plugins/acl/Makefile.am Sun Nov 16 04:45:06 2008 +0200 +++ b/src/plugins/acl/Makefile.am Sun Nov 16 04:46:14 2008 +0200 @@ -26,7 +26,8 @@ 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 \ diff -r 106ad33091ca -r 29b1ec15880a src/plugins/acl/acl-api-private.h --- a/src/plugins/acl/acl-api-private.h Sun Nov 16 04:45:06 2008 +0200 +++ b/src/plugins/acl/acl-api-private.h Sun Nov 16 04:46:14 2008 +0200 @@ -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 @@ 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 *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); diff -r 106ad33091ca -r 29b1ec15880a src/plugins/acl/acl-api.c --- a/src/plugins/acl/acl-api.c Sun Nov 16 04:45:06 2008 +0200 +++ b/src/plugins/acl/acl-api.c Sun Nov 16 04:46:14 2008 +0200 @@ -52,31 +52,14 @@ 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 @@ /* @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_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) diff -r 106ad33091ca -r 29b1ec15880a src/plugins/acl/acl-api.h --- a/src/plugins/acl/acl-api.h Sun Nov 16 04:45:06 2008 +0200 +++ b/src/plugins/acl/acl-api.h Sun Nov 16 04:46:14 2008 +0200 @@ -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_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 = 0, - /* Remove rights from existing ACL */ - ACL_MODIFY_MODE_REMOVE, + 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 @@ /* 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); diff -r 106ad33091ca -r 29b1ec15880a src/plugins/acl/acl-backend-vfile.c --- a/src/plugins/acl/acl-backend-vfile.c Sun Nov 16 04:45:06 2008 +0200 +++ b/src/plugins/acl/acl-backend-vfile.c Sun Nov 16 04:46:14 2008 +0200 @@ -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 @@ { '\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 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 @@ 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 @@ } } - return acl_rights_alloc(pool, &rights); + return acl_rights_alloc(pool, &rights, FALSE); } static int @@ -362,31 +381,34 @@ 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 @@ 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 @@ 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 @@ /* 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 @@ 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_object_update(struct acl_object *aclobj ATTR_UNUSED, - const struct acl_rights_update *rights - ATTR_UNUSED) +acl_backend_vfile_update_write(struct acl_object_vfile *aclobj, + int fd, const char *path) { - /* FIXME */ - return -1; + 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, + const struct acl_rights_update *update) +{ + 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); + } - if (aclobj->backend->v.object_refresh_cache(aclobj) < 0) + /* 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) iter->failed = TRUE; return iter; } @@ -793,11 +1100,26 @@ (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; } diff -r 106ad33091ca -r 29b1ec15880a src/plugins/acl/acl-cache.c --- a/src/plugins/acl/acl-cache.c Sun Nov 16 04:45:06 2008 +0200 +++ b/src/plugins/acl/acl-cache.c Sun Nov 16 04:46:14 2008 +0200 @@ -244,6 +244,8 @@ changed = FALSE; new_mask = change_mask; break; + case ACL_MODIFY_MODE_CLEAR: + i_unreached(); } if (new_mask != old_mask) { diff -r 106ad33091ca -r 29b1ec15880a src/plugins/acl/acl-mailbox.c --- a/src/plugins/acl/acl-mailbox.c Sun Nov 16 04:45:06 2008 +0200 +++ b/src/plugins/acl/acl-mailbox.c Sun Nov 16 04:46:14 2008 +0200 @@ -30,7 +30,21 @@ 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 @@ 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 @@ 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 @@ { 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 @@ 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 @@ 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 @@ 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 @@ { 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 @@ 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; diff -r 106ad33091ca -r 29b1ec15880a src/plugins/acl/acl-plugin.h --- a/src/plugins/acl/acl-plugin.h Sun Nov 16 04:45:06 2008 +0200 +++ b/src/plugins/acl/acl-plugin.h Sun Nov 16 04:46:14 2008 +0200 @@ -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 -r 106ad33091ca -r 29b1ec15880a src/plugins/acl/acl-storage.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/plugins/acl/acl-storage.h Sun Nov 16 04:46:14 2008 +0200 @@ -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 -r 106ad33091ca -r 29b1ec15880a src/plugins/imap-acl/Makefile.am --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/plugins/imap-acl/Makefile.am Sun Nov 16 04:46:14 2008 +0200 @@ -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 -r 106ad33091ca -r 29b1ec15880a src/plugins/imap-acl/imap-acl-plugin.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/plugins/imap-acl/imap-acl-plugin.c Sun Nov 16 04:46:14 2008 +0200 @@ -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 + +#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 -r 106ad33091ca -r 29b1ec15880a src/plugins/imap-acl/imap-acl-plugin.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/plugins/imap-acl/imap-acl-plugin.h Sun Nov 16 04:46:14 2008 +0200 @@ -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