diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 17:36:47 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 17:36:47 +0000 |
commit | 0441d265f2bb9da249c7abf333f0f771fadb4ab5 (patch) | |
tree | 3f3789daa2f6db22da6e55e92bee0062a7d613fe /src/plugins/acl/acl-backend-vfile-acllist.c | |
parent | Initial commit. (diff) | |
download | dovecot-0441d265f2bb9da249c7abf333f0f771fadb4ab5.tar.xz dovecot-0441d265f2bb9da249c7abf333f0f771fadb4ab5.zip |
Adding upstream version 1:2.3.21+dfsg1.upstream/1%2.3.21+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/plugins/acl/acl-backend-vfile-acllist.c')
-rw-r--r-- | src/plugins/acl/acl-backend-vfile-acllist.c | 424 |
1 files changed, 424 insertions, 0 deletions
diff --git a/src/plugins/acl/acl-backend-vfile-acllist.c b/src/plugins/acl/acl-backend-vfile-acllist.c new file mode 100644 index 0000000..c6029a2 --- /dev/null +++ b/src/plugins/acl/acl-backend-vfile-acllist.c @@ -0,0 +1,424 @@ +/* Copyright (c) 2007-2018 Dovecot authors, see the included COPYING file */ + +#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-lookup-dict.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 idx; +}; + +static void +acllist_clear(struct acl_backend_vfile *backend, uoff_t file_size) +{ + 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 bool acl_list_get_root_dir(struct acl_backend_vfile *backend, + const char **root_dir_r, + enum mailbox_list_path_type *type_r) +{ + struct mail_storage *storage; + const char *rootdir, *maildir; + enum mailbox_list_path_type type; + + if (backend->backend.globals_only) + return FALSE; + + storage = mailbox_list_get_namespace(backend->backend.list)->storage; + type = mail_storage_get_acl_list_path_type(storage); + if (!mailbox_list_get_root_path(backend->backend.list, type, &rootdir)) + return FALSE; + *type_r = type; + + if (type == MAILBOX_LIST_PATH_TYPE_DIR && + mail_storage_is_mailbox_file(storage)) { + maildir = mailbox_list_get_root_forced(backend->backend.list, + MAILBOX_LIST_PATH_TYPE_MAILBOX); + if (strcmp(maildir, rootdir) == 0) { + /* dovecot-acl-list would show up as a mailbox if we + created it to root dir. since we don't really have + any other good alternatives, place it to control + dir */ + rootdir = mailbox_list_get_root_forced(backend->backend.list, + MAILBOX_LIST_PATH_TYPE_CONTROL); + *type_r = MAILBOX_LIST_PATH_TYPE_CONTROL; + } + } + *root_dir_r = rootdir; + return TRUE; +} + +static bool acl_list_get_path(struct acl_backend_vfile *backend, + const char **path_r) +{ + enum mailbox_list_path_type type; + const char *root_dir; + + if (!acl_list_get_root_dir(backend, &root_dir, &type)) + return FALSE; + *path_r = t_strconcat(root_dir, "/"ACLLIST_FILENAME, NULL); + return TRUE; +} + +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 *path, *line, *p; + int fd, ret = 0; + + backend->acllist_last_check = ioloop_time; + + if (!acl_list_get_path(backend, &path)) { + /* we're never going to build acllist for this namespace. */ + acllist_clear(backend, 0); + return 0; + } + + 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); + i_close_fd(&fd); + return -1; + } + backend->acllist_mtime = st.st_mtime; + acllist_clear(backend, st.st_size); + + input = i_stream_create_fd(fd, SIZE_MAX); + 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); + i_unlink_if_exists(path); + i_close_fd(&fd); + return -1; + } + acllist.name = p_strdup(backend->acllist_pool, p + 1); + array_push_back(&backend->acllist, &acllist); + } + 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) +{ + i_assert(!backend->iterating_acllist); + + if (backend->acllist_last_check + + (time_t)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 int +acllist_append(struct acl_backend_vfile *backend, struct ostream *output, + const char *vname) +{ + struct acl_object *aclobj; + struct acl_object_list_iter *iter; + struct acl_rights rights; + struct acl_backend_vfile_acllist acllist; + const char *name; + int ret; + + name = mailbox_list_get_storage_name(backend->backend.list, vname); + acl_cache_flush(backend->backend.cache, name); + aclobj = acl_object_init_from_name(&backend->backend, name); + + iter = acl_object_list_init(aclobj); + while (acl_object_list_next(iter, &rights)) { + if (acl_rights_has_nonowner_lookup_changes(&rights)) + break; + } + ret = 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_push_back(&backend->acllist, &acllist); + + o_stream_nsend_str(output, t_strdup_printf( + "%s %s\n", dec2str(acllist.mtime), name)); + } + acl_object_deinit(&aclobj); + return ret < 0 ? -1 : 0; +} + +static int +acl_backend_vfile_acllist_try_rebuild(struct acl_backend_vfile *backend) +{ + struct mailbox_list *list = backend->backend.list; + struct mail_namespace *ns; + struct mailbox_list_iterate_context *iter; + enum mailbox_list_path_type type; + const struct mailbox_info *info; + const char *rootdir, *acllist_path; + struct ostream *output; + struct stat st; + struct mailbox_permissions perm; + string_t *path; + int fd, ret; + + i_assert(!backend->rebuilding_acllist); + + if (!acl_list_get_root_dir(backend, &rootdir, &type)) + return 0; + + ns = mailbox_list_get_namespace(list); + if ((ns->flags & NAMESPACE_FLAG_UNUSABLE) != 0) { + /* we can't write anything here */ + return 0; + } + + path = t_str_new(256); + 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. */ + mailbox_list_get_root_permissions(list, &perm); + fd = safe_mkstemp_group(path, perm.file_create_mode, + perm.file_create_gid, + perm.file_create_gid_origin); + if (fd == -1 && errno == ENOENT) { + if (mailbox_list_mkdir_root(backend->backend.list, + rootdir, type) < 0) + return -1; + fd = safe_mkstemp_group(path, perm.file_create_mode, + perm.file_create_gid, + perm.file_create_gid_origin); + } + if (fd == -1) { + if (errno == EACCES) { + /* Ignore silently if we can't create it */ + return 0; + } + i_error("dovecot-acl-list creation failed: " + "safe_mkstemp(%s) failed: %m", str_c(path)); + return -1; + } + output = o_stream_create_fd_file(fd, 0, FALSE); + o_stream_cork(output); + + ret = 0; + acllist_clear(backend, 0); + + backend->rebuilding_acllist = TRUE; + iter = mailbox_list_iter_init(list, "*", + MAILBOX_LIST_ITER_RAW_LIST | + MAILBOX_LIST_ITER_RETURN_NO_FLAGS); + while (ret == 0 && (info = mailbox_list_iter_next(iter)) != NULL) T_BEGIN { + ret = acllist_append(backend, output, info->vname); + } T_END; + + if (o_stream_finish(output) < 0) { + i_error("write(%s) failed: %s", str_c(path), + o_stream_get_error(output)); + ret = -1; + } + if (mailbox_list_iter_deinit(&iter) < 0) + ret = -1; + o_stream_destroy(&output); + + 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) { + if (!acl_list_get_path(backend, &acllist_path)) + i_unreached(); + 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) { + struct acl_user *auser = ACL_USER_CONTEXT(ns->user); + i_assert(auser != NULL); + backend->acllist_mtime = st.st_mtime; + backend->acllist_last_check = ioloop_time; + /* FIXME: dict rebuild is expensive, try to avoid it */ + (void)acl_lookup_dict_rebuild(auser->acl_lookup_dict); + } else { + acllist_clear(backend, 0); + i_unlink_if_exists(str_c(path)); + } + backend->rebuilding_acllist = FALSE; + return ret; +} + +int acl_backend_vfile_acllist_rebuild(struct acl_backend_vfile *backend) +{ + const char *acllist_path; + + if (acl_backend_vfile_acllist_try_rebuild(backend) == 0) + return 0; + else { + /* delete it to make sure it gets rebuilt later */ + if (!acl_list_get_path(backend, &acllist_path)) + i_unreached(); + i_unlink_if_exists(acllist_path); + return -1; + } +} + +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; + + array_foreach(&backend->acllist, acllist) { + if (strcmp(acllist->name, name) == 0) + return acllist; + } + 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; + + if (backend->rebuilding_acllist || backend->iterating_acllist) + return; + + acl_backend_vfile_acllist_refresh(backend); + acllist = acl_backend_vfile_acllist_find(backend, name); + if (acllist != NULL && acllist->mtime != mtime) + (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; + backend->iterating_acllist = TRUE; + return &ctx->ctx; +} + +bool 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->failed) + return FALSE; + + acllist = array_get(&backend->acllist, &count); + if (count == 0) + _ctx->empty = TRUE; + if (ctx->idx == count) + return FALSE; + + *name_r = acllist[ctx->idx++].name; + return TRUE; +} + +int +acl_backend_vfile_nonowner_iter_deinit(struct acl_mailbox_list_context *ctx) +{ + struct acl_backend_vfile *backend = + (struct acl_backend_vfile *)ctx->backend; + int ret; + + backend->iterating_acllist = FALSE; + if (ctx->failed) + ret = -1; + else if (ctx->empty) + ret = 0; + else + ret = 1; + i_free(ctx); + return ret; +} + +int acl_backend_vfile_nonowner_lookups_rebuild(struct acl_backend *_backend) +{ + struct acl_backend_vfile *backend = + (struct acl_backend_vfile *)_backend; + + return acl_backend_vfile_acllist_rebuild(backend); +} |