summaryrefslogtreecommitdiffstats
path: root/src/plugins/mail-crypt/doveadm-mail-crypt.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 09:51:24 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 09:51:24 +0000
commitf7548d6d28c313cf80e6f3ef89aed16a19815df1 (patch)
treea3f6f2a3f247293bee59ecd28e8cd8ceb6ca064a /src/plugins/mail-crypt/doveadm-mail-crypt.c
parentInitial commit. (diff)
downloaddovecot-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/doveadm-mail-crypt.c')
-rw-r--r--src/plugins/mail-crypt/doveadm-mail-crypt.c1048
1 files changed, 1048 insertions, 0 deletions
diff --git a/src/plugins/mail-crypt/doveadm-mail-crypt.c b/src/plugins/mail-crypt/doveadm-mail-crypt.c
new file mode 100644
index 0000000..a4322ed
--- /dev/null
+++ b/src/plugins/mail-crypt/doveadm-mail-crypt.c
@@ -0,0 +1,1048 @@
+/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "askpass.h"
+#include "doveadm-mail.h"
+#include "getopt.h"
+#include "array.h"
+#include "str.h"
+#include "buffer.h"
+#include "ioloop.h"
+#include "ioloop-private.h"
+#include "mail-namespace.h"
+#include "mail-storage.h"
+#include "mail-storage-settings.h"
+#include "mailbox-attribute.h"
+#include "mail-crypt-common.h"
+#include "mail-crypt-key.h"
+#include "mailbox-list-iter.h"
+#include "doveadm-print.h"
+#include "hex-binary.h"
+
+#define DOVEADM_MCP_SUCCESS "\xE2\x9C\x93" /* emits a utf-8 CHECK MARK (U+2713) */
+#define DOVEADM_MCP_FAIL "x"
+#define DOVEADM_MCP_USERKEY "<userkey>"
+
+struct generated_key {
+ const char *name;
+ const char *id;
+ const char *error;
+ struct mailbox *box;
+ bool success:1;
+ bool active:1;
+};
+
+ARRAY_DEFINE_TYPE(generated_keys, struct generated_key);
+
+struct mcp_cmd_context {
+ struct doveadm_mail_cmd_context ctx;
+
+ const char *old_password;
+ const char *new_password;
+
+ unsigned int matched_keys;
+
+ bool userkey_only:1;
+ bool recrypt_box_keys:1;
+ bool force:1;
+ bool ask_old_password:1;
+ bool ask_new_password:1;
+ bool clear_password:1;
+};
+
+struct mcp_key_iter_ctx {
+ pool_t pool;
+ ARRAY_TYPE(generated_keys) keys;
+};
+
+void doveadm_mail_crypt_plugin_init(struct module *mod ATTR_UNUSED);
+void doveadm_mail_crypt_plugin_deinit(void);
+
+static int
+mcp_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;
+
+ ret = mail_storage_service_lookup_next(service_ctx, &input,
+ dest_service_user_r,
+ dest_user_r, error_r);
+
+ if (ret == 0)
+ *error_r = "User not found";
+
+ return ret;
+}
+
+static int
+mcp_update_shared_key(struct mailbox_transaction_context *t,
+ struct mail_user *user, const char *target_uid,
+ struct dcrypt_private_key *key, const char **error_r)
+{
+ const char *error;
+ struct mail_user *dest_user;
+ struct mail_storage_service_user *dest_service_user;
+ struct ioloop_context *cur_ioloop_ctx;
+ struct dcrypt_public_key *pkey;
+ const char *dest_username;
+ int ret = 0;
+
+ bool disallow_insecure = mail_crypt_acl_secure_sharing_enabled(user);
+
+ ret = mcp_user_create(user, target_uid, &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(user->_service_user);
+
+ if (ret <= 0) {
+ i_error("Cannot initialize destination user %s: %s",
+ target_uid, error);
+ return ret;
+ } else {
+ i_assert(dest_user != NULL);
+ dest_username = dest_user->username;
+
+ /* get public key from target user */
+ if ((ret = mail_crypt_user_get_public_key(dest_user,
+ &pkey, error_r)) <= 0) {
+ if (ret == 0 && disallow_insecure) {
+ *error_r = t_strdup_printf("User %s has no active public key",
+ dest_user->username);
+ ret = -1;
+ } else if (ret == 0) {
+ /* perform insecure sharing */
+ dest_username = NULL;
+ pkey = NULL;
+ ret = 1;
+ }
+ }
+
+ if (ret == 1) {
+ ARRAY_TYPE(dcrypt_private_key) keys;
+ t_array_init(&keys, 1);
+ array_push_back(&keys, &key);
+ ret = mail_crypt_box_share_private_keys(t, pkey,
+ dest_username,
+ &keys, error_r);
+ }
+
+ }
+
+ /* logging context swap again */
+ mail_storage_service_io_deactivate_user(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(user->_service_user);
+
+ return ret;
+}
+
+static int mcp_update_shared_keys(struct doveadm_mail_cmd_context *ctx,
+ struct mailbox *box, struct mail_user *user,
+ const char *pubid, struct dcrypt_private_key *key)
+{
+ const char *error;
+ int ret;
+
+ ARRAY_TYPE(const_string) ids;
+ t_array_init(&ids, 8);
+
+ /* figure out who needs the key */
+ if (mail_crypt_box_get_pvt_digests(box, pool_datastack_create(),
+ MAIL_ATTRIBUTE_TYPE_SHARED,
+ &ids, &error) < 0) {
+ i_error("mail_crypt_box_get_pvt_digests(%s, /shared) failed: %s",
+ mailbox_get_vname(box),
+ error);
+ return -1;
+ }
+
+ const char *id;
+ bool found = FALSE;
+ string_t *uid = t_str_new(64);
+
+ struct mailbox_transaction_context *t =
+ mailbox_transaction_begin(box, ctx->transaction_flags, __func__);
+
+ ret = 0;
+
+ /* then perform sharing */
+ array_foreach_elem(&ids, id) {
+ if (strchr(id, '/') != NULL) {
+ str_truncate(uid, 0);
+ const char *hexuid = t_strcut(id, '/');
+ hex_to_binary(hexuid, uid);
+ if (mcp_update_shared_key(t, user, str_c(uid), key,
+ &error) < 0) {
+ i_error("mcp_update_shared_key(%s, %s) failed: %s",
+ mailbox_get_vname(box),
+ str_c(uid),
+ error);
+ ret = -1;
+ break;
+ }
+ } else if (!found) {
+ found = TRUE;
+ if (mail_crypt_box_set_shared_key(t, pubid, key,
+ NULL, NULL,
+ &error) < 0) {
+ i_error("mail_crypt_box_set_shared_key(%s) failed: %s",
+ mailbox_get_vname(box),
+ error);
+ ret = -1;
+ break;
+ }
+ }
+ }
+
+ if (ret < 0) {
+ mailbox_transaction_rollback(&t);
+ } else if (mailbox_transaction_commit(&t) < 0) {
+ i_error("mailbox_transaction_commit(%s) failed: %s",
+ mailbox_get_vname(box),
+ error);
+ ret = -1;
+ }
+
+ return ret;
+}
+
+static int mcp_keypair_generate(struct mcp_cmd_context *ctx,
+ struct dcrypt_public_key *user_key,
+ struct mailbox *box, struct dcrypt_keypair *pair_r,
+ const char **pubid_r, const char **error_r)
+{
+ struct dcrypt_keypair pair = {NULL, NULL};
+
+ int ret;
+
+ if ((ret = mail_crypt_box_get_public_key(box, &pair.pub, error_r)) < 0) {
+ ret = -1;
+ } else if (ret == 1 && !ctx->force) {
+ i_info("Folder key exists. Use -f to generate a new one");
+ buffer_t *key_id = t_str_new(MAIL_CRYPT_HASH_BUF_SIZE);
+ const char *error;
+ if (!dcrypt_key_id_public(pair.pub,
+ MAIL_CRYPT_KEY_ID_ALGORITHM,
+ key_id, &error)) {
+ i_error("dcrypt_key_id_public() failed: %s",
+ error);
+ return -1;
+ }
+ *pubid_r = p_strdup(ctx->ctx.pool, binary_to_hex(key_id->data,
+ key_id->used));
+ *pair_r = pair;
+ return 1;
+ } else if (ret == 1 && ctx->recrypt_box_keys) {
+ /* do nothing, because force isn't being used *OR*
+ we are recrypting box keys and force refers to
+ user keypair.
+
+ FIXME: this could be less confusing altogether */
+ ret = 0;
+ } else {
+ if (mail_crypt_box_generate_keypair(box, &pair, user_key,
+ pubid_r, error_r) < 0) {
+ ret = -1;
+ } else {
+ *pubid_r = p_strdup(ctx->ctx.pool, *pubid_r);
+ *pair_r = pair;
+ return 1;
+ }
+ }
+
+ if (pair.pub != NULL)
+ dcrypt_key_unref_public(&pair.pub);
+ if (pair.priv != NULL)
+ dcrypt_key_unref_private(&pair.priv);
+
+ return ret;
+}
+
+static int mcp_keypair_generate_run(struct doveadm_mail_cmd_context *_ctx,
+ struct mail_user *user,
+ ARRAY_TYPE(generated_keys) *result)
+{
+ const char *error;
+ int ret;
+ struct dcrypt_public_key *user_key;
+ struct mcp_cmd_context *ctx =
+ (struct mcp_cmd_context *)_ctx;
+ const char *pubid;
+ bool user_key_generated = FALSE;
+ struct generated_key *res;
+
+ if ((ret = mail_crypt_user_get_public_key(user, &user_key,
+ &error)) <= 0) {
+ struct dcrypt_keypair pair;
+ if (ret < 0) {
+ i_error("mail_crypt_user_get_public_key(%s) failed: %s",
+ user->username,
+ error);
+ } else if (mail_crypt_user_generate_keypair(user, &pair,
+ &pubid, &error) < 0) {
+ ret = -1;
+ i_error("mail_crypt_user_generate_keypair(%s) failed: %s",
+ user->username,
+ error);
+ res = array_append_space(result);
+ res->name = "";
+ res->error = p_strdup(_ctx->pool, error);
+ res->success = FALSE;
+ } else {
+ res = array_append_space(result);
+ res->name = DOVEADM_MCP_USERKEY;
+ res->id = p_strdup(_ctx->pool, pubid);
+ res->success = TRUE;
+ /* don't do it again later on */
+ user_key_generated = TRUE;
+ ret = 1;
+ user_key = pair.pub;
+ dcrypt_key_unref_private(&pair.priv);
+ }
+ if (ret < 0) return ret;
+ ctx->matched_keys++;
+ }
+ if (ret == 1 && ctx->userkey_only && !user_key_generated) {
+ if (!ctx->force) {
+ i_info("userkey exists. Use -f to generate a new one");
+ buffer_t *key_id = t_str_new(MAIL_CRYPT_HASH_BUF_SIZE);
+ if (!dcrypt_key_id_public(user_key,
+ MAIL_CRYPT_KEY_ID_ALGORITHM,
+ key_id, &error)) {
+ i_error("dcrypt_key_id_public() failed: %s",
+ error);
+ dcrypt_key_unref_public(&user_key);
+ return -1;
+ }
+ const char *hash = binary_to_hex(key_id->data,
+ key_id->used);
+ res = array_append_space(result);
+ res->name = DOVEADM_MCP_USERKEY;
+ res->id = p_strdup(_ctx->pool, hash);
+ res->success = TRUE;
+ ctx->matched_keys++;
+ dcrypt_key_unref_public(&user_key);
+ return 1;
+ }
+ struct dcrypt_keypair pair;
+ dcrypt_key_unref_public(&user_key);
+ /* regen user key */
+ res = array_append_space(result);
+ res->name = DOVEADM_MCP_USERKEY;
+ if (mail_crypt_user_generate_keypair(user, &pair, &pubid,
+ &error) < 0) {
+ res->success = FALSE;
+ res->error = p_strdup(_ctx->pool, error);
+ return -1;
+ }
+ res->success = TRUE;
+ res->id = p_strdup(_ctx->pool, pubid);
+ user_key = pair.pub;
+ dcrypt_key_unref_private(&pair.priv);
+ ctx->matched_keys++;
+ }
+
+ if (ctx->userkey_only) {
+ dcrypt_key_unref_public(&user_key);
+ return 0;
+ }
+
+ const char *const *patterns = (const char *const[]){ "*", NULL };
+
+ /* only re-encrypt all folder keys if wanted */
+ if (!ctx->recrypt_box_keys) {
+ patterns = ctx->ctx.args;
+ }
+
+ const struct mailbox_info *info;
+ struct mailbox_list_iterate_context *iter =
+ mailbox_list_iter_init_namespaces(user->namespaces,
+ patterns,
+ MAIL_NAMESPACE_TYPE_PRIVATE,
+ MAILBOX_LIST_ITER_SKIP_ALIASES |
+ MAILBOX_LIST_ITER_NO_AUTO_BOXES |
+ MAILBOX_LIST_ITER_RETURN_NO_FLAGS);
+ while((info = mailbox_list_iter_next(iter)) != NULL) {
+ if ((info->flags & MAILBOX_NOSELECT) != 0 ||
+ (info->flags & MAILBOX_NONEXISTENT) != 0) continue;
+ struct dcrypt_keypair pair;
+
+ struct mailbox *box =
+ mailbox_alloc(info->ns->list,
+ info->vname, 0);
+ if (mailbox_open(box) < 0) {
+ res = array_append_space(result);
+ res->name = p_strdup(_ctx->pool, info->vname);
+ res->success = FALSE;
+ res->error = p_strdup(_ctx->pool,
+ mailbox_get_last_internal_error(box, NULL));
+ } else if ((ret = mcp_keypair_generate(ctx, user_key, box,
+ &pair, &pubid,
+ &error)) < 0) {
+ res = array_append_space(result);
+ res->name = p_strdup(_ctx->pool, info->vname);
+ res->success = FALSE;
+ res->error = p_strdup(_ctx->pool, error);
+ } else if (ret == 0) {
+ /* nothing happened because key already existed and
+ force wasn't used, skip */
+ } else if (ret > 0) {
+ res = array_append_space(result);
+ res->name = p_strdup(_ctx->pool, info->vname);
+ res->success = TRUE;
+ res->id = pubid;
+ T_BEGIN {
+ mcp_update_shared_keys(&ctx->ctx, box, user,
+ pubid, pair.priv);
+ } T_END;
+ if (pair.pub != NULL)
+ dcrypt_key_unref_public(&pair.pub);
+ if (pair.priv != NULL)
+ dcrypt_key_unref_private(&pair.priv);
+ ctx->matched_keys++;
+ }
+ mailbox_free(&box);
+ }
+
+ (void)mailbox_list_iter_deinit(&iter);
+
+ dcrypt_key_unref_public(&user_key);
+ return 0;
+}
+
+static int cmd_mcp_keypair_generate_run(struct doveadm_mail_cmd_context *_ctx,
+ struct mail_user *user)
+{
+ struct mcp_cmd_context *ctx =
+ (struct mcp_cmd_context *)_ctx;
+
+ int ret = 0;
+
+ ARRAY_TYPE(generated_keys) result;
+ p_array_init(&result, _ctx->pool, 8);
+
+ if (mcp_keypair_generate_run(_ctx, user, &result) < 0)
+ _ctx->exit_code = EX_DATAERR;
+
+ doveadm_print_init(DOVEADM_PRINT_TYPE_TABLE);
+ doveadm_print_header("success", " ", 0);
+ doveadm_print_header("box", "Folder", 0);
+ doveadm_print_header("pubid", "Public ID", 0);
+
+ const struct generated_key *res;
+
+ array_foreach(&result, res) {
+ if (res->success)
+ doveadm_print(DOVEADM_MCP_SUCCESS);
+ else {
+ _ctx->exit_code = EX_DATAERR;
+ ret = -1;
+ doveadm_print(DOVEADM_MCP_FAIL);
+ }
+ doveadm_print(res->name);
+ if (!res->success)
+ doveadm_print(t_strdup_printf("ERROR: %s", res->error));
+ else
+ doveadm_print(res->id);
+ }
+
+ if (ctx->matched_keys == 0)
+ i_warning("mailbox cryptokey generate: Nothing was matched. "
+ "Use -U or specify mask?");
+ return ret;
+}
+
+static void mcp_key_list(struct mcp_cmd_context *ctx,
+ struct mail_user *user,
+ void(*callback)(const struct generated_key *, void *),
+ void *context)
+{
+ const char *error;
+
+ /* we need to use the mailbox attribute API here, as we
+ are not necessarily able to decrypt any of these keys
+ */
+
+ ARRAY_TYPE(const_string) ids;
+ t_array_init(&ids, 8);
+
+ if (ctx->userkey_only) {
+ struct mailbox_attribute_iter *iter;
+ struct mail_namespace *ns =
+ mail_namespace_find_inbox(user->namespaces);
+ struct mailbox *box =
+ mailbox_alloc(ns->list, "INBOX", MAILBOX_FLAG_READONLY);
+ struct mail_attribute_value value;
+ i_zero(&value);
+
+ if (mailbox_attribute_get(box, MAIL_ATTRIBUTE_TYPE_SHARED,
+ USER_CRYPT_PREFIX ACTIVE_KEY_NAME,
+ &value) < 0) {
+ i_error("mailbox_get_attribute(%s, %s) failed: %s",
+ mailbox_get_vname(box),
+ USER_CRYPT_PREFIX ACTIVE_KEY_NAME,
+ mailbox_get_last_internal_error(box, NULL));
+ }
+
+ iter = mailbox_attribute_iter_init(box,
+ MAIL_ATTRIBUTE_TYPE_PRIVATE,
+ USER_CRYPT_PREFIX
+ PRIVKEYS_PREFIX);
+ const char *key_id;
+ if (value.value == NULL)
+ value.value = "<NO ACTIVE KEY>";
+ while ((key_id = mailbox_attribute_iter_next(iter)) != NULL) {
+ struct generated_key key;
+ key.id = key_id;
+ key.active = strcmp(value.value, key_id) == 0;
+ key.name = "";
+ key.box = box;
+ callback(&key, context);
+ ctx->matched_keys++;
+ }
+ if (mailbox_attribute_iter_deinit(&iter) < 0)
+ i_error("mailbox_attribute_iter_deinit(%s) failed: %s",
+ mailbox_get_vname(box),
+ mailbox_get_last_internal_error(box, NULL));
+ mailbox_free(&box);
+ return;
+ }
+
+ const struct mailbox_info *info;
+ struct mailbox_list_iterate_context *iter =
+ mailbox_list_iter_init_namespaces(user->namespaces,
+ ctx->ctx.args,
+ MAIL_NAMESPACE_TYPE_PRIVATE,
+ MAILBOX_LIST_ITER_SKIP_ALIASES |
+ MAILBOX_LIST_ITER_NO_AUTO_BOXES |
+ MAILBOX_LIST_ITER_RETURN_NO_FLAGS);
+
+ while((info = mailbox_list_iter_next(iter)) != NULL) {
+ if ((info->flags & MAILBOX_NOSELECT) != 0 ||
+ (info->flags & MAILBOX_NONEXISTENT) != 0) continue;
+
+ struct mailbox *box =
+ mailbox_alloc(info->ns->list,
+ info->vname, MAILBOX_FLAG_READONLY);
+ struct mail_attribute_value value;
+ i_zero(&value);
+ array_clear(&ids);
+
+ /* get active ID */
+ if (mailbox_attribute_get(box, MAIL_ATTRIBUTE_TYPE_SHARED,
+ BOX_CRYPT_PREFIX ACTIVE_KEY_NAME,
+ &value) < 0) {
+ i_error("mailbox_get_attribute(%s, %s) failed: %s",
+ mailbox_get_vname(box),
+ BOX_CRYPT_PREFIX ACTIVE_KEY_NAME,
+ mailbox_get_last_internal_error(box, NULL));
+ } else if (mail_crypt_box_get_pvt_digests(box, pool_datastack_create(),
+ MAIL_ATTRIBUTE_TYPE_PRIVATE,
+ &ids, &error) < 0) {
+ i_error("mail_crypt_box_get_pvt_digests(%s) failed: %s",
+ mailbox_get_vname(box),
+ error);
+ } else {
+ const char *id;
+ const char *boxname = mailbox_get_vname(box);
+ if (value.value == NULL)
+ value.value = "<NO ACTIVE KEY>";
+ array_foreach_elem(&ids, id) {
+ struct generated_key key;
+ key.name = boxname;
+ key.id = id;
+ if (value.value != NULL)
+ key.active = strcmp(id, value.value) == 0;
+ else
+ key.active = FALSE;
+ key.box = box;
+ callback(&key, context);
+ ctx->matched_keys++;
+ }
+ }
+ mailbox_free(&box);
+ }
+
+ (void)mailbox_list_iter_deinit(&iter);
+}
+
+static void cmd_mcp_key_list_cb(const struct generated_key *_key, void *context)
+{
+ struct mcp_key_iter_ctx *ctx = context;
+ struct generated_key *key = array_append_space(&ctx->keys);
+ key->name = p_strdup(ctx->pool, _key->name);
+ key->id = p_strdup(ctx->pool, _key->id);
+ key->active = _key->active;
+}
+
+static int cmd_mcp_key_list_run(struct doveadm_mail_cmd_context *_ctx,
+ struct mail_user *user)
+{
+ struct mcp_cmd_context *ctx =
+ (struct mcp_cmd_context *)_ctx;
+ struct mcp_key_iter_ctx iter_ctx;
+ i_zero(&iter_ctx);
+ iter_ctx.pool = _ctx->pool;
+ p_array_init(&iter_ctx.keys, _ctx->pool, 8);
+
+ mcp_key_list(ctx, user, cmd_mcp_key_list_cb, &iter_ctx);
+
+ doveadm_print_init(DOVEADM_PRINT_TYPE_TABLE);
+ doveadm_print_header("box", "Folder", 0);
+ doveadm_print_header("active", "Active", 0);
+ doveadm_print_header("pubid", "Public ID", 0);
+
+ const struct generated_key *key;
+ array_foreach(&iter_ctx.keys, key) {
+ doveadm_print(key->name);
+ doveadm_print(key->active ? "yes" : "no");
+ doveadm_print(key->id);
+ }
+
+ if (ctx->matched_keys == 0)
+ i_warning("mailbox cryptokey list: Nothing was matched. "
+ "Use -U or specify mask?");
+
+ return 0;
+}
+
+static void cmd_mcp_key_export_cb(const struct generated_key *key,
+ void *context ATTR_UNUSED)
+{
+ struct dcrypt_private_key *pkey;
+ bool user_key = FALSE;
+ const char *error = NULL;
+ int ret;
+
+ if (*key->name == '\0')
+ user_key = TRUE;
+
+ doveadm_print(key->name);
+ doveadm_print(key->id);
+
+ if ((ret = mail_crypt_get_private_key(key->box, key->id, user_key, FALSE,
+ &pkey, &error)) <= 0) {
+ if (ret == 0)
+ error = "key not found";
+ doveadm_print(t_strdup_printf("ERROR: %s", error));
+ doveadm_print("");
+ } else {
+ string_t *out = t_str_new(64);
+ if (!dcrypt_key_store_private(pkey, DCRYPT_FORMAT_PEM, NULL, out,
+ NULL, NULL, &error)) {
+ doveadm_print(t_strdup_printf("ERROR: %s", error));
+ doveadm_print("");
+ } else {
+ /* this is to make it more compatible with openssl cli
+ as it expects BEGIN on it's own line */
+ doveadm_print(t_strdup_printf("\n%s", str_c(out)));
+ }
+ dcrypt_key_unref_private(&pkey);
+ }
+}
+
+static int cmd_mcp_key_export_run(struct doveadm_mail_cmd_context *_ctx,
+ struct mail_user *user)
+{
+ struct mcp_cmd_context *ctx =
+ (struct mcp_cmd_context *)_ctx;
+
+ doveadm_print_init(DOVEADM_PRINT_TYPE_PAGER);
+ doveadm_print_header("box", "Folder", 0);
+ doveadm_print_header("name", "Public ID", 0);
+ doveadm_print_header("error", "Error", 0);
+ doveadm_print_header("key", "Key", 0);
+
+ mcp_key_list(ctx, user, cmd_mcp_key_export_cb, NULL);
+
+ return 0;
+}
+
+static int cmd_mcp_key_password_run(struct doveadm_mail_cmd_context *_ctx,
+ struct mail_user *user)
+{
+ struct mcp_cmd_context *ctx =
+ (struct mcp_cmd_context *)_ctx;
+ bool cli = (_ctx->cctx->conn_type == DOVEADM_CONNECTION_TYPE_CLI);
+
+ struct raw_key {
+ const char *attr;
+ const char *id;
+ const char *data;
+ };
+
+ ARRAY(struct raw_key) raw_keys;
+
+ doveadm_print_init(DOVEADM_PRINT_TYPE_PAGER);
+
+ doveadm_print_header_simple("result");
+
+ if (ctx->ask_old_password) {
+ if (ctx->old_password != NULL) {
+ doveadm_print("old password specified, cannot ask for it");
+ _ctx->exit_code = EX_USAGE;
+ return -1;
+ }
+ if (!cli) {
+ doveadm_print("No cli - cannot ask for password");
+ _ctx->exit_code = EX_USAGE;
+ return -1;
+ }
+ ctx->old_password =
+ p_strdup(_ctx->pool, t_askpass("Old password: "));
+ }
+
+ if (ctx->ask_new_password) {
+ if (ctx->new_password != NULL) {
+ doveadm_print("new password specified, cannot ask for it");
+ _ctx->exit_code = EX_USAGE;
+ return -1;
+ }
+ if (!cli) {
+ doveadm_print("No cli - cannot ask for password");
+ _ctx->exit_code = EX_USAGE;
+ return -1;
+ }
+ const char *passw;
+ passw = t_askpass("New password: ");
+ if (strcmp(passw, t_askpass("Confirm new password: ")) != 0) {
+ doveadm_print("Passwords don't match, aborting");
+ _ctx->exit_code = EX_USAGE;
+ return -1;
+ }
+ ctx->new_password = p_strdup(_ctx->pool, passw);
+ }
+
+ if (ctx->clear_password &&
+ (ctx->new_password != NULL ||
+ mail_user_plugin_getenv(user, MAIL_CRYPT_USERENV_PASSWORD) != NULL)) {
+ doveadm_print("clear password and new password specified");
+ _ctx->exit_code = EX_USAGE;
+ return -1;
+ }
+
+ struct mail_namespace *ns = mail_namespace_find_inbox(user->namespaces);
+ struct mailbox *box = mailbox_alloc(ns->list, "INBOX", 0);
+ if (mailbox_open(box) < 0) {
+ doveadm_print(t_strdup_printf("mailbox_open(%s) failed: %s",
+ mailbox_get_vname(box),
+ mailbox_get_last_internal_error(box, NULL)));
+ _ctx->exit_code = EX_TEMPFAIL;
+ return -1;
+ }
+
+ t_array_init(&raw_keys, 8);
+
+ /* then get the current user keys, all of them */
+ struct mailbox_attribute_iter *iter =
+ mailbox_attribute_iter_init(box,
+ MAIL_ATTRIBUTE_TYPE_PRIVATE,
+ USER_CRYPT_PREFIX
+ PRIVKEYS_PREFIX);
+ const char *error;
+ const char *key_id;
+ int ret = 1;
+ unsigned int count = 0;
+
+ while ((key_id = mailbox_attribute_iter_next(iter)) != NULL) {
+ const char *attr =
+ t_strdup_printf(USER_CRYPT_PREFIX PRIVKEYS_PREFIX "%s",
+ key_id);
+
+ struct mail_attribute_value value;
+ if ((ret = mailbox_attribute_get(box, MAIL_ATTRIBUTE_TYPE_PRIVATE,
+ attr, &value)) < 0) {
+ doveadm_print(t_strdup_printf("mailbox_attribute_get(%s, %s) failed: %s",
+ mailbox_get_vname(box), attr,
+ mailbox_get_last_internal_error(box, NULL)));
+ _ctx->exit_code = EX_TEMPFAIL;
+ break;
+ } else if (ret > 0) {
+ struct raw_key *raw_key = array_append_space(&raw_keys);
+ raw_key->attr = p_strdup(_ctx->pool, attr);
+ raw_key->id = p_strdup(_ctx->pool, key_id);
+ raw_key->data = p_strdup(_ctx->pool, value.value);
+ }
+ }
+
+ if (ret == 1) {
+ struct mailbox_transaction_context *t =
+ mailbox_transaction_begin(box, _ctx->transaction_flags,
+ __func__);
+ struct dcrypt_private_key *key;
+ const struct raw_key *raw_key;
+ const char *algo = ctx->new_password != NULL ?
+ MAIL_CRYPT_PW_CIPHER :
+ NULL;
+ string_t *newkey = t_str_new(256);
+
+ array_foreach(&raw_keys, raw_key) {
+ struct mail_attribute_value value;
+
+ if (!dcrypt_key_load_private(&key, raw_key->data,
+ ctx->old_password, NULL,
+ &error)) {
+ doveadm_print(t_strdup_printf("dcrypt_key_load_private(%s) failed: %s",
+ raw_key->id,
+ error));
+ _ctx->exit_code = EX_DATAERR;
+ ret = -1;
+ break;
+ }
+
+ /* save it */
+ str_truncate(newkey, 0);
+
+ if (!dcrypt_key_store_private(key, DCRYPT_FORMAT_DOVECOT,
+ algo, newkey,
+ ctx->new_password,
+ NULL, &error)) {
+ doveadm_print(t_strdup_printf("dcrypt_key_store_private(%s) failed: %s",
+ raw_key->id,
+ error));
+ _ctx->exit_code = EX_DATAERR;
+ ret = -1;
+ }
+
+ dcrypt_key_unref_private(&key);
+ if (ret == -1) break;
+
+ i_zero(&value);
+ value.value = str_c(newkey);
+
+ /* and store it */
+ if (mailbox_attribute_set(t, MAIL_ATTRIBUTE_TYPE_PRIVATE,
+ raw_key->attr, &value) < 0) {
+ doveadm_print(t_strdup_printf("mailbox_attribute_set(%s, %s) failed: %s",
+ mailbox_get_vname(box),
+ raw_key->attr,
+ mailbox_get_last_internal_error(box, NULL)));
+ _ctx->exit_code = EX_TEMPFAIL;
+ ret = -1;
+ break;
+ }
+ count++;
+ }
+
+ if (ret < 1) {
+ mailbox_transaction_rollback(&t);
+ } else {
+ if (mailbox_transaction_commit(&t) < 0) {
+ doveadm_print(t_strdup_printf("mailbox_transaction_commit(%s) failed: %s",
+ mailbox_get_vname(box),
+ mailbox_get_last_internal_error(box, NULL)));
+ } else {
+ doveadm_print(t_strdup_printf("Changed password for %u key(s)",
+ count));
+ }
+ }
+ }
+
+ (void)mailbox_attribute_iter_deinit(&iter);
+ mailbox_free(&box);
+
+ return ret;
+}
+
+
+static bool cmd_mcp_keypair_generate_parse_arg(struct doveadm_mail_cmd_context *_ctx, int c)
+{
+ struct mcp_cmd_context *ctx =
+ (struct mcp_cmd_context *)_ctx;
+
+ switch (c) {
+ case 'U':
+ ctx->userkey_only = TRUE;
+ break;
+ case 'R':
+ ctx->recrypt_box_keys = TRUE;
+ break;
+ case 'f':
+ ctx->force = TRUE;
+ break;
+ default:
+ return FALSE;
+ }
+ return TRUE;
+
+}
+
+static bool cmd_mcp_key_password_parse_arg(struct doveadm_mail_cmd_context *_ctx, int c)
+{
+ struct mcp_cmd_context *ctx =
+ (struct mcp_cmd_context *)_ctx;
+
+ switch (c) {
+ case 'N':
+ ctx->ask_new_password = TRUE;
+ break;
+ case 'O':
+ ctx->ask_old_password = TRUE;
+ break;
+ case 'C':
+ ctx->clear_password = TRUE;
+ break;
+ case 'o':
+ ctx->old_password = p_strdup(_ctx->pool, optarg);
+ break;
+ case 'n':
+ ctx->new_password = p_strdup(_ctx->pool, optarg);
+ break;
+ default:
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static bool cmd_mcp_key_parse_arg(struct doveadm_mail_cmd_context *_ctx, int c)
+{
+ struct mcp_cmd_context *ctx =
+ (struct mcp_cmd_context *)_ctx;
+
+ switch (c) {
+ case 'U':
+ ctx->userkey_only = TRUE;
+ break;
+ default:
+ return FALSE;
+ }
+ return TRUE;
+
+}
+
+static struct doveadm_mail_cmd_context *cmd_mcp_keypair_generate_alloc(void)
+{
+ struct mcp_cmd_context *ctx;
+
+ ctx = doveadm_mail_cmd_alloc(struct mcp_cmd_context);
+ ctx->ctx.getopt_args = "URf";
+ ctx->ctx.v.parse_arg = cmd_mcp_keypair_generate_parse_arg;
+ ctx->ctx.v.run = cmd_mcp_keypair_generate_run;
+ return &ctx->ctx;
+}
+
+static struct doveadm_mail_cmd_context *cmd_mcp_key_list_alloc(void)
+{
+ struct mcp_cmd_context *ctx;
+
+ ctx = doveadm_mail_cmd_alloc(struct mcp_cmd_context);
+ ctx->ctx.getopt_args = "U";
+ ctx->ctx.v.parse_arg = cmd_mcp_key_parse_arg;
+ ctx->ctx.v.run = cmd_mcp_key_list_run;
+ return &ctx->ctx;
+}
+
+static struct doveadm_mail_cmd_context *cmd_mcp_key_export_alloc(void)
+{
+ struct mcp_cmd_context *ctx;
+
+ ctx = doveadm_mail_cmd_alloc(struct mcp_cmd_context);
+ ctx->ctx.getopt_args = "U";
+ ctx->ctx.v.parse_arg = cmd_mcp_key_parse_arg;
+ ctx->ctx.v.run = cmd_mcp_key_export_run;
+ return &ctx->ctx;
+}
+
+static struct doveadm_mail_cmd_context *cmd_mcp_key_password_alloc(void)
+{
+ struct mcp_cmd_context *ctx;
+
+ ctx = doveadm_mail_cmd_alloc(struct mcp_cmd_context);
+ ctx->ctx.getopt_args = "NOCo:n:";
+ ctx->ctx.v.parse_arg = cmd_mcp_key_password_parse_arg;
+ ctx->ctx.v.run = cmd_mcp_key_password_run;
+ return &ctx->ctx;
+}
+
+struct doveadm_cmd_ver2 doveadm_cmd_mcp_keypair_generate = {
+ .name = "mailbox cryptokey generate",
+ .mail_cmd = cmd_mcp_keypair_generate_alloc,
+ .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX "[-URf] mailbox [ mailbox .. ]",
+DOVEADM_CMD_PARAMS_START
+DOVEADM_CMD_MAIL_COMMON
+DOVEADM_CMD_PARAM('U', "user-key-only", CMD_PARAM_BOOL, 0)
+DOVEADM_CMD_PARAM('R', "re-encrypt-box-keys", CMD_PARAM_BOOL, 0)
+DOVEADM_CMD_PARAM('f', "force", CMD_PARAM_BOOL, 0)
+DOVEADM_CMD_PARAM('\0', "mailbox", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL)
+DOVEADM_CMD_PARAMS_END
+};
+
+struct doveadm_cmd_ver2 doveadm_cmd_mcp_key_list = {
+ .name = "mailbox cryptokey list",
+ .mail_cmd = cmd_mcp_key_list_alloc,
+ .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX "-U | mailbox [ mailbox .. ]",
+DOVEADM_CMD_PARAMS_START
+DOVEADM_CMD_MAIL_COMMON
+DOVEADM_CMD_PARAM('U', "user-key", CMD_PARAM_BOOL, 0)
+DOVEADM_CMD_PARAM('\0', "mailbox", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL)
+DOVEADM_CMD_PARAMS_END
+};
+
+struct doveadm_cmd_ver2 doveadm_cmd_mcp_key_export = {
+ .name = "mailbox cryptokey export",
+ .mail_cmd = cmd_mcp_key_export_alloc,
+ .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX "-U | mailbox [ mailbox .. ]",
+DOVEADM_CMD_PARAMS_START
+DOVEADM_CMD_MAIL_COMMON
+DOVEADM_CMD_PARAM('U', "user-key", CMD_PARAM_BOOL, 0)
+DOVEADM_CMD_PARAM('\0', "mailbox", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL)
+DOVEADM_CMD_PARAMS_END
+};
+
+struct doveadm_cmd_ver2 doveadm_cmd_mcp_key_password = {
+ .name = "mailbox cryptokey password",
+ .mail_cmd = cmd_mcp_key_password_alloc,
+ .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX "[-NOC] [-opassword] [-npassword]",
+DOVEADM_CMD_PARAMS_START
+DOVEADM_CMD_MAIL_COMMON
+DOVEADM_CMD_PARAM('C', "clear-password", CMD_PARAM_BOOL, 0)
+DOVEADM_CMD_PARAM('N', "ask-new-password", CMD_PARAM_BOOL, 0)
+DOVEADM_CMD_PARAM('n', "new-password", CMD_PARAM_STR, 0)
+DOVEADM_CMD_PARAM('O', "ask-old-password", CMD_PARAM_BOOL, 0)
+DOVEADM_CMD_PARAM('o', "old-password", CMD_PARAM_STR, 0)
+DOVEADM_CMD_PARAMS_END
+};
+
+void doveadm_mail_crypt_plugin_init(struct module *mod ATTR_UNUSED)
+{
+ doveadm_cmd_register_ver2(&doveadm_cmd_mcp_keypair_generate);
+ doveadm_cmd_register_ver2(&doveadm_cmd_mcp_key_list);
+ doveadm_cmd_register_ver2(&doveadm_cmd_mcp_key_export);
+ doveadm_cmd_register_ver2(&doveadm_cmd_mcp_key_password);
+}
+
+void doveadm_mail_crypt_plugin_deinit(void)
+{
+}