changeset 19767:a9fd771f2cae

doveadm: Add infrastructure for doveadm_cmd_ver2 Version 2 commands have named parameters, which also have types. This is especially useful for reading input from HTTP/JSON API. This also simplifies the parameter parsing for command line input. For v2.3 the plan is to replace all the old doveadm_cmds with this new version and get rid of the _ver2 suffixes. But for now we'll have two versions of commands. For backwards compatibility with old commands we have also implemented wrappers so that v2 structs can be defined and there's a function to convert the named parameters to old v1 style args[] string, so the old command handlers can still be run. This will also be removed in v2.3. This change also adds requirement for getopt_long(). It's already available in all the Linuxes and BSDs, so this shouldn't be too big of a requirement. Other systems can install it from an external library.
author Aki Tuomi <aki.tuomi@dovecot.fi>
date Mon, 15 Feb 2016 14:02:19 +0200
parents f2d7c8fb2a94
children 603fdee3685f
files src/doveadm/doveadm-cmd.c src/doveadm/doveadm-cmd.h src/doveadm/doveadm-mail.c src/doveadm/doveadm-mail.h src/doveadm/doveadm.c src/doveadm/doveadm.h
diffstat 6 files changed, 528 insertions(+), 7 deletions(-) [+]
line wrap: on
line diff
--- a/src/doveadm/doveadm-cmd.c	Thu Feb 18 15:49:03 2016 +0200
+++ b/src/doveadm/doveadm-cmd.c	Mon Feb 15 14:02:19 2016 +0200
@@ -2,7 +2,14 @@
 
 #include "lib.h"
 #include "array.h"
+#include "istream.h"
+#include "str.h"
 #include "doveadm-cmd.h"
+#include "doveadm.h"
+
+#include <stdio.h>
+#include <unistd.h>
+#include <getopt.h>
 
 static struct doveadm_cmd *doveadm_commands[] = {
 	&doveadm_cmd_stop,
@@ -16,13 +23,65 @@
 	&doveadm_cmd_stats_dump
 };
 
+static struct doveadm_cmd_ver2 *doveadm_commands_ver2[] = {
+};
+
 ARRAY_TYPE(doveadm_cmd) doveadm_cmds;
+ARRAY_TYPE(doveadm_cmd_ver2) doveadm_cmds_ver2;
+ARRAY_DEFINE_TYPE(getopt_option_array, struct option);
 
 void doveadm_register_cmd(const struct doveadm_cmd *cmd)
 {
 	array_append(&doveadm_cmds, cmd, 1);
 }
 
+void doveadm_cmd_register_ver2(struct doveadm_cmd_ver2 *cmd)
+{
+	if (cmd->cmd == NULL) {
+		if (cmd->mail_cmd != NULL)
+			cmd->cmd = doveadm_cmd_ver2_to_mail_cmd_wrapper;
+		else if (cmd->old_cmd != NULL)
+			cmd->cmd = doveadm_cmd_ver2_to_cmd_wrapper;
+		else i_unreached();
+	}
+	array_append(&doveadm_cmds_ver2, cmd, 1);
+}
+
+const struct doveadm_cmd_ver2* doveadm_cmd_find_ver2(const char *cmd_name,
+	int argc, const char *argv[])
+{
+	int i;
+	const struct doveadm_cmd_ver2 *cmd;
+	const char *cptr;
+
+	for(i=0;i<argc;i++) {
+		if (strcmp(argv[i],cmd_name)==0) break;
+	}
+
+	i_assert(i != argc);
+
+	array_foreach(&doveadm_cmds_ver2, cmd) {
+		cptr = cmd->name;
+		/* cannot reuse i here because this needs be
+		   done more than once */
+		for(int k=0; cptr != NULL && i+k < argc; k++) {
+			size_t alen = strlen(argv[i+k]);
+			/* make sure we don't overstep */
+			if (strlen(cptr) < alen) break;
+			/* did not match */
+			if (strncmp(cptr, argv[i+k], alen) != 0) break;
+			/* do not accept abbreviations */
+			if (cptr[alen] != ' ' && cptr[alen] != '\0') break;
+			cptr += alen;
+			if (*cptr != '\0') cptr++; /* consume space */
+		}
+		/* name was fully consumed */
+		if (*cptr == '\0') return cmd;
+	}
+
+	return NULL;
+}
+
 static const struct doveadm_cmd *
 doveadm_cmd_find_multi_word(const struct doveadm_cmd *cmd,
 			    const char *cmdname, int *_argc, char **_argv[])
@@ -88,9 +147,14 @@
 	unsigned int i;
 
 	i_array_init(&doveadm_cmds, 32);
+	i_array_init(&doveadm_cmds_ver2, 2);
+
 	for (i = 0; i < N_ELEMENTS(doveadm_commands); i++)
 		doveadm_register_cmd(doveadm_commands[i]);
 
+	for (i = 0; i < N_ELEMENTS(doveadm_commands_ver2); i++)
+		doveadm_cmd_register_ver2(doveadm_commands_ver2[i]);
+
 	doveadm_register_auth_commands();
 	doveadm_register_director_commands();
 	doveadm_register_instance_commands();
@@ -106,3 +170,287 @@
 {
 	array_free(&doveadm_cmds);
 }
+
+static const struct doveadm_cmd_param*
+doveadm_cmd_param_get(int argc, const struct doveadm_cmd_param* params, const char *name)
+{
+	i_assert(params != NULL);
+	for(int i = 0; i < argc; i++) {
+		if (strcmp(params[i].name, name) == 0 && params[i].value_set)
+			return &(params[i]);
+	}
+	return NULL;
+}
+
+bool doveadm_cmd_param_bool(int argc, const struct doveadm_cmd_param* params, const char *name, bool* value)
+{
+	const struct doveadm_cmd_param* param;
+	if ((param = doveadm_cmd_param_get(argc, params, name))==NULL) return FALSE;
+
+	if (param->type == CMD_PARAM_NONE || param->type == CMD_PARAM_BOOL) {
+		*value = param->value.v_bool;
+		return TRUE;
+	}
+	return FALSE;
+}
+
+bool doveadm_cmd_param_int64(int argc, const struct doveadm_cmd_param* params, const char *name, int64_t* value)
+{
+	const struct doveadm_cmd_param* param;
+	if ((param = doveadm_cmd_param_get(argc, params, name))==NULL) return FALSE;
+
+	if (param->type == CMD_PARAM_INT64) {
+		*value = param->value.v_int64;
+		return TRUE;
+	}
+	return FALSE;
+}
+
+bool doveadm_cmd_param_str(int argc, const struct doveadm_cmd_param* params, const char *name, const char** value)
+{
+	const struct doveadm_cmd_param* param;
+	if ((param = doveadm_cmd_param_get(argc, params, name))==NULL) return FALSE;
+
+	if (param->type == CMD_PARAM_STR) {
+		*value = param->value.v_string;
+		return TRUE;
+	}
+	return FALSE;
+}
+
+bool doveadm_cmd_param_array(int argc, struct doveadm_cmd_param* params, const char *name, ARRAY_TYPE(const_string)** value)
+{
+	const struct doveadm_cmd_param* param;
+	if ((param = doveadm_cmd_param_get(argc, params, name))==NULL) return FALSE;
+	if (param->type == CMD_PARAM_STR) {
+		*value = (ARRAY_TYPE(const_string)*)&(param->value.v_array);
+		return TRUE;
+	}
+	return FALSE;
+}
+
+bool doveadm_cmd_param_istream(int argc, struct doveadm_cmd_param* params, const char *name, struct istream** value)
+{
+	const struct doveadm_cmd_param* param;
+	if ((param = doveadm_cmd_param_get(argc, params, name))==NULL) return FALSE;
+
+	if (param->type == CMD_PARAM_ISTREAM) {
+		*value = param->value.v_istream;
+		return TRUE;
+	}
+	return FALSE;
+}
+
+static void
+doveadm_cmd_params_to_argv(const char *name, int pargc, const struct doveadm_cmd_param* params,
+	ARRAY_TYPE(const_string) *argv)
+{
+	int i;
+	const char * const * cptr;
+	i_assert(array_count(argv) == 0);
+	array_append(argv, &name, 1);
+	for(i=0;i<pargc;i++) {
+		if (params[i].value_set && params[i].opt != NULL &&
+			*(params[i].opt) != ':' && *(params[i].opt) != '?') {
+			const char *optarg = t_strdup_printf("-%c", params[i].opt[0]);
+			if (params[i].type == CMD_PARAM_STR) {
+	                        array_append(argv, &optarg, 1);
+				array_append(argv, &params[i].value.v_string,1);
+			} else if (params[i].type == CMD_PARAM_ARRAY) {
+				array_foreach(&params[i].value.v_array, cptr) {
+					array_append(argv, &optarg, 1);
+					array_append(argv, cptr, 1);
+				}
+			}
+		} else if (params[i].value_set) {
+			if (params[i].type == CMD_PARAM_ARRAY) {
+				array_append_array(argv, &params[i].value.v_array);
+			} else {
+				array_append(argv, &params[i].value.v_string,1);
+			}
+		}
+	}
+	array_append_zero(argv);
+}
+
+int
+doveadm_cmd_ver2_to_cmd_wrapper(const struct doveadm_cmd_ver2* cmd,
+	int argc, const struct doveadm_cmd_param* param)
+{
+	unsigned int pargc;
+	const char **pargv;
+
+	i_assert(cmd->old_cmd != NULL);
+
+	ARRAY_TYPE(const_string) nargv;
+	t_array_init(&nargv, 8);
+	doveadm_cmd_params_to_argv(cmd->name, argc, param, &nargv);
+	pargv = array_get_modifiable(&nargv, &pargc);
+	i_getopt_reset();
+	cmd->old_cmd(pargc-1, (char**)pargv);
+
+	return 0;
+}
+
+static void
+doveadm_build_options(const struct doveadm_cmd_param par[],
+		string_t *shortopts,
+		ARRAY_TYPE(getopt_option_array) *longopts)
+{
+	const char *optp;
+	for(size_t i=0; par[i].name != NULL; i++) {
+		struct option longopt;
+		if ((par[i].flags & CMD_PARAM_FLAG_DO_NOT_EXPOSE) != 0) continue;
+		longopt.name = par[i].name;
+		longopt.flag = 0;
+		longopt.val = 0;
+		if (par[i].opt) {
+			optp = par[i].opt;
+			if (*optp != ':' && *optp != '?') {
+				longopt.val = *optp;
+				str_append_c(shortopts, *optp);
+				optp++;
+				if (optp[0] != '\0')
+					str_append_c(shortopts, *optp);
+			}
+			switch(*optp) {
+			case ':': longopt.has_arg = 1; break;
+			case '?': longopt.has_arg = 2; break;
+			default:
+				longopt.has_arg = 0;
+			}
+		} else {
+			longopt.has_arg = 0;
+		}
+		array_append(longopts, &longopt, 1);
+	}
+	array_append_zero(longopts);
+}
+
+static void doveadm_fill_param(struct doveadm_cmd_param *param,
+	const char *value, pool_t pool)
+{
+	param->value_set = TRUE;
+	switch(param->type) {
+	case CMD_PARAM_NONE:
+	case CMD_PARAM_BOOL:
+		param->value.v_bool = TRUE; break;
+	case CMD_PARAM_INT64:
+		if (str_to_int64(value, &param->value.v_int64) != 0) {
+			param->value_set = FALSE;
+		}
+		break;
+	case CMD_PARAM_STR:
+		if (value != NULL) {
+			param->value.v_string = p_strdup(pool, value);
+		} else {
+			param->value.v_string = NULL;
+		}
+		break;
+	case CMD_PARAM_ARRAY:
+		if (!array_is_created(&param->value.v_array))
+			p_array_init(&param->value.v_array, pool, 8);
+		const char *val = p_strdup(pool, value);
+		array_append(&param->value.v_array, &val, 1);
+		break;
+	case CMD_PARAM_ISTREAM: {
+		struct istream *is;
+		if (strcmp(value,"-") == 0) {
+			is = i_stream_create_fd(STDIN_FILENO, IO_BLOCK_SIZE, FALSE);
+		} else {
+			is = i_stream_create_file(value, IO_BLOCK_SIZE);
+		}
+		param->value.v_istream = is;
+	}
+	}
+}
+
+bool doveadm_cmd_try_run_ver2(const char *cmd_name, int argc, const char *argv[])
+{
+	const struct doveadm_cmd_ver2 *cmd;
+
+	cmd = doveadm_cmd_find_ver2(cmd_name, argc, argv);
+	if (cmd == NULL)
+		return FALSE;
+
+	if (doveadm_cmd_run_ver2(cmd, argc, argv) < 0)
+		doveadm_exit_code = EX_USAGE;
+	return TRUE;
+}
+
+int doveadm_cmd_run_ver2(const struct doveadm_cmd_ver2 *cmd, int argc, const char *argv[])
+{
+	struct doveadm_cmd_param *param;
+	ARRAY(struct doveadm_cmd_param) pargv;
+	ARRAY_TYPE(getopt_option_array) opts;
+	const char *cptr;
+	unsigned int pargc;
+	int c,li;
+	pool_t pool = pool_datastack_create();
+	string_t *optbuf = str_new(pool, 64);
+
+	p_array_init(&opts, pool, 4);
+
+	// build parameters
+	doveadm_build_options(cmd->parameters, optbuf, &opts);
+
+	p_array_init(&pargv, pool, 20);
+
+	for(pargc=0;cmd->parameters[pargc].name != NULL;pargc++) {
+		param = array_append_space(&pargv);
+		memcpy(param, &(cmd->parameters[pargc]), sizeof(struct doveadm_cmd_param));
+		param->value_set = FALSE;
+	}
+	i_assert(pargc == array_count(&opts)-1); /* opts is NULL-terminated */
+
+	while((c = getopt_long(argc, (char*const*)argv, str_c(optbuf), array_idx(&opts, 0), &li)) > -1) {
+		switch(c) {
+		case 0:
+			doveadm_fill_param(array_idx_modifiable(&pargv,li), optarg, pool);
+			break;
+		case '?':
+		case ':':
+			return -1;
+		default:
+			// hunt the option
+			for(unsigned int i = 0; i < pargc; i++) {
+				const struct option *longopt = array_idx(&opts,i);
+				if (longopt->val == c)
+					doveadm_fill_param(array_idx_modifiable(&pargv,i), optarg, pool);
+			}
+		}
+	}
+
+	cptr = cmd->name;
+	while((cptr = strchr(cptr+1, ' ')) != NULL) optind++;
+
+	/* process positional arguments */
+	for(;optind<argc;optind++) {
+		struct doveadm_cmd_param *ptr;
+		bool found = FALSE;
+		array_foreach_modifiable(&pargv, ptr) {
+			if ((ptr->flags & CMD_PARAM_FLAG_POSITIONAL) != 0 &&
+			    (ptr->value_set == FALSE || ptr->type == CMD_PARAM_ARRAY)) {
+				doveadm_fill_param(ptr, argv[optind], pool);
+				found = TRUE;
+				break;
+			}
+		}
+		if (!found) {
+			i_error("Extraneous arguments found");
+			return -1;
+		}
+	}
+
+	param = array_get_modifiable(&pargv, &pargc);
+
+	// FIXME: Unsure what do to with return value
+	cmd->cmd(cmd, pargc, param);
+
+	// unref istreams
+	array_foreach_modifiable(&pargv, param) {
+		if (param->type == CMD_PARAM_ISTREAM && param->value.v_istream != NULL)
+			i_stream_unref(&param->value.v_istream);
+	}
+	return 0;
+}
--- a/src/doveadm/doveadm-cmd.h	Thu Feb 18 15:49:03 2016 +0200
+++ b/src/doveadm/doveadm-cmd.h	Mon Feb 15 14:02:19 2016 +0200
@@ -1,18 +1,71 @@
 #ifndef DOVEADM_CMD_H
 #define DOVEADM_CMD_H
 
+#define DOVEADM_CMD_PARAMS_START .parameters = (const struct doveadm_cmd_param[]){
+#define DOVEADM_CMD_PARAM(optP, nameP, typeP, flagP ) { .opt = optP, .name = nameP, .type = typeP, .flags = flagP },
+#define DOVEADM_CMD_PARAMS_END { .opt = NULL, .name = NULL, .type = CMD_PARAM_NONE, .flags = CMD_PARAM_FLAG_NONE } }
+
+struct doveadm_cmd_ver2;
+struct doveadm_mail_cmd_context;
+
 typedef void doveadm_command_t(int argc, char *argv[]);
 
+typedef enum {
+	CMD_PARAM_NONE,    /* same as below, used for empty */
+	CMD_PARAM_BOOL,    /* value will contain 1 (not pointer) */
+	CMD_PARAM_INT64,    /* ditto but contains number (not pointer) */
+	CMD_PARAM_STR,     /* value contains const char* */
+	CMD_PARAM_ARRAY,   /* value contains const char*[] */
+	CMD_PARAM_ISTREAM  /* value contains struct istream* */
+} doveadm_cmd_param_t;
+
+typedef enum {
+	CMD_PARAM_FLAG_NONE		= 0x0,
+	CMD_PARAM_FLAG_POSITIONAL 	= 0x1,
+	CMD_PARAM_FLAG_DO_NOT_EXPOSE	= 0x2,
+} doveadm_cmd_param_flag_t;
+
+struct doveadm_cmd_param {
+	const char *opt;
+	const char *name;
+	doveadm_cmd_param_t type;
+	bool value_set;
+	struct {
+		bool v_bool;
+		int64_t v_int64;
+		const char* v_string;
+		ARRAY_TYPE(const_string) v_array;
+		struct istream* v_istream;
+	} value;
+	doveadm_cmd_param_flag_t flags;
+};
+
+typedef int doveadm_command_ver2_t(const struct doveadm_cmd_ver2* cmd,
+	int argc, const struct doveadm_cmd_param[]);
+
 struct doveadm_cmd {
 	doveadm_command_t *cmd;
 	const char *name;
 	const char *short_usage;
 };
+
+struct doveadm_cmd_ver2 {
+	doveadm_command_ver2_t *cmd;
+	doveadm_command_t *old_cmd;
+	struct doveadm_mail_cmd_context *(*mail_cmd)(void);
+	const char *name;
+	const char *usage;
+	const struct doveadm_cmd_param *parameters;
+};
+
 ARRAY_DEFINE_TYPE(doveadm_cmd, struct doveadm_cmd);
 extern ARRAY_TYPE(doveadm_cmd) doveadm_cmds;
 
 extern struct doveadm_cmd doveadm_cmd_stop;
 extern struct doveadm_cmd doveadm_cmd_reload;
+ARRAY_DEFINE_TYPE(doveadm_cmd_ver2, struct doveadm_cmd_ver2);
+extern ARRAY_TYPE(doveadm_cmd_ver2) doveadm_cmds_ver2;
+
 extern struct doveadm_cmd doveadm_cmd_dump;
 extern struct doveadm_cmd doveadm_cmd_pw;
 extern struct doveadm_cmd doveadm_cmd_who;
@@ -44,4 +97,25 @@
 void doveadm_cmds_init(void);
 void doveadm_cmds_deinit(void);
 
+int doveadm_cmd_ver2_to_cmd_wrapper(const struct doveadm_cmd_ver2* cmd,
+	int argc, const struct doveadm_cmd_param[]);
+int doveadm_cmd_ver2_to_mail_cmd_wrapper(const struct doveadm_cmd_ver2* cmd,
+	int argc, const struct doveadm_cmd_param argv[]);
+
+void doveadm_cmd_register_ver2(struct doveadm_cmd_ver2 *cmd);
+const struct doveadm_cmd_ver2 *doveadm_cmd_find_ver2(const char *cmd_name,
+	int argc, const char *argv[]);
+/* Returns FALSE if cmd_name doesn't exist, TRUE if it exists. */
+bool doveadm_cmd_try_run_ver2(const char *cmd_name, int argc,
+	const char *argv[]);
+/* Returns 0 if success, -1 if parameters were invalid. */
+int doveadm_cmd_run_ver2(const struct doveadm_cmd_ver2 *cmd,
+	int argc, const char *argv[]);
+
+bool doveadm_cmd_param_bool(int argc, const struct doveadm_cmd_param* params, const char *name, bool* value);
+bool doveadm_cmd_param_int64(int argc, const struct doveadm_cmd_param* params, const char *name, int64_t* value);
+bool doveadm_cmd_param_str(int argc, const struct doveadm_cmd_param* params, const char *name, const char** value);
+bool doveadm_cmd_param_array(int argc, struct doveadm_cmd_param* params, const char *name, ARRAY_TYPE(const_string)** value);
+bool doveadm_cmd_param_istream(int argc, struct doveadm_cmd_param* params, const char *name, struct istream** value);
+
 #endif
--- a/src/doveadm/doveadm-mail.c	Thu Feb 18 15:49:03 2016 +0200
+++ b/src/doveadm/doveadm-mail.c	Mon Feb 15 14:02:19 2016 +0200
@@ -534,6 +534,19 @@
 	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;
+	ctx->cur_username = getenv("USER");
+	return ctx;
+}
+
 static void
 doveadm_mail_cmd_exec(struct doveadm_mail_cmd_context *ctx,
 		      const char *wildcard_user)
@@ -599,20 +612,15 @@
 	const char *getopt_args, *wildcard_user;
 	int c;
 
-	ctx = doveadm_mail_cmd_init(cmd, doveadm_settings);
+	ctx = doveadm_mail_cmdline_init(cmd);
 	ctx->full_args = (const void *)(argv + 1);
 
-	ctx->service_flags |= MAIL_STORAGE_SERVICE_FLAG_NO_LOG_INIT;
-	if (doveadm_debug)
-		ctx->service_flags |= MAIL_STORAGE_SERVICE_FLAG_DEBUG;
-
 	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));
 
-	ctx->cur_username = getenv("USER");
 	wildcard_user = NULL;
 	while ((c = getopt(argc, argv, getopt_args)) > 0) {
 		switch (c) {
@@ -836,6 +844,9 @@
 	&cmd_dsync_server
 };
 
+static struct doveadm_cmd_ver2 *mail_commands_ver2[] = {
+};
+
 void doveadm_mail_init(void)
 {
 	struct module_dir_load_settings mod_set;
@@ -845,6 +856,9 @@
 	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;
@@ -864,3 +878,68 @@
 	mail_storage_hooks_deinit();
 	array_free(&doveadm_mail_cmds);
 }
+
+int
+doveadm_cmd_ver2_to_mail_cmd_wrapper(const struct doveadm_cmd_ver2* cmd,
+	int argc, const struct doveadm_cmd_param argv[])
+{
+	struct doveadm_mail_cmd_context *ctx;
+	const char *wildcard_user;
+	ARRAY_TYPE(const_string) pargv;
+	int i;
+	struct doveadm_mail_cmd mail_cmd = {
+		.alloc = cmd->mail_cmd
+	};
+
+	ctx = doveadm_mail_cmdline_init(&mail_cmd);
+
+	ctx->iterate_all_users = FALSE;
+	wildcard_user = NULL;
+	t_array_init(&pargv, 8);
+
+	for(i=0;i<argc;i++) {
+		if (!argv[i].value_set)
+			continue;
+
+		if (strcmp(argv[i].name, "all_users") == 0) {
+			ctx->iterate_all_users = argv[i].value.v_bool;
+		} else if (strcmp(argv[i].name, "socket_path") == 0) {
+			doveadm_settings->doveadm_socket_path = argv[i].value.v_string;
+			if (doveadm_settings->doveadm_worker_count == 0)
+				doveadm_settings->doveadm_worker_count = 1;
+		} else if (strcmp(argv[i].name, "user") == 0) {
+			ctx->service_flags |= MAIL_STORAGE_SERVICE_FLAG_USERDB_LOOKUP;
+			ctx->cur_username = argv[i].value.v_string;
+			if (strchr(ctx->cur_username, '*') != NULL ||
+			    strchr(ctx->cur_username, '?') != NULL) {
+				wildcard_user = ctx->cur_username;
+				ctx->cur_username = NULL;
+			}
+		} else if (strcmp(argv[i].name, "user_list") == 0) {
+			ctx->service_flags |= MAIL_STORAGE_SERVICE_FLAG_USERDB_LOOKUP;
+			wildcard_user = "*";
+			ctx->users_list_input = argv[i].value.v_istream;
+		} else if (ctx->v.parse_arg != NULL && argv[i].opt != NULL &&
+			   *(argv[i]).opt != '?' && *(argv[i]).opt != ':') {
+			optarg = (char*)argv[i].value.v_string;
+			ctx->v.parse_arg(ctx, *(argv[i].opt));
+		} else if ((argv[i].flags & CMD_PARAM_FLAG_POSITIONAL) != 0) {
+			/* feed this into pargv */
+			if (argv[i].type == CMD_PARAM_ARRAY)
+				array_append_array(&pargv, &argv[i].value.v_array);
+			else if (argv[i].type == CMD_PARAM_STR)
+				array_append(&pargv, &argv[i].value.v_string, 1);
+		} else {
+			doveadm_exit_code = EX_USAGE;
+			i_error("invalid parameter: %s", argv[i].name);
+			return -1;
+		}
+	}
+
+	array_append_zero(&pargv);
+	ctx->args = array_idx(&pargv, 0);
+	ctx->full_args = ctx->args;
+
+	doveadm_mail_cmd_exec(ctx, wildcard_user);
+	return 0;
+}
--- a/src/doveadm/doveadm-mail.h	Thu Feb 18 15:49:03 2016 +0200
+++ b/src/doveadm/doveadm-mail.h	Mon Feb 15 14:02:19 2016 +0200
@@ -195,4 +195,10 @@
 extern struct doveadm_mail_cmd cmd_mailbox_metadata_list;
 extern struct doveadm_mail_cmd cmd_batch;
 
+#define DOVEADM_CMD_MAIL_COMMON \
+DOVEADM_CMD_PARAM("A", "all-users", CMD_PARAM_BOOL, 0) \
+DOVEADM_CMD_PARAM("S:", "socket-path", CMD_PARAM_STR, 0) \
+DOVEADM_CMD_PARAM("u:", "user", CMD_PARAM_STR, 0) \
+DOVEADM_CMD_PARAM("F:", "user-file", CMD_PARAM_ISTREAM, 0)
+
 #endif
--- a/src/doveadm/doveadm.c	Thu Feb 18 15:49:03 2016 +0200
+++ b/src/doveadm/doveadm.c	Mon Feb 15 14:02:19 2016 +0200
@@ -144,6 +144,18 @@
 	help_to(cmd, stdout);
 }
 
+static void ATTR_NORETURN
+help_to_ver2(const struct doveadm_cmd_ver2 *cmd, FILE *out)
+{
+	fprintf(out, "doveadm %s %s\n", cmd->name, cmd->usage);
+	exit(EX_USAGE);
+}
+
+void help_ver2(const struct doveadm_cmd_ver2 *cmd)
+{
+	help_to_ver2(cmd, stdout);
+}
+
 static void cmd_help(int argc ATTR_UNUSED, char *argv[])
 {
 	const char *man_argv[3];
@@ -341,7 +353,8 @@
 		i_set_debug_file("/dev/null");
 	}
 
-	if (!doveadm_try_run(cmd_name, argc, argv) &&
+	if (!doveadm_cmd_try_run_ver2(cmd_name, argc, (const char**)argv) &&
+	    !doveadm_try_run(cmd_name, argc, argv) &&
 	    !doveadm_mail_try_run(cmd_name, argc, argv)) {
 		if (doveadm_has_subcommands(cmd_name))
 			usage_to(stdout, cmd_name);
--- a/src/doveadm/doveadm.h	Thu Feb 18 15:49:03 2016 +0200
+++ b/src/doveadm/doveadm.h	Mon Feb 15 14:02:19 2016 +0200
@@ -15,6 +15,7 @@
 
 void usage(void) ATTR_NORETURN;
 void help(const struct doveadm_cmd *cmd) ATTR_NORETURN;
+void help_ver2(const struct doveadm_cmd_ver2 *cmd) ATTR_NORETURN;
 void doveadm_master_send_signal(int signo);
 
 #endif