diff options
Diffstat (limited to 'src/doveadm/doveadm-mail-expunge.c')
-rw-r--r-- | src/doveadm/doveadm-mail-expunge.c | 286 |
1 files changed, 286 insertions, 0 deletions
diff --git a/src/doveadm/doveadm-mail-expunge.c b/src/doveadm/doveadm-mail-expunge.c new file mode 100644 index 0000000..4ab7e3d --- /dev/null +++ b/src/doveadm/doveadm-mail-expunge.c @@ -0,0 +1,286 @@ +/* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "mail-index.h" +#include "mail-storage.h" +#include "mail-search.h" +#include "doveadm-mailbox-list-iter.h" +#include "doveadm-mail-iter.h" +#include "doveadm-mail.h" + +struct expunge_cmd_context { + struct doveadm_mail_cmd_context ctx; + bool delete_empty_mailbox; +}; + +static int +cmd_expunge_box(struct doveadm_mail_cmd_context *_ctx, + const struct mailbox_info *info, + struct mail_search_args *search_args) +{ + struct expunge_cmd_context *ctx = (struct expunge_cmd_context *)_ctx; + struct doveadm_mail_iter *iter; + struct mailbox *box; + struct mail *mail; + enum mail_error error; + int ret = 0; + + if (doveadm_mail_iter_init(_ctx, info, search_args, 0, NULL, 0, + &iter) < 0) + return -1; + + while (doveadm_mail_iter_next(iter, &mail)) { + if (doveadm_debug) { + i_debug("expunge: box=%s uid=%u", + info->vname, mail->uid); + } + mail_expunge(mail); + } + + if (doveadm_mail_iter_deinit_keep_box(&iter, &box) < 0) + ret = -1; + else if (mailbox_sync(box, 0) < 0) { + i_error("Syncing mailbox '%s' failed: %s", + mailbox_get_vname(box), + mailbox_get_last_internal_error(box, NULL)); + doveadm_mail_failed_mailbox(_ctx, box); + ret = -1; + } + + if (ctx->delete_empty_mailbox && ret == 0) { + if (mailbox_delete_empty(box) < 0) { + error = mailbox_get_last_mail_error(box); + if (error != MAIL_ERROR_EXISTS) { + i_error("Deleting mailbox '%s' failed: %s", + mailbox_get_vname(box), + mailbox_get_last_internal_error(box, NULL)); + doveadm_mail_failed_mailbox(_ctx, box); + ret = -1; + } + } else { + if (mailbox_set_subscribed(box, FALSE) < 0) { + i_error("Unsubscribing mailbox '%s' failed: %s", + mailbox_get_vname(box), + mailbox_get_last_internal_error(box, NULL)); + doveadm_mail_failed_mailbox(_ctx, box); + ret = -1; + } + } + } + mailbox_free(&box); + return ret; +} + +static bool +expunge_search_args_is_mailbox_ok(struct mail_search_arg *args); + +static bool +expunge_search_args_is_mailbox_or_ok(struct mail_search_arg *args) +{ + struct mail_search_arg *arg; + + for (arg = args; arg != NULL; arg = arg->next) { + switch (arg->type) { + case SEARCH_OR: + if (!expunge_search_args_is_mailbox_or_ok(arg->value.subargs)) + return FALSE; + break; + case SEARCH_SUB: + case SEARCH_INTHREAD: + if (!expunge_search_args_is_mailbox_ok(arg->value.subargs)) + return FALSE; + break; + case SEARCH_MAILBOX: + case SEARCH_MAILBOX_GUID: + case SEARCH_MAILBOX_GLOB: + break; + default: + return FALSE; + } + } + return TRUE; +} + +static bool +expunge_search_args_is_mailbox_ok(struct mail_search_arg *args) +{ + struct mail_search_arg *arg; + bool have_or = FALSE; + + /* a) we find one mailbox here in the SUB block */ + for (arg = args; arg != NULL; arg = arg->next) { + switch (arg->type) { + case SEARCH_MAILBOX: + case SEARCH_MAILBOX_GUID: + case SEARCH_MAILBOX_GLOB: + return TRUE; + case SEARCH_OR: + have_or = TRUE; + break; + case SEARCH_SUB: + case SEARCH_INTHREAD: + if (expunge_search_args_is_mailbox_ok(arg->value.subargs)) + return TRUE; + break; + default: + break; + } + } + + /* b) there is at least one OR block, and all of the ORs must have + mailbox */ + if (!have_or) + return FALSE; + + for (arg = args; arg != NULL; arg = arg->next) { + if (arg->type == SEARCH_OR && + !expunge_search_args_is_mailbox_or_ok(arg->value.subargs)) + return FALSE; + } + return TRUE; +} + +static bool +expunge_search_args_is_msgset_ok(struct mail_search_arg *args); + +static bool +expunge_search_args_is_msgset_or_ok(struct mail_search_arg *args) +{ + struct mail_search_arg *arg; + + /* we're done if all OR branches contain something else besides + MAILBOXes */ + for (arg = args; arg != NULL; arg = arg->next) { + switch (arg->type) { + case SEARCH_MAILBOX: + case SEARCH_MAILBOX_GUID: + case SEARCH_MAILBOX_GLOB: + return FALSE; + case SEARCH_OR: + if (!expunge_search_args_is_msgset_or_ok(arg->value.subargs)) + return FALSE; + break; + case SEARCH_SUB: + if (!expunge_search_args_is_msgset_ok(arg->value.subargs)) + return FALSE; + break; + default: + break; + } + } + return TRUE; +} + +static bool +expunge_search_args_is_msgset_ok(struct mail_search_arg *args) +{ + struct mail_search_arg *arg; + + /* all args can't be just MAILBOXes */ + for (arg = args; arg != NULL; arg = arg->next) { + switch (arg->type) { + case SEARCH_MAILBOX: + case SEARCH_MAILBOX_GUID: + case SEARCH_MAILBOX_GLOB: + break; + case SEARCH_OR: + /* if each OR branch has something else than just + MAILBOXes, we're ok */ + if (expunge_search_args_is_msgset_or_ok(arg->value.subargs)) + return TRUE; + break; + case SEARCH_SUB: + if (expunge_search_args_is_msgset_ok(arg->value.subargs)) + return TRUE; + break; + default: + return TRUE; + } + } + return FALSE; +} + +static int +cmd_expunge_run(struct doveadm_mail_cmd_context *ctx, struct mail_user *user) +{ + const enum mailbox_list_iter_flags iter_flags = + MAILBOX_LIST_ITER_NO_AUTO_BOXES | + MAILBOX_LIST_ITER_RETURN_NO_FLAGS; + struct doveadm_mailbox_list_iter *iter; + const struct mailbox_info *info; + int ret = 0; + + iter = doveadm_mailbox_list_iter_init(ctx, user, ctx->search_args, + iter_flags); + while ((info = doveadm_mailbox_list_iter_next(iter)) != NULL) T_BEGIN { + if (cmd_expunge_box(ctx, info, ctx->search_args) < 0) + ret = -1; + } T_END; + if (doveadm_mailbox_list_iter_deinit(&iter) < 0) + ret = -1; + return ret; +} + +void expunge_search_args_check(struct mail_search_args *args, const char *cmd) +{ + if (!expunge_search_args_is_mailbox_ok(args->args)) { + i_fatal_status(EX_USAGE, + "%s: To avoid accidents, search query " + "must contain MAILBOX in all search branches", cmd); + } + if (!expunge_search_args_is_msgset_ok(args->args)) { + i_fatal_status(EX_USAGE, + "%s: To avoid accidents, each branch in search query " + "must contain something else besides MAILBOX " + "(e.g. just add \"all\" if you want everything)", cmd); + } + mail_search_args_simplify(args); +} + +static void cmd_expunge_init(struct doveadm_mail_cmd_context *ctx, + const char *const args[]) +{ + if (args[0] == NULL) + doveadm_mail_help_name("expunge"); + + ctx->search_args = doveadm_mail_build_search_args(args); + expunge_search_args_check(ctx->search_args, "expunge"); +} + +static bool cmd_expunge_parse_arg(struct doveadm_mail_cmd_context *_ctx, int c) +{ + struct expunge_cmd_context *ctx = (struct expunge_cmd_context *)_ctx; + + switch (c) { + case 'd': + ctx->delete_empty_mailbox = TRUE; + break; + default: + return FALSE; + } + return TRUE; +} + +static struct doveadm_mail_cmd_context *cmd_expunge_alloc(void) +{ + struct expunge_cmd_context *ctx; + + ctx = doveadm_mail_cmd_alloc(struct expunge_cmd_context); + ctx->ctx.getopt_args = "d"; + ctx->ctx.v.parse_arg = cmd_expunge_parse_arg; + ctx->ctx.v.init = cmd_expunge_init; + ctx->ctx.v.run = cmd_expunge_run; + return &ctx->ctx; +} + +struct doveadm_cmd_ver2 doveadm_cmd_expunge_ver2 = { + .name = "expunge", + .mail_cmd = cmd_expunge_alloc, + .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX "[-m] <search query>", +DOVEADM_CMD_PARAMS_START +DOVEADM_CMD_MAIL_COMMON +DOVEADM_CMD_PARAM('d', "delete-empty-mailbox", CMD_PARAM_BOOL, 0) +DOVEADM_CMD_PARAM('\0', "query", CMD_PARAM_ARRAY, CMD_PARAM_FLAG_POSITIONAL) +DOVEADM_CMD_PARAMS_END +}; |