summaryrefslogtreecommitdiffstats
path: root/src/plugins/mail-crypt/mail-crypt-acl-plugin.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/mail-crypt/mail-crypt-acl-plugin.c')
-rw-r--r--src/plugins/mail-crypt/mail-crypt-acl-plugin.c431
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 };