Mercurial > dovecot > original-hg > dovecot-1.2
changeset 5552:71b5fd371b9a HEAD
Create dovecot-acl-list file that lists all mailboxes where non-owner has
lookup right. Use the file when listing mailboxes in shared/public
namespace.
author | Timo Sirainen <tss@iki.fi> |
---|---|
date | Wed, 11 Apr 2007 22:33:37 +0300 |
parents | 0e7a8c5c7c13 |
children | ed1e0985b9ea |
files | 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-acllist.c src/plugins/acl/acl-backend-vfile.c src/plugins/acl/acl-backend-vfile.h src/plugins/acl/acl-backend.c src/plugins/acl/acl-cache.c src/plugins/acl/acl-cache.h src/plugins/acl/acl-mailbox-list.c src/plugins/acl/acl-plugin.c src/plugins/acl/acl-storage.c |
diffstat | 13 files changed, 792 insertions(+), 126 deletions(-) [+] |
line wrap: on
line diff
--- a/src/plugins/acl/Makefile.am Wed Apr 11 22:31:29 2007 +0300 +++ b/src/plugins/acl/Makefile.am Wed Apr 11 22:33:37 2007 +0300 @@ -1,6 +1,7 @@ AM_CPPFLAGS = \ -I$(top_srcdir)/src/lib \ -I$(top_srcdir)/src/lib-mail \ + -I$(top_srcdir)/src/lib-imap \ -I$(top_srcdir)/src/lib-index \ -I$(top_srcdir)/src/lib-storage @@ -13,6 +14,7 @@ acl-api.c \ acl-backend.c \ acl-backend-vfile.c \ + acl-backend-vfile-acllist.c \ acl-cache.c \ acl-mailbox.c \ acl-mailbox-list.c \ @@ -22,6 +24,7 @@ noinst_HEADERS = \ acl-api.h \ acl-api-private.h \ + acl-backend-vfile.h \ acl-cache.h \ acl-plugin.h
--- a/src/plugins/acl/acl-api-private.h Wed Apr 11 22:31:29 2007 +0300 +++ b/src/plugins/acl/acl-api-private.h Wed Apr 11 22:33:37 2007 +0300 @@ -8,6 +8,13 @@ int (*init)(struct acl_backend *backend, const char *data); void (*deinit)(struct acl_backend *backend); + struct acl_mailbox_list_context * + (*nonowner_lookups_iter_init)(struct acl_backend *backend); + int (*nonowner_lookups_iter_next)(struct acl_mailbox_list_context *ctx, + const char **name_r); + void (*nonowner_lookups_iter_deinit) + (struct acl_mailbox_list_context *ctx); + struct acl_object *(*object_init)(struct acl_backend *backend, struct mail_storage *storage, const char *name); @@ -15,7 +22,7 @@ int (*object_refresh_cache)(struct acl_object *aclobj); int (*object_update)(struct acl_object *aclobj, - const struct acl_rights *rights); + const struct acl_rights_update *rights); struct acl_object_list_iter * (*object_list_init)(struct acl_object *aclobj); @@ -26,7 +33,7 @@ struct acl_backend { pool_t pool; - const char *username, *owner_username; + const char *username; const char **groups; unsigned int group_count; @@ -38,9 +45,14 @@ struct acl_backend_vfuncs v; + unsigned int owner:1; unsigned int debug:1; }; +struct acl_mailbox_list_context { + struct acl_backend *backend; +}; + struct acl_object { struct acl_backend *backend; char *name; @@ -48,6 +60,12 @@ struct acl_object_list_iter { struct acl_object *aclobj; + + unsigned int idx; + unsigned int failed:1; }; +int acl_backend_get_default_rights(struct acl_backend *backend, + const struct acl_mask **mask_r); + #endif
--- a/src/plugins/acl/acl-api.c Wed Apr 11 22:31:29 2007 +0300 +++ b/src/plugins/acl/acl-api.c Wed Apr 11 22:33:37 2007 +0300 @@ -20,23 +20,10 @@ aclobj->backend->v.object_deinit(aclobj); } -static int acl_backend_get_default_rights(struct acl_backend *backend, - const struct acl_mask **mask_r) -{ - if (backend->v.object_refresh_cache(backend->default_aclobj) < 0) - return -1; - - *mask_r = acl_cache_get_my_rights(backend->cache, ""); - if (*mask_r == NULL) - *mask_r = backend->default_aclmask; - return 0; -} - int acl_object_have_right(struct acl_object *aclobj, unsigned int right_idx) { struct acl_backend *backend = aclobj->backend; const struct acl_mask *have_mask; - unsigned int mask_idx; if (*aclobj->name == '\0') { /* we want to look up default rights */ @@ -55,10 +42,7 @@ } } - mask_idx = right_idx / CHAR_BIT; - return mask_idx < have_mask->size && - (have_mask->mask[mask_idx] & - (1 << (right_idx % CHAR_BIT))) != 0; + return acl_cache_mask_isset(have_mask, right_idx); } int acl_object_get_my_rights(struct acl_object *aclobj, pool_t pool, @@ -118,7 +102,7 @@ } int acl_object_update(struct acl_object *aclobj, - const struct acl_rights *rights) + const struct acl_rights_update *rights) { return aclobj->backend->v.object_update(aclobj, rights); } @@ -131,7 +115,10 @@ int acl_object_list_next(struct acl_object_list_iter *iter, struct acl_rights *rights_r) { - return iter->aclobj->backend->v.object_list_next(iter, rights_r); + if (iter->failed) + return -1; + + return iter->aclobj->backend->v.object_list_next(iter, rights_r); } void acl_object_list_deinit(struct acl_object_list_iter **_iter) @@ -141,3 +128,24 @@ *_iter = NULL; iter->aclobj->backend->v.object_list_deinit(iter); } + +struct acl_mailbox_list_context * +acl_backend_nonowner_lookups_iter_init(struct acl_backend *backend) +{ + return backend->v.nonowner_lookups_iter_init(backend); +} + +int acl_backend_nonowner_lookups_iter_next(struct acl_mailbox_list_context *ctx, + const char **name_r) +{ + return ctx->backend->v.nonowner_lookups_iter_next(ctx, name_r); +} + +void +acl_backend_nonowner_lookups_iter_deinit(struct acl_mailbox_list_context **_ctx) +{ + struct acl_mailbox_list_context *ctx = *_ctx; + + *_ctx = NULL; + ctx->backend->v.nonowner_lookups_iter_deinit(ctx); +}
--- a/src/plugins/acl/acl-api.h Wed Apr 11 22:31:29 2007 +0300 +++ b/src/plugins/acl/acl-api.h Wed Apr 11 22:33:37 2007 +0300 @@ -27,16 +27,19 @@ /* Allow changing ACL state in this mailbox */ #define MAIL_ACL_ADMIN "admin" +/* 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, overrides anyone's rights. - identifier name is ignored. */ + /* Authenticate users' rights. identifier name is ignored. */ ACL_ID_AUTHENTICATED, - /* Group's rights, overrides authenticated users' rights */ + /* Group's rights */ ACL_ID_GROUP, - /* User's rights, overrides group's rights */ + /* 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, @@ -63,8 +66,11 @@ const char *const *rights; /* Negative rights assigned */ const char *const *neg_rights; +}; - /* For set: */ +struct acl_rights_update { + struct acl_rights rights; + enum acl_modify_mode modify_mode; enum acl_modify_mode neg_modify_mode; }; @@ -75,10 +81,12 @@ struct acl_backend * acl_backend_init(const char *data, struct mailbox_list *list, const char *acl_username, const char *const *groups, - const char *owner_username); + bool owner); void acl_backend_deinit(struct acl_backend **backend); /* Returns TRUE if user isn't anonymous. */ bool acl_backend_user_is_authenticated(struct acl_backend *backend); +/* Returns TRUE if user owns the storage. */ +bool acl_backend_user_is_owner(struct acl_backend *backend); /* Returns TRUE if given name matches the ACL user name. */ bool acl_backend_user_name_equals(struct acl_backend *backend, const char *username); @@ -89,6 +97,14 @@ unsigned int acl_backend_lookup_right(struct acl_backend *backend, const char *right); +/* List mailboxes that have lookup right to some non-owners. */ +struct acl_mailbox_list_context * +acl_backend_nonowner_lookups_iter_init(struct acl_backend *backend); +int acl_backend_nonowner_lookups_iter_next(struct acl_mailbox_list_context *ctx, + const char **name_r); +void +acl_backend_nonowner_lookups_iter_deinit(struct acl_mailbox_list_context **ctx); + struct acl_object *acl_object_init_from_name(struct acl_backend *backend, struct mail_storage *storage, const char *name); @@ -103,7 +119,7 @@ /* Update ACL of given object. */ int acl_object_update(struct acl_object *aclobj, - const struct acl_rights *rights); + const struct acl_rights_update *rights); /* List all identifiers. */ struct acl_object_list_iter *acl_object_list_init(struct acl_object *aclobj);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/plugins/acl/acl-backend-vfile-acllist.c Wed Apr 11 22:33:37 2007 +0300 @@ -0,0 +1,329 @@ +/* Copyright (C) 2007 Timo Sirainen */ + +#include "lib.h" +#include "array.h" +#include "str.h" +#include "safe-mkstemp.h" +#include "istream.h" +#include "ostream.h" +#include "mail-namespace.h" +#include "mail-storage.h" +#include "acl-plugin.h" +#include "acl-cache.h" +#include "acl-backend-vfile.h" + +#include <stdio.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/stat.h> + +struct acl_mailbox_list_context_vfile { + struct acl_mailbox_list_context ctx; + + unsigned int acllist_change_counter; + unsigned int idx; +}; + +static void +acllist_clear(struct acl_backend_vfile *backend, uoff_t file_size) +{ + backend->acllist_change_counter++; + if (backend->acllist_pool == NULL) { + backend->acllist_pool = + pool_alloconly_create("vfile acllist", + I_MAX(file_size / 2, 128)); + i_array_init(&backend->acllist, I_MAX(16, file_size / 60)); + } else { + p_clear(backend->acllist_pool); + array_clear(&backend->acllist); + } +} + +static int acl_backend_vfile_acllist_read(struct acl_backend_vfile *backend) +{ + struct acl_backend_vfile_acllist acllist; + struct istream *input; + struct stat st; + const char *rootdir, *path, *line, *p; + int fd, ret = 0; + + backend->acllist_last_check = ioloop_time; + + rootdir = mailbox_list_get_path(backend->backend.list, NULL, + MAILBOX_LIST_PATH_TYPE_DIR); + path = t_strdup_printf("%s/"ACLLIST_FILENAME, rootdir); + + if (backend->acllist_mtime != 0) { + /* see if the file's mtime has changed */ + if (stat(path, &st) < 0) { + if (errno == ENOENT) + backend->acllist_mtime = 0; + else + i_error("stat(%s) failed: %m", path); + return -1; + } + if (st.st_mtime == backend->acllist_mtime) + return 0; + } + + fd = open(path, O_RDONLY); + if (fd == -1) { + if (errno == ENOENT) { + backend->acllist_mtime = 0; + return -1; + } + i_error("open(%s) failed: %m", path); + return -1; + } + if (fstat(fd, &st) < 0) { + i_error("fstat(%s) failed: %m", path); + (void)close(fd); + return -1; + } + backend->acllist_mtime = st.st_mtime; + acllist_clear(backend, st.st_size); + + input = i_stream_create_file(fd, default_pool, (size_t)-1, FALSE); + while ((line = i_stream_read_next_line(input)) != NULL) { + acllist.mtime = 0; + for (p = line; *p >= '0' && *p <= '9'; p++) + acllist.mtime = acllist.mtime * 10 + (*p - '0'); + + if (p == line || *p != ' ' || p[1] == '\0') { + i_error("Broken acllist file: %s", path); + if (unlink(path) < 0 && errno != ENOENT) + i_error("unlink(%s) failed: %m", path); + return -1; + } + acllist.name = p_strdup(backend->acllist_pool, p + 1); + array_append(&backend->acllist, &acllist, 1); + } + if (input->stream_errno != 0) + ret = -1; + i_stream_destroy(&input); + + if (close(fd) < 0) + i_error("close(%s) failed: %m", path); + return ret; +} + +void acl_backend_vfile_acllist_refresh(struct acl_backend_vfile *backend) +{ + if (backend->acllist_last_check + backend->cache_secs > ioloop_time) + return; + + if (acl_backend_vfile_acllist_read(backend) < 0) { + acllist_clear(backend, 0); + if (!backend->rebuilding_acllist) + (void)acl_backend_vfile_acllist_rebuild(backend); + } +} + +static bool rights_has_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; +} + +static int +acllist_append(struct acl_backend_vfile *backend, struct ostream *output, + struct mail_storage *storage, const char *name) +{ + struct acl_object *aclobj; + struct acl_object_list_iter *iter; + struct acl_rights rights; + struct acl_backend_vfile_acllist acllist; + const char *line; + int ret; + + acl_cache_flush(backend->backend.cache, name); + aclobj = acl_object_init_from_name(&backend->backend, storage, name); + + iter = acl_object_list_init(aclobj); + while ((ret = acl_object_list_next(iter, &rights)) > 0) { + if (rights_has_lookup_changes(&rights)) + break; + } + acl_object_list_deinit(&iter); + + if (acl_backend_vfile_object_get_mtime(aclobj, &acllist.mtime) < 0) + ret = -1; + + if (ret > 0) { + acllist.name = p_strdup(backend->acllist_pool, name); + array_append(&backend->acllist, &acllist, 1); + + t_push(); + line = t_strdup_printf("%s %s\n", dec2str(acllist.mtime), name); + o_stream_send_str(output, line); + t_pop(); + } + acl_object_deinit(&aclobj); + return ret < 0 ? -1 : 0; +} + +int acl_backend_vfile_acllist_rebuild(struct acl_backend_vfile *backend) +{ + struct mailbox_list *list = backend->backend.list; + struct mail_namespace *ns; + struct mailbox_list_iterate_context *iter; + struct mailbox_info *info; + const char *rootdir, *acllist_path; + struct ostream *output; + struct stat st; + string_t *path; + mode_t mode; + uid_t uid; + gid_t gid; + int fd, ret; + + ret = mailbox_list_get_permissions(list, NULL, &mode, &uid, &gid); + if (ret <= 0) { + /* Return success if the whole root directory was lost */ + return ret; + } + + path = t_str_new(256); + rootdir = mailbox_list_get_path(list, NULL, + MAILBOX_LIST_PATH_TYPE_DIR); + str_printfa(path, "%s/%s", rootdir, mailbox_list_get_temp_prefix(list)); + + /* Build it into a temporary file and rename() over. There's no need + to use locking, because even if multiple processes are rebuilding + the file at the same time the result should be the same. */ + fd = safe_mkstemp(path, mode, uid, gid); + if (fd == -1) + return -1; + output = o_stream_create_file(fd, default_pool, 0, FALSE); + + ret = 0; + acllist_clear(backend, 0); + ns = mailbox_list_get_namespace(list); + + backend->rebuilding_acllist = TRUE; + iter = mailbox_list_iter_init(list, "*", MAILBOX_LIST_ITER_FAST_FLAGS | + MAILBOX_LIST_ITER_RAW_LIST); + while ((info = mailbox_list_iter_next(iter)) != NULL) { + if (acllist_append(backend, output, ns->storage, + info->name) < 0) { + ret = -1; + break; + } + } + + if (mailbox_list_iter_deinit(&iter) < 0) + ret = -1; + o_stream_destroy(&output); + backend->rebuilding_acllist = FALSE; + + if (ret == 0) { + if (fstat(fd, &st) < 0) { + i_error("fstat(%s) failed: %m", str_c(path)); + ret = -1; + } + } + if (close(fd) < 0) { + i_error("close(%s) failed: %m", str_c(path)); + ret = -1; + } + + if (ret == 0) { + acllist_path = t_strdup_printf("%s/"ACLLIST_FILENAME, rootdir); + if (rename(str_c(path), acllist_path) < 0) { + i_error("rename(%s, %s) failed: %m", + str_c(path), acllist_path); + ret = -1; + } + } + if (ret == 0) { + backend->acllist_mtime = st.st_mtime; + backend->acllist_last_check = ioloop_time; + } else { + acllist_clear(backend, 0); + if (unlink(str_c(path)) < 0 && errno != ENOENT) + i_error("unlink(%s) failed: %m", str_c(path)); + } + return ret; +} + +static const struct acl_backend_vfile_acllist * +acl_backend_vfile_acllist_find(struct acl_backend_vfile *backend, + const char *name) +{ + const struct acl_backend_vfile_acllist *acllist; + unsigned int i, count; + + acllist = array_get(&backend->acllist, &count); + for (i = 0; i < count; i++) { + if (strcmp(acllist[i].name, name) == 0) + return &acllist[i]; + } + return NULL; +} + +void acl_backend_vfile_acllist_verify(struct acl_backend_vfile *backend, + const char *name, time_t mtime) +{ + const struct acl_backend_vfile_acllist *acllist; + + acl_backend_vfile_acllist_refresh(backend); + acllist = acl_backend_vfile_acllist_find(backend, name); + if (acllist != NULL && acllist->mtime != mtime && + !backend->rebuilding_acllist) + (void)acl_backend_vfile_acllist_rebuild(backend); +} + +struct acl_mailbox_list_context * +acl_backend_vfile_nonowner_iter_init(struct acl_backend *_backend) +{ + struct acl_backend_vfile *backend = + (struct acl_backend_vfile *)_backend; + struct acl_mailbox_list_context_vfile *ctx; + + acl_backend_vfile_acllist_refresh(backend); + + ctx = i_new(struct acl_mailbox_list_context_vfile, 1); + ctx->ctx.backend = _backend; + ctx->acllist_change_counter = backend->acllist_change_counter; + return &ctx->ctx; +} + +int acl_backend_vfile_nonowner_iter_next(struct acl_mailbox_list_context *_ctx, + const char **name_r) +{ + struct acl_mailbox_list_context_vfile *ctx = + (struct acl_mailbox_list_context_vfile *)_ctx; + struct acl_backend_vfile *backend = + (struct acl_backend_vfile *)_ctx->backend; + const struct acl_backend_vfile_acllist *acllist; + unsigned int count; + + if (ctx->acllist_change_counter != backend->acllist_change_counter) + return -1; + + acllist = array_get(&backend->acllist, &count); + if (ctx->idx == count) + return 0; + + *name_r = acllist[ctx->idx++].name; + return 1; +} + +void +acl_backend_vfile_nonowner_iter_deinit(struct acl_mailbox_list_context *ctx) +{ + i_free(ctx); +}
--- a/src/plugins/acl/acl-backend-vfile.c Wed Apr 11 22:31:29 2007 +0300 +++ b/src/plugins/acl/acl-backend-vfile.c Wed Apr 11 22:33:37 2007 +0300 @@ -1,4 +1,4 @@ -/* Copyright (C) 2006 Timo Sirainen */ +/* Copyright (C) 2006-2007 Timo Sirainen */ #include "lib.h" #include "ioloop.h" @@ -7,15 +7,13 @@ #include "nfs-workarounds.h" #include "mail-storage-private.h" #include "acl-cache.h" -#include "acl-api-private.h" +#include "acl-backend-vfile.h" #include <stdlib.h> #include <fcntl.h> #include <unistd.h> #include <sys/stat.h> -#define ACL_FILENAME "dovecot-acl" - #define ACL_ESTALE_RETRY_COUNT NFS_ESTALE_RETRY_COUNT #define ACL_VFILE_DEFAULT_CACHE_SECS (60*5) @@ -31,18 +29,6 @@ struct acl_vfile_validity global_validity, local_validity; }; -struct acl_backend_vfile { - struct acl_backend backend; - const char *global_dir; - unsigned int cache_secs; -}; - -struct acl_object_vfile { - struct acl_object aclobj; - - char *global_path, *local_path; -}; - struct acl_letter_map { char letter; const char *name; @@ -148,6 +134,11 @@ { struct acl_object_vfile *aclobj = (struct acl_object_vfile *)_aclobj; + if (array_is_created(&aclobj->rights)) + array_free(&aclobj->rights); + if (aclobj->rights_pool != NULL) + pool_unref(aclobj->rights_pool); + i_free(aclobj->local_path); i_free(aclobj->global_path); i_free(aclobj->aclobj.name); @@ -155,11 +146,11 @@ } static const char *const * -acl_parse_rights(const char *acl, const char **error_r) +acl_parse_rights(pool_t pool, const char *acl, const char **error_r) { ARRAY_DEFINE(rights, const char *); - const char *const *names; - unsigned int i; + const char *const *names, **ret_rights; + unsigned int i, count; /* parse IMAP ACL list */ while (*acl == ' ' || *acl == '\t') @@ -180,29 +171,32 @@ array_append(&rights, &acl_letter_map[i].name, 1); } - if (*acl == '\0') { - (void)array_append_space(&rights); - return array_idx(&rights, 0); + if (*acl != '\0') { + /* parse our own extended ACLs */ + i_assert(*acl == ':'); + + names = t_strsplit_spaces(acl, ", "); + for (; *names != NULL; names++) { + const char *name = p_strdup(pool, *names); + array_append(&rights, &name, 1); + } } - /* parse our own extended ACLs */ - i_assert(*acl == ':'); - - names = t_strsplit_spaces(acl, ", "); - if (array_count(&rights) == 0) - return names; - - for (; *names != NULL; names++) - array_append(&rights, names, 1); - (void)array_append_space(&rights); - return array_idx(&rights, 0); + /* @UNSAFE */ + count = array_count(&rights); + ret_rights = p_new(pool, const char *, count + 1); + if (count > 0) { + memcpy(ret_rights, array_idx(&rights, 0), + sizeof(const char *) * count); + } + return ret_rights; } static int -acl_object_vfile_parse_line(struct acl_object *aclobj, const char *path, +acl_object_vfile_parse_line(struct acl_object_vfile *aclobj, const char *path, const char *line, unsigned int linenum) { - struct acl_rights rights; + struct acl_rights_update rights; const char *p, *const *right_names, *error = NULL; if (*line == '\0' || *line == '#') @@ -220,35 +214,50 @@ memset(&rights, 0, sizeof(rights)); - right_names = acl_parse_rights(p, &error); + right_names = acl_parse_rights(aclobj->rights_pool, p, &error); if (*line != '-') { rights.modify_mode = ACL_MODIFY_MODE_REPLACE; - rights.rights = right_names; + rights.rights.rights = right_names; } else { line++; rights.neg_modify_mode = ACL_MODIFY_MODE_REPLACE; - rights.neg_rights = right_names; + rights.rights.neg_rights = right_names; } - if (strncmp(line, "user=", 5) == 0) { - rights.id_type = ACL_ID_USER; - rights.identifier = line + 5; - } else if (strncmp(line, "group=", 6) == 0) { - rights.id_type = ACL_ID_GROUP; - rights.identifier = line + 6; - } else if (strncmp(line, "group-override=", 15) == 0) { - rights.id_type = ACL_ID_GROUP_OVERRIDE; - rights.identifier = line + 15; - } else if (strcmp(line, "owner") == 0) { - rights.id_type = ACL_ID_USER; - rights.identifier = aclobj->backend->owner_username; - } else if (strcmp(line, "authenticated") == 0) { - rights.id_type = ACL_ID_AUTHENTICATED; - } else if (strcmp(line, "anyone") == 0 || - strcmp(line, "anonymous") == 0) { - rights.id_type = ACL_ID_ANYONE; - } else { + switch (*line) { + case 'u': + if (strncmp(line, "user=", 5) == 0) { + rights.rights.id_type = ACL_ID_USER; + rights.rights.identifier = line + 5; + break; + } + case 'o': + if (strcmp(line, "owner") == 0) { + rights.rights.id_type = ACL_ID_OWNER; + break; + } + case 'g': + if (strncmp(line, "group=", 6) == 0) { + rights.rights.id_type = ACL_ID_GROUP; + rights.rights.identifier = line + 6; + break; + } else if (strncmp(line, "group-override=", 15) == 0) { + rights.rights.id_type = ACL_ID_GROUP_OVERRIDE; + rights.rights.identifier = line + 15; + break; + } + case 'a': + if (strcmp(line, "authenticated") == 0) { + rights.rights.id_type = ACL_ID_AUTHENTICATED; + break; + } else if (strcmp(line, "anyone") == 0 || + strcmp(line, "anonymous") == 0) { + rights.rights.id_type = ACL_ID_ANYONE; + break; + } + default: error = t_strdup_printf("Unknown ID '%s'", line); + break; } if (error != NULL) { @@ -257,14 +266,19 @@ return -1; } - acl_cache_update(aclobj->backend->cache, aclobj->name, &rights); + rights.rights.identifier = + p_strdup(aclobj->rights_pool, rights.rights.identifier); + array_append(&aclobj->rights, &rights.rights, 1); + + acl_cache_update(aclobj->aclobj.backend->cache, + aclobj->aclobj.name, &rights); t_pop(); return 0; } static int -acl_backend_vfile_read(struct acl_object *aclobj, const char *path, +acl_backend_vfile_read(struct acl_object_vfile *aclobj, const char *path, struct acl_vfile_validity *validity, bool try_retry, bool *is_dir_r) { @@ -279,7 +293,7 @@ fd = nfs_safe_open(path, O_RDONLY); if (fd == -1) { if (errno == ENOENT) { - if (aclobj->backend->debug) + if (aclobj->aclobj.backend->debug) i_info("acl vfile: file %s not found", path); validity->last_size = 0; @@ -308,11 +322,21 @@ return 0; } - if (aclobj->backend->debug) + if (aclobj->aclobj.backend->debug) i_info("acl vfile: reading file %s", path); input = i_stream_create_file(fd, default_pool, 4096, FALSE); + if (!array_is_created(&aclobj->rights)) { + aclobj->rights_pool = + pool_alloconly_create("acl rights", + I_MAX(256, st.st_size / 2)); + i_array_init(&aclobj->rights, I_MAX(16, st.st_size / 40)); + } else { + array_clear(&aclobj->rights); + p_clear(aclobj->rights_pool); + } + linenum = 1; while ((line = i_stream_read_next_line(input)) != NULL) { if (acl_object_vfile_parse_line(aclobj, path, line, @@ -358,7 +382,8 @@ } static int -acl_backend_vfile_read_with_retry(struct acl_object *aclobj, const char *path, +acl_backend_vfile_read_with_retry(struct acl_object_vfile *aclobj, + const char *path, struct acl_vfile_validity *validity) { unsigned int i; @@ -427,11 +452,32 @@ return 1; } +int acl_backend_vfile_object_get_mtime(struct acl_object *aclobj, + time_t *mtime_r) +{ + struct acl_backend_vfile_validity *validity; + + validity = acl_cache_get_validity(aclobj->backend->cache, aclobj->name); + if (validity == NULL) + return -1; + + if (validity->local_validity.last_mtime != 0) + *mtime_r = validity->local_validity.last_mtime; + else if (validity->global_validity.last_mtime != 0) + *mtime_r = validity->global_validity.last_mtime; + else + *mtime_r = 0; + return 0; +} + static int acl_backend_vfile_object_refresh_cache(struct acl_object *_aclobj) { struct acl_object_vfile *aclobj = (struct acl_object_vfile *)_aclobj; + struct acl_backend_vfile *backend = + (struct acl_backend_vfile *)_aclobj->backend; struct acl_backend_vfile_validity *old_validity; struct acl_backend_vfile_validity validity; + time_t mtime; int ret; old_validity = acl_cache_get_validity(_aclobj->backend->cache, @@ -451,21 +497,25 @@ acl_cache_flush(_aclobj->backend->cache, _aclobj->name); memset(&validity, 0, sizeof(validity)); - if (acl_backend_vfile_read_with_retry(_aclobj, aclobj->global_path, + if (acl_backend_vfile_read_with_retry(aclobj, aclobj->global_path, &validity.global_validity) < 0) return -1; - if (acl_backend_vfile_read_with_retry(_aclobj, aclobj->local_path, + if (acl_backend_vfile_read_with_retry(aclobj, aclobj->local_path, &validity.local_validity) < 0) return -1; acl_cache_set_validity(_aclobj->backend->cache, _aclobj->name, &validity); + + if (acl_backend_vfile_object_get_mtime(_aclobj, &mtime) == 0) + acl_backend_vfile_acllist_verify(backend, _aclobj->name, mtime); return 0; } static int acl_backend_vfile_object_update(struct acl_object *aclobj __attr_unused__, - const struct acl_rights *rights __attr_unused__) + const struct acl_rights_update *rights + __attr_unused__) { /* FIXME */ return -1; @@ -478,15 +528,27 @@ iter = i_new(struct acl_object_list_iter, 1); iter->aclobj = aclobj; + + if (aclobj->backend->v.object_refresh_cache(aclobj) < 0) + iter->failed = TRUE; return iter; } static int -acl_backend_vfile_object_list_next(struct acl_object_list_iter *iter - __attr_unused__, - struct acl_rights *rights_r __attr_unused__) +acl_backend_vfile_object_list_next(struct acl_object_list_iter *iter, + struct acl_rights *rights_r) { - return -1; + struct acl_object_vfile *aclobj = + (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; + + rights = array_idx(&aclobj->rights, iter->idx++); + *rights_r = *rights; + return 1; } static void @@ -499,6 +561,9 @@ acl_backend_vfile_alloc, acl_backend_vfile_init, acl_backend_vfile_deinit, + acl_backend_vfile_nonowner_iter_init, + acl_backend_vfile_nonowner_iter_next, + acl_backend_vfile_nonowner_iter_deinit, acl_backend_vfile_object_init, acl_backend_vfile_object_deinit, acl_backend_vfile_object_refresh_cache,
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/plugins/acl/acl-backend-vfile.h Wed Apr 11 22:33:37 2007 +0300 @@ -0,0 +1,53 @@ +#ifndef __ACL_BACKEND_VFILE_H +#define __ACL_BACKEND_VFILE_H + +#include "acl-api-private.h" + +#define ACL_FILENAME "dovecot-acl" +#define ACLLIST_FILENAME "dovecot-acl-list" + +struct acl_object_vfile { + struct acl_object aclobj; + + pool_t rights_pool; + ARRAY_DEFINE(rights, struct acl_rights); + + char *global_path, *local_path; +}; + +struct acl_backend_vfile_acllist { + time_t mtime; + const char *name; +}; + +struct acl_backend_vfile { + struct acl_backend backend; + const char *global_dir; + + pool_t acllist_pool; + ARRAY_DEFINE(acllist, struct acl_backend_vfile_acllist); + + time_t acllist_last_check; + time_t acllist_mtime; + unsigned int acllist_change_counter; + + unsigned int cache_secs; + unsigned int rebuilding_acllist:1; +}; + +void acl_backend_vfile_acllist_refresh(struct acl_backend_vfile *backend); +int acl_backend_vfile_acllist_rebuild(struct acl_backend_vfile *backend); +void acl_backend_vfile_acllist_verify(struct acl_backend_vfile *backend, + const char *name, time_t mtime); + +struct acl_mailbox_list_context * +acl_backend_vfile_nonowner_iter_init(struct acl_backend *backend); +int acl_backend_vfile_nonowner_iter_next(struct acl_mailbox_list_context *ctx, + const char **name_r); +void +acl_backend_vfile_nonowner_iter_deinit(struct acl_mailbox_list_context *ctx); + +int acl_backend_vfile_object_get_mtime(struct acl_object *aclobj, + time_t *mtime_r); + +#endif
--- a/src/plugins/acl/acl-backend.c Wed Apr 11 22:31:29 2007 +0300 +++ b/src/plugins/acl/acl-backend.c Wed Apr 11 22:33:37 2007 +0300 @@ -28,18 +28,17 @@ struct acl_backend * acl_backend_init(const char *data, struct mailbox_list *list, const char *acl_username, const char *const *groups, - const char *owner_username) + bool owner) { struct acl_backend *backend; unsigned int i, group_count; - bool storage_owner, debug; + bool debug; debug = getenv("DEBUG") != NULL; if (debug) { i_info("acl: initializing backend with data: %s", data); i_info("acl: acl username = %s", acl_username); - i_info("acl: owner username = %s", - owner_username != NULL ? owner_username : ""); + i_info("acl: owner = %d", owner); } group_count = strarray_length(groups); @@ -56,8 +55,7 @@ backend->v = acl_backend_vfile; backend->list = list; backend->username = p_strdup(backend->pool, acl_username); - backend->owner_username = owner_username == NULL ? "" : - p_strdup(backend->pool, owner_username); + backend->owner = owner; if (group_count > 0) { backend->group_count = group_count; @@ -72,11 +70,9 @@ if (acl_backend_vfile.init(backend, data) < 0) i_fatal("acl: backend vfile init failed with data: %s", data); - storage_owner = owner_username != NULL && - strcmp(acl_username, owner_username) == 0; backend->default_aclmask = acl_cache_mask_init(backend->cache, backend->pool, - storage_owner ? owner_mailbox_rights : + owner ? owner_mailbox_rights : non_owner_mailbox_rights); backend->default_aclobj = acl_object_init_from_name(backend, NULL, ""); @@ -99,6 +95,11 @@ return backend->username != NULL; } +bool acl_backend_user_is_owner(struct acl_backend *backend) +{ + return backend->owner; +} + bool acl_backend_user_name_equals(struct acl_backend *backend, const char *username) { @@ -122,3 +123,15 @@ { return acl_cache_right_lookup(backend->cache, right); } + +int acl_backend_get_default_rights(struct acl_backend *backend, + const struct acl_mask **mask_r) +{ + if (backend->v.object_refresh_cache(backend->default_aclobj) < 0) + return -1; + + *mask_r = acl_cache_get_my_rights(backend->cache, ""); + if (*mask_r == NULL) + *mask_r = backend->default_aclmask; + return 0; +}
--- a/src/plugins/acl/acl-cache.c Wed Apr 11 22:31:29 2007 +0300 +++ b/src/plugins/acl/acl-cache.c Wed Apr 11 22:33:37 2007 +0300 @@ -251,15 +251,15 @@ static void acl_cache_update_rights(struct acl_cache *cache, struct acl_object_cache *obj_cache, - const struct acl_rights *rights) + const struct acl_rights_update *rights) { - enum acl_id_type id_type = rights->id_type; + enum acl_id_type id_type = rights->rights.id_type; acl_cache_update_rights_mask(cache, obj_cache, rights->modify_mode, - rights->rights, + rights->rights.rights, &obj_cache->my_rights[id_type]); acl_cache_update_rights_mask(cache, obj_cache, rights->neg_modify_mode, - rights->neg_rights, + rights->rights.neg_rights, &obj_cache->my_neg_rights[id_type]); } @@ -283,7 +283,7 @@ } void acl_cache_update(struct acl_cache *cache, const char *objname, - const struct acl_rights *rights) + const struct acl_rights_update *rights) { struct acl_object_cache *obj_cache; bool created; @@ -291,7 +291,7 @@ obj_cache = acl_cache_object_get(cache, objname, &created); i_assert(obj_cache->my_current_rights != &negative_cache_entry); - switch (rights->id_type) { + switch (rights->rights.id_type) { case ACL_ID_ANYONE: acl_cache_update_rights(cache, obj_cache, rights); break; @@ -302,12 +302,16 @@ case ACL_ID_GROUP: case ACL_ID_GROUP_OVERRIDE: if (acl_backend_user_is_in_group(cache->backend, - rights->identifier)) + rights->rights.identifier)) acl_cache_update_rights(cache, obj_cache, rights); break; case ACL_ID_USER: if (acl_backend_user_name_equals(cache->backend, - rights->identifier)) + rights->rights.identifier)) + acl_cache_update_rights(cache, obj_cache, rights); + break; + case ACL_ID_OWNER: + if (acl_backend_user_is_owner(cache->backend)) acl_cache_update_rights(cache, obj_cache, rights); break; case ACL_ID_TYPE_COUNT: @@ -402,3 +406,12 @@ acl_cache_my_current_rights_recalculate(obj_cache); return obj_cache->my_current_rights; } + +bool acl_cache_mask_isset(const struct acl_mask *mask, unsigned int right_idx) +{ + unsigned int mask_idx; + + mask_idx = right_idx / CHAR_BIT; + return mask_idx < mask->size && + (mask->mask[mask_idx] & (1 << (right_idx % CHAR_BIT))) != 0; +}
--- a/src/plugins/acl/acl-cache.h Wed Apr 11 22:31:29 2007 +0300 +++ b/src/plugins/acl/acl-cache.h Wed Apr 11 22:33:37 2007 +0300 @@ -2,7 +2,7 @@ #define __ACL_CACHE_H struct acl_backend; -struct acl_rights; +struct acl_rights_update; struct acl_mask { pool_t pool; @@ -34,7 +34,7 @@ /* Update object ACLs. */ void acl_cache_update(struct acl_cache *cache, const char *objname, - const struct acl_rights *rights); + const struct acl_rights_update *rights); /* Return ACL object validity, or NULL if object doesn't exit. */ void *acl_cache_get_validity(struct acl_cache *cache, const char *objname); /* Update ACL object validity, creating the object if needed. */ @@ -51,4 +51,7 @@ const struct acl_mask * acl_cache_get_my_rights(struct acl_cache *cache, const char *objname); +/* Returns TRUE if given right index is set in mask. */ +bool acl_cache_mask_isset(const struct acl_mask *mask, unsigned int right_idx); + #endif
--- a/src/plugins/acl/acl-mailbox-list.c Wed Apr 11 22:31:29 2007 +0300 +++ b/src/plugins/acl/acl-mailbox-list.c Wed Apr 11 22:33:37 2007 +0300 @@ -2,8 +2,11 @@ #include "lib.h" #include "array.h" +#include "imap-match.h" +#include "mailbox-tree.h" #include "mail-namespace.h" #include "mailbox-list-private.h" +#include "acl-cache.h" #include "acl-api-private.h" #include "acl-plugin.h" @@ -12,12 +15,23 @@ #define ACL_LIST_CONTEXT(obj) \ MODULE_CONTEXT(obj, acl_mailbox_list_module) +#define MAILBOX_FLAG_MATCHED 0x40000000 + struct acl_mailbox_list { union mailbox_list_module_context module_ctx; struct acl_storage_rights_context rights; }; +struct acl_mailbox_list_iterate_context { + struct mailbox_list_iterate_context ctx; + struct mailbox_list_iterate_context *super_ctx; + + struct mailbox_tree_context *tree; + struct mailbox_tree_iterate_context *tree_iter; + struct mailbox_info info; +}; + static MODULE_CONTEXT_DEFINE_INIT(acl_mailbox_list_module, &mailbox_list_module_register); @@ -48,17 +62,127 @@ can_see_r); } -static struct mailbox_info * -acl_mailbox_list_iter_next(struct mailbox_list_iterate_context *ctx) +static bool +acl_mailbox_try_list_fast(struct acl_mailbox_list_iterate_context *ctx, + const char *mask) { - struct acl_mailbox_list *alist = ACL_LIST_CONTEXT(ctx->list); + struct acl_mailbox_list *alist = ACL_LIST_CONTEXT(ctx->ctx.list); + struct acl_backend *backend = alist->rights.backend; + const unsigned int *idxp = + alist->rights.acl_storage_right_idx + ACL_STORAGE_RIGHT_LOOKUP; + const struct acl_mask *acl_mask; + struct acl_mailbox_list_context *nonowner_list_ctx; + struct imap_match_glob *glob; + struct mailbox_node *node; + const char *name; + char sep; + int try, ret; + bool created; + + if ((ctx->ctx.flags & MAILBOX_LIST_ITER_RAW_LIST) != 0) + return FALSE; + + if (acl_backend_get_default_rights(backend, &acl_mask) < 0 || + acl_cache_mask_isset(acl_mask, *idxp)) + return FALSE; + + /* default is to not list mailboxes. we can optimize this. */ + t_push(); + sep = mailbox_list_get_hierarchy_sep(ctx->ctx.list); + glob = imap_match_init(pool_datastack_create(), mask, TRUE, sep); + + for (try = 0; try < 2; try++) { + nonowner_list_ctx = + acl_backend_nonowner_lookups_iter_init(backend); + ctx->tree = mailbox_tree_init(sep); + + while ((ret = acl_backend_nonowner_lookups_iter_next( + nonowner_list_ctx, &name)) > 0) { + switch (imap_match(glob, name)) { + case IMAP_MATCH_YES: + node = mailbox_tree_get(ctx->tree, name, + &created); + if (created) + node->flags |= MAILBOX_NOCHILDREN; + node->flags |= MAILBOX_FLAG_MATCHED; + node->flags &= ~MAILBOX_NONEXISTENT; + break; + case IMAP_MATCH_PARENT: + node = mailbox_tree_get(ctx->tree, name, + &created); + if (created) + node->flags |= MAILBOX_NONEXISTENT; + node->flags |= MAILBOX_FLAG_MATCHED | + MAILBOX_CHILDREN; + node->flags &= ~MAILBOX_NOCHILDREN; + break; + default: + break; + } + } + if (ret == 0) + break; + + /* try again */ + mailbox_tree_deinit(&ctx->tree); + acl_backend_nonowner_lookups_iter_deinit(&nonowner_list_ctx); + } + t_pop(); + if (ret < 0) + return FALSE; + + ctx->tree_iter = mailbox_tree_iterate_init(ctx->tree, NULL, + MAILBOX_FLAG_MATCHED); + return TRUE; +} + +static struct mailbox_list_iterate_context * +acl_mailbox_list_iter_init(struct mailbox_list *list, const char *mask, + enum mailbox_list_iter_flags flags) +{ + struct acl_mailbox_list *alist = ACL_LIST_CONTEXT(list); + struct acl_mailbox_list_iterate_context *ctx; + + ctx = i_new(struct acl_mailbox_list_iterate_context, 1); + ctx->ctx.list = list; + ctx->ctx.flags = flags; + + if (!acl_mailbox_try_list_fast(ctx, mask)) { + ctx->super_ctx = + alist->module_ctx.super.iter_init(list, mask, flags); + } + return &ctx->ctx; +} + +static struct mailbox_info * +acl_mailbox_list_iter_next(struct mailbox_list_iterate_context *_ctx) +{ + struct acl_mailbox_list_iterate_context *ctx = + (struct acl_mailbox_list_iterate_context *)_ctx; + struct acl_mailbox_list *alist = ACL_LIST_CONTEXT(_ctx->list); struct mailbox_info *info; + struct mailbox_node *node; int ret; for (;;) { - info = alist->module_ctx.super.iter_next(ctx); - if (info == NULL) - return NULL; + if (ctx->tree_iter != NULL) { + node = mailbox_tree_iterate_next(ctx->tree_iter, + &ctx->info.name); + if (node == NULL) + return NULL; + info = &ctx->info; + info->flags = node->flags; + } else { + info = alist->module_ctx.super. + iter_next(ctx->super_ctx); + if (info == NULL) + return NULL; + } + + if ((ctx->ctx.flags & MAILBOX_LIST_ITER_RAW_LIST) != 0) { + /* skip ACL checks. */ + return info; + } ret = acl_mailbox_list_have_right(alist, info->name, ACL_STORAGE_RIGHT_LOOKUP, @@ -66,14 +190,15 @@ if (ret > 0) return info; if (ret < 0) { - ctx->failed = TRUE; + ctx->ctx.failed = TRUE; return NULL; } /* no permission to see this mailbox */ - if ((ctx->flags & MAILBOX_LIST_ITER_SUBSCRIBED) != 0) { + if ((ctx->ctx.flags & MAILBOX_LIST_ITER_SUBSCRIBED) != 0) { /* it's subscribed, show it as non-existent */ - if ((ctx->flags & MAILBOX_LIST_ITER_FAST_FLAGS) == 0) + if ((ctx->ctx.flags & + MAILBOX_LIST_ITER_FAST_FLAGS) == 0) info->flags = MAILBOX_NONEXISTENT; return info; } @@ -82,6 +207,25 @@ } } +static int +acl_mailbox_list_iter_deinit(struct mailbox_list_iterate_context *_ctx) +{ + struct acl_mailbox_list_iterate_context *ctx = + (struct acl_mailbox_list_iterate_context *)_ctx; + struct acl_mailbox_list *alist = ACL_LIST_CONTEXT(_ctx->list); + int ret = ctx->ctx.failed ? -1 : 0; + + if (ctx->super_ctx != NULL) { + if (alist->module_ctx.super.iter_deinit(ctx->super_ctx) < 0) + ret = -1; + } + if (ctx->tree_iter != NULL) + mailbox_tree_iterate_deinit(&ctx->tree_iter); + + i_free(ctx); + return ret; +} + static int acl_get_mailbox_name_status(struct mailbox_list *list, const char *name, enum mailbox_name_status *status) @@ -243,7 +387,9 @@ alist = p_new(list->pool, struct acl_mailbox_list, 1); alist->module_ctx.super = list->v; + list->v.iter_init = acl_mailbox_list_iter_init; list->v.iter_next = acl_mailbox_list_iter_next; + list->v.iter_deinit = acl_mailbox_list_iter_deinit; list->v.get_mailbox_name_status = acl_get_mailbox_name_status; list->v.delete_mailbox = acl_mailbox_list_delete; list->v.rename_mailbox = acl_mailbox_list_rename;
--- a/src/plugins/acl/acl-plugin.c Wed Apr 11 22:31:29 2007 +0300 +++ b/src/plugins/acl/acl-plugin.c Wed Apr 11 22:33:37 2007 +0300 @@ -1,7 +1,6 @@ /* Copyright (C) 2005 Timo Sirainen */ #include "lib.h" -#include "mail-storage.h" #include "mailbox-list-private.h" #include "acl-api.h" #include "acl-plugin.h"
--- a/src/plugins/acl/acl-storage.c Wed Apr 11 22:31:29 2007 +0300 +++ b/src/plugins/acl/acl-storage.c Wed Apr 11 22:33:37 2007 +0300 @@ -3,9 +3,9 @@ #include "lib.h" #include "array.h" #include "istream.h" -#include "acl-api-private.h" #include "mail-namespace.h" #include "mailbox-list-private.h" +#include "acl-api-private.h" #include "acl-plugin.h" struct acl_storage_module acl_storage_module =