summaryrefslogtreecommitdiffstats
path: root/src/plugins/acl/acl-mailbox.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-mailbox.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-mailbox.c')
-rw-r--r--src/plugins/acl/acl-mailbox.c714
1 files changed, 714 insertions, 0 deletions
diff --git a/src/plugins/acl/acl-mailbox.c b/src/plugins/acl/acl-mailbox.c
new file mode 100644
index 0000000..134be7f
--- /dev/null
+++ b/src/plugins/acl/acl-mailbox.c
@@ -0,0 +1,714 @@
+/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */
+
+/* FIXME: If we don't have permission to change flags/keywords, the changes
+ should still be stored temporarily for this session. However most clients
+ don't care and it's a huge job, so I currently this isn't done. The same
+ problem actually exists when opening read-only mailboxes. */
+#include "lib.h"
+#include "array.h"
+#include "ioloop.h"
+#include "istream.h"
+#include "mailbox-list-private.h"
+#include "acl-api-private.h"
+#include "acl-plugin.h"
+
+#include <sys/stat.h>
+
+#define ACL_MAIL_CONTEXT(obj) \
+ MODULE_CONTEXT_REQUIRE(obj, acl_mail_module)
+
+struct acl_transaction_context {
+ union mailbox_transaction_module_context module_ctx;
+};
+
+static MODULE_CONTEXT_DEFINE_INIT(acl_mail_module, &mail_module_register);
+static struct acl_transaction_context acl_transaction_failure;
+
+struct acl_object *acl_mailbox_get_aclobj(struct mailbox *box)
+{
+ struct acl_mailbox *abox = ACL_CONTEXT_REQUIRE(box);
+
+ return abox->aclobj;
+}
+
+int acl_mailbox_right_lookup(struct mailbox *box, unsigned int right_idx)
+{
+ struct acl_mailbox *abox = ACL_CONTEXT_REQUIRE(box);
+ int ret;
+
+ if (abox->skip_acl_checks)
+ return 1;
+
+ struct acl_mailbox_list *alist = ACL_LIST_CONTEXT_REQUIRE(box->list);
+
+ /* If acls are ignored for this namespace do not check if
+ there are rights. */
+ if (alist->ignore_acls)
+ return 1;
+
+ ret = acl_object_have_right(abox->aclobj,
+ alist->rights.acl_storage_right_idx[right_idx]);
+ if (ret > 0)
+ return 1;
+ if (ret < 0) {
+ mail_storage_set_internal_error(box->storage);
+ return -1;
+ }
+
+ mail_storage_set_error(box->storage, MAIL_ERROR_PERM,
+ MAIL_ERRSTR_NO_PERMISSION);
+ return 0;
+}
+
+static bool acl_is_readonly(struct mailbox *box)
+{
+ struct acl_mailbox *abox = ACL_CONTEXT_REQUIRE(box);
+ enum acl_storage_rights save_right;
+
+ if (abox->module_ctx.super.is_readonly(box))
+ return TRUE;
+
+ save_right = (box->flags & MAILBOX_FLAG_POST_SESSION) != 0 ?
+ ACL_STORAGE_RIGHT_POST : ACL_STORAGE_RIGHT_INSERT;
+ if (acl_mailbox_right_lookup(box, save_right) > 0)
+ return FALSE;
+ if (acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_EXPUNGE) > 0)
+ return FALSE;
+
+ if (acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_WRITE) > 0)
+ return FALSE;
+ if (acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_WRITE_DELETED) > 0)
+ return FALSE;
+ if (acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_WRITE_SEEN) > 0)
+ return FALSE;
+
+ return TRUE;
+}
+
+static void acl_mailbox_free(struct mailbox *box)
+{
+ struct acl_mailbox *abox = ACL_CONTEXT_REQUIRE(box);
+
+ if (abox->aclobj != NULL)
+ acl_object_deinit(&abox->aclobj);
+ abox->module_ctx.super.free(box);
+}
+
+static void acl_mailbox_copy_acls_from_parent(struct mailbox *box)
+{
+ struct acl_mailbox *abox = ACL_CONTEXT_REQUIRE(box);
+ struct acl_mailbox_list *alist = ACL_LIST_CONTEXT_REQUIRE(box->list);
+ struct acl_object *parent_aclobj;
+ struct acl_object_list_iter *iter;
+ struct acl_rights_update update;
+
+ i_zero(&update);
+ update.modify_mode = ACL_MODIFY_MODE_REPLACE;
+ update.neg_modify_mode = ACL_MODIFY_MODE_REPLACE;
+
+ parent_aclobj = acl_object_init_from_parent(alist->rights.backend,
+ box->name);
+ iter = acl_object_list_init(parent_aclobj);
+ while (acl_object_list_next(iter, &update.rights)) {
+ /* don't copy global ACL rights. */
+ if (!update.rights.global)
+ (void)acl_object_update(abox->aclobj, &update);
+ }
+ /* FIXME: Add error handling */
+ (void)acl_object_list_deinit(&iter);
+ acl_object_deinit(&parent_aclobj);
+}
+
+static int
+acl_mailbox_create(struct mailbox *box, const struct mailbox_update *update,
+ bool directory)
+{
+ struct acl_mailbox *abox = ACL_CONTEXT_REQUIRE(box);
+ int ret;
+
+ if (!mailbox_is_autocreated(box)) {
+ /* we're looking up CREATE permission from our parent's rights */
+ ret = acl_mailbox_list_have_right(box->list, box->name, TRUE,
+ ACL_STORAGE_RIGHT_CREATE, NULL);
+ } else {
+ /* mailbox is autocreated, so we need to treat it as if it
+ already exists. ignore the "create" ACL here. */
+ ret = 1;
+ }
+ if (ret <= 0) {
+ if (ret < 0) {
+ mail_storage_set_internal_error(box->storage);
+ return -1;
+ }
+ /* Note that if user didn't have LOOKUP permission to parent
+ mailbox, this may reveal the mailbox's existence to user.
+ Can't help it. */
+ mail_storage_set_error(box->storage, MAIL_ERROR_PERM,
+ MAIL_ERRSTR_NO_PERMISSION);
+ return -1;
+ }
+
+ /* ignore ACLs in this mailbox until creation is complete, because
+ super.create() may call e.g. mailbox_open() which will fail since
+ we haven't yet copied ACLs to this mailbox. */
+ abox->skip_acl_checks = TRUE;
+ ret = abox->module_ctx.super.create_box(box, update, directory);
+ abox->skip_acl_checks = FALSE;
+ /* update local acl object, otherwise with LAYOUT=INDEX, we end up
+ without local path to acl file, and copying fails. */
+ struct acl_backend *acl_be = abox->aclobj->backend;
+ acl_object_deinit(&abox->aclobj);
+ abox->aclobj = acl_object_init_from_name(acl_be, box->name);
+
+ if (ret == 0)
+ acl_mailbox_copy_acls_from_parent(box);
+ return ret;
+}
+
+static int
+acl_mailbox_update(struct mailbox *box, const struct mailbox_update *update)
+{
+ struct acl_mailbox *abox = ACL_CONTEXT_REQUIRE(box);
+ int ret;
+
+ ret = acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_ADMIN);
+ if (ret <= 0)
+ return -1;
+ return abox->module_ctx.super.update_box(box, update);
+}
+
+static void acl_mailbox_fail_not_found(struct mailbox *box)
+{
+ int ret;
+
+ ret = acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_LOOKUP);
+ if (ret > 0) {
+ mail_storage_set_error(box->storage, MAIL_ERROR_PERM,
+ MAIL_ERRSTR_NO_PERMISSION);
+ } else if (ret == 0) {
+ box->acl_no_lookup_right = TRUE;
+ mail_storage_set_error(box->storage, MAIL_ERROR_NOTFOUND,
+ T_MAIL_ERR_MAILBOX_NOT_FOUND(box->vname));
+ }
+}
+
+static int
+acl_mailbox_delete(struct mailbox *box)
+{
+ struct acl_mailbox *abox = ACL_CONTEXT_REQUIRE(box);
+ int ret;
+
+ ret = acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_DELETE);
+ if (ret <= 0) {
+ if (ret == 0)
+ acl_mailbox_fail_not_found(box);
+ return -1;
+ }
+
+ return abox->module_ctx.super.delete_box(box);
+}
+
+static int
+acl_mailbox_rename(struct mailbox *src, struct mailbox *dest)
+{
+ struct acl_mailbox *abox = ACL_CONTEXT_REQUIRE(src);
+ int ret;
+
+ /* renaming requires rights to delete the old mailbox */
+ ret = acl_mailbox_right_lookup(src, ACL_STORAGE_RIGHT_DELETE);
+ if (ret <= 0) {
+ if (ret == 0)
+ acl_mailbox_fail_not_found(src);
+ return -1;
+ }
+
+ /* and create the new one under the parent mailbox */
+ T_BEGIN {
+ ret = acl_mailbox_list_have_right(dest->list, dest->name, TRUE,
+ ACL_STORAGE_RIGHT_CREATE, NULL);
+ } T_END;
+
+ if (ret <= 0) {
+ if (ret == 0) {
+ /* Note that if the mailbox didn't have LOOKUP
+ permission, this now reveals to user the mailbox's
+ existence. Can't help it. */
+ mail_storage_set_error(src->storage, MAIL_ERROR_PERM,
+ MAIL_ERRSTR_NO_PERMISSION);
+ } else {
+ mail_storage_set_internal_error(src->storage);
+ }
+ return -1;
+ }
+
+ return abox->module_ctx.super.rename_box(src, dest);
+}
+
+static int
+acl_get_write_rights(struct mailbox *box,
+ bool *flags_r, bool *flag_seen_r, bool *flag_del_r)
+{
+ int ret;
+
+ ret = acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_WRITE);
+ if (ret < 0)
+ return -1;
+ *flags_r = ret > 0;
+
+ ret = acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_WRITE_SEEN);
+ if (ret < 0)
+ return -1;
+ *flag_seen_r = ret > 0;
+
+ ret = acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_WRITE_DELETED);
+ if (ret < 0)
+ return -1;
+ *flag_del_r = ret > 0;
+ return 0;
+}
+
+static void acl_transaction_set_failure(struct mailbox_transaction_context *t)
+{
+ MODULE_CONTEXT_SET(t, acl_storage_module,
+ &acl_transaction_failure);
+}
+
+static void
+acl_mail_update_flags(struct mail *_mail, enum modify_type modify_type,
+ enum mail_flags flags)
+{
+ struct mail_private *mail = (struct mail_private *)_mail;
+ union mail_module_context *amail = ACL_MAIL_CONTEXT(mail);
+ bool acl_flags, acl_flag_seen, acl_flag_del;
+
+ if (acl_get_write_rights(_mail->box, &acl_flags, &acl_flag_seen,
+ &acl_flag_del) < 0) {
+ acl_transaction_set_failure(_mail->transaction);
+ return;
+ }
+
+ if (modify_type != MODIFY_REPLACE) {
+ /* adding/removing flags. just remove the disallowed
+ flags from the mask. */
+ if (!acl_flags)
+ flags &= MAIL_SEEN | MAIL_DELETED;
+ if (!acl_flag_seen)
+ flags &= ENUM_NEGATE(MAIL_SEEN);
+ if (!acl_flag_del)
+ flags &= ENUM_NEGATE(MAIL_DELETED);
+ } else if (!acl_flags || !acl_flag_seen || !acl_flag_del) {
+ /* we don't have permission to replace all the flags. */
+ if (!acl_flags && !acl_flag_seen && !acl_flag_del) {
+ /* no flag changes allowed. ignore silently. */
+ return;
+ }
+
+ /* handle this by first removing the allowed flags and
+ then adding the allowed flags */
+ acl_mail_update_flags(_mail, MODIFY_REMOVE,
+ ENUM_NEGATE(flags));
+ if (flags != 0)
+ acl_mail_update_flags(_mail, MODIFY_ADD, flags);
+ return;
+ }
+
+ amail->super.update_flags(_mail, modify_type, flags);
+}
+
+static void
+acl_mail_update_keywords(struct mail *_mail, enum modify_type modify_type,
+ struct mail_keywords *keywords)
+{
+ struct mail_private *mail = (struct mail_private *)_mail;
+ union mail_module_context *amail = ACL_MAIL_CONTEXT(mail);
+ int ret;
+
+ 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)
+ acl_transaction_set_failure(_mail->transaction);
+ return;
+ }
+
+ amail->super.update_keywords(_mail, modify_type, keywords);
+}
+
+static void acl_mail_expunge(struct mail *_mail)
+{
+ struct mail_private *mail = (struct mail_private *)_mail;
+ union mail_module_context *amail = ACL_MAIL_CONTEXT(mail);
+ int ret;
+
+ 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
+ clients try automatic expunging. */
+ acl_transaction_set_failure(_mail->transaction);
+ return;
+ }
+
+ amail->super.expunge(_mail);
+}
+
+void acl_mail_allocated(struct mail *_mail)
+{
+ struct acl_mailbox *abox = ACL_CONTEXT(_mail->box);
+ struct mail_private *mail = (struct mail_private *)_mail;
+ struct mail_vfuncs *v = mail->vlast;
+ union mail_module_context *amail;
+
+ if (abox == NULL || !abox->acl_enabled)
+ return;
+
+ amail = p_new(mail->pool, union mail_module_context, 1);
+ amail->super = *v;
+ mail->vlast = &amail->super;
+
+ v->update_flags = acl_mail_update_flags;
+ v->update_keywords = acl_mail_update_keywords;
+ v->expunge = acl_mail_expunge;
+ MODULE_CONTEXT_SET_SELF(mail, acl_mail_module, amail);
+}
+
+static int
+acl_save_get_flags(struct mailbox *box, enum mail_flags *flags,
+ enum mail_flags *pvt_flags, struct mail_keywords **keywords)
+{
+ bool acl_flags, acl_flag_seen, acl_flag_del;
+
+ if (acl_get_write_rights(box, &acl_flags, &acl_flag_seen,
+ &acl_flag_del) < 0)
+ return -1;
+
+ if (!acl_flag_seen) {
+ *flags &= ENUM_NEGATE(MAIL_SEEN);
+ *pvt_flags &= ENUM_NEGATE(MAIL_SEEN);
+ }
+ if (!acl_flag_del) {
+ *flags &= ENUM_NEGATE(MAIL_DELETED);
+ *pvt_flags &= ENUM_NEGATE(MAIL_DELETED);
+ }
+ if (!acl_flags) {
+ *flags &= MAIL_SEEN | MAIL_DELETED;
+ *pvt_flags &= MAIL_SEEN | MAIL_DELETED;
+ *keywords = NULL;
+ }
+ return 0;
+}
+
+static int
+acl_save_begin(struct mail_save_context *ctx, struct istream *input)
+{
+ struct mailbox *box = ctx->transaction->box;
+ struct acl_mailbox *abox = ACL_CONTEXT_REQUIRE(box);
+ enum acl_storage_rights save_right;
+
+ save_right = (box->flags & MAILBOX_FLAG_POST_SESSION) != 0 ?
+ ACL_STORAGE_RIGHT_POST : ACL_STORAGE_RIGHT_INSERT;
+ if (acl_mailbox_right_lookup(box, save_right) <= 0)
+ return -1;
+ if (acl_save_get_flags(box, &ctx->data.flags,
+ &ctx->data.pvt_flags, &ctx->data.keywords) < 0)
+ return -1;
+
+ return abox->module_ctx.super.save_begin(ctx, input);
+}
+
+static bool
+acl_copy_has_rights(struct mail_save_context *ctx, struct mail *mail)
+{
+ struct mailbox *destbox = ctx->transaction->box;
+ enum acl_storage_rights save_right;
+
+ if (ctx->moving) {
+ if (acl_mailbox_right_lookup(mail->box,
+ ACL_STORAGE_RIGHT_EXPUNGE) <= 0)
+ return FALSE;
+ }
+
+ save_right = (destbox->flags & MAILBOX_FLAG_POST_SESSION) != 0 ?
+ ACL_STORAGE_RIGHT_POST : ACL_STORAGE_RIGHT_INSERT;
+ if (acl_mailbox_right_lookup(destbox, save_right) <= 0)
+ return FALSE;
+ if (acl_save_get_flags(destbox, &ctx->data.flags,
+ &ctx->data.pvt_flags, &ctx->data.keywords) < 0)
+ return FALSE;
+ return TRUE;
+}
+
+static int
+acl_copy(struct mail_save_context *ctx, struct mail *mail)
+{
+ struct mailbox_transaction_context *t = ctx->transaction;
+ struct acl_mailbox *abox = ACL_CONTEXT_REQUIRE(t->box);
+
+ if (!acl_copy_has_rights(ctx, mail)) {
+ mailbox_save_cancel(&ctx);
+ return -1;
+ }
+
+ return abox->module_ctx.super.copy(ctx, mail);
+}
+
+static int
+acl_transaction_commit(struct mailbox_transaction_context *ctx,
+ struct mail_transaction_commit_changes *changes_r)
+{
+ struct acl_mailbox *abox = ACL_CONTEXT_REQUIRE(ctx->box);
+ void *at = ACL_CONTEXT(ctx);
+ int ret;
+
+ if (at != NULL) {
+ abox->module_ctx.super.transaction_rollback(ctx);
+ return -1;
+ }
+
+ ret = abox->module_ctx.super.transaction_commit(ctx, changes_r);
+ if (abox->no_read_right) {
+ /* don't allow IMAP client to see what UIDs the messages got */
+ changes_r->no_read_perm = TRUE;
+ }
+ return ret;
+}
+
+static int acl_mailbox_exists(struct mailbox *box, bool auto_boxes,
+ enum mailbox_existence *existence_r)
+{
+ struct acl_mailbox *abox = ACL_CONTEXT_REQUIRE(box);
+ const char *const *rights;
+ unsigned int i;
+
+ if (acl_object_get_my_rights(abox->aclobj, pool_datastack_create(), &rights) < 0) {
+ mail_storage_set_internal_error(box->storage);
+ return -1;
+ }
+
+ /* for now this is used only by IMAP SUBSCRIBE. we'll intentionally
+ violate RFC 4314 here, because it says SUBSCRIBE should succeed only
+ when mailbox has 'l' right. But there's no point in not allowing
+ a subscribe for a mailbox that can be selected anyway. Just the
+ opposite: subscribing to such mailboxes is a very useful feature. */
+ for (i = 0; rights[i] != NULL; i++) {
+ if (strcmp(rights[i], MAIL_ACL_LOOKUP) == 0 ||
+ strcmp(rights[i], MAIL_ACL_READ) == 0 ||
+ strcmp(rights[i], MAIL_ACL_INSERT) == 0)
+ return abox->module_ctx.super.exists(box, auto_boxes,
+ existence_r);
+ }
+ *existence_r = MAILBOX_EXISTENCE_NONE;
+ return 0;
+}
+
+bool acl_mailbox_have_extra_attribute_rights(struct mailbox *box)
+{
+ /* RFC 5464:
+
+ When the ACL extension [RFC4314] is present, users can only set and
+ retrieve private or shared mailbox annotations on a mailbox on which
+ they have the "l" right and any one of the "r", "s", "w", "i", or "p"
+ rights.
+ */
+ const enum acl_storage_rights check_rights[] = {
+ ACL_STORAGE_RIGHT_READ,
+ ACL_STORAGE_RIGHT_WRITE_SEEN,
+ ACL_STORAGE_RIGHT_WRITE,
+ ACL_STORAGE_RIGHT_INSERT,
+ ACL_STORAGE_RIGHT_POST,
+ };
+ for (unsigned int i = 0; i < N_ELEMENTS(check_rights); i++) {
+ int ret = acl_mailbox_right_lookup(box, check_rights[i]);
+ if (ret > 0)
+ return TRUE;
+ if (ret < 0) {
+ /* unexpected error - stop checking further */
+ return FALSE;
+ }
+ }
+ return FALSE;
+}
+
+static int acl_mailbox_open_check_acl(struct mailbox *box)
+{
+ struct acl_mailbox *abox = ACL_CONTEXT_REQUIRE(box);
+ struct acl_mailbox_list *alist = ACL_LIST_CONTEXT_REQUIRE(box->list);
+ const unsigned int *idx_arr = alist->rights.acl_storage_right_idx;
+ enum acl_storage_rights open_right;
+ int ret;
+
+ /* mailbox can be opened either for reading or appending new messages */
+ if ((box->flags & MAILBOX_FLAG_IGNORE_ACLS) != 0 ||
+ (box->list->ns->flags & NAMESPACE_FLAG_NOACL) != 0 ||
+ abox->skip_acl_checks)
+ return 0;
+
+ if ((box->flags & MAILBOX_FLAG_SAVEONLY) != 0) {
+ open_right = (box->flags & MAILBOX_FLAG_POST_SESSION) != 0 ?
+ ACL_STORAGE_RIGHT_POST : ACL_STORAGE_RIGHT_INSERT;
+ } else if (box->deleting) {
+ open_right = ACL_STORAGE_RIGHT_DELETE;
+ } else if ((box->flags & MAILBOX_FLAG_ATTRIBUTE_SESSION) != 0) {
+ /* GETMETADATA/SETMETADATA requires "l" right and another one
+ which is checked afterwards. */
+ open_right = ACL_STORAGE_RIGHT_LOOKUP;
+ } else {
+ open_right = ACL_STORAGE_RIGHT_READ;
+ }
+
+ ret = acl_object_have_right(abox->aclobj, idx_arr[open_right]);
+ if (ret <= 0) {
+ if (ret == 0) {
+ /* no access. */
+ acl_mailbox_fail_not_found(box);
+ }
+ return -1;
+ }
+ if (open_right != ACL_STORAGE_RIGHT_READ) {
+ ret = acl_object_have_right(abox->aclobj,
+ idx_arr[ACL_STORAGE_RIGHT_READ]);
+ if (ret < 0)
+ return -1;
+ if (ret == 0)
+ abox->no_read_right = TRUE;
+ }
+ if ((box->flags & MAILBOX_FLAG_ATTRIBUTE_SESSION) != 0) {
+ if (!acl_mailbox_have_extra_attribute_rights(box))
+ return -1;
+ }
+ return 0;
+}
+
+static int acl_mailbox_open(struct mailbox *box)
+{
+ struct acl_mailbox *abox = ACL_CONTEXT_REQUIRE(box);
+
+ if (acl_mailbox_open_check_acl(box) < 0)
+ return -1;
+
+ return abox->module_ctx.super.open(box);
+}
+
+static int acl_mailbox_get_status(struct mailbox *box,
+ enum mailbox_status_items items,
+ struct mailbox_status *status_r)
+{
+ struct acl_mailbox *abox = ACL_CONTEXT_REQUIRE(box);
+
+ if (abox->module_ctx.super.get_status(box, items, status_r) < 0)
+ return -1;
+
+ if ((items & STATUS_PERMANENT_FLAGS) != 0) {
+ if (acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_WRITE) <= 0) {
+ status_r->permanent_flags &= MAIL_DELETED|MAIL_SEEN;
+ status_r->permanent_keywords = FALSE;
+ status_r->allow_new_keywords = FALSE;
+ }
+ if (acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_WRITE_DELETED) <= 0)
+ status_r->permanent_flags &= ENUM_NEGATE(MAIL_DELETED);
+ if (acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_WRITE_SEEN) <= 0)
+ status_r->permanent_flags &= ENUM_NEGATE(MAIL_SEEN);
+ }
+ return 0;
+}
+
+void acl_mailbox_allocated(struct mailbox *box)
+{
+ struct acl_mailbox_list *alist = ACL_LIST_CONTEXT(box->list);
+ struct mailbox_vfuncs *v = box->vlast;
+ struct acl_mailbox *abox;
+ bool ignore_acls = (box->flags & MAILBOX_FLAG_IGNORE_ACLS) != 0;
+
+ if (alist == NULL) {
+ /* ACLs disabled */
+ return;
+ }
+
+ if (mail_namespace_is_shared_user_root(box->list->ns) || alist->ignore_acls) {
+ /* this is the root shared namespace, which itself doesn't
+ have any existing mailboxes. */
+ ignore_acls = TRUE;
+ }
+
+ abox = p_new(box->pool, struct acl_mailbox, 1);
+ abox->module_ctx.super = *v;
+ box->vlast = &abox->module_ctx.super;
+ /* aclobj can be used for setting ACLs, even when mailbox is opened
+ with IGNORE_ACLS flag */
+ if (alist->rights.backend != NULL)
+ abox->aclobj = acl_object_init_from_name(alist->rights.backend,
+ mailbox_get_name(box));
+ else
+ i_assert(ignore_acls);
+
+ v->free = acl_mailbox_free;
+ if (!ignore_acls) {
+ abox->acl_enabled = TRUE;
+ v->is_readonly = acl_is_readonly;
+ v->exists = acl_mailbox_exists;
+ v->open = acl_mailbox_open;
+ v->get_status = acl_mailbox_get_status;
+ v->create_box = acl_mailbox_create;
+ v->update_box = acl_mailbox_update;
+ v->delete_box = acl_mailbox_delete;
+ v->rename_box = acl_mailbox_rename;
+ v->save_begin = acl_save_begin;
+ v->copy = acl_copy;
+ v->transaction_commit = acl_transaction_commit;
+ v->attribute_set = acl_attribute_set;
+ v->attribute_get = acl_attribute_get;
+ v->attribute_iter_init = acl_attribute_iter_init;
+ v->attribute_iter_next = acl_attribute_iter_next;
+ v->attribute_iter_deinit = acl_attribute_iter_deinit;
+ }
+ MODULE_CONTEXT_SET(box, acl_storage_module, abox);
+}
+
+static bool
+acl_mailbox_update_removed_id(struct acl_object *aclobj,
+ const struct acl_rights_update *update)
+{
+ struct acl_object_list_iter *iter;
+ struct acl_rights rights;
+
+ if (update->modify_mode != ACL_MODIFY_MODE_CLEAR &&
+ update->neg_modify_mode != ACL_MODIFY_MODE_CLEAR)
+ return FALSE;
+ if (update->modify_mode == ACL_MODIFY_MODE_CLEAR &&
+ update->neg_modify_mode == ACL_MODIFY_MODE_CLEAR)
+ return TRUE;
+
+ /* mixed clear/non-clear. see if the identifier exists anymore */
+ iter = acl_object_list_init(aclobj);
+ while (acl_object_list_next(iter, &rights)) {
+ if (rights.id_type == update->rights.id_type &&
+ null_strcmp(rights.identifier, update->rights.identifier) == 0)
+ break;
+ }
+ return acl_object_list_deinit(&iter) >= 0;
+}
+
+int acl_mailbox_update_acl(struct mailbox_transaction_context *t,
+ const struct acl_rights_update *update)
+{
+ struct acl_object *aclobj;
+ const char *key;
+ time_t ts = update->last_change != 0 ?
+ update->last_change : ioloop_time;
+
+ key = t_strdup_printf(MAILBOX_ATTRIBUTE_PREFIX_ACL"%s",
+ acl_rights_get_id(&update->rights));
+ aclobj = acl_mailbox_get_aclobj(t->box);
+ if (acl_object_update(aclobj, update) < 0) {
+ mailbox_set_critical(t->box, "Failed to set ACL");
+ return -1;
+ }
+
+ /* FIXME: figure out some value lengths, so maybe some day
+ quota could apply to ACLs as well. */
+ if (acl_mailbox_update_removed_id(aclobj, update))
+ mail_index_attribute_unset(t->itrans, FALSE, key, ts);
+ else
+ mail_index_attribute_set(t->itrans, FALSE, key, ts, 0);
+ return 0;
+}