From: Timo Sirainen Date: Fri, 4 Mar 2011 01:22:45 +0000 (+0200) Subject: acl: Added doveadm plugin supporting acl get/set/rights/debug commands. X-Git-Tag: 2.0.10~19 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=fe25191fe9f906a0dc831732d1ef085aadd28a13;p=thirdparty%2Fdovecot%2Fcore.git acl: Added doveadm plugin supporting acl get/set/rights/debug commands. --- diff --git a/src/plugins/acl/Makefile.am b/src/plugins/acl/Makefile.am index 63083df0da..ad3b407eec 100644 --- a/src/plugins/acl/Makefile.am +++ b/src/plugins/acl/Makefile.am @@ -1,11 +1,15 @@ +doveadm_moduledir = $(moduledir)/doveadm + AM_CPPFLAGS = \ -I$(top_srcdir)/src/lib \ -I$(top_srcdir)/src/lib-dict \ -I$(top_srcdir)/src/lib-mail \ -I$(top_srcdir)/src/lib-imap \ -I$(top_srcdir)/src/lib-index \ - -I$(top_srcdir)/src/lib-storage + -I$(top_srcdir)/src/lib-storage \ + -I$(top_srcdir)/src/doveadm +lib10_doveadm_acl_plugin_la_LDFLAGS = -module -avoid-version lib01_acl_plugin_la_LDFLAGS = -module -avoid-version module_LTLIBRARIES = \ @@ -33,3 +37,9 @@ noinst_HEADERS = \ acl-plugin.h \ acl-shared-storage.h \ acl-storage.h + +doveadm_module_LTLIBRARIES = \ + lib10_doveadm_acl_plugin.la + +lib10_doveadm_acl_plugin_la_SOURCES = \ + doveadm-acl.c diff --git a/src/plugins/acl/acl-api-private.h b/src/plugins/acl/acl-api-private.h index 46478e9e62..fc2211d2c0 100644 --- a/src/plugins/acl/acl-api-private.h +++ b/src/plugins/acl/acl-api-private.h @@ -74,6 +74,8 @@ struct acl_object_list_iter { unsigned int failed:1; }; +extern const char *const all_mailbox_rights[]; + const char *const * acl_backend_mask_get_names(struct acl_backend *backend, const struct acl_mask *mask, pool_t pool); @@ -82,4 +84,6 @@ int acl_backend_get_default_rights(struct acl_backend *backend, void acl_rights_write_id(string_t *dest, const struct acl_rights *right); bool acl_rights_has_nonowner_lookup_changes(const struct acl_rights *rights); +int acl_identifier_parse(const char *line, struct acl_rights *rights); + #endif diff --git a/src/plugins/acl/acl-api.c b/src/plugins/acl/acl-api.c index 3c04e2e2b3..532c882225 100644 --- a/src/plugins/acl/acl-api.c +++ b/src/plugins/acl/acl-api.c @@ -210,3 +210,30 @@ bool acl_rights_has_nonowner_lookup_changes(const struct acl_rights *rights) } return FALSE; } + +int acl_identifier_parse(const char *line, struct acl_rights *rights) +{ + if (strncmp(line, ACL_ID_NAME_USER_PREFIX, + strlen(ACL_ID_NAME_USER_PREFIX)) == 0) { + rights->id_type = ACL_ID_USER; + rights->identifier = line + 5; + } else if (strcmp(line, ACL_ID_NAME_OWNER) == 0) { + rights->id_type = ACL_ID_OWNER; + } else if (strncmp(line, ACL_ID_NAME_GROUP_PREFIX, + strlen(ACL_ID_NAME_GROUP_PREFIX)) == 0) { + rights->id_type = ACL_ID_GROUP; + rights->identifier = line + 6; + } else if (strncmp(line, ACL_ID_NAME_GROUP_OVERRIDE_PREFIX, + strlen(ACL_ID_NAME_GROUP_OVERRIDE_PREFIX)) == 0) { + rights->id_type = ACL_ID_GROUP_OVERRIDE; + rights->identifier = line + 15; + } else if (strcmp(line, ACL_ID_NAME_AUTHENTICATED) == 0) { + rights->id_type = ACL_ID_AUTHENTICATED; + } else if (strcmp(line, ACL_ID_NAME_ANYONE) == 0 || + strcmp(line, "anonymous") == 0) { + rights->id_type = ACL_ID_ANYONE; + } else { + return -1; + } + return 0; +} diff --git a/src/plugins/acl/acl-backend-vfile.c b/src/plugins/acl/acl-backend-vfile.c index 98800c89b7..c64bc7e3f3 100644 --- a/src/plugins/acl/acl-backend-vfile.c +++ b/src/plugins/acl/acl-backend-vfile.c @@ -419,44 +419,8 @@ acl_object_vfile_parse_line(struct acl_object_vfile *aclobj, bool global, rights.neg_rights = right_names; } - switch (*line) { - case 'u': - if (strncmp(line, ACL_ID_NAME_USER_PREFIX, - strlen(ACL_ID_NAME_USER_PREFIX)) == 0) { - rights.id_type = ACL_ID_USER; - rights.identifier = line + 5; - break; - } - case 'o': - if (strcmp(line, ACL_ID_NAME_OWNER) == 0) { - rights.id_type = ACL_ID_OWNER; - break; - } - case 'g': - if (strncmp(line, ACL_ID_NAME_GROUP_PREFIX, - strlen(ACL_ID_NAME_GROUP_PREFIX)) == 0) { - rights.id_type = ACL_ID_GROUP; - rights.identifier = line + 6; - break; - } else if (strncmp(line, ACL_ID_NAME_GROUP_OVERRIDE_PREFIX, - strlen(ACL_ID_NAME_GROUP_OVERRIDE_PREFIX)) == 0) { - rights.id_type = ACL_ID_GROUP_OVERRIDE; - rights.identifier = line + 15; - break; - } - case 'a': - if (strcmp(line, ACL_ID_NAME_AUTHENTICATED) == 0) { - rights.id_type = ACL_ID_AUTHENTICATED; - break; - } else if (strcmp(line, ACL_ID_NAME_ANYONE) == 0 || - strcmp(line, "anonymous") == 0) { - rights.id_type = ACL_ID_ANYONE; - break; - } - default: + if (acl_identifier_parse(line, &rights) < 0) error = t_strdup_printf("Unknown ID '%s'", line); - break; - } if (error != NULL) { i_error("ACL file %s line %u: %s", path, linenum, error); @@ -1110,12 +1074,16 @@ acl_backend_vfile_update_write(struct acl_object_vfile *aclobj, first global */ rights = array_get(&aclobj->rights, &count); for (i = 0; i < count && !rights[i].global; i++) { - if (rights[i].rights != NULL) + if (rights[i].rights != NULL) { vfile_write_right(str, &rights[i], FALSE); - if (rights[i].neg_rights != NULL) + o_stream_send(output, str_data(str), str_len(str)); + str_truncate(str, 0); + } + if (rights[i].neg_rights != NULL) { vfile_write_right(str, &rights[i], TRUE); - o_stream_send(output, str_data(str), str_len(str)); - str_truncate(str, 0); + o_stream_send(output, str_data(str), str_len(str)); + str_truncate(str, 0); + } } str_free(&str); if (o_stream_flush(output) < 0) { diff --git a/src/plugins/acl/acl-backend.c b/src/plugins/acl/acl-backend.c index 99e6ed21d9..d01f0d627e 100644 --- a/src/plugins/acl/acl-backend.c +++ b/src/plugins/acl/acl-backend.c @@ -12,7 +12,7 @@ extern struct acl_backend_vfuncs acl_backend_vfile; -static const char *const owner_mailbox_rights[] = { +const char *const all_mailbox_rights[] = { MAIL_ACL_LOOKUP, MAIL_ACL_READ, MAIL_ACL_WRITE, @@ -27,6 +27,7 @@ static const char *const owner_mailbox_rights[] = { NULL }; +static const char *const *owner_mailbox_rights = all_mailbox_rights; static const char *const non_owner_mailbox_rights[] = { NULL }; struct acl_backend * diff --git a/src/plugins/acl/acl-lookup-dict.c b/src/plugins/acl/acl-lookup-dict.c index 8e16861323..42b9e13325 100644 --- a/src/plugins/acl/acl-lookup-dict.c +++ b/src/plugins/acl/acl-lookup-dict.c @@ -63,6 +63,11 @@ void acl_lookup_dict_deinit(struct acl_lookup_dict **_dict) i_free(dict); } +bool acl_lookup_dict_is_enabled(struct acl_lookup_dict *dict) +{ + return dict->dict != NULL; +} + static void acl_lookup_dict_write_rights_id(string_t *dest, const struct acl_rights *right) { diff --git a/src/plugins/acl/acl-lookup-dict.h b/src/plugins/acl/acl-lookup-dict.h index 8fca8c0095..856aaf4576 100644 --- a/src/plugins/acl/acl-lookup-dict.h +++ b/src/plugins/acl/acl-lookup-dict.h @@ -4,6 +4,8 @@ struct acl_lookup_dict *acl_lookup_dict_init(struct mail_user *user); void acl_lookup_dict_deinit(struct acl_lookup_dict **dict); +bool acl_lookup_dict_is_enabled(struct acl_lookup_dict *dict); + int acl_lookup_dict_rebuild(struct acl_lookup_dict *dict); struct acl_lookup_dict_iter * diff --git a/src/plugins/acl/doveadm-acl.c b/src/plugins/acl/doveadm-acl.c new file mode 100644 index 0000000000..f9000d0b5e --- /dev/null +++ b/src/plugins/acl/doveadm-acl.c @@ -0,0 +1,421 @@ +/* Copyright (c) 2011 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "str.h" +#include "module-dir.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; +}; + +const char *doveadm_acl_plugin_version = DOVECOT_VERSION; + +void doveadm_acl_plugin_init(struct module *module); +void doveadm_acl_plugin_deinit(void); + +static int +cmd_acl_mailbox_open(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; + const char *storage_name; + + if (auser == NULL) { + i_error("ACL not enabled for %s", user->username); + return -1; + } + + storage_name = mailbox; + ns = mail_namespace_find(user->namespaces, &storage_name); + if (ns == NULL) { + i_error("No namespace found for mailbox %s", mailbox); + return -1; + } + box = mailbox_alloc(ns->list, storage_name, + MAILBOX_FLAG_READONLY | MAILBOX_FLAG_KEEP_RECENT | + MAILBOX_FLAG_IGNORE_ACLS); + if (mailbox_open(box) < 0) { + i_error("Can't open mailbox %s: %s", mailbox, + mail_storage_get_last_error(box->storage, NULL)); + mailbox_free(&box); + return -1; + } + *box_r = box; + return 0; +} + +static void cmd_acl_get_right(const struct acl_rights *rights) +{ + const char *id = ""; + string_t *str; + + switch (rights->id_type) { + case ACL_ID_ANYONE: + id = ACL_ID_NAME_ANYONE; + break; + case ACL_ID_AUTHENTICATED: + id = ACL_ID_NAME_AUTHENTICATED; + break; + case ACL_ID_OWNER: + id = ACL_ID_NAME_OWNER; + break; + case ACL_ID_USER: + id = t_strconcat(ACL_ID_NAME_USER_PREFIX, + rights->identifier, NULL); + break; + case ACL_ID_GROUP: + id = t_strconcat(ACL_ID_NAME_GROUP_PREFIX, + rights->identifier, NULL); + break; + case ACL_ID_GROUP_OVERRIDE: + id = t_strconcat(ACL_ID_NAME_GROUP_OVERRIDE_PREFIX, + rights->identifier, NULL); + break; + case ACL_ID_TYPE_COUNT: + i_unreached(); + } + doveadm_print(id); + + 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 void 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 ((ret = acl_object_list_next(iter, &rights)) > 0) T_BEGIN { + if (!ctx->get_match_me || + acl_backend_rights_match_me(backend, &rights)) + cmd_acl_get_right(&rights); + } T_END; + acl_object_list_deinit(&iter); + + if (ret < 0) + i_error("ACL iteration failed"); +} + +static void +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; + + if (cmd_acl_mailbox_open(user, mailbox, &box) < 0) + return; + + cmd_acl_get_mailbox(ctx, box); + mailbox_free(&box); +} + +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 void +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; + + if (cmd_acl_mailbox_open(user, mailbox, &box) < 0) + return; + + aclobj = acl_mailbox_get_aclobj(box); + if (acl_object_get_my_rights(aclobj, pool_datastack_create(), + &rights) < 0) + i_error("Failed to get rights"); + else + doveadm_print(t_strarray_join(rights, " ")); + mailbox_free(&box); +} + +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 void +cmd_acl_set_run(struct doveadm_mail_cmd_context *ctx, struct mail_user *user) +{ + const char *mailbox = ctx->args[0], *id = ctx->args[1]; + const char *const *rights = ctx->args + 2; + ARRAY_TYPE(const_string) dest_rights, dest_neg_rights, *dest; + struct mailbox *box; + struct acl_object *aclobj; + struct acl_rights_update update; + unsigned int i, j; + + if (cmd_acl_mailbox_open(user, mailbox, &box) < 0) + return; + + memset(&update, 0, sizeof(update)); + update.modify_mode = ACL_MODIFY_MODE_REPLACE; + update.neg_modify_mode = ACL_MODIFY_MODE_REPLACE; + + if (acl_identifier_parse(id, &update.rights) < 0) + i_fatal("Invalid ID: %s", id); + + t_array_init(&dest_rights, 8); + t_array_init(&dest_neg_rights, 8); + for (i = 0; rights[i] != NULL; i++) { + const char *right = rights[i]; + + if (right[0] != '-') + dest = &dest_rights; + else { + right++; + dest = &dest_neg_rights; + } + if (strcmp(right, "all") != 0) + array_append(dest, &right, 1); + else { + for (j = 0; all_mailbox_rights[j] != NULL; j++) + array_append(dest, &all_mailbox_rights[j], 1); + } + } + if (array_count(&dest_rights) > 0) { + (void)array_append_space(&dest_rights); + update.rights.rights = array_idx(&dest_rights, 0); + } + if (array_count(&dest_neg_rights) > 0) { + (void)array_append_space(&dest_neg_rights); + update.rights.neg_rights = array_idx(&dest_neg_rights, 0); + } + + aclobj = acl_mailbox_get_aclobj(box); + if (acl_object_update(aclobj, &update) < 0) + i_error("Failed to set ACL"); + mailbox_free(&box); +} + +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_set_alloc(void) +{ + struct doveadm_mail_cmd_context *ctx; + + ctx = doveadm_mail_cmd_alloc(struct doveadm_mail_cmd_context); + ctx->v.run = cmd_acl_set_run; + ctx->v.init = cmd_acl_set_init; + return ctx; +} + +static bool cmd_acl_debug_mailbox(struct mailbox *box) +{ + struct mail_namespace *ns = mailbox_get_namespace(box); + struct acl_user *auser = ACL_USER_CONTEXT(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; + int ret; + + /* 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 == NULL || 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 == NAMESPACE_PRIVATE) { + i_info("Mailbox in user's private namespace"); + return TRUE; + } + + iter = acl_backend_nonowner_lookups_iter_init(backend); + while ((ret = acl_backend_nonowner_lookups_iter_next(iter, &name)) > 0) { + if (strcmp(name, box->name) == 0) + break; + } + acl_backend_nonowner_lookups_iter_deinit(&iter); + if (ret < 0) + i_fatal("ACL non-owner iteration failed"); + if (ret == 0) { + i_error("Mailbox not found from dovecot-acl-list"); + return FALSE; + } + i_info("Mailbox found from dovecot-acl-list"); + + if (ns->type == NAMESPACE_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", + ns->owner->username); + return FALSE; + } + + i_info("User %s found from ACL shared dict", ns->owner->username); + return TRUE; +} + +static void +cmd_acl_debug_run(struct doveadm_mail_cmd_context *ctx, struct mail_user *user) +{ + const char *mailbox = ctx->args[0]; + struct mailbox *box; + + if (cmd_acl_mailbox_open(user, mailbox, &box) < 0) + return; + + if (cmd_acl_debug_mailbox(box)) + 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); +} + +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_mail_cmd acl_commands[] = { + { cmd_acl_get_alloc, "acl get", "[-m] " }, + { cmd_acl_rights_alloc, "acl rights", "" }, + { cmd_acl_set_alloc, "acl set", " " }, + { cmd_acl_debug_alloc, "acl debug", "" } +}; + +void doveadm_acl_plugin_init(struct module *module ATTR_UNUSED) +{ + unsigned int i; + + for (i = 0; i < N_ELEMENTS(acl_commands); i++) + doveadm_mail_register_cmd(&acl_commands[i]); +} + +void doveadm_acl_plugin_deinit(void) +{ +}