summaryrefslogtreecommitdiffstats
path: root/src/plugins/acl/acl-backend-vfile-acllist.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 17:36:47 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 17:36:47 +0000
commit0441d265f2bb9da249c7abf333f0f771fadb4ab5 (patch)
tree3f3789daa2f6db22da6e55e92bee0062a7d613fe /src/plugins/acl/acl-backend-vfile-acllist.c
parentInitial commit. (diff)
downloaddovecot-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.c424
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);
+}