/* 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 #include #include #include 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); }