view src/doveadm/doveadm-mail.c @ 21322:5ab8dc1a4a6f

global: Change string position/length from unsigned int to size_t Mainly to avoid truncating >4GB strings, which might potentially cause some security holes. Normally there are other limits, which prevent such excessive strings from being created in the first place. I'm sure this didn't find everything. Maybe everything could be found with compiler warnings. -Wconversion kind of does it, but it gives way too many unnecessary warnings. These were mainly found with: grep " = strlen" egrep "unsigned int.*(size|len)"
author Timo Sirainen <timo.sirainen@dovecot.fi>
date Mon, 12 Dec 2016 07:19:55 +0200
parents aa676841ed89
children 59437f8764c6
line wrap: on
line source

/* Copyright (c) 2009-2016 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 "client-connection.h"
#include "doveadm.h"
#include "doveadm-settings.h"
#include "doveadm-print.h"
#include "doveadm-dsync.h"
#include "doveadm-mail.h"

#include <stdio.h>

#define DOVEADM_MAIL_CMD_INPUT_TIMEOUT_MSECS (5*60*1000)

ARRAY_TYPE(doveadm_mail_cmd) doveadm_mail_cmds;
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 = 0;

	switch (error) {
	case MAIL_ERROR_NONE:
		i_unreached();
	case MAIL_ERROR_TEMP:
		exit_code = EX_TEMPFAIL;
		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:
	case MAIL_ERROR_INUSE:
		exit_code = EX_TEMPFAIL;
		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_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();
	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);
	/* read the pending input from stream. */
	io_loop_set_running(ioloop);
	doveadm_mail_cmd_input_input(ctx);
	if (io_loop_is_running(ioloop))
		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)
{
	struct istream *inputs[2];

	if (ctx->cmd_input != NULL)
		return;

	if (ctx->conn != NULL)
		inputs[0] = i_stream_create_dot(ctx->conn->input, FALSE);
	else {
		inputs[0] = i_stream_create_fd(STDIN_FILENO, 1024*1024, FALSE);
		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 mailbox *box;
	int ret = 0;

	box = mailbox_alloc(info->ns->list, info->vname,
			    MAILBOX_FLAG_IGNORE_ACLS);
	if (mailbox_open(box) < 0) {
		i_error("Opening mailbox %s failed: %s", info->vname,
			mailbox_get_last_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_error(box, NULL));
		doveadm_mail_failed_mailbox(ctx, box);
		ret = -1;
	}
	mailbox_free(&box);
	return ret;
}

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_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_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 struct doveadm_mail_cmd_context *cmd_force_resync_alloc(void)
{
	struct doveadm_mail_cmd_context *ctx;

	ctx = doveadm_mail_cmd_alloc(struct doveadm_mail_cmd_context);
	ctx->v.init = cmd_force_resync_init;
	ctx->v.run = cmd_force_resync_run;
	return ctx;
}

static void
doveadm_cctx_to_storage_service_input(const struct doveadm_cmd_context *cctx,
					struct mail_storage_service_input *input_r)
{
	memset(input_r, 0, sizeof(*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 struct doveadm_cmd_context *cctx,
		       const char **error_r)
{
	struct mail_storage_service_input input;
	const char *error, *ip;
	int ret;

	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_free(&ctx->cur_service_user);
			return -1;
		}
	}

	ret = mail_storage_service_next(ctx->storage_service,
					ctx->cur_service_user,
					&ctx->cur_mail_user);
	if (ret < 0) {
		*error_r = "User init failed";
		mail_storage_service_user_free(&ctx->cur_service_user);
		return ret;
	}

	if (ctx->v.run(ctx, ctx->cur_mail_user) < 0) {
		i_assert(ctx->exit_code != 0);
	}
	mail_user_unref(&ctx->cur_mail_user);
	mail_storage_service_user_free(&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 struct doveadm_cmd_context *cctx,
			     const char **error_r)
{
	i_assert(cctx->username != NULL);

	doveadm_cctx_to_storage_service_input(cctx, &ctx->storage_service_input);
	ctx->cur_client_ip = cctx->remote_ip;
	ctx->cur_username = cctx->username;
	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, cctx, error_r);
}

static void
doveadm_mail_all_users(struct doveadm_mail_cmd_context *ctx,
		       struct doveadm_cmd_context *cctx,
		       const char *wildcard_user)
{
	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;
		ctx->cur_username = user;
		doveadm_print_sticky("username", user);
		T_BEGIN {
			ret = doveadm_mail_next_user(ctx, cctx, &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(&ctx->cur_client_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,
		      struct doveadm_cmd_context *cctx,
		      const char *wildcard_user)
{
	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 (ctx->cur_username == NULL)
			i_fatal_status(EX_USAGE, "USER environment is missing and -u option not used");
		if (!ctx->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, cctx, &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, cctx, wildcard_user);
	}
	if (ctx->search_args != NULL)
		mail_search_args_unref(&ctx->search_args);
	doveadm_mail_server_flush();
	ctx->v.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;
}

static void doveadm_mail_cmd_free(struct doveadm_mail_cmd_context *ctx)
{
	if (ctx->users_list_input != NULL)
		i_stream_unref(&ctx->users_list_input);
	if (ctx->cmd_input != NULL)
		i_stream_unref(&ctx->cmd_input);
	pool_unref(&ctx->pool);
}

static void
doveadm_mail_cmd(const struct doveadm_mail_cmd *cmd, int argc, char *argv[])
{
	struct doveadm_cmd_context cctx;
	struct doveadm_mail_cmd_context *ctx;
	const char *getopt_args, *wildcard_user;
	int c;

	ctx = doveadm_mail_cmdline_init(cmd);
	ctx->full_args = (const void *)(argv + 1);
	ctx->cli = TRUE;
	ctx->cur_username = getenv("USER");

	memset(&cctx, 0, sizeof(cctx));

	getopt_args = "AF:S:u:";
	/* keep context's getopt_args first in case it contains '+' */
	if (ctx->getopt_args != NULL)
		getopt_args = t_strconcat(ctx->getopt_args, getopt_args, NULL);
	i_assert(master_getopt_str_is_valid(getopt_args));

	wildcard_user = NULL;
	while ((c = getopt(argc, argv, getopt_args)) > 0) {
		switch (c) {
		case 'A':
			ctx->iterate_all_users = TRUE;
			break;
		case 'S':
			doveadm_settings->doveadm_socket_path = optarg;
			if (doveadm_settings->doveadm_worker_count == 0)
				doveadm_settings->doveadm_worker_count = 1;
			break;
		case 'u':
			ctx->service_flags |=
				MAIL_STORAGE_SERVICE_FLAG_USERDB_LOOKUP;
			ctx->cur_username = optarg;
			if (strchr(ctx->cur_username, '*') != NULL ||
			    strchr(ctx->cur_username, '?') != NULL) {
				wildcard_user = ctx->cur_username;
				ctx->cur_username = NULL;
			}
			break;
		case 'F':
			ctx->service_flags |=
				MAIL_STORAGE_SERVICE_FLAG_USERDB_LOOKUP;
			wildcard_user = "*";
			ctx->users_list_input =
				i_stream_create_file(optarg, 1024);
			break;
		default:
			if (ctx->v.parse_arg == NULL ||
			    !ctx->v.parse_arg(ctx, c))
				doveadm_mail_help(cmd);
		}
	}
	argv += optind;
	if (argv[0] != NULL && cmd->usage_args == NULL) {
		i_fatal_status(EX_USAGE, "doveadm %s: Unknown parameter: %s",
			       cmd->name, argv[0]);
	}
	ctx->args = (const void *)argv;
	cctx.username = ctx->cur_username;
	doveadm_mail_cmd_exec(ctx, &cctx, wildcard_user);
	doveadm_mail_cmd_free(ctx);
}

static bool
doveadm_mail_cmd_try_find_multi_word(const struct doveadm_mail_cmd *cmd,
				     const char *cmdname, int *argc,
				     const char *const **argv)
{
	size_t len;

	if (*argc < 2)
		return FALSE;
	*argc -= 1;
	*argv += 1;

	len = strlen((*argv)[0]);
	if (strncmp(cmdname, (*argv)[0], len) != 0)
		return FALSE;

	if (cmdname[len] == ' ') {
		/* more args */
		return doveadm_mail_cmd_try_find_multi_word(cmd, cmdname + len + 1,
							    argc, argv);
	}
	if (cmdname[len] != '\0')
		return FALSE;

	/* match */
	return TRUE;
}

const struct doveadm_mail_cmd *
doveadm_mail_cmd_find_from_argv(const char *cmd_name, int *argc,
				const char *const **argv)
{
	const struct doveadm_mail_cmd *cmd;
	size_t cmd_name_len;
	const char *const *orig_argv;
	int orig_argc;

	i_assert(*argc > 0);

	cmd_name_len = strlen(cmd_name);
	array_foreach(&doveadm_mail_cmds, cmd) {
		if (strcmp(cmd->name, cmd_name) == 0)
			return cmd;

		/* see if it matches a multi-word command */
		if (strncmp(cmd->name, cmd_name, cmd_name_len) == 0 &&
		    cmd->name[cmd_name_len] == ' ') {
			const char *subcmd = cmd->name + cmd_name_len + 1;

			orig_argc = *argc;
			orig_argv = *argv;
			if (doveadm_mail_cmd_try_find_multi_word(cmd, subcmd,
								 argc, argv))
				return cmd;
			*argc = orig_argc;
			*argv = orig_argv;
		}
	}

	return NULL;
}

bool doveadm_mail_try_run(const char *cmd_name, int argc, char *argv[])
{
	const struct doveadm_mail_cmd *cmd;

	cmd = doveadm_mail_cmd_find_from_argv(cmd_name, &argc, (void *)&argv);
	if (cmd == NULL)
		return FALSE;
	doveadm_mail_cmd(cmd, argc, argv);
	return TRUE;
}

void doveadm_mail_register_cmd(const struct doveadm_mail_cmd *cmd)
{
	/* for now we'll just assume that cmd will be permanently in memory */
	array_append(&doveadm_mail_cmds, cmd, 1);
}

const struct doveadm_mail_cmd *doveadm_mail_cmd_find(const char *cmd_name)
{
	const struct doveadm_mail_cmd *cmd;

	array_foreach(&doveadm_mail_cmds, cmd) {
		if (strcmp(cmd->name, cmd_name) == 0)
			return cmd;
	}
	return NULL;
}

void doveadm_mail_usage(string_t *out)
{
	const struct doveadm_mail_cmd *cmd;

	array_foreach(&doveadm_mail_cmds, cmd) {
		if (cmd->usage_args == &doveadm_mail_cmd_hide)
			continue;
		str_printfa(out, "%s\t"DOVEADM_CMD_MAIL_USAGE_PREFIX, cmd->name);
		if (cmd->usage_args != NULL)
			str_append(out, cmd->usage_args);
		str_append_c(out, '\n');
	}
}

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);
	exit(EX_USAGE);
}

void doveadm_mail_try_help_name(const char *cmd_name)
{
	const struct doveadm_cmd_ver2 *cmd2;
	const struct doveadm_mail_cmd *cmd;

	cmd2 = doveadm_cmd_find_ver2(cmd_name);
	if (cmd2 != NULL)
		help_ver2(cmd2);

	cmd = doveadm_mail_cmd_find(cmd_name);
	if (cmd != NULL)
		doveadm_mail_help(cmd);
}

bool doveadm_mail_has_subcommands(const char *cmd_name)
{
	const struct doveadm_mail_cmd *cmd;
	size_t len = strlen(cmd_name);

	array_foreach(&doveadm_mail_cmds, cmd) {
		if (strncmp(cmd->name, cmd_name, len) == 0 &&
		    cmd->name[len] == ' ')
			return TRUE;
	}
	return FALSE;
}

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 "<mailbox mask>",
DOVEADM_CMD_PARAMS_START
DOVEADM_CMD_MAIL_COMMON
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_mail_cmd *mail_commands[] = {
	&cmd_batch,
	&cmd_dsync_backup,
	&cmd_dsync_mirror,
	&cmd_dsync_server
};

static struct doveadm_cmd_ver2 *mail_commands_ver2[] = {
	&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_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
};

void doveadm_mail_init(void)
{
	struct module_dir_load_settings mod_set;
	unsigned int i;

	i_array_init(&doveadm_mail_cmds, 32);
	for (i = 0; i < N_ELEMENTS(mail_commands); i++)
		doveadm_mail_register_cmd(mail_commands[i]);

	for (i = 0; i < N_ELEMENTS(mail_commands_ver2); i++)
		doveadm_cmd_register_ver2(mail_commands_ver2[i]);

	memset(&mod_set, 0, sizeof(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);
}

void doveadm_mail_deinit(void)
{
	mail_storage_hooks_deinit();
	array_free(&doveadm_mail_cmds);
}

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;
	struct doveadm_mail_cmd mail_cmd = {
		cctx->cmd->mail_cmd, cctx->cmd->name, cctx->cmd->usage
	};

	if (!cctx->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->cur_username = cctx->username;
	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;i<cctx->argc;i++) {
		const struct doveadm_cmd_param *arg = &cctx->argv[i];

		if (!arg->value_set)
			continue;

		if (strcmp(arg->name, "all-users") == 0) {
			if (cctx->tcp_server)
				mctx->add_username_header = TRUE;
			else
				mctx->iterate_all_users = arg->value.v_bool;
			fieldstr = "-A";
			array_append(&full_args, &fieldstr, 1);
		} 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 (!cctx->tcp_server)
				mctx->cur_username = arg->value.v_string;

			fieldstr = "-u";
			array_append(&full_args, &fieldstr, 1);
			array_append(&full_args, &arg->value.v_string, 1);
			if (strchr(arg->value.v_string, '*') != NULL ||
			    strchr(arg->value.v_string, '?') != NULL) {
				if (cctx->tcp_server)
					mctx->add_username_header = TRUE;
				else {
					wildcard_user = arg->value.v_string;
					mctx->cur_username = NULL;
				}
			} else if (!cctx->tcp_server) {
				cctx->username = mctx->cur_username;
			}
		} 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_append(&full_args, &fieldstr, 1);
			fieldstr = ""; /* value doesn't really matter */
			array_append(&full_args, &fieldstr, 1);
			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_append(&pargv, &fieldstr, 1);
		} 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, "allow-empty-mailbox-name") == 0) {
			/* allow an empty mailbox name - to access server
			   attributes */
			mctx->allow_empty_mailbox_name = arg->value.v_bool;

		/* Keep all named special parameters above this line */

		} else if (mctx->v.parse_arg != NULL && arg->short_opt != '\0') {
			const char *short_opt_str = p_strdup_printf(
				mctx->pool, "-%c", arg->short_opt);

			optarg = (char*)arg->value.v_string;
			mctx->v.parse_arg(mctx, arg->short_opt);

			array_append(&full_args, &short_opt_str, 1);
			if (arg->type == CMD_PARAM_STR)
				array_append(&full_args, &arg->value.v_string, 1);
		} 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_append(&pargv, &arg->value.v_string, 1);
		} else {
			doveadm_exit_code = EX_USAGE;
			i_error("invalid parameter: %s", arg->name);
			doveadm_mail_cmd_free(mctx);
			return;
		}
	}

	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_idx(&full_args, 0);
	mctx->cli = cctx->cli;
	mctx->conn = cctx->conn;

	doveadm_mail_cmd_exec(mctx, cctx, wildcard_user);
	doveadm_mail_cmd_free(mctx);
}