diff options
Diffstat (limited to 'src/plugins/acl/doveadm-acl.c')
-rw-r--r-- | src/plugins/acl/doveadm-acl.c | 629 |
1 files changed, 629 insertions, 0 deletions
diff --git a/src/plugins/acl/doveadm-acl.c b/src/plugins/acl/doveadm-acl.c new file mode 100644 index 0000000..0ab416a --- /dev/null +++ b/src/plugins/acl/doveadm-acl.c @@ -0,0 +1,629 @@ +/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "str.h" +#include "module-dir.h" +#include "imap-util.h" +#include "acl-plugin.h" +#include "acl-api-private.h" +#include "acl-lookup-dict.h" +#include "doveadm-print.h" +#include "doveadm-mail.h" + +struct doveadm_acl_cmd_context { + struct doveadm_mail_cmd_context ctx; + bool get_match_me; + enum acl_modify_mode modify_mode; +}; + +const char *doveadm_acl_plugin_version = DOVECOT_ABI_VERSION; + +void doveadm_acl_plugin_init(struct module *module); +void doveadm_acl_plugin_deinit(void); + +static int +cmd_acl_mailbox_open(struct doveadm_mail_cmd_context *ctx, + struct mail_user *user, const char *mailbox, + struct mailbox **box_r) +{ + struct acl_user *auser = ACL_USER_CONTEXT(user); + struct mail_namespace *ns; + struct mailbox *box; + + if (auser == NULL) { + i_error("ACL not enabled for %s", user->username); + doveadm_mail_failed_error(ctx, MAIL_ERROR_NOTFOUND); + return -1; + } + + ns = mail_namespace_find(user->namespaces, mailbox); + box = mailbox_alloc(ns->list, mailbox, + MAILBOX_FLAG_READONLY | MAILBOX_FLAG_IGNORE_ACLS); + if (mailbox_open(box) < 0) { + i_error("Can't open mailbox %s: %s", mailbox, + mailbox_get_last_internal_error(box, NULL)); + doveadm_mail_failed_mailbox(ctx, box); + mailbox_free(&box); + return -1; + } + *box_r = box; + return 0; +} + +static void cmd_acl_get_right(const struct acl_rights *rights) +{ + string_t *str; + + doveadm_print(acl_rights_get_id(rights)); + + if (rights->global) + doveadm_print("global"); + else + doveadm_print(""); + + str = t_str_new(256); + if (rights->rights != NULL) + str_append(str, t_strarray_join(rights->rights, " ")); + if (rights->neg_rights != NULL) { + if (str_len(str) > 0) + str_append_c(str, ' '); + str_append_c(str, '-'); + str_append(str, t_strarray_join(rights->neg_rights, " -")); + } + doveadm_print(str_c(str)); +} + +static int cmd_acl_get_mailbox(struct doveadm_acl_cmd_context *ctx, + struct mailbox *box) +{ + struct acl_object *aclobj = acl_mailbox_get_aclobj(box); + struct acl_backend *backend; + struct acl_object_list_iter *iter; + struct acl_rights rights; + int ret; + + backend = acl_mailbox_list_get_backend(box->list); + + iter = acl_object_list_init(aclobj); + while (acl_object_list_next(iter, &rights)) T_BEGIN { + if (!ctx->get_match_me || + acl_backend_rights_match_me(backend, &rights)) + cmd_acl_get_right(&rights); + } T_END; + + if ((ret = acl_object_list_deinit(&iter))<0) { + i_error("ACL iteration failed"); + doveadm_mail_failed_error(&ctx->ctx, MAIL_ERROR_TEMP); + } + return ret; +} + +static int +cmd_acl_get_run(struct doveadm_mail_cmd_context *_ctx, + struct mail_user *user) +{ + struct doveadm_acl_cmd_context *ctx = + (struct doveadm_acl_cmd_context *)_ctx; + const char *mailbox = _ctx->args[0]; + struct mailbox *box; + int ret; + + if (cmd_acl_mailbox_open(_ctx, user, mailbox, &box) < 0) + return -1; + + ret = cmd_acl_get_mailbox(ctx, box); + mailbox_free(&box); + return ret; +} + +static bool cmd_acl_get_parse_arg(struct doveadm_mail_cmd_context *_ctx, int c) +{ + struct doveadm_acl_cmd_context *ctx = + (struct doveadm_acl_cmd_context *)_ctx; + + switch (c) { + case 'm': + ctx->get_match_me = TRUE; + break; + default: + return FALSE; + } + return TRUE; +} + +static void cmd_acl_get_init(struct doveadm_mail_cmd_context *ctx ATTR_UNUSED, + const char *const args[]) +{ + if (args[0] == NULL) + doveadm_mail_help_name("acl get"); + doveadm_print_header("id", "ID", 0); + doveadm_print_header("global", "Global", 0); + doveadm_print_header("rights", "Rights", 0); +} + +static struct doveadm_mail_cmd_context * +cmd_acl_get_alloc(void) +{ + struct doveadm_acl_cmd_context *ctx; + + ctx = doveadm_mail_cmd_alloc(struct doveadm_acl_cmd_context); + ctx->ctx.getopt_args = "m"; + ctx->ctx.v.parse_arg = cmd_acl_get_parse_arg; + ctx->ctx.v.run = cmd_acl_get_run; + ctx->ctx.v.init = cmd_acl_get_init; + doveadm_print_init(DOVEADM_PRINT_TYPE_TABLE); + return &ctx->ctx; +} + +static int +cmd_acl_rights_run(struct doveadm_mail_cmd_context *ctx, struct mail_user *user) +{ + const char *mailbox = ctx->args[0]; + struct mailbox *box; + struct acl_object *aclobj; + const char *const *rights; + int ret = 0; + + if (cmd_acl_mailbox_open(ctx, user, mailbox, &box) < 0) + return -1; + + aclobj = acl_mailbox_get_aclobj(box); + if (acl_object_get_my_rights(aclobj, pool_datastack_create(), + &rights) < 0) { + doveadm_mail_failed_error(ctx, MAIL_ERROR_TEMP); + i_error("Failed to get rights"); + ret = -1; + } else { + doveadm_print(t_strarray_join(rights, " ")); + } + mailbox_free(&box); + return ret; +} + +static void cmd_acl_rights_init(struct doveadm_mail_cmd_context *ctx ATTR_UNUSED, + const char *const args[]) +{ + if (args[0] == NULL) + doveadm_mail_help_name("acl rights"); + doveadm_print_header("rights", "Rights", 0); +} + +static struct doveadm_mail_cmd_context * +cmd_acl_rights_alloc(void) +{ + struct doveadm_mail_cmd_context *ctx; + + ctx = doveadm_mail_cmd_alloc(struct doveadm_mail_cmd_context); + ctx->v.run = cmd_acl_rights_run; + ctx->v.init = cmd_acl_rights_init; + doveadm_print_init(DOVEADM_PRINT_TYPE_TABLE); + return ctx; +} + +static int +cmd_acl_mailbox_update(struct doveadm_mail_cmd_context *ctx, struct mailbox *box, + const struct acl_rights_update *update) +{ + struct mailbox_transaction_context *t; + int ret; + + t = mailbox_transaction_begin(box, MAILBOX_TRANSACTION_FLAG_EXTERNAL | + ctx->transaction_flags, __func__); + ret = acl_mailbox_update_acl(t, update); + if (mailbox_transaction_commit(&t) < 0) + ret = -1; + return ret; +} + +static int +cmd_acl_set_run(struct doveadm_mail_cmd_context *_ctx, struct mail_user *user) +{ + struct doveadm_acl_cmd_context *ctx = + (struct doveadm_acl_cmd_context *)_ctx; + const char *mailbox = _ctx->args[0], *id = _ctx->args[1]; + const char *const *rights = _ctx->args + 2; + struct mailbox *box; + struct acl_rights_update update; + const char *error; + int ret; + + if (cmd_acl_mailbox_open(_ctx, user, mailbox, &box) < 0) + return -1; + + i_zero(&update); + update.modify_mode = ctx->modify_mode; + update.neg_modify_mode = ctx->modify_mode; + if (acl_rights_update_import(&update, id, rights, &error) < 0) + i_fatal_status(EX_USAGE, "%s", error); + if ((ret = cmd_acl_mailbox_update(&ctx->ctx, box, &update)) < 0) { + i_error("Failed to set ACL: %s", + mailbox_get_last_internal_error(box, NULL)); + doveadm_mail_failed_error(_ctx, MAIL_ERROR_TEMP); + } + mailbox_free(&box); + return ret; +} + +static void cmd_acl_set_init(struct doveadm_mail_cmd_context *ctx ATTR_UNUSED, + const char *const args[]) +{ + if (str_array_length(args) < 3) + doveadm_mail_help_name("acl set"); +} + +static struct doveadm_mail_cmd_context * +cmd_acl_change_alloc(enum acl_modify_mode modify_mode) +{ + struct doveadm_acl_cmd_context *ctx; + + ctx = doveadm_mail_cmd_alloc(struct doveadm_acl_cmd_context); + ctx->ctx.v.run = cmd_acl_set_run; + ctx->ctx.v.init = cmd_acl_set_init; + ctx->modify_mode = modify_mode; + return &ctx->ctx; +} + +static struct doveadm_mail_cmd_context *cmd_acl_set_alloc(void) +{ + return cmd_acl_change_alloc(ACL_MODIFY_MODE_REPLACE); +} + +static struct doveadm_mail_cmd_context *cmd_acl_add_alloc(void) +{ + return cmd_acl_change_alloc(ACL_MODIFY_MODE_ADD); +} + +static struct doveadm_mail_cmd_context *cmd_acl_remove_alloc(void) +{ + return cmd_acl_change_alloc(ACL_MODIFY_MODE_REMOVE); +} + +static int +cmd_acl_delete_run(struct doveadm_mail_cmd_context *ctx, struct mail_user *user) +{ + const char *mailbox = ctx->args[0], *id = ctx->args[1]; + struct mailbox *box; + struct acl_rights_update update; + const char *error; + int ret = 0; + + if (cmd_acl_mailbox_open(ctx, user, mailbox, &box) < 0) + return -1; + + i_zero(&update); + if (acl_rights_update_import(&update, id, NULL, &error) < 0) + i_fatal_status(EX_USAGE, "%s", error); + if ((ret = cmd_acl_mailbox_update(ctx, box, &update)) < 0) { + i_error("Failed to delete ACL: %s", + mailbox_get_last_internal_error(box, NULL)); + doveadm_mail_failed_error(ctx, MAIL_ERROR_TEMP); + } + mailbox_free(&box); + return ret; +} + +static void cmd_acl_delete_init(struct doveadm_mail_cmd_context *ctx ATTR_UNUSED, + const char *const args[]) +{ + if (str_array_length(args) < 2) + doveadm_mail_help_name("acl delete"); +} + +static struct doveadm_mail_cmd_context * +cmd_acl_delete_alloc(void) +{ + struct doveadm_mail_cmd_context *ctx; + + ctx = doveadm_mail_cmd_alloc(struct doveadm_mail_cmd_context); + ctx->v.run = cmd_acl_delete_run; + ctx->v.init = cmd_acl_delete_init; + return ctx; +} + +static int +cmd_acl_recalc_run(struct doveadm_mail_cmd_context *ctx, struct mail_user *user) +{ + struct acl_user *auser = ACL_USER_CONTEXT(user); + + if (auser == NULL) { + i_error("ACL not enabled for %s", user->username); + doveadm_mail_failed_error(ctx, MAIL_ERROR_NOTFOUND); + return -1; + } + if (acl_lookup_dict_rebuild(auser->acl_lookup_dict) < 0) { + i_error("Failed to recalculate ACL dicts"); + doveadm_mail_failed_error(ctx, MAIL_ERROR_TEMP); + return -1; + } + return 0; +} + +static struct doveadm_mail_cmd_context * +cmd_acl_recalc_alloc(void) +{ + struct doveadm_mail_cmd_context *ctx; + + ctx = doveadm_mail_cmd_alloc(struct doveadm_mail_cmd_context); + ctx->v.run = cmd_acl_recalc_run; + return ctx; +} + +static int +cmd_acl_debug_mailbox_open(struct doveadm_mail_cmd_context *ctx, + struct mail_user *user, const char *mailbox, + struct mailbox **box_r) +{ + struct acl_user *auser = ACL_USER_CONTEXT_REQUIRE(user); + struct mail_namespace *ns; + struct mailbox *box; + const char *path, *errstr; + enum mail_error error; + + ns = mail_namespace_find(user->namespaces, mailbox); + box = mailbox_alloc(ns->list, mailbox, + MAILBOX_FLAG_READONLY | MAILBOX_FLAG_IGNORE_ACLS); + if (mailbox_open(box) < 0) { + errstr = mail_storage_get_last_internal_error(box->storage, &error); + errstr = t_strdup(errstr); + doveadm_mail_failed_error(ctx, error); + + if (error != MAIL_ERROR_NOTFOUND || + mailbox_get_path_to(box, MAILBOX_LIST_PATH_TYPE_MAILBOX, + &path) <= 0) + i_error("Can't open mailbox %s: %s", mailbox, errstr); + else { + i_error("Mailbox '%s' in namespace '%s' doesn't exist in %s", + box->name, ns->prefix, path); + } + mailbox_free(&box); + return -1; + } + + if (auser == NULL) { + i_info("ACL not enabled for user %s, mailbox can be accessed", + user->username); + doveadm_mail_failed_error(ctx, MAIL_ERROR_NOTFOUND); + mailbox_free(&box); + return -1; + } + + *box_r = box; + return 0; +} + +static bool cmd_acl_debug_mailbox(struct mailbox *box, bool *retry_r) +{ + struct mail_namespace *ns = mailbox_get_namespace(box); + struct acl_user *auser = ACL_USER_CONTEXT_REQUIRE(ns->user); + struct acl_object *aclobj = acl_mailbox_get_aclobj(box); + struct acl_backend *backend = acl_mailbox_list_get_backend(box->list); + struct acl_mailbox_list_context *iter; + struct acl_lookup_dict_iter *diter; + const char *const *rights, *name, *path; + enum mail_flags private_flags_mask; + string_t *str; + int ret; + bool all_ok = TRUE; + + *retry_r = FALSE; + + i_info("Mailbox '%s' is in namespace '%s'", + box->name, box->list->ns->prefix); + if (mailbox_get_path_to(box, MAILBOX_LIST_PATH_TYPE_MAILBOX, &path) > 0) + i_info("Mailbox path: %s", path); + + private_flags_mask = mailbox_get_private_flags_mask(box); + if (private_flags_mask == 0) + i_info("All message flags are shared across users in mailbox"); + else { + str = t_str_new(64); + imap_write_flags(str, private_flags_mask, NULL); + i_info("Per-user private flags in mailbox: %s", str_c(str)); + } + + /* check if user has lookup right */ + if (acl_object_get_my_rights(aclobj, pool_datastack_create(), + &rights) < 0) + i_fatal("Failed to get rights"); + + if (rights[0] == NULL) + i_info("User %s has no rights for mailbox", ns->user->username); + else { + i_info("User %s has rights: %s", + ns->user->username, t_strarray_join(rights, " ")); + } + if (!str_array_find(rights, MAIL_ACL_LOOKUP)) { + i_error("User %s is missing 'lookup' right", + ns->user->username); + return FALSE; + } + + /* check if mailbox is listable */ + if (ns->type == MAIL_NAMESPACE_TYPE_PRIVATE) { + i_info("Mailbox in user's private namespace"); + return TRUE; + } + + iter = acl_backend_nonowner_lookups_iter_init(backend); + while (acl_backend_nonowner_lookups_iter_next(iter, &name)) { + if (strcmp(name, box->name) == 0) + break; + } + if ((ret = acl_backend_nonowner_lookups_iter_deinit(&iter))<0) + i_fatal("ACL non-owner iteration failed"); + if (ret == 0) { + i_error("Mailbox not found from dovecot-acl-list, rebuilding"); + if (acl_backend_nonowner_lookups_rebuild(backend) < 0) + i_fatal("dovecot-acl-list rebuilding failed"); + all_ok = FALSE; + *retry_r = TRUE; + } else { + i_info("Mailbox found from dovecot-acl-list"); + } + + if (ns->type == MAIL_NAMESPACE_TYPE_PUBLIC) { + i_info("Mailbox is in public namespace"); + return TRUE; + } + + if (!acl_lookup_dict_is_enabled(auser->acl_lookup_dict)) { + i_error("acl_lookup_dict not enabled"); + return FALSE; + } + + /* shared namespace. see if it's in acl lookup dict */ + diter = acl_lookup_dict_iterate_visible_init(auser->acl_lookup_dict); + while ((name = acl_lookup_dict_iterate_visible_next(diter)) != NULL) { + if (strcmp(name, ns->owner->username) == 0) + break; + } + if (acl_lookup_dict_iterate_visible_deinit(&diter) < 0) + i_fatal("ACL shared dict iteration failed"); + if (name == NULL) { + i_error("User %s not found from ACL shared dict, rebuilding", + ns->owner->username); + if (acl_lookup_dict_rebuild(auser->acl_lookup_dict) < 0) + i_fatal("ACL lookup dict rebuild failed"); + all_ok = FALSE; + *retry_r = TRUE; + } else { + i_info("User %s found from ACL shared dict", + ns->owner->username); + } + return all_ok; +} + +static int +cmd_acl_debug_run(struct doveadm_mail_cmd_context *ctx, struct mail_user *user) +{ + const char *mailbox = ctx->args[0]; + struct mailbox *box; + bool ret, retry; + + if (cmd_acl_debug_mailbox_open(ctx, user, mailbox, &box) < 0) + return -1; + + ret = cmd_acl_debug_mailbox(box, &retry); + if (!ret && retry) { + i_info("Retrying after rebuilds:"); + ret = cmd_acl_debug_mailbox(box, &retry); + } + if (ret) + i_info("Mailbox %s is visible in LIST", box->vname); + else + i_info("Mailbox %s is NOT visible in LIST", box->vname); + mailbox_free(&box); + return 0; +} + +static void cmd_acl_debug_init(struct doveadm_mail_cmd_context *ctx ATTR_UNUSED, + const char *const args[]) +{ + if (args[0] == NULL) + doveadm_mail_help_name("acl debug"); +} + +static struct doveadm_mail_cmd_context * +cmd_acl_debug_alloc(void) +{ + struct doveadm_mail_cmd_context *ctx; + + ctx = doveadm_mail_cmd_alloc(struct doveadm_mail_cmd_context); + ctx->v.run = cmd_acl_debug_run; + ctx->v.init = cmd_acl_debug_init; + return ctx; +} + +static struct doveadm_cmd_ver2 acl_commands[] = { +{ + .name = "acl get", + .mail_cmd = cmd_acl_get_alloc, + .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX "[-m] <mailbox>", +DOVEADM_CMD_PARAMS_START +DOVEADM_CMD_MAIL_COMMON +DOVEADM_CMD_PARAM('m', "match-me", CMD_PARAM_BOOL, 0) +DOVEADM_CMD_PARAM('\0', "mailbox", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) +DOVEADM_CMD_PARAMS_END +}, +{ + .name = "acl rights", + .mail_cmd = cmd_acl_rights_alloc, + .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX "<mailbox>", +DOVEADM_CMD_PARAMS_START +DOVEADM_CMD_MAIL_COMMON +DOVEADM_CMD_PARAM('\0', "mailbox", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) +DOVEADM_CMD_PARAMS_END +}, +{ + .name = "acl set", + .mail_cmd = cmd_acl_set_alloc, + .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX "<mailbox> <id> <right> [<right> ...]", +DOVEADM_CMD_PARAMS_START +DOVEADM_CMD_MAIL_COMMON +DOVEADM_CMD_PARAM('\0', "mailbox", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) +DOVEADM_CMD_PARAM('\0', "id", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) +DOVEADM_CMD_PARAM('\0', "right", CMD_PARAM_ARRAY, CMD_PARAM_FLAG_POSITIONAL) +DOVEADM_CMD_PARAMS_END +}, +{ + .name = "acl add", + .mail_cmd = cmd_acl_add_alloc, + .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX "<mailbox> <id> <right> [<right> ...]", +DOVEADM_CMD_PARAMS_START +DOVEADM_CMD_MAIL_COMMON +DOVEADM_CMD_PARAM('\0', "mailbox", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) +DOVEADM_CMD_PARAM('\0', "id", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) +DOVEADM_CMD_PARAM('\0', "right", CMD_PARAM_ARRAY, CMD_PARAM_FLAG_POSITIONAL) +DOVEADM_CMD_PARAMS_END +}, +{ + .name = "acl remove", + .mail_cmd = cmd_acl_remove_alloc, + .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX "<mailbox> <id> <right> [<right> ...]", +DOVEADM_CMD_PARAMS_START +DOVEADM_CMD_MAIL_COMMON +DOVEADM_CMD_PARAM('\0', "mailbox", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) +DOVEADM_CMD_PARAM('\0', "id", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) +DOVEADM_CMD_PARAM('\0', "right", CMD_PARAM_ARRAY, CMD_PARAM_FLAG_POSITIONAL) +DOVEADM_CMD_PARAMS_END +}, +{ + .name = "acl delete", + .mail_cmd = cmd_acl_delete_alloc, + .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX "<mailbox> <id>", +DOVEADM_CMD_PARAMS_START +DOVEADM_CMD_MAIL_COMMON +DOVEADM_CMD_PARAM('\0', "mailbox", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) +DOVEADM_CMD_PARAM('\0', "id", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) +DOVEADM_CMD_PARAMS_END +}, +{ + .name = "acl recalc", + .mail_cmd = cmd_acl_recalc_alloc, + .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX, +DOVEADM_CMD_PARAMS_START +DOVEADM_CMD_MAIL_COMMON +DOVEADM_CMD_PARAMS_END +}, +{ + .name = "acl debug", + .mail_cmd = cmd_acl_debug_alloc, + .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX "<mailbox>", +DOVEADM_CMD_PARAMS_START +DOVEADM_CMD_MAIL_COMMON +DOVEADM_CMD_PARAM('\0', "mailbox", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) +DOVEADM_CMD_PARAMS_END +} +}; + +void doveadm_acl_plugin_init(struct module *module ATTR_UNUSED) +{ + unsigned int i; + + for (i = 0; i < N_ELEMENTS(acl_commands); i++) + doveadm_cmd_register_ver2(&acl_commands[i]); +} + +void doveadm_acl_plugin_deinit(void) +{ +} |