/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "lib-signals.h" #include "ioloop.h" #include "istream.h" #include "istream-dot.h" #include "istream-seekable.h" #include "str.h" #include "unichar.h" #include "module-dir.h" #include "wildcard-match.h" #include "master-service.h" #include "mail-user.h" #include "mail-namespace.h" #include "mail-storage.h" #include "mail-storage-settings.h" #include "mail-storage-service.h" #include "mail-storage-hooks.h" #include "mail-search-build.h" #include "mail-search-parser.h" #include "mailbox-list-iter.h" #include "doveadm.h" #include "client-connection.h" #include "doveadm-settings.h" #include "doveadm-print.h" #include "doveadm-dsync.h" #include "doveadm-mail.h" #include #define DOVEADM_MAIL_CMD_INPUT_TIMEOUT_MSECS (5*60*1000) struct force_resync_cmd_context { struct doveadm_mail_cmd_context ctx; bool fsck; }; void (*hook_doveadm_mail_init)(struct doveadm_mail_cmd_context *ctx); struct doveadm_mail_cmd_module_register doveadm_mail_cmd_module_register = { 0 }; char doveadm_mail_cmd_hide = '\0'; static int killed_signo = 0; bool doveadm_is_killed(void) { return killed_signo != 0; } int doveadm_killed_signo(void) { return killed_signo; } void doveadm_mail_failed_error(struct doveadm_mail_cmd_context *ctx, enum mail_error error) { int exit_code = EX_TEMPFAIL; switch (error) { case MAIL_ERROR_NONE: i_unreached(); case MAIL_ERROR_TEMP: case MAIL_ERROR_UNAVAILABLE: break; case MAIL_ERROR_NOTPOSSIBLE: case MAIL_ERROR_EXISTS: case MAIL_ERROR_CONVERSION: case MAIL_ERROR_INVALIDDATA: exit_code = DOVEADM_EX_NOTPOSSIBLE; break; case MAIL_ERROR_PARAMS: exit_code = EX_USAGE; break; case MAIL_ERROR_PERM: exit_code = EX_NOPERM; break; case MAIL_ERROR_NOQUOTA: exit_code = EX_CANTCREAT; break; case MAIL_ERROR_NOTFOUND: exit_code = DOVEADM_EX_NOTFOUND; break; case MAIL_ERROR_EXPUNGED: break; case MAIL_ERROR_INUSE: case MAIL_ERROR_LIMIT: exit_code = DOVEADM_EX_NOTPOSSIBLE; break; case MAIL_ERROR_LOOKUP_ABORTED: break; } /* tempfail overrides all other exit codes, otherwise use whatever error happened first */ if (ctx->exit_code == 0 || exit_code == EX_TEMPFAIL) ctx->exit_code = exit_code; } void doveadm_mail_failed_storage(struct doveadm_mail_cmd_context *ctx, struct mail_storage *storage) { enum mail_error error; mail_storage_get_last_error(storage, &error); doveadm_mail_failed_error(ctx, error); } void doveadm_mail_failed_mailbox(struct doveadm_mail_cmd_context *ctx, struct mailbox *box) { doveadm_mail_failed_storage(ctx, mailbox_get_storage(box)); } void doveadm_mail_failed_list(struct doveadm_mail_cmd_context *ctx, struct mailbox_list *list) { enum mail_error error; mailbox_list_get_last_error(list, &error); doveadm_mail_failed_error(ctx, error); } struct doveadm_mail_cmd_context * doveadm_mail_cmd_alloc_size(size_t size) { struct doveadm_mail_cmd_context *ctx; pool_t pool; i_assert(size >= sizeof(struct doveadm_mail_cmd_context)); pool = pool_alloconly_create("doveadm mail cmd", 1024); ctx = p_malloc(pool, size); ctx->pool = pool; ctx->cmd_input_fd = -1; return ctx; } static int cmd_purge_run(struct doveadm_mail_cmd_context *ctx, struct mail_user *user) { struct mail_namespace *ns; struct mail_storage *storage; int ret = 0; for (ns = user->namespaces; ns != NULL; ns = ns->next) { if (ns->type != MAIL_NAMESPACE_TYPE_PRIVATE || ns->alias_for != NULL) continue; storage = mail_namespace_get_default_storage(ns); if (mail_storage_purge(storage) < 0) { i_error("Purging namespace '%s' failed: %s", ns->prefix, mail_storage_get_last_internal_error(storage, NULL)); doveadm_mail_failed_storage(ctx, storage); ret = -1; } } return ret; } static struct doveadm_mail_cmd_context *cmd_purge_alloc(void) { struct doveadm_mail_cmd_context *ctx; ctx = doveadm_mail_cmd_alloc(struct doveadm_mail_cmd_context); ctx->v.run = cmd_purge_run; return ctx; } static void doveadm_mail_cmd_input_input(struct doveadm_mail_cmd_context *ctx) { const unsigned char *data; size_t size; while (i_stream_read_more(ctx->cmd_input, &data, &size) > 0) i_stream_skip(ctx->cmd_input, size); if (!ctx->cmd_input->eof) return; if (ctx->cmd_input->stream_errno != 0) { i_error("read(%s) failed: %s", i_stream_get_name(ctx->cmd_input), i_stream_get_error(ctx->cmd_input)); } io_loop_stop(current_ioloop); } static void doveadm_mail_cmd_input_timeout(struct doveadm_mail_cmd_context *ctx) { struct istream *input; input = i_stream_create_error_str(ETIMEDOUT, "Timed out in %u secs", DOVEADM_MAIL_CMD_INPUT_TIMEOUT_MSECS/1000); i_stream_set_name(input, i_stream_get_name(ctx->cmd_input)); i_stream_destroy(&ctx->cmd_input); ctx->cmd_input = input; ctx->exit_code = EX_TEMPFAIL; io_loop_stop(current_ioloop); } static void doveadm_mail_cmd_input_read(struct doveadm_mail_cmd_context *ctx) { struct ioloop *ioloop; struct io *io; struct timeout *to; ioloop = io_loop_create(); /* Read the pending input from stream. Delay adding the IO in case we're reading from a file. That would cause a panic with epoll. */ io_loop_set_running(ioloop); doveadm_mail_cmd_input_input(ctx); if (io_loop_is_running(ioloop)) { io = io_add(ctx->cmd_input_fd, IO_READ, doveadm_mail_cmd_input_input, ctx); to = timeout_add(DOVEADM_MAIL_CMD_INPUT_TIMEOUT_MSECS, doveadm_mail_cmd_input_timeout, ctx); io_loop_run(ioloop); io_remove(&io); timeout_remove(&to); } io_loop_destroy(&ioloop); i_assert(ctx->cmd_input->eof); i_stream_seek(ctx->cmd_input, 0); } void doveadm_mail_get_input(struct doveadm_mail_cmd_context *ctx) { const struct doveadm_cmd_context *cctx = ctx->cctx; bool cli = (cctx->conn_type == DOVEADM_CONNECTION_TYPE_CLI); struct istream *inputs[2]; if (ctx->cmd_input != NULL) return; if (!cli && cctx->input == NULL) { ctx->cmd_input = i_stream_create_error_str(EINVAL, "Input stream missing (provide with file parameter)"); return; } if (!cli) inputs[0] = i_stream_create_dot(cctx->input, FALSE); else { inputs[0] = i_stream_create_fd(STDIN_FILENO, 1024*1024); i_stream_set_name(inputs[0], "stdin"); } inputs[1] = NULL; ctx->cmd_input_fd = i_stream_get_fd(inputs[0]); ctx->cmd_input = i_stream_create_seekable_path(inputs, 1024*256, "/tmp/doveadm."); i_stream_set_name(ctx->cmd_input, i_stream_get_name(inputs[0])); i_stream_unref(&inputs[0]); doveadm_mail_cmd_input_read(ctx); } struct mailbox * doveadm_mailbox_find(struct mail_user *user, const char *mailbox) { struct mail_namespace *ns; if (!uni_utf8_str_is_valid(mailbox)) { i_fatal_status(EX_DATAERR, "Mailbox name not valid UTF-8: %s", mailbox); } ns = mail_namespace_find(user->namespaces, mailbox); return mailbox_alloc(ns->list, mailbox, MAILBOX_FLAG_IGNORE_ACLS); } struct mail_search_args * doveadm_mail_build_search_args(const char *const args[]) { struct mail_search_parser *parser; struct mail_search_args *sargs; const char *error, *charset = "UTF-8"; parser = mail_search_parser_init_cmdline(args); if (mail_search_build(mail_search_register_get_human(), parser, &charset, &sargs, &error) < 0) i_fatal("%s", error); mail_search_parser_deinit(&parser); return sargs; } static int cmd_force_resync_box(struct doveadm_mail_cmd_context *_ctx, const struct mailbox_info *info) { struct force_resync_cmd_context *ctx = (struct force_resync_cmd_context *)_ctx; enum mailbox_flags flags = MAILBOX_FLAG_IGNORE_ACLS; struct mailbox *box; int ret = 0; if (ctx->fsck) flags |= MAILBOX_FLAG_FSCK; box = mailbox_alloc(info->ns->list, info->vname, flags); if (mailbox_open(box) < 0) { i_error("Opening mailbox %s failed: %s", info->vname, mailbox_get_last_internal_error(box, NULL)); doveadm_mail_failed_mailbox(_ctx, box); ret = -1; } else if (mailbox_sync(box, MAILBOX_SYNC_FLAG_FORCE_RESYNC | MAILBOX_SYNC_FLAG_FIX_INCONSISTENT) < 0) { i_error("Forcing a resync on mailbox %s failed: %s", info->vname, mailbox_get_last_internal_error(box, NULL)); doveadm_mail_failed_mailbox(_ctx, box); ret = -1; } mailbox_free(&box); return ret; } static int cmd_force_resync_prerun(struct doveadm_mail_cmd_context *ctx ATTR_UNUSED, struct mail_storage_service_user *service_user, const char **error_r) { if (mail_storage_service_user_set_setting(service_user, "mailbox_list_index_very_dirty_syncs", "no", error_r) <= 0) i_unreached(); return 0; } static int cmd_force_resync_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 | MAILBOX_LIST_ITER_STAR_WITHIN_NS; const enum mail_namespace_type ns_mask = MAIL_NAMESPACE_TYPE_MASK_ALL; struct mailbox_list_iterate_context *iter; const struct mailbox_info *info; int ret = 0; iter = mailbox_list_iter_init_namespaces(user->namespaces, ctx->args, ns_mask, iter_flags); while ((info = mailbox_list_iter_next(iter)) != NULL) { if ((info->flags & (MAILBOX_NOSELECT | MAILBOX_NONEXISTENT)) == 0) T_BEGIN { if (cmd_force_resync_box(ctx, info) < 0) ret = -1; } T_END; } if (mailbox_list_iter_deinit(&iter) < 0) { i_error("Listing mailboxes failed: %s", mailbox_list_get_last_internal_error(user->namespaces->list, NULL)); doveadm_mail_failed_list(ctx, user->namespaces->list); ret = -1; } return ret; } static void cmd_force_resync_init(struct doveadm_mail_cmd_context *_ctx ATTR_UNUSED, const char *const args[]) { if (args[0] == NULL) doveadm_mail_help_name("force-resync"); } static bool cmd_force_resync_parse_arg(struct doveadm_mail_cmd_context *_ctx, int c) { struct force_resync_cmd_context *ctx = (struct force_resync_cmd_context *)_ctx; switch (c) { case 'f': ctx->fsck = TRUE; break; default: return FALSE; } return TRUE; } static struct doveadm_mail_cmd_context *cmd_force_resync_alloc(void) { struct force_resync_cmd_context *ctx; ctx = doveadm_mail_cmd_alloc(struct force_resync_cmd_context); ctx->ctx.getopt_args = "f"; ctx->ctx.v.parse_arg = cmd_force_resync_parse_arg; ctx->ctx.v.init = cmd_force_resync_init; ctx->ctx.v.run = cmd_force_resync_run; ctx->ctx.v.prerun = cmd_force_resync_prerun; return &ctx->ctx; } static void doveadm_cctx_to_storage_service_input(const struct doveadm_cmd_context *cctx, struct mail_storage_service_input *input_r) { i_zero(input_r); input_r->service = "doveadm"; input_r->remote_ip = cctx->remote_ip; input_r->remote_port = cctx->remote_port; input_r->local_ip = cctx->local_ip; input_r->local_port = cctx->local_port; input_r->username = cctx->username; } static int doveadm_mail_next_user(struct doveadm_mail_cmd_context *ctx, const char **error_r) { const struct doveadm_cmd_context *cctx = ctx->cctx; struct mail_storage_service_input input; const char *error, *ip; int ret; i_assert(cctx != NULL); ip = net_ip2addr(&cctx->remote_ip); if (ip[0] == '\0') i_set_failure_prefix("doveadm(%s): ", cctx->username); else i_set_failure_prefix("doveadm(%s,%s): ", ip, cctx->username); doveadm_cctx_to_storage_service_input(cctx, &input); if (ctx->cmd_input != NULL) i_stream_seek(ctx->cmd_input, 0); /* see if we want to execute this command via (another) doveadm server */ ret = doveadm_mail_server_user(ctx, &input, error_r); if (ret != 0) return ret; ret = mail_storage_service_lookup(ctx->storage_service, &input, &ctx->cur_service_user, &error); if (ret <= 0) { if (ret < 0) { *error_r = t_strdup_printf("User lookup failed: %s", error); } return ret; } if (ctx->v.prerun != NULL) { if (ctx->v.prerun(ctx, ctx->cur_service_user, error_r) < 0) { mail_storage_service_user_unref(&ctx->cur_service_user); return -1; } } ret = mail_storage_service_next(ctx->storage_service, ctx->cur_service_user, &ctx->cur_mail_user, error_r); if (ret < 0) { mail_storage_service_user_unref(&ctx->cur_service_user); return ret; } struct event_reason *reason = event_reason_begin(event_reason_code_prefix("doveadm", "cmd_", ctx->cmd->name)); T_BEGIN { if (ctx->v.run(ctx, ctx->cur_mail_user) < 0) { i_assert(ctx->exit_code != 0); } } T_END; mail_user_deinit(&ctx->cur_mail_user); /* user deinit may still do some work, so finish the reason after it */ event_reason_end(&reason); mail_storage_service_user_unref(&ctx->cur_service_user); return 1; } static void sig_die(const siginfo_t *si, void *context ATTR_UNUSED) { killed_signo = si->si_signo; } int doveadm_mail_single_user(struct doveadm_mail_cmd_context *ctx, const char **error_r) { const struct doveadm_cmd_context *cctx = ctx->cctx; i_assert(cctx->username != NULL); doveadm_cctx_to_storage_service_input(cctx, &ctx->storage_service_input); ctx->storage_service = mail_storage_service_init(master_service, NULL, ctx->service_flags); ctx->v.init(ctx, ctx->args); if (hook_doveadm_mail_init != NULL) hook_doveadm_mail_init(ctx); lib_signals_set_handler(SIGINT, 0, sig_die, NULL); lib_signals_set_handler(SIGTERM, 0, sig_die, NULL); return doveadm_mail_next_user(ctx, error_r); } static void doveadm_mail_all_users(struct doveadm_mail_cmd_context *ctx, const char *wildcard_user) { struct doveadm_cmd_context *cctx = ctx->cctx; unsigned int user_idx; const char *ip, *user, *error; int ret; ctx->service_flags |= MAIL_STORAGE_SERVICE_FLAG_USERDB_LOOKUP; doveadm_cctx_to_storage_service_input(cctx, &ctx->storage_service_input); ctx->storage_service = mail_storage_service_init(master_service, NULL, ctx->service_flags); lib_signals_set_handler(SIGINT, 0, sig_die, NULL); lib_signals_set_handler(SIGTERM, 0, sig_die, NULL); ctx->v.init(ctx, ctx->args); mail_storage_service_all_init_mask(ctx->storage_service, wildcard_user != NULL ? wildcard_user : ""); if (hook_doveadm_mail_init != NULL) hook_doveadm_mail_init(ctx); user_idx = 0; while ((ret = ctx->v.get_next_user(ctx, &user)) > 0) { if (wildcard_user != NULL) { if (!wildcard_match_icase(user, wildcard_user)) continue; } cctx->username = user; doveadm_print_sticky("username", user); T_BEGIN { ret = doveadm_mail_next_user(ctx, &error); if (ret < 0) i_error("%s", error); else if (ret == 0) i_info("User no longer exists, skipping"); } T_END; if (ret == -1) break; if (doveadm_verbose) { if (++user_idx % 100 == 0) { printf("\r%d", user_idx); fflush(stdout); } } if (killed_signo != 0) { i_warning("Killed with signal %d", killed_signo); ret = -1; break; } } if (doveadm_verbose) printf("\n"); ip = net_ip2addr(&cctx->remote_ip); if (ip[0] == '\0') i_set_failure_prefix("doveadm: "); else i_set_failure_prefix("doveadm(%s): ", ip); if (ret < 0) { i_error("Failed to iterate through some users"); ctx->exit_code = EX_TEMPFAIL; } } static void doveadm_mail_cmd_init_noop(struct doveadm_mail_cmd_context *ctx ATTR_UNUSED, const char *const args[] ATTR_UNUSED) { } static int doveadm_mail_cmd_get_next_user(struct doveadm_mail_cmd_context *ctx, const char **username_r) { if (ctx->users_list_input == NULL) return mail_storage_service_all_next(ctx->storage_service, username_r); *username_r = i_stream_read_next_line(ctx->users_list_input); if (ctx->users_list_input->stream_errno != 0) { i_error("read(%s) failed: %s", i_stream_get_name(ctx->users_list_input), i_stream_get_error(ctx->users_list_input)); return -1; } return *username_r != NULL ? 1 : 0; } static void doveadm_mail_cmd_deinit_noop(struct doveadm_mail_cmd_context *ctx ATTR_UNUSED) { } struct doveadm_mail_cmd_context * doveadm_mail_cmd_init(const struct doveadm_mail_cmd *cmd, const struct doveadm_settings *set) { struct doveadm_mail_cmd_context *ctx; ctx = cmd->alloc(); ctx->set = set; ctx->cmd = cmd; if (ctx->v.init == NULL) ctx->v.init = doveadm_mail_cmd_init_noop; if (ctx->v.get_next_user == NULL) ctx->v.get_next_user = doveadm_mail_cmd_get_next_user; if (ctx->v.deinit == NULL) ctx->v.deinit = doveadm_mail_cmd_deinit_noop; p_array_init(&ctx->module_contexts, ctx->pool, 5); return ctx; } static struct doveadm_mail_cmd_context * doveadm_mail_cmdline_init(const struct doveadm_mail_cmd *cmd) { struct doveadm_mail_cmd_context *ctx; ctx = doveadm_mail_cmd_init(cmd, doveadm_settings); ctx->service_flags |= MAIL_STORAGE_SERVICE_FLAG_NO_LOG_INIT; if (doveadm_debug) ctx->service_flags |= MAIL_STORAGE_SERVICE_FLAG_DEBUG; return ctx; } static void doveadm_mail_cmd_exec(struct doveadm_mail_cmd_context *ctx, const char *wildcard_user) { const struct doveadm_cmd_context *cctx = ctx->cctx; bool cli = (cctx->conn_type == DOVEADM_CONNECTION_TYPE_CLI); int ret; const char *error; if (ctx->v.preinit != NULL) ctx->v.preinit(ctx); ctx->iterate_single_user = !ctx->iterate_all_users && wildcard_user == NULL; if (doveadm_print_is_initialized() && (!ctx->iterate_single_user || ctx->add_username_header)) { doveadm_print_header("username", "Username", DOVEADM_PRINT_HEADER_FLAG_STICKY | DOVEADM_PRINT_HEADER_FLAG_HIDE_TITLE); } if (ctx->iterate_single_user) { if (cctx->username == NULL) i_fatal_status(EX_USAGE, "USER environment is missing and -u option not used"); if (!cli) { /* we may access multiple users */ ctx->service_flags |= MAIL_STORAGE_SERVICE_FLAG_TEMP_PRIV_DROP; } if (ctx->add_username_header) doveadm_print_sticky("username", cctx->username); ret = doveadm_mail_single_user(ctx, &error); if (ret < 0) { /* user lookup/init failed somehow */ doveadm_exit_code = EX_TEMPFAIL; i_error("%s", error); } else if (ret == 0) { doveadm_exit_code = EX_NOUSER; i_error("User doesn't exist"); } } else { ctx->service_flags |= MAIL_STORAGE_SERVICE_FLAG_TEMP_PRIV_DROP; doveadm_mail_all_users(ctx, wildcard_user); } doveadm_mail_server_flush(); doveadm_mail_cmd_deinit(ctx); doveadm_print_flush(); /* service deinit unloads mail plugins, so do it late */ mail_storage_service_deinit(&ctx->storage_service); if (ctx->exit_code != 0) doveadm_exit_code = ctx->exit_code; } void doveadm_mail_cmd_deinit(struct doveadm_mail_cmd_context *ctx) { ctx->v.deinit(ctx); if (ctx->search_args != NULL) mail_search_args_unref(&ctx->search_args); } void doveadm_mail_cmd_free(struct doveadm_mail_cmd_context *ctx) { i_stream_unref(&ctx->users_list_input); i_stream_unref(&ctx->cmd_input); pool_unref(&ctx->pool); } void doveadm_mail_help(const struct doveadm_mail_cmd *cmd) { fprintf(stderr, "doveadm %s "DOVEADM_CMD_MAIL_USAGE_PREFIX" %s\n", cmd->name, cmd->usage_args == NULL ? "" : cmd->usage_args); lib_exit(EX_USAGE); } void doveadm_mail_try_help_name(const char *cmd_name) { const struct doveadm_cmd_ver2 *cmd2; cmd2 = doveadm_cmd_find_ver2(cmd_name); if (cmd2 != NULL) help_ver2(cmd2); } void doveadm_mail_help_name(const char *cmd_name) { doveadm_mail_try_help_name(cmd_name); i_fatal("Missing help for command %s", cmd_name); } static struct doveadm_cmd_ver2 doveadm_cmd_force_resync_ver2 = { .name = "force-resync", .mail_cmd = cmd_force_resync_alloc, .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX "[-f] ", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_MAIL_COMMON DOVEADM_CMD_PARAM('f', "fsck", CMD_PARAM_BOOL, 0) DOVEADM_CMD_PARAM('\0', "mailbox-mask", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAMS_END }; static struct doveadm_cmd_ver2 doveadm_cmd_purge_ver2 = { .name = "purge", .mail_cmd = cmd_purge_alloc, .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX, DOVEADM_CMD_PARAMS_START DOVEADM_CMD_MAIL_COMMON DOVEADM_CMD_PARAMS_END }; static struct doveadm_cmd_ver2 *mail_commands_ver2[] = { &doveadm_cmd_batch, &doveadm_cmd_dsync_backup, &doveadm_cmd_dsync_mirror, &doveadm_cmd_dsync_server, &doveadm_cmd_mailbox_metadata_set_ver2, &doveadm_cmd_mailbox_metadata_unset_ver2, &doveadm_cmd_mailbox_metadata_get_ver2, &doveadm_cmd_mailbox_metadata_list_ver2, &doveadm_cmd_mailbox_status_ver2, &doveadm_cmd_mailbox_list_ver2, &doveadm_cmd_mailbox_create_ver2, &doveadm_cmd_mailbox_delete_ver2, &doveadm_cmd_mailbox_rename_ver2, &doveadm_cmd_mailbox_subscribe_ver2, &doveadm_cmd_mailbox_unsubscribe_ver2, &doveadm_cmd_mailbox_update_ver2, &doveadm_cmd_mailbox_path_ver2, &doveadm_cmd_fetch_ver2, &doveadm_cmd_save_ver2, &doveadm_cmd_index_ver2, &doveadm_cmd_altmove_ver2, &doveadm_cmd_deduplicate_ver2, &doveadm_cmd_expunge_ver2, &doveadm_cmd_flags_add_ver2, &doveadm_cmd_flags_remove_ver2, &doveadm_cmd_flags_replace_ver2, &doveadm_cmd_import_ver2, &doveadm_cmd_force_resync_ver2, &doveadm_cmd_purge_ver2, &doveadm_cmd_search_ver2, &doveadm_cmd_copy_ver2, &doveadm_cmd_move_ver2, &doveadm_cmd_mailbox_cache_decision, &doveadm_cmd_mailbox_cache_remove, &doveadm_cmd_mailbox_cache_purge, &doveadm_cmd_rebuild_attachments, }; void doveadm_mail_init(void) { unsigned int i; for (i = 0; i < N_ELEMENTS(mail_commands_ver2); i++) doveadm_cmd_register_ver2(mail_commands_ver2[i]); } void doveadm_mail_init_finish(void) { struct module_dir_load_settings mod_set; i_zero(&mod_set); mod_set.abi_version = DOVECOT_ABI_VERSION; mod_set.require_init_funcs = TRUE; mod_set.debug = doveadm_debug; mod_set.binary_name = "doveadm"; /* load all configured mail plugins */ mail_storage_service_modules = module_dir_load_missing(mail_storage_service_modules, doveadm_settings->mail_plugin_dir, doveadm_settings->mail_plugins, &mod_set); /* keep mail_storage_init() referenced so that its _deinit() doesn't try to free doveadm plugins' hooks too early. */ mail_storage_init(); } void doveadm_mail_deinit(void) { mail_storage_deinit(); module_dir_unload(&mail_storage_service_modules); } static int doveadm_cmd_parse_arg(struct doveadm_mail_cmd_context *mctx, const struct doveadm_cmd_param *arg, ARRAY_TYPE(const_string) *full_args) { const char *short_opt_str = p_strdup_printf(mctx->pool, "-%c", arg->short_opt); const char *arg_value = NULL; switch (arg->type) { case CMD_PARAM_BOOL: break; case CMD_PARAM_INT64: arg_value = dec2str(arg->value.v_int64); break; case CMD_PARAM_IP: arg_value = net_ip2addr(&arg->value.v_ip); break; case CMD_PARAM_STR: arg_value = arg->value.v_string; break; case CMD_PARAM_ARRAY: { const char *str; array_foreach_elem(&arg->value.v_array, str) { optarg = (char *)str; if (!mctx->v.parse_arg(mctx, arg->short_opt)) return -1; array_push_back(full_args, &short_opt_str); array_push_back(full_args, &str); } return 0; } default: i_panic("Cannot convert parameter %s to short opt", arg->name); } optarg = (char *)arg_value; if (!mctx->v.parse_arg(mctx, arg->short_opt)) return -1; array_push_back(full_args, &short_opt_str); if (arg_value != NULL) array_push_back(full_args, &arg_value); return 0; } void doveadm_cmd_ver2_to_mail_cmd_wrapper(struct doveadm_cmd_context *cctx) { struct doveadm_mail_cmd_context *mctx; const char *wildcard_user; const char *fieldstr; ARRAY_TYPE(const_string) pargv, full_args; int i; bool cli = (cctx->conn_type == DOVEADM_CONNECTION_TYPE_CLI); bool tcp_server = (cctx->conn_type == DOVEADM_CONNECTION_TYPE_TCP); struct doveadm_mail_cmd mail_cmd = { cctx->cmd->mail_cmd, cctx->cmd->name, cctx->cmd->usage }; if (!cli) { mctx = doveadm_mail_cmd_init(&mail_cmd, doveadm_settings); /* doveadm-server always does userdb lookups */ mctx->service_flags |= MAIL_STORAGE_SERVICE_FLAG_USERDB_LOOKUP; } else { mctx = doveadm_mail_cmdline_init(&mail_cmd); } mctx->cctx = cctx; mctx->iterate_all_users = FALSE; wildcard_user = NULL; p_array_init(&full_args, mctx->pool, 8); p_array_init(&pargv, mctx->pool, 8); for(i=0;iargc;i++) { const struct doveadm_cmd_param *arg = &cctx->argv[i]; if (!arg->value_set) continue; if (strcmp(arg->name, "all-users") == 0) { if (tcp_server) mctx->add_username_header = TRUE; else mctx->iterate_all_users = arg->value.v_bool; fieldstr = "-A"; array_push_back(&full_args, &fieldstr); } else if (strcmp(arg->name, "socket-path") == 0) { doveadm_settings->doveadm_socket_path = arg->value.v_string; if (doveadm_settings->doveadm_worker_count == 0) doveadm_settings->doveadm_worker_count = 1; } else if (strcmp(arg->name, "user") == 0) { mctx->service_flags |= MAIL_STORAGE_SERVICE_FLAG_USERDB_LOOKUP; if (!tcp_server) cctx->username = arg->value.v_string; fieldstr = "-u"; array_push_back(&full_args, &fieldstr); array_push_back(&full_args, &arg->value.v_string); if (strchr(arg->value.v_string, '*') != NULL || strchr(arg->value.v_string, '?') != NULL) { if (tcp_server) mctx->add_username_header = TRUE; else { wildcard_user = arg->value.v_string; cctx->username = NULL; } } } else if (strcmp(arg->name, "user-file") == 0) { mctx->service_flags |= MAIL_STORAGE_SERVICE_FLAG_USERDB_LOOKUP; wildcard_user = "*"; mctx->users_list_input = arg->value.v_istream; fieldstr = "-F"; array_push_back(&full_args, &fieldstr); fieldstr = ""; /* value doesn't really matter */ array_push_back(&full_args, &fieldstr); i_stream_ref(mctx->users_list_input); } else if (strcmp(arg->name, "field") == 0 || strcmp(arg->name, "flag") == 0) { /* mailbox status, fetch, flags: convert an array into a single space-separated parameter (alternative to fieldstr) */ fieldstr = p_array_const_string_join(mctx->pool, &arg->value.v_array, " "); array_push_back(&pargv, &fieldstr); } else if (strcmp(arg->name, "file") == 0) { /* input for doveadm_mail_get_input(), used by e.g. save */ if (mctx->cmd_input != NULL) { i_error("Only one file input allowed: %s", arg->name); doveadm_mail_cmd_free(mctx); doveadm_exit_code = EX_USAGE; return; } mctx->cmd_input = arg->value.v_istream; i_stream_ref(mctx->cmd_input); } else if (strcmp(arg->name, "trans-flags") == 0) { /* This parameter allows to set additional * mailbox transaction flags. */ mctx->transaction_flags = arg->value.v_int64; /* Keep all named special parameters above this line */ } else if (mctx->v.parse_arg != NULL && arg->short_opt != '\0') { if (doveadm_cmd_parse_arg(mctx, arg, &full_args) < 0) { i_error("Invalid parameter %c", arg->short_opt); doveadm_mail_cmd_free(mctx); doveadm_exit_code = EX_USAGE; } } else if ((arg->flags & CMD_PARAM_FLAG_POSITIONAL) != 0) { /* feed this into pargv */ if (arg->type == CMD_PARAM_ARRAY) array_append_array(&pargv, &arg->value.v_array); else if (arg->type == CMD_PARAM_STR) array_push_back(&pargv, &arg->value.v_string); } else { doveadm_exit_code = EX_USAGE; i_error("invalid parameter: %s", arg->name); doveadm_mail_cmd_free(mctx); return; } } const char *dashdash = "--"; array_push_back(&full_args, &dashdash); array_append_zero(&pargv); /* All the -parameters need to be included in full_args so that they're sent to doveadm-server. */ unsigned int args_pos = array_count(&full_args); array_append_array(&full_args, &pargv); mctx->args = array_idx(&full_args, args_pos); mctx->full_args = array_front(&full_args); doveadm_mail_cmd_exec(mctx, wildcard_user); doveadm_mail_cmd_free(mctx); }