diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 09:51:24 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 09:51:24 +0000 |
commit | f7548d6d28c313cf80e6f3ef89aed16a19815df1 (patch) | |
tree | a3f6f2a3f247293bee59ecd28e8cd8ceb6ca064a /src/plugins/mail-crypt/mail-crypt-acl-plugin.c | |
parent | Initial commit. (diff) | |
download | dovecot-f7548d6d28c313cf80e6f3ef89aed16a19815df1.tar.xz dovecot-f7548d6d28c313cf80e6f3ef89aed16a19815df1.zip |
Adding upstream version 1:2.3.19.1+dfsg1.upstream/1%2.3.19.1+dfsg1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/plugins/mail-crypt/mail-crypt-acl-plugin.c')
-rw-r--r-- | src/plugins/mail-crypt/mail-crypt-acl-plugin.c | 431 |
1 files changed, 431 insertions, 0 deletions
diff --git a/src/plugins/mail-crypt/mail-crypt-acl-plugin.c b/src/plugins/mail-crypt/mail-crypt-acl-plugin.c new file mode 100644 index 0000000..71ab1e7 --- /dev/null +++ b/src/plugins/mail-crypt/mail-crypt-acl-plugin.c @@ -0,0 +1,431 @@ +/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "ioloop-private.h" +#include "str.h" +#include "sha2.h" +#include "module-dir.h" +#include "var-expand.h" +#include "hex-binary.h" +#include "mail-namespace.h" +#include "mail-storage-hooks.h" +#include "mail-storage-service.h" +#include "acl-plugin.h" +#include "acl-api-private.h" +#include "mail-crypt-common.h" +#include "mail-crypt-key.h" +#include "mail-crypt-plugin.h" + +#define MAIL_CRYPT_ACL_LIST_CONTEXT(obj) \ + MODULE_CONTEXT_REQUIRE(obj, mail_crypt_acl_mailbox_list_module) + +struct mail_crypt_acl_mailbox_list { + union mailbox_list_module_context module_ctx; + struct acl_backend_vfuncs acl_vprev; +}; + +static MODULE_CONTEXT_DEFINE_INIT(mail_crypt_acl_mailbox_list_module, + &mailbox_list_module_register); + +void mail_crypt_acl_plugin_init(struct module *module); +void mail_crypt_acl_plugin_deinit(void); + +static int +mail_crypt_acl_has_user_read_right(struct acl_object *aclobj, + const char *username, + const char **error_r) +{ + struct acl_object_list_iter *iter; + struct acl_rights rights; + int ret = 0; + + iter = acl_object_list_init(aclobj); + while (acl_object_list_next(iter, &rights)) { + if (rights.id_type == ACL_ID_USER && + strcmp(rights.identifier, username) == 0) { + ret = str_array_find(rights.rights, MAIL_ACL_READ) ? 1 : 0; + break; + } + } + if (acl_object_list_deinit(&iter) < 0) { + *error_r = "Failed to iterate ACL objects"; + return -1; + } + + return ret; +} + +static int mail_crypt_acl_has_nonuser_read_right(struct acl_object *aclobj, + const char **error_r) +{ + struct acl_object_list_iter *iter; + struct acl_rights rights; + int ret = 0; + + iter = acl_object_list_init(aclobj); + while (acl_object_list_next(iter, &rights)) { + if (rights.id_type != ACL_ID_USER && + rights.id_type != ACL_ID_OWNER && + rights.rights != NULL && + str_array_find(rights.rights, MAIL_ACL_READ)) { + ret = 1; + break; + } + } + if (acl_object_list_deinit(&iter) < 0) { + *error_r = "Failed to iterate ACL objects"; + return -1; + } + return ret; +} + +static int +mail_crypt_acl_unset_private_keys(struct mailbox *src_box, + const char *dest_user, + enum mail_attribute_type type, + const char **error_r) +{ + ARRAY_TYPE(const_string) digests; + const char *error; + int ret = 1; + + if (mailbox_open(src_box) < 0) { + *error_r = t_strdup_printf("mail-crypt-acl-plugin: " + "mailbox_open(%s) failed: %s", + mailbox_get_vname(src_box), + mailbox_get_last_internal_error(src_box, NULL)); + return -1; + } + + t_array_init(&digests, 4); + if (mail_crypt_box_get_pvt_digests(src_box, pool_datastack_create(), + type, &digests, &error) < 0) { + *error_r = t_strdup_printf("mail-crypt-acl-plugin: " + "Failed to lookup public key digests: %s", + error); + mailbox_free(&src_box); + return -1; + } + + struct mailbox_transaction_context *t; + t = mailbox_transaction_begin(src_box, 0, __func__); + + const char *hash; + array_foreach_elem(&digests, hash) { + const char *ptr; + /* if the id contains username part, skip to key public id */ + if ((ptr = strchr(hash, '/')) != NULL) + ptr++; + else + ptr = hash; + if ((ret = mail_crypt_box_unset_shared_key(t, ptr, dest_user, + error_r)) < 0) { + ret = -1; + break; + } + } + + if (ret < 0) { + mailbox_transaction_rollback(&t); + } else if (mailbox_transaction_commit(&t) < 0) { + *error_r = t_strdup_printf("mail-crypt-acl-plugin: " + "mailbox_transaction_commit(%s) failed: %s", + mailbox_get_vname(src_box), + mailbox_get_last_internal_error(src_box, NULL)); + return -1; + } + return 0; +} + +static int +mail_crypt_acl_user_create(struct mail_user *user, const char *dest_username, + struct mail_user **dest_user_r, + struct mail_storage_service_user **dest_service_user_r, + const char **error_r) +{ + const struct mail_storage_service_input *old_input; + struct mail_storage_service_input input; + struct mail_storage_service_ctx *service_ctx; + struct ioloop_context *cur_ioloop_ctx; + + int ret; + + i_assert(user->_service_user != NULL); + service_ctx = mail_storage_service_user_get_service_ctx(user->_service_user); + old_input = mail_storage_service_user_get_input(user->_service_user); + + if ((cur_ioloop_ctx = io_loop_get_current_context(current_ioloop)) != NULL) + io_loop_context_deactivate(cur_ioloop_ctx); + + i_zero(&input); + input.module = old_input->module; + input.service = old_input->service; + input.username = dest_username; + input.session_id_prefix = user->session_id; + input.flags_override_add = MAIL_STORAGE_SERVICE_FLAG_NO_PLUGINS | + MAIL_STORAGE_SERVICE_FLAG_NO_LOG_INIT; + input.flags_override_remove = MAIL_STORAGE_SERVICE_FLAG_NO_NAMESPACES; + + ret = mail_storage_service_lookup_next(service_ctx, &input, + dest_service_user_r, + dest_user_r, error_r); + + return ret; +} + +static int +mail_crypt_acl_update_private_key(struct mailbox *src_box, + struct mail_user *dest_user, bool set, + bool disallow_insecure, + const char **error_r) +{ + struct dcrypt_public_key *key = NULL; + struct dcrypt_private_key *priv_key; + int ret = 0; + + if (!set) { + return mail_crypt_acl_unset_private_keys(src_box, + dest_user->username, + MAIL_ATTRIBUTE_TYPE_SHARED, + error_r); + } + + if (dest_user != NULL) { + /* get public key from target user */ + if ((ret = mail_crypt_user_get_public_key(dest_user, + &key, error_r)) <= 0) { + if (ret == 0 && disallow_insecure) { + *error_r = t_strdup_printf("User %s has no active public key", + dest_user->username); + return -1; + } else if (ret < 0) { + return -1; + } else if (ret == 0) { + /* perform insecure sharing */ + dest_user = NULL; + key = NULL; + } + } + } + + ARRAY_TYPE(dcrypt_private_key) keys; + t_array_init(&keys, 8); + + struct mailbox_transaction_context *t = + mailbox_transaction_begin(src_box, 0, __func__); + + /* get private keys from box */ + if (mail_crypt_box_get_private_keys(src_box, &keys, error_r) < 0 || + mail_crypt_box_share_private_keys(t, key, + dest_user == NULL ? NULL : + dest_user->username, + &keys, error_r) < 0) + ret = -1; + if (key != NULL) + dcrypt_key_unref_public(&key); + + if (ret >= 0) { + array_foreach_elem(&keys, priv_key) + dcrypt_key_unref_private(&priv_key); + } + + if (mailbox_transaction_commit(&t) < 0) { + *error_r = mailbox_get_last_internal_error(src_box, NULL); + ret = -1; + } + + return ret; +} + +static int mail_crypt_acl_object_update(struct acl_object *aclobj, + const struct acl_rights_update *update) +{ + const char *error; + struct mail_crypt_acl_mailbox_list *mlist = + MAIL_CRYPT_ACL_LIST_CONTEXT(aclobj->backend->list); + const char *username; + struct mail_user *dest_user; + struct mail_storage_service_user *dest_service_user; + struct ioloop_context *cur_ioloop_ctx; + bool have_rights; + int ret = 0; + + if (mlist->acl_vprev.object_update(aclobj, update) < 0) + return -1; + + bool disallow_insecure = + mail_crypt_acl_secure_sharing_enabled(aclobj->backend->list->ns->user); + + const char *box_name = mailbox_list_get_vname(aclobj->backend->list, + aclobj->name); + struct mailbox *box = mailbox_alloc(aclobj->backend->list, box_name, 0); + + switch (update->rights.id_type) { + case ACL_ID_USER: + /* setting rights for specific user: we can encrypt the + mailbox key for the user. */ + username = update->rights.identifier; + ret = mail_crypt_acl_has_user_read_right(aclobj, username, &error); + + if (ret < 0) { + i_error("mail-crypt-acl-plugin: " + "mail_crypt_acl_has_user_read_right(%s) failed: %s", + username, + error); + break; + } + + have_rights = ret > 0; + + ret = mail_crypt_acl_user_create(aclobj->backend->list->ns->user, + username, &dest_user, + &dest_service_user, &error); + + /* to make sure we get correct logging context */ + if (ret > 0) + mail_storage_service_io_deactivate_user(dest_service_user); + mail_storage_service_io_activate_user( + aclobj->backend->list->ns->user->_service_user + ); + + if (ret <= 0) { + i_error("mail-crypt-acl-plugin: " + "Cannot initialize destination user %s: %s", + username, error); + break; + } else { + i_assert(dest_user != NULL); + if ((ret = mailbox_open(box)) < 0) { + i_error("mail-crypt-acl-plugin: " + "mailbox_open(%s) failed: %s", + mailbox_get_vname(box), + mailbox_get_last_internal_error(box, NULL)); + } else if ((ret = mail_crypt_acl_update_private_key(box, dest_user, + have_rights, + disallow_insecure, + &error)) < 0) { + i_error("mail-crypt-acl-plugin: " + "acl_update_private_key(%s, %s) failed: %s", + mailbox_get_vname(box), + username, + error); + } + } + + /* logging context swap again */ + mail_storage_service_io_deactivate_user( + aclobj->backend->list->ns->user->_service_user + ); + mail_storage_service_io_activate_user(dest_service_user); + + mail_user_deinit(&dest_user); + mail_storage_service_user_unref(&dest_service_user); + + if ((cur_ioloop_ctx = io_loop_get_current_context(current_ioloop)) != NULL) + io_loop_context_deactivate(cur_ioloop_ctx); + mail_storage_service_io_activate_user( + aclobj->backend->list->ns->user->_service_user + ); + break; + case ACL_ID_OWNER: + /* we should be the one doing this? ignore */ + break; + case ACL_ID_ANYONE: + case ACL_ID_AUTHENTICATED: + case ACL_ID_GROUP: + case ACL_ID_GROUP_OVERRIDE: + if (disallow_insecure) { + i_error("mail-crypt-acl-plugin: " + "Secure key sharing is enabled -" + "Remove or set plugin { %s = no }", + MAIL_CRYPT_ACL_SECURE_SHARE_SETTING); + ret = -1; + break; + } + /* the mailbox key needs to be stored unencrypted. for groups + we could in theory use per-group encrypted keys, which the + users belonging to the group would able to decrypt with + their private key, but that becomes quite complicated. */ + if ((ret = mail_crypt_acl_has_nonuser_read_right(aclobj, &error)) < 0) { + i_error("mail-crypt-acl-plugin: %s", error); + } else if ((ret = mailbox_open(box)) < 0) { + i_error("mail-crypt-acl-plugin: " + "mailbox_open(%s) failed: %s", + mailbox_get_vname(box), + mailbox_get_last_internal_error(box, NULL)); + } else if ((ret = mail_crypt_acl_update_private_key(box, + NULL, + TRUE, + disallow_insecure, + &error)) < 0) { + i_error("mail-crypt-acl-plugin: " + "acl_update_private_key(%s, %s) failed: %s", + mailbox_get_vname(box), + "", + error); + } + break; + case ACL_ID_TYPE_COUNT: + i_unreached(); + } + + mailbox_free(&box); + return ret; +} + +static void +mail_crypt_acl_mail_namespace_storage_added(struct mail_namespace *ns) +{ + struct acl_mailbox_list *alist = ACL_LIST_CONTEXT(ns->list); + struct mail_crypt_acl_mailbox_list *mlist = + MAIL_CRYPT_ACL_LIST_CONTEXT(ns->list); + struct acl_backend *backend; + + if (alist == NULL) + return; + + /* FIXME: this method works only if there's a single plugin doing it. + if there are ever multiple plugins hooking into ACL commands the + ACL core code would need some changing to make it work correctly. */ + backend = alist->rights.backend; + mlist->acl_vprev = backend->v; + backend->v.object_update = mail_crypt_acl_object_update; +} + +static void mail_crypt_acl_mailbox_list_deinit(struct mailbox_list *list) +{ + struct mail_crypt_acl_mailbox_list *mlist = + MAIL_CRYPT_ACL_LIST_CONTEXT(list); + + mlist->module_ctx.super.deinit(list); +} + +static void mail_crypt_acl_mailbox_list_created(struct mailbox_list *list) +{ + struct mailbox_list_vfuncs *v = list->vlast; + struct mail_crypt_acl_mailbox_list *mlist; + + mlist = p_new(list->pool, struct mail_crypt_acl_mailbox_list, 1); + mlist->module_ctx.super = *v; + list->vlast = &mlist->module_ctx.super; + v->deinit = mail_crypt_acl_mailbox_list_deinit; + + MODULE_CONTEXT_SET(list, mail_crypt_acl_mailbox_list_module, mlist); +} + +static struct mail_storage_hooks mail_crypt_acl_mail_storage_hooks = { + .mailbox_list_created = mail_crypt_acl_mailbox_list_created, + .mail_namespace_storage_added = mail_crypt_acl_mail_namespace_storage_added +}; + +void mail_crypt_acl_plugin_init(struct module *module) +{ + mail_storage_hooks_add(module, &mail_crypt_acl_mail_storage_hooks); +} + +void mail_crypt_acl_plugin_deinit(void) +{ + mail_storage_hooks_remove(&mail_crypt_acl_mail_storage_hooks); +} + +const char *mail_crypt_acl_plugin_dependencies[] = { "acl", NULL }; |