Mercurial > dovecot > core-2.2
view src/plugins/acl/acl-mailbox.c @ 12299:7b47c5a321b9
acl: Fixed memory leaks.
author | Timo Sirainen <tss@iki.fi> |
---|---|
date | Mon, 18 Oct 2010 16:07:17 +0100 |
parents | 4b20e692c606 |
children | 0908326bf4b9 |
line wrap: on
line source
/* Copyright (c) 2006-2010 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 "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(obj, acl_mail_module) struct acl_mailbox { union mailbox_module_context module_ctx; struct acl_object *aclobj; bool skip_acl_checks; bool acl_enabled; }; 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(box); return abox->aclobj; } int acl_mailbox_right_lookup(struct mailbox *box, unsigned int right_idx) { struct acl_mailbox *abox = ACL_CONTEXT(box); struct acl_mailbox_list *alist = ACL_LIST_CONTEXT(box->list); int ret; if (abox->skip_acl_checks) 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(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; /* Next up is the "shared flag rights" */ if (acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_WRITE) > 0) return FALSE; if ((box->private_flags_mask & MAIL_DELETED) == 0 && acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_WRITE_DELETED) > 0) return FALSE; if ((box->private_flags_mask & MAIL_SEEN) == 0 && acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_WRITE_SEEN) > 0) return FALSE; return TRUE; } static bool acl_allow_new_keywords(struct mailbox *box) { struct acl_mailbox *abox = ACL_CONTEXT(box); if (!abox->module_ctx.super.allow_new_keywords(box)) return FALSE; return acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_WRITE) > 0; } static void acl_mailbox_free(struct mailbox *box) { struct acl_mailbox *abox = ACL_CONTEXT(box); 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(box); struct acl_mailbox_list *alist = ACL_LIST_CONTEXT(box->list); struct acl_object *parent_aclobj; struct acl_object_list_iter *iter; struct acl_rights_update update; memset(&update, 0, sizeof(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) > 0) { /* don't copy global ACL rights. */ if (!update.rights.global) (void)acl_object_update(abox->aclobj, &update); } 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(box); /* we already checked permissions in list.mailbox_create_dir(). */ if (abox->module_ctx.super.create(box, update, directory) < 0) return -1; acl_mailbox_copy_acls_from_parent(box); return 0; } static int acl_mailbox_update(struct mailbox *box, const struct mailbox_update *update) { struct acl_mailbox *abox = ACL_CONTEXT(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, 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) { mail_storage_set_error(box->storage, MAIL_ERROR_NOTFOUND, T_MAIL_ERR_MAILBOX_NOT_FOUND(box->name)); } } static int acl_mailbox_delete(struct mailbox *box) { struct acl_mailbox *abox = ACL_CONTEXT(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; } /* deletion might internally open the mailbox. let it succeed even if we don't have READ permission. */ abox->skip_acl_checks = TRUE; ret = abox->module_ctx.super.delete(box); abox->skip_acl_checks = FALSE; return ret; } static int acl_mailbox_rename(struct mailbox *src, struct mailbox *dest, bool rename_children) { struct acl_mailbox *abox = ACL_CONTEXT(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(src, dest, rename_children); } 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 &= ~MAIL_SEEN; if (!acl_flag_del) flags &= ~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, ~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. */ if (ret < 0) 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, 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 &= ~MAIL_SEEN; if (!acl_flag_del) *flags &= ~MAIL_DELETED; if (!acl_flags) { *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(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->flags, &ctx->keywords) < 0) return -1; return abox->module_ctx.super.save_begin(ctx, input); } 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(t->box); enum acl_storage_rights save_right; save_right = (t->box->flags & MAILBOX_FLAG_POST_SESSION) != 0 ? ACL_STORAGE_RIGHT_POST : ACL_STORAGE_RIGHT_INSERT; if (acl_mailbox_right_lookup(t->box, save_right) <= 0) return -1; if (acl_save_get_flags(t->box, &ctx->flags, &ctx->keywords) < 0) 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(ctx->box); void *at = ACL_CONTEXT(ctx); if (at != NULL) { abox->module_ctx.super.transaction_rollback(ctx); return -1; } return abox->module_ctx.super.transaction_commit(ctx, changes_r); } static int acl_keywords_create(struct mailbox *box, const char *const keywords[], struct mail_keywords **keywords_r, bool skip_invalid) { struct acl_mailbox *abox = ACL_CONTEXT(box); int ret; ret = acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_WRITE); if (ret < 0) { if (!skip_invalid) return -1; /* we can't return failure. assume we don't have permissions. */ ret = 0; } if (ret == 0) { /* no permission to update any flags. just return empty keywords list. */ const char *null = NULL; return abox->module_ctx.super.keywords_create(box, &null, keywords_r, skip_invalid); } return abox->module_ctx.super.keywords_create(box, keywords, keywords_r, skip_invalid); } static int acl_mailbox_open_check_acl(struct mailbox *box) { struct acl_mailbox *abox = ACL_CONTEXT(box); struct acl_mailbox_list *alist = ACL_LIST_CONTEXT(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 { 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; } return 0; } static int acl_mailbox_open(struct mailbox *box) { struct acl_mailbox *abox = ACL_CONTEXT(box); if (acl_mailbox_open_check_acl(box) < 0) return -1; return abox->module_ctx.super.open(box); } 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; if (alist == NULL) { /* ACLs disabled */ return; } abox = p_new(box->pool, struct acl_mailbox, 1); abox->module_ctx.super = *v; box->vlast = &abox->module_ctx.super; if ((box->flags & MAILBOX_FLAG_IGNORE_ACLS) == 0) { abox->aclobj = acl_object_init_from_name(alist->rights.backend, mailbox_get_name(box)); abox->acl_enabled = TRUE; v->is_readonly = acl_is_readonly; v->allow_new_keywords = acl_allow_new_keywords; v->open = acl_mailbox_open; v->free = acl_mailbox_free; v->create = acl_mailbox_create; v->update = acl_mailbox_update; v->delete = acl_mailbox_delete; v->rename = acl_mailbox_rename; v->save_begin = acl_save_begin; v->keywords_create = acl_keywords_create; v->copy = acl_copy; v->transaction_commit = acl_transaction_commit; } MODULE_CONTEXT_SET(box, acl_storage_module, abox); }