diff options
Diffstat (limited to 'src/doveadm/doveadm-dsync.c')
-rw-r--r-- | src/doveadm/doveadm-dsync.c | 1562 |
1 files changed, 1562 insertions, 0 deletions
diff --git a/src/doveadm/doveadm-dsync.c b/src/doveadm/doveadm-dsync.c new file mode 100644 index 0000000..e490def --- /dev/null +++ b/src/doveadm/doveadm-dsync.c @@ -0,0 +1,1562 @@ +/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "lib-signals.h" +#include "array.h" +#include "execv-const.h" +#include "child-wait.h" +#include "istream.h" +#include "ostream.h" +#include "iostream-ssl.h" +#include "iostream-rawlog.h" +#include "write-full.h" +#include "str.h" +#include "strescape.h" +#include "var-expand.h" +#include "process-title.h" +#include "settings-parser.h" +#include "imap-util.h" +#include "master-service.h" +#include "master-service-settings.h" +#include "master-service-ssl-settings.h" +#include "mail-storage.h" +#include "mail-storage-service.h" +#include "mail-user.h" +#include "mail-namespace.h" +#include "mailbox-list-private.h" +#include "doveadm-settings.h" +#include "doveadm-mail.h" +#include "doveadm-print.h" +#include "doveadm-server.h" +#include "client-connection.h" +#include "server-connection.h" +#include "dsync/dsync-brain.h" +#include "dsync/dsync-ibc.h" +#include "doveadm-dsync.h" + +#include <stdio.h> +#include <unistd.h> +#include <ctype.h> +#include <sys/wait.h> + +#define DSYNC_COMMON_GETOPT_ARGS "+1a:dDEfg:I:l:m:n:NO:Pr:Rs:t:e:T:Ux:" +#define DSYNC_REMOTE_CMD_EXIT_WAIT_SECS 30 +/* The default vname_escape_char to use unless overridden by BROKENCHAR + setting. Note that it's only used for internal dsync names, so it won't end + up in permanent storage names. The only requirement for it is that it's not + the same as the hierarchy separator. */ +#define DSYNC_LIST_VNAME_ESCAPE_CHAR '%' +/* In case DSYNC_LIST_VNAME_ESCAPE_CHAR is the hierarchy separator, + use this instead. */ +#define DSYNC_LIST_VNAME_ALT_ESCAPE_CHAR '~' + +#define DSYNC_DEFAULT_IO_STREAM_TIMEOUT_SECS (60*10) + +enum dsync_run_type { + DSYNC_RUN_TYPE_LOCAL, + DSYNC_RUN_TYPE_STREAM, + DSYNC_RUN_TYPE_CMD +}; + +struct dsync_cmd_context { + struct doveadm_mail_cmd_context ctx; + enum dsync_brain_sync_type sync_type; + const char *mailbox; + const char *sync_flags; + const char *virtual_all_box; + guid_128_t mailbox_guid; + const char *state_input, *rawlog_path; + ARRAY_TYPE(const_string) exclude_mailboxes; + ARRAY_TYPE(const_string) namespace_prefixes; + time_t sync_since_timestamp; + time_t sync_until_timestamp; + uoff_t sync_max_size; + unsigned int io_timeout_secs; + + const char *remote_name; + const char *local_location; + pid_t remote_pid; + const char *const *remote_cmd_args; + struct child_wait *child_wait; + int exit_status; + + int fd_in, fd_out, fd_err; + struct io *io_err; + struct istream *input, *err_stream; + struct ostream *output; + size_t input_orig_bufsize, output_orig_bufsize; + + struct ssl_iostream_context *ssl_ctx; + struct ssl_iostream *ssl_iostream; + + enum dsync_run_type run_type; + struct server_connection *tcp_conn; + const char *error; + + unsigned int lock_timeout; + unsigned int import_commit_msgs_interval; + + bool lock:1; + bool purge_remote:1; + bool sync_visible_namespaces:1; + bool default_replica_location:1; + bool oneway:1; + bool backup:1; + bool reverse_backup:1; + bool remote_user_prefix:1; + bool no_mail_sync:1; + bool local_location_from_arg:1; + bool replicator_notify:1; + bool exited:1; + bool empty_hdr_workaround:1; +}; + +static bool legacy_dsync = FALSE; + +static void dsync_cmd_switch_ioloop_to(struct dsync_cmd_context *ctx, + struct ioloop *ioloop) +{ + if (ctx->input != NULL) + i_stream_switch_ioloop_to(ctx->input, ioloop); + if (ctx->output != NULL) + o_stream_switch_ioloop_to(ctx->output, ioloop); +} + +static void remote_error_input(struct dsync_cmd_context *ctx) +{ + const unsigned char *data; + size_t size; + const char *line; + + switch (i_stream_read(ctx->err_stream)) { + case -2: + data = i_stream_get_data(ctx->err_stream, &size); + fprintf(stderr, "%.*s", (int)size, data); + i_stream_skip(ctx->err_stream, size); + break; + case -1: + io_remove(&ctx->io_err); + break; + default: + while ((line = i_stream_next_line(ctx->err_stream)) != NULL) + fprintf(stderr, "%s\n", line); + break; + } +} + +static void +run_cmd(struct dsync_cmd_context *ctx, const char *const *args) +{ + struct doveadm_cmd_context *cctx = ctx->ctx.cctx; + int fd_in[2], fd_out[2], fd_err[2]; + + ctx->remote_cmd_args = p_strarray_dup(ctx->ctx.pool, args); + + if (pipe(fd_in) < 0 || pipe(fd_out) < 0 || pipe(fd_err) < 0) + i_fatal("pipe() failed: %m"); + + ctx->remote_pid = fork(); + switch (ctx->remote_pid) { + case -1: + i_fatal("fork() failed: %m"); + case 0: + /* child, which will execute the proxy server. stdin/stdout + goes to pipes which we'll pass to proxy client. */ + if (dup2(fd_in[0], STDIN_FILENO) < 0 || + dup2(fd_out[1], STDOUT_FILENO) < 0 || + dup2(fd_err[1], STDERR_FILENO) < 0) + i_fatal("dup2() failed: %m"); + + i_close_fd(&fd_in[0]); + i_close_fd(&fd_in[1]); + i_close_fd(&fd_out[0]); + i_close_fd(&fd_out[1]); + i_close_fd(&fd_err[0]); + i_close_fd(&fd_err[1]); + + execvp_const(args[0], args); + default: + /* parent */ + break; + } + + i_close_fd(&fd_in[0]); + i_close_fd(&fd_out[1]); + i_close_fd(&fd_err[1]); + ctx->fd_in = fd_out[0]; + ctx->fd_out = fd_in[1]; + ctx->fd_err = fd_err[0]; + + if (ctx->remote_user_prefix) { + const char *prefix = + t_strdup_printf("%s\n", cctx->username); + if (write_full(ctx->fd_out, prefix, strlen(prefix)) < 0) + i_fatal("write(remote out) failed: %m"); + } + + fd_set_nonblock(ctx->fd_err, TRUE); + ctx->err_stream = i_stream_create_fd(ctx->fd_err, IO_BLOCK_SIZE); + i_stream_set_return_partial_line(ctx->err_stream, TRUE); +} + +static void +mirror_get_remote_cmd_line(const char *const *argv, + const char *const **cmd_args_r) +{ + ARRAY_TYPE(const_string) cmd_args; + unsigned int i; + const char *p; + + i_assert(argv[0] != NULL); + + t_array_init(&cmd_args, 16); + for (i = 0; argv[i] != NULL; i++) { + p = argv[i]; + array_push_back(&cmd_args, &p); + } + + if (legacy_dsync) { + /* we're executing dsync */ + p = "server"; + } else if (i > 0 && strcmp(argv[i-1], "dsync-server") == 0) { + /* Caller already specified dsync-server in parameters. + This is a common misconfiguration, so just allow it. */ + p = NULL; + } else { + /* we're executing doveadm */ + p = "dsync-server"; + } + if (p != NULL) + array_push_back(&cmd_args, &p); + array_append_zero(&cmd_args); + *cmd_args_r = array_front(&cmd_args); +} + +static const char *const * +get_ssh_cmd_args(const char *host, const char *login, const char *mail_user) +{ + static struct var_expand_table static_tab[] = { + { 'u', NULL, "user" }, + { '\0', NULL, "login" }, + { '\0', NULL, "host" }, + { '\0', NULL, NULL } + }; + struct var_expand_table *tab; + ARRAY_TYPE(const_string) cmd_args; + string_t *str, *str2; + const char *value, *const *args, *error; + + tab = t_malloc_no0(sizeof(static_tab)); + memcpy(tab, static_tab, sizeof(static_tab)); + + tab[0].value = mail_user; + tab[1].value = login; + tab[2].value = host; + + t_array_init(&cmd_args, 8); + str = t_str_new(128); + str2 = t_str_new(128); + args = t_strsplit(doveadm_settings->dsync_remote_cmd, " "); + for (; *args != NULL; args++) { + if (strchr(*args, '%') == NULL) + value = *args; + else { + /* some automation: if parameter's all %variables + expand to empty, but the %variable isn't the only + text in the parameter, skip it. */ + str_truncate(str, 0); + str_truncate(str2, 0); + if (var_expand(str, *args, tab, &error) <= 0 || + var_expand(str2, *args, static_tab, &error) <= 0) { + i_error("Failed to expand dsync_remote_cmd=%s: %s", + *args, error); + } + if (strcmp(str_c(str), str_c(str2)) == 0 && + str_len(str) > 0) + continue; + value = t_strdup(str_c(str)); + } + array_push_back(&cmd_args, &value); + } + array_append_zero(&cmd_args); + return array_front(&cmd_args); +} + +static bool mirror_get_remote_cmd(struct dsync_cmd_context *ctx, + const char *user, + const char *const **cmd_args_r) +{ + const char *p, *host, *const *argv = ctx->ctx.args; + + if (argv[1] != NULL) { + /* more than one parameter, so it contains a full command + (e.g. ssh host dsync) */ + mirror_get_remote_cmd_line(argv, cmd_args_r); + return TRUE; + } + + /* if it begins with /[a-z0-9]+:/, it's a mail location + (e.g. mdbox:~/mail) */ + for (p = argv[0]; *p != '\0'; p++) { + if (!i_isalnum(*p)) { + if (*p == ':') + return FALSE; + break; + } + } + + if (strchr(argv[0], ' ') != NULL || strchr(argv[0], '/') != NULL) { + /* a) the whole command is in one string. this is mainly for + backwards compatibility. + b) script/path */ + mirror_get_remote_cmd_line(t_strsplit(argv[0], " "), + cmd_args_r); + return TRUE; + } + + /* [user@]host */ + host = strchr(argv[0], '@'); + if (host != NULL) + user = t_strdup_until(argv[0], host++); + else + host = argv[0]; + + /* we'll assume virtual users, so in user@host it really means not to + give ssh a username, but to give dsync -u user parameter. */ + *cmd_args_r = get_ssh_cmd_args(host, "", user); + return TRUE; +} + +static void doveadm_user_init_dsync(struct mail_user *user) +{ + struct mail_namespace *ns; + char ns_sep = mail_namespaces_get_root_sep(user->namespaces); + + user->dsyncing = TRUE; + for (ns = user->namespaces; ns != NULL; ns = ns->next) { + struct dsync_mailbox_list *dlist = + p_new(ns->list->pool, struct dsync_mailbox_list, 1); + MODULE_CONTEXT_SET(ns->list, dsync_mailbox_list_module, dlist); + + if (ns->list->set.vname_escape_char == '\0') { + ns->list->set.vname_escape_char = + ns_sep != DSYNC_LIST_VNAME_ESCAPE_CHAR ? + DSYNC_LIST_VNAME_ESCAPE_CHAR : + DSYNC_LIST_VNAME_ALT_ESCAPE_CHAR; + } else { + dlist->have_orig_escape_char = TRUE; + } + } +} + +static bool +paths_are_equal(struct mail_namespace *ns1, struct mail_namespace *ns2, + enum mailbox_list_path_type type) +{ + const char *path1, *path2; + + return mailbox_list_get_root_path(ns1->list, type, &path1) && + mailbox_list_get_root_path(ns2->list, type, &path2) && + strcmp(path1, path2) == 0; +} + +static int +get_dsync_verify_namespace(struct dsync_cmd_context *ctx, + struct mail_user *user, struct mail_namespace **ns_r) +{ + struct mail_namespace *ns; + + /* Use the first -n namespace if given */ + if (array_count(&ctx->namespace_prefixes) > 0) { + const char *prefix = + array_idx_elem(&ctx->namespace_prefixes, 0); + ns = mail_namespace_find(user->namespaces, prefix); + if (ns == NULL) { + i_error("Namespace not found: '%s'", prefix); + ctx->ctx.exit_code = DOVEADM_EX_NOTFOUND; + return -1; + } + *ns_r = ns; + return 0; + } + + /* Prefer prefix="" namespace over inbox=yes namespace. Either it uses + the global mail_location, which is good, or it might have + overwritten location in case of e.g. using subscriptions file for + all namespaces. This isn't necessarily obvious, so lets make it + clearer by failing if it happens. */ + if ((user->namespaces->flags & NAMESPACE_FLAG_UNUSABLE) == 0) { + *ns_r = user->namespaces; + i_assert((*ns_r)->prefix_len == 0); + } else { + /* fallback to inbox=yes */ + *ns_r = mail_namespace_find_inbox(user->namespaces); + } + return 0; +} + +static int +cmd_dsync_run_local(struct dsync_cmd_context *ctx, struct mail_user *user, + struct dsync_brain *brain, struct dsync_ibc *ibc2, + const char **changes_during_sync_r, + enum mail_error *mail_error_r) +{ + struct dsync_brain *brain2; + struct mail_user *user2; + struct mail_namespace *ns, *ns2; + struct setting_parser_context *set_parser; + const char *location, *error; + bool brain1_running, brain2_running, changed1, changed2; + bool remote_only_changes; + int ret; + + *mail_error_r = 0; + + if (ctx->local_location_from_arg) + location = ctx->ctx.args[0]; + else { + i_assert(ctx->local_location != NULL); + location = ctx->local_location; + } + + i_set_failure_prefix("dsync(%s): ", user->username); + + /* update mail_location and create another user for the + second location. */ + set_parser = mail_storage_service_user_get_settings_parser(ctx->ctx.cur_service_user); + if (settings_parse_keyvalue(set_parser, "mail_location", location) < 0) + i_unreached(); + ret = mail_storage_service_next(ctx->ctx.storage_service, + ctx->ctx.cur_service_user, + &user2, &error); + if (ret < 0) { + i_error("Failed to initialize user: %s", error); + ctx->ctx.exit_code = ret == -1 ? EX_TEMPFAIL : EX_CONFIG; + return -1; + } + doveadm_user_init_dsync(user2); + + if (get_dsync_verify_namespace(ctx, user, &ns) < 0 || + get_dsync_verify_namespace(ctx, user2, &ns2) < 0) + return -1; + if (mail_namespace_get_sep(ns) != mail_namespace_get_sep(ns2)) { + i_error("Mail locations must use the same hierarchy separator " + "(specify namespace prefix=\"%s\" " + "{ separator } explicitly)", ns->prefix); + ctx->ctx.exit_code = EX_CONFIG; + mail_user_deinit(&user2); + return -1; + } + if (paths_are_equal(ns, ns2, MAILBOX_LIST_PATH_TYPE_MAILBOX) && + paths_are_equal(ns, ns2, MAILBOX_LIST_PATH_TYPE_INDEX)) { + i_error("Both source and destination mail_location " + "points to same directory: %s (namespace " + "prefix=\"%s\" { location } is set explicitly?)", + mailbox_list_get_root_forced(user->namespaces->list, + MAILBOX_LIST_PATH_TYPE_MAILBOX), + ns->prefix); + ctx->ctx.exit_code = EX_CONFIG; + mail_user_deinit(&user2); + return -1; + } + + brain2 = dsync_brain_slave_init(user2, ibc2, TRUE, "", + doveadm_settings->dsync_alt_char[0]); + mail_user_unref(&user2); + + brain1_running = brain2_running = TRUE; + changed1 = changed2 = TRUE; + while (brain1_running || brain2_running) { + if (dsync_brain_has_failed(brain) || + dsync_brain_has_failed(brain2)) + break; + if (doveadm_is_killed()) { + i_warning("Killed with signal %d", doveadm_killed_signo()); + break; + } + + i_assert(changed1 || changed2); + brain1_running = dsync_brain_run(brain, &changed1); + brain2_running = dsync_brain_run(brain2, &changed2); + } + *changes_during_sync_r = t_strdup(dsync_brain_get_unexpected_changes_reason(brain2, &remote_only_changes)); + if (dsync_brain_deinit(&brain2, mail_error_r) < 0) + return -1; + return doveadm_is_killed() ? -1 : 0; +} + +static void cmd_dsync_remote_exited(const struct child_wait_status *status, + struct dsync_cmd_context *ctx) +{ + ctx->exited = TRUE; + ctx->exit_status = status->status; + io_loop_stop(current_ioloop); +} + +static void cmd_dsync_wait_remote(struct dsync_cmd_context *ctx) +{ + struct timeout *to; + + /* wait in ioloop for the remote process to die. while we're running + we're also reading and printing all errors that still coming from + it. */ + to = timeout_add(DSYNC_REMOTE_CMD_EXIT_WAIT_SECS*1000, + io_loop_stop, current_ioloop); + io_loop_run(current_ioloop); + timeout_remove(&to); + + /* io_loop_run() deactivates the context - put it back */ + mail_storage_service_io_activate_user(ctx->ctx.cur_service_user); + + if (!ctx->exited) { + i_error("Remote command process isn't dying, killing it"); + if (kill(ctx->remote_pid, SIGKILL) < 0 && errno != ESRCH) { + i_error("kill(%ld, SIGKILL) failed: %m", + (long)ctx->remote_pid); + } + } +} + +static void cmd_dsync_log_remote_status(int status, bool remote_errors_logged, + const char *const *remote_cmd_args) +{ + if (status == -1) + ; + else if (WIFSIGNALED(status)) { + i_error("Remote command died with signal %d: %s", WTERMSIG(status), + t_strarray_join(remote_cmd_args, " ")); + } else if (!WIFEXITED(status)) { + i_error("Remote command failed with status %d: %s", status, + t_strarray_join(remote_cmd_args, " ")); + } else if (WEXITSTATUS(status) == EX_TEMPFAIL && remote_errors_logged) { + /* remote most likely already logged the error. + don't bother logging another line about it */ + } else if (WEXITSTATUS(status) != 0) { + i_error("Remote command returned error %d: %s", WEXITSTATUS(status), + t_strarray_join(remote_cmd_args, " ")); + } +} + +static void cmd_dsync_run_remote(struct mail_user *user) +{ + i_set_failure_prefix("dsync-local(%s)<%s>: ", user->username, user->session_id); + io_loop_run(current_ioloop); +} + +static const char *const * +parse_ssh_location(const char *location, const char *username) +{ + const char *host, *login; + + host = strrchr(location, '@'); + if (host != NULL) + login = t_strdup_until(location, host++); + else { + host = location; + login = ""; + } + return get_ssh_cmd_args(host, login, username); +} + +static struct dsync_ibc * +cmd_dsync_ibc_stream_init(struct dsync_cmd_context *ctx, + const char *name, const char *temp_prefix) +{ + if (ctx->input == NULL) { + fd_set_nonblock(ctx->fd_in, TRUE); + fd_set_nonblock(ctx->fd_out, TRUE); + ctx->input = i_stream_create_fd(ctx->fd_in, SIZE_MAX); + ctx->output = o_stream_create_fd(ctx->fd_out, SIZE_MAX); + } else { + i_assert(ctx->fd_in == -1 && ctx->fd_out == -1); + ctx->fd_in = i_stream_get_fd(ctx->input); + ctx->fd_out = o_stream_get_fd(ctx->output); + ctx->input_orig_bufsize = i_stream_get_max_buffer_size(ctx->input); + ctx->output_orig_bufsize = o_stream_get_max_buffer_size(ctx->output); + i_stream_set_max_buffer_size(ctx->input, SIZE_MAX); + o_stream_set_max_buffer_size(ctx->output, SIZE_MAX); + } + if (ctx->rawlog_path != NULL) { + iostream_rawlog_create_path(ctx->rawlog_path, + &ctx->input, &ctx->output); + } + return dsync_ibc_init_stream(ctx->input, ctx->output, + name, temp_prefix, ctx->io_timeout_secs); +} + +static void +dsync_replicator_notify(struct dsync_cmd_context *ctx, + enum dsync_brain_sync_type sync_type, + const char *state_str) +{ +#define REPLICATOR_HANDSHAKE "VERSION\treplicator-doveadm-client\t1\t0\n" + const char *path; + string_t *str; + int fd; + + path = t_strdup_printf("%s/replicator-doveadm", + ctx->ctx.cur_mail_user->set->base_dir); + fd = net_connect_unix(path); + if (fd == -1) { + if (errno == ECONNREFUSED || errno == ENOENT) { + /* replicator not running on this server. ignore. */ + return; + } + i_error("net_connect_unix(%s) failed: %m", path); + return; + } + fd_set_nonblock(fd, FALSE); + + str = t_str_new(128); + str_append(str, REPLICATOR_HANDSHAKE"NOTIFY\t"); + str_append_tabescaped(str, ctx->ctx.cur_mail_user->username); + str_append_c(str, '\t'); + if (sync_type == DSYNC_BRAIN_SYNC_TYPE_FULL) + str_append_c(str, 'f'); + str_append_c(str, '\t'); + str_append_tabescaped(str, state_str); + str_append_c(str, '\n'); + if (write_full(fd, str_data(str), str_len(str)) < 0) + i_error("write(%s) failed: %m", path); + /* we only wanted to notify replicator. we don't care enough about the + answer to wait for it. */ + if (close(fd) < 0) + i_error("close(%s) failed: %m", path); +} + +static int +cmd_dsync_run(struct doveadm_mail_cmd_context *_ctx, struct mail_user *user) +{ + struct dsync_cmd_context *ctx = (struct dsync_cmd_context *)_ctx; + struct doveadm_cmd_context *cctx = _ctx->cctx; + struct dsync_ibc *ibc, *ibc2 = NULL; + struct dsync_brain *brain; + struct dsync_brain_settings set; + struct mail_namespace *ns; + const char *const *strp; + enum dsync_brain_flags brain_flags; + enum mail_error mail_error = 0, mail_error2; + bool remote_errors_logged = FALSE; + bool cli = (cctx->conn_type == DOVEADM_CONNECTION_TYPE_CLI); + const char *changes_during_sync, *changes_during_sync2 = NULL; + bool remote_only_changes; + int ret = 0; + + /* replicator_notify indicates here automated attempt, + we still want to allow manual sync/backup */ + if (!cli && ctx->replicator_notify && + mail_user_plugin_getenv_bool(_ctx->cur_mail_user, "noreplicate")) { + ctx->ctx.exit_code = DOVEADM_EX_NOREPLICATE; + return -1; + } + + i_zero(&set); + if (cctx->remote_ip.family != 0) { + /* include the doveadm client's IP address in the ps output */ + set.process_title_prefix = t_strdup_printf( + "%s ", net_ip2addr(&cctx->remote_ip)); + } + set.sync_since_timestamp = ctx->sync_since_timestamp; + set.sync_until_timestamp = ctx->sync_until_timestamp; + + if (set.sync_since_timestamp > 0 && set.sync_until_timestamp > 0 && + set.sync_since_timestamp > set.sync_until_timestamp) { + i_fatal("start date is later than end date"); + } + + set.sync_max_size = ctx->sync_max_size; + set.sync_box = ctx->mailbox; + set.sync_flag = ctx->sync_flags; + set.virtual_all_box = ctx->virtual_all_box; + memcpy(set.sync_box_guid, ctx->mailbox_guid, sizeof(set.sync_box_guid)); + set.lock_timeout_secs = ctx->lock_timeout; + set.import_commit_msgs_interval = ctx->import_commit_msgs_interval; + set.state = ctx->state_input; + set.mailbox_alt_char = doveadm_settings->dsync_alt_char[0]; + if (*doveadm_settings->dsync_hashed_headers == '\0') { + i_error("dsync_hashed_headers must not be empty"); + ctx->ctx.exit_code = EX_USAGE; + return -1; + } + set.hashed_headers = + t_strsplit_spaces(doveadm_settings->dsync_hashed_headers, " ,"); + if (array_count(&ctx->exclude_mailboxes) > 0) { + /* array is NULL-terminated in init() */ + set.exclude_mailboxes = array_front(&ctx->exclude_mailboxes); + } + doveadm_user_init_dsync(user); + + t_array_init(&set.sync_namespaces, array_count(&ctx->namespace_prefixes)); + array_foreach(&ctx->namespace_prefixes, strp) { + ns = mail_namespace_find(user->namespaces, *strp); + if (ns == NULL) { + i_error("Namespace not found: '%s'", *strp); + ctx->ctx.exit_code = EX_USAGE; + return -1; + } + array_push_back(&set.sync_namespaces, &ns); + } + + if (ctx->run_type == DSYNC_RUN_TYPE_LOCAL) + dsync_ibc_init_pipe(&ibc, &ibc2); + else { + string_t *temp_prefix = t_str_new(64); + mail_user_set_get_temp_prefix(temp_prefix, user->set); + ibc = cmd_dsync_ibc_stream_init(ctx, ctx->remote_name, + str_c(temp_prefix)); + if (ctx->fd_err != -1) { + ctx->io_err = io_add(ctx->fd_err, IO_READ, + remote_error_input, ctx); + } + } + + brain_flags = DSYNC_BRAIN_FLAG_SEND_MAIL_REQUESTS; + if (ctx->sync_visible_namespaces) + brain_flags |= DSYNC_BRAIN_FLAG_SYNC_VISIBLE_NAMESPACES; + if (ctx->purge_remote) + brain_flags |= DSYNC_BRAIN_FLAG_PURGE_REMOTE; + + if (ctx->backup) { + if (ctx->reverse_backup) + brain_flags |= DSYNC_BRAIN_FLAG_BACKUP_RECV; + else + brain_flags |= DSYNC_BRAIN_FLAG_BACKUP_SEND; + } + + if (ctx->no_mail_sync) + brain_flags |= DSYNC_BRAIN_FLAG_NO_MAIL_SYNC; + if (ctx->oneway) + brain_flags |= DSYNC_BRAIN_FLAG_NO_BACKUP_OVERWRITE; + if (ctx->empty_hdr_workaround) + brain_flags |= DSYNC_BRAIN_FLAG_EMPTY_HDR_WORKAROUND; + if (doveadm_debug) + brain_flags |= DSYNC_BRAIN_FLAG_DEBUG; + + child_wait_init(); + brain = dsync_brain_master_init(user, ibc, ctx->sync_type, + brain_flags, &set); + + switch (ctx->run_type) { + case DSYNC_RUN_TYPE_LOCAL: + if (cmd_dsync_run_local(ctx, user, brain, ibc2, + &changes_during_sync2, &mail_error) < 0) + ret = -1; + break; + case DSYNC_RUN_TYPE_CMD: + ctx->child_wait = child_wait_new_with_pid(ctx->remote_pid, + cmd_dsync_remote_exited, ctx); + /* fall through */ + case DSYNC_RUN_TYPE_STREAM: + cmd_dsync_run_remote(user); + /* io_loop_run() deactivates the context - put it back */ + mail_storage_service_io_activate_user(ctx->ctx.cur_service_user); + break; + } + + if (ctx->state_input != NULL) { + string_t *state_str = t_str_new(128); + dsync_brain_get_state(brain, state_str); + doveadm_print(str_c(state_str)); + } + + changes_during_sync = dsync_brain_get_unexpected_changes_reason(brain, &remote_only_changes); + if (changes_during_sync != NULL || changes_during_sync2 != NULL) { + /* don't log a warning when running via doveadm server + (e.g. called by replicator) */ + if (cctx->conn_type == DOVEADM_CONNECTION_TYPE_CLI) { + i_warning("Mailbox changes caused a desync. " + "You may want to run dsync again: %s", + changes_during_sync == NULL || + (remote_only_changes && changes_during_sync2 != NULL) ? + changes_during_sync2 : changes_during_sync); + } + ctx->ctx.exit_code = 2; + } + if (dsync_brain_deinit(&brain, &mail_error2) < 0) + ret = -1; + if (ret < 0) { + /* tempfail is the default error. prefer to use a non-tempfail + if that exists. */ + if (mail_error2 != 0 && + (mail_error == 0 || mail_error == MAIL_ERROR_TEMP)) + mail_error = mail_error2; + doveadm_mail_failed_error(&ctx->ctx, mail_error); + } + dsync_ibc_deinit(&ibc); + if (ibc2 != NULL) + dsync_ibc_deinit(&ibc2); + ssl_iostream_destroy(&ctx->ssl_iostream); + if (ctx->ssl_ctx != NULL) + ssl_iostream_context_unref(&ctx->ssl_ctx); + if (ctx->input != NULL) { + i_stream_set_max_buffer_size(ctx->input, ctx->input_orig_bufsize); + i_stream_unref(&ctx->input); + } + if (ctx->output != NULL) { + o_stream_set_max_buffer_size(ctx->output, ctx->output_orig_bufsize); + o_stream_unref(&ctx->output); + } + if (ctx->fd_in != -1) { + if (ctx->fd_out != ctx->fd_in) + i_close_fd(&ctx->fd_out); + i_close_fd(&ctx->fd_in); + } + /* print any final errors after the process has died. not closing + stdin/stdout before wait() may cause the process to hang, but stderr + shouldn't (at least with ssh) and we need stderr to be open to be + able to print the final errors */ + if (ctx->run_type == DSYNC_RUN_TYPE_CMD) { + cmd_dsync_wait_remote(ctx); + remote_error_input(ctx); + remote_errors_logged = ctx->err_stream->v_offset > 0; + i_stream_destroy(&ctx->err_stream); + cmd_dsync_log_remote_status(ctx->exit_status, remote_errors_logged, + ctx->remote_cmd_args); + } else { + i_assert(ctx->err_stream == NULL); + } + io_remove(&ctx->io_err); + i_close_fd(&ctx->fd_err); + + if (ctx->child_wait != NULL) + child_wait_free(&ctx->child_wait); + child_wait_deinit(); + return ret; +} + +static void dsync_connected_callback(int exit_code, const char *error, + void *context) +{ + struct dsync_cmd_context *ctx = context; + + ctx->ctx.exit_code = exit_code; + switch (exit_code) { + case 0: + server_connection_extract(ctx->tcp_conn, &ctx->input, + &ctx->output, &ctx->ssl_iostream); + break; + case SERVER_EXIT_CODE_DISCONNECTED: + ctx->ctx.exit_code = EX_TEMPFAIL; + ctx->error = p_strdup_printf(ctx->ctx.pool, + "Disconnected from remote: %s", error); + break; + case EX_NOUSER: + ctx->error = "Unknown user in remote"; + break; + case DOVEADM_EX_NOREPLICATE: + if (doveadm_debug) + i_debug("user is disabled for replication"); + break; + default: + ctx->error = p_strdup_printf(ctx->ctx.pool, + "Failed to start remote dsync-server command: " + "Remote exit_code=%u %s", + exit_code, error == NULL ? "" : error); + break; + } + io_loop_stop(current_ioloop); +} + +static int dsync_init_ssl_ctx(struct dsync_cmd_context *ctx, + const struct master_service_ssl_settings *ssl_set, + const char **error_r) +{ + struct ssl_iostream_settings ssl_ctx_set; + + if (ctx->ssl_ctx != NULL) + return 0; + + master_service_ssl_client_settings_to_iostream_set(ssl_set, + pool_datastack_create(), &ssl_ctx_set); + return ssl_iostream_client_context_cache_get(&ssl_ctx_set, + &ctx->ssl_ctx, error_r); +} + +static void dsync_server_run_command(struct dsync_cmd_context *ctx, + struct server_connection *conn) +{ + struct doveadm_cmd_context *cctx = ctx->ctx.cctx; + /* <flags> <username> <command> [<args>] */ + string_t *cmd = t_str_new(256); + if (doveadm_debug) + str_append_c(cmd, 'D'); + str_append_c(cmd, '\t'); + str_append_tabescaped(cmd, cctx->username); + str_append(cmd, "\tdsync-server\t-u"); + str_append_tabescaped(cmd, cctx->username); + if (ctx->replicator_notify) + str_append(cmd, "\t-U"); + str_append_c(cmd, '\n'); + + ctx->tcp_conn = conn; + server_connection_cmd(conn, str_c(cmd), NULL, + dsync_connected_callback, ctx); + io_loop_run(current_ioloop); + ctx->tcp_conn = NULL; +} + +static int +dsync_connect_tcp(struct dsync_cmd_context *ctx, + const struct master_service_ssl_settings *ssl_set, + const char *target, bool ssl, const char **error_r) +{ + struct doveadm_server *server; + struct server_connection *conn; + struct ioloop *prev_ioloop, *ioloop; + const char *p, *error; + + server = p_new(ctx->ctx.pool, struct doveadm_server, 1); + server->name = p_strdup(ctx->ctx.pool, target); + p = strrchr(server->name, ':'); + server->hostname = p == NULL ? server->name : + p_strdup_until(ctx->ctx.pool, server->name, p); + if (ssl) { + if (dsync_init_ssl_ctx(ctx, ssl_set, &error) < 0) { + *error_r = t_strdup_printf( + "Couldn't initialize SSL context: %s", error); + return -1; + } + server->ssl_flags = PROXY_SSL_FLAG_YES; + server->ssl_ctx = ctx->ssl_ctx; + } + p_array_init(&server->connections, ctx->ctx.pool, 1); + p_array_init(&server->queue, ctx->ctx.pool, 1); + + prev_ioloop = current_ioloop; + ioloop = io_loop_create(); + dsync_cmd_switch_ioloop_to(ctx, ioloop); + + if (doveadm_verbose_proctitle) { + process_title_set(t_strdup_printf( + "[dsync - connecting to %s]", server->name)); + } + if (server_connection_create(server, &conn, &error) < 0) { + ctx->error = p_strdup_printf(ctx->ctx.pool, + "Couldn't create server connection: %s", error); + } else { + if (doveadm_verbose_proctitle) { + process_title_set(t_strdup_printf( + "[dsync - running dsync-server on %s]", server->name)); + } + + dsync_server_run_command(ctx, conn); + } + + if (array_count(&server->connections) > 0) + server_connection_destroy(&conn); + + dsync_cmd_switch_ioloop_to(ctx, prev_ioloop); + io_loop_destroy(&ioloop); + + if (ctx->error != NULL) { + ssl_iostream_context_unref(&ctx->ssl_ctx); + *error_r = ctx->error; + ctx->error = NULL; + return -1; + } + ctx->run_type = DSYNC_RUN_TYPE_STREAM; + return 0; +} + +static int +parse_location(struct dsync_cmd_context *ctx, + const struct master_service_ssl_settings *ssl_set, + const char *location, + const char *const **remote_cmd_args_r, const char **error_r) +{ + struct doveadm_cmd_context *cctx = ctx->ctx.cctx; + + if (str_begins(location, "tcp:")) { + /* TCP connection to remote dsync */ + ctx->remote_name = location+4; + return dsync_connect_tcp(ctx, ssl_set, ctx->remote_name, + FALSE, error_r); + } + if (str_begins(location, "tcps:")) { + /* TCP+SSL connection to remote dsync */ + ctx->remote_name = location+5; + return dsync_connect_tcp(ctx, ssl_set, ctx->remote_name, + TRUE, error_r); + } + + if (str_begins(location, "remote:")) { + /* this is a remote (ssh) command */ + ctx->remote_name = location+7; + } else if (str_begins(location, "remoteprefix:")) { + /* this is a remote (ssh) command with a "user\n" + prefix sent before dsync actually starts */ + ctx->remote_name = location+13; + ctx->remote_user_prefix = TRUE; + } else { + /* local with e.g. maildir:path */ + ctx->remote_name = NULL; + return 0; + } + *remote_cmd_args_r = + parse_ssh_location(ctx->remote_name, cctx->username); + return 0; +} + +static int cmd_dsync_prerun(struct doveadm_mail_cmd_context *_ctx, + struct mail_storage_service_user *service_user, + const char **error_r) +{ + struct dsync_cmd_context *ctx = (struct dsync_cmd_context *)_ctx; + struct doveadm_cmd_context *cctx = _ctx->cctx; + const char *const *remote_cmd_args = NULL; + const struct mail_user_settings *user_set; + const struct master_service_ssl_settings *ssl_set; + const char *username = ""; + + user_set = mail_storage_service_user_get_set(service_user)[0]; + ssl_set = mail_storage_service_user_get_ssl_settings(service_user); + + ctx->fd_in = -1; + ctx->fd_out = -1; + ctx->fd_err = -1; + ctx->run_type = DSYNC_RUN_TYPE_LOCAL; + ctx->remote_name = "remote"; + + if (ctx->default_replica_location) { + ctx->local_location = + mail_user_set_plugin_getenv(user_set, "mail_replica"); + if (ctx->local_location == NULL || + *ctx->local_location == '\0') { + *error_r = "User has no mail_replica in userdb"; + _ctx->exit_code = DOVEADM_EX_NOTFOUND; + return -1; + } + } else { + /* if we're executing remotely, give -u parameter if we also + did a userdb lookup. */ + if ((_ctx->service_flags & MAIL_STORAGE_SERVICE_FLAG_USERDB_LOOKUP) != 0) + username = cctx->username; + + if (!mirror_get_remote_cmd(ctx, username, &remote_cmd_args)) { + /* it's a mail_location */ + if (_ctx->args[1] != NULL) + doveadm_mail_help_name(_ctx->cmd->name); + ctx->local_location = _ctx->args[0]; + ctx->local_location_from_arg = TRUE; + } + } + + if (remote_cmd_args == NULL && ctx->local_location != NULL) { + if (parse_location(ctx, ssl_set, ctx->local_location, + &remote_cmd_args, error_r) < 0) + return -1; + } + + if (remote_cmd_args != NULL) { + /* do this before mail_storage_service_next() in case it + drops process privileges */ + run_cmd(ctx, remote_cmd_args); + ctx->run_type = DSYNC_RUN_TYPE_CMD; + } + + if (ctx->sync_visible_namespaces && + ctx->run_type == DSYNC_RUN_TYPE_LOCAL) + i_fatal("-N parameter requires syncing with remote host"); + return 0; +} + +static void cmd_dsync_init(struct doveadm_mail_cmd_context *_ctx, + const char *const args[]) +{ + struct dsync_cmd_context *ctx = (struct dsync_cmd_context *)_ctx; + + if (ctx->default_replica_location) { + if (args[0] != NULL) + i_error("Don't give mail location with -d parameter"); + } else { + if (args[0] == NULL) + doveadm_mail_help_name(_ctx->cmd->name); + } + if (array_count(&ctx->exclude_mailboxes) > 0) + array_append_zero(&ctx->exclude_mailboxes); + + lib_signals_ignore(SIGHUP, TRUE); +} + +static void cmd_dsync_preinit(struct doveadm_mail_cmd_context *ctx) +{ + if ((ctx->service_flags & MAIL_STORAGE_SERVICE_FLAG_USERDB_LOOKUP) == 0) + ctx->service_flags |= MAIL_STORAGE_SERVICE_FLAG_NO_CHDIR; +} + +static bool +cmd_mailbox_dsync_parse_arg(struct doveadm_mail_cmd_context *_ctx, int c) +{ + struct dsync_cmd_context *ctx = (struct dsync_cmd_context *)_ctx; + const char *str, *error; + bool utc; + + switch (c) { + case '1': + ctx->oneway = TRUE; + ctx->backup = TRUE; + break; + case 'a': + ctx->virtual_all_box = optarg; + break; + case 'd': + ctx->default_replica_location = TRUE; + break; + case 'E': + /* dsync wrapper detection flag */ + legacy_dsync = TRUE; + break; + case 'f': + ctx->sync_type = DSYNC_BRAIN_SYNC_TYPE_FULL; + break; + case 'O': { + const char *str = optarg; + + if (strchr(str, ' ') != NULL) + i_fatal("-O parameter doesn't support multiple flags currently"); + if (str[0] == '-') + str++; + if (str[0] == '\\' && imap_parse_system_flag(str) == 0) + i_fatal("Invalid system flag given for -O parameter: '%s'", str); + ctx->sync_flags = optarg; + break; + } + case 'g': + if (optarg[0] == '\0') + ctx->no_mail_sync = TRUE; + else if (guid_128_from_string(optarg, ctx->mailbox_guid) < 0 || + guid_128_is_empty(ctx->mailbox_guid)) + i_fatal("Invalid -g parameter: %s", optarg); + break; + case 'l': + ctx->lock = TRUE; + if (str_to_uint(optarg, &ctx->lock_timeout) < 0) + i_fatal("Invalid -l parameter: %s", optarg); + break; + case 'm': + if (optarg[0] == '\0') + ctx->no_mail_sync = TRUE; + else + ctx->mailbox = optarg; + break; + case 'x': + str = optarg; + array_push_back(&ctx->exclude_mailboxes, &str); + break; + case 'n': + str = optarg; + array_push_back(&ctx->namespace_prefixes, &str); + break; + case 'N': + ctx->sync_visible_namespaces = TRUE; + break; + case 'P': + ctx->purge_remote = TRUE; + break; + case 'r': + ctx->rawlog_path = optarg; + break; + case 'R': + ctx->reverse_backup = TRUE; + break; + case 's': + if (ctx->sync_type != DSYNC_BRAIN_SYNC_TYPE_FULL && + *optarg != '\0') + ctx->sync_type = DSYNC_BRAIN_SYNC_TYPE_STATE; + ctx->state_input = optarg; + break; + case 't': + if (mail_parse_human_timestamp(optarg, &ctx->sync_since_timestamp, &utc) < 0) + i_fatal("Invalid -t parameter: %s", optarg); + break; + case 'e': + if (mail_parse_human_timestamp(optarg, &ctx->sync_until_timestamp, &utc) < 0) + i_fatal("Invalid -e parameter: %s", optarg); + break; + case 'I': + if (settings_get_size(optarg, &ctx->sync_max_size, &error) < 0) + i_fatal("Invalid -I parameter '%s': %s", optarg, error); + break; + case 'T': + if (str_to_uint(optarg, &ctx->io_timeout_secs) < 0) + i_fatal("Invalid -T parameter: %s", optarg); + break; + case 'U': + ctx->replicator_notify = TRUE; + break; + default: + return FALSE; + } + return TRUE; +} + +static struct doveadm_mail_cmd_context *cmd_dsync_alloc(void) +{ + struct dsync_cmd_context *ctx; + + ctx = doveadm_mail_cmd_alloc(struct dsync_cmd_context); + ctx->io_timeout_secs = DSYNC_DEFAULT_IO_STREAM_TIMEOUT_SECS; + ctx->ctx.getopt_args = DSYNC_COMMON_GETOPT_ARGS; + ctx->ctx.v.parse_arg = cmd_mailbox_dsync_parse_arg; + ctx->ctx.v.preinit = cmd_dsync_preinit; + ctx->ctx.v.init = cmd_dsync_init; + ctx->ctx.v.prerun = cmd_dsync_prerun; + ctx->ctx.v.run = cmd_dsync_run; + ctx->sync_type = DSYNC_BRAIN_SYNC_TYPE_CHANGED; + doveadm_print_init(DOVEADM_PRINT_TYPE_FLOW); + doveadm_print_header("state", "state", + DOVEADM_PRINT_HEADER_FLAG_HIDE_TITLE); + p_array_init(&ctx->exclude_mailboxes, ctx->ctx.pool, 4); + p_array_init(&ctx->namespace_prefixes, ctx->ctx.pool, 4); + if ((doveadm_settings->parsed_features & DSYNC_FEATURE_EMPTY_HDR_WORKAROUND) != 0) + ctx->empty_hdr_workaround = TRUE; + ctx->import_commit_msgs_interval = doveadm_settings->dsync_commit_msgs_interval; + return &ctx->ctx; +} + +static struct doveadm_mail_cmd_context *cmd_dsync_backup_alloc(void) +{ + struct doveadm_mail_cmd_context *_ctx; + struct dsync_cmd_context *ctx; + + _ctx = cmd_dsync_alloc(); + ctx = (struct dsync_cmd_context *)_ctx; + ctx->backup = TRUE; + return _ctx; +} + +static int +cmd_dsync_server_run(struct doveadm_mail_cmd_context *_ctx, + struct mail_user *user) +{ + struct dsync_cmd_context *ctx = (struct dsync_cmd_context *)_ctx; + struct doveadm_cmd_context *cctx = _ctx->cctx; + bool cli = (cctx->conn_type == DOVEADM_CONNECTION_TYPE_CLI); + struct dsync_ibc *ibc; + struct dsync_brain *brain; + string_t *temp_prefix, *state_str = NULL; + enum dsync_brain_sync_type sync_type; + const char *name, *process_title_prefix = ""; + enum mail_error mail_error; + + if (!cli) { + /* replicator_notify indicates here automated attempt, + we still want to allow manual sync/backup */ + if (ctx->replicator_notify && + mail_user_plugin_getenv_bool(_ctx->cur_mail_user, "noreplicate")) { + _ctx->exit_code = DOVEADM_EX_NOREPLICATE; + return -1; + } + + /* doveadm-server connection. start with a success reply. + after that follows the regular dsync protocol. */ + ctx->fd_in = ctx->fd_out = -1; + ctx->input = cctx->input; + ctx->output = cctx->output; + i_stream_ref(ctx->input); + o_stream_ref(ctx->output); + o_stream_set_finish_also_parent(ctx->output, FALSE); + o_stream_nsend(ctx->output, "\n+\n", 3); + i_set_failure_prefix("dsync-server(%s): ", user->username); + name = i_stream_get_name(ctx->input); + + if (cctx->remote_ip.family != 0) { + /* include the doveadm client's IP address in the ps output */ + process_title_prefix = t_strdup_printf( + "%s ", net_ip2addr(&cctx->remote_ip)); + } + } else { + /* the log messages go via stderr to the remote dsync, + so the names are reversed */ + i_set_failure_prefix("dsync-remote(%s)<%s>: ", user->username, user->session_id); + name = "local"; + } + + doveadm_user_init_dsync(user); + + temp_prefix = t_str_new(64); + mail_user_set_get_temp_prefix(temp_prefix, user->set); + + ibc = cmd_dsync_ibc_stream_init(ctx, name, str_c(temp_prefix)); + brain = dsync_brain_slave_init(user, ibc, FALSE, process_title_prefix, + doveadm_settings->dsync_alt_char[0]); + + io_loop_run(current_ioloop); + /* io_loop_run() deactivates the context - put it back */ + mail_storage_service_io_activate_user(ctx->ctx.cur_service_user); + + if (ctx->replicator_notify) { + state_str = t_str_new(128); + dsync_brain_get_state(brain, state_str); + } + sync_type = dsync_brain_get_sync_type(brain); + + if (dsync_brain_deinit(&brain, &mail_error) < 0) + doveadm_mail_failed_error(_ctx, mail_error); + dsync_ibc_deinit(&ibc); + + if (!cli) { + /* make sure nothing more is written by the generic doveadm + connection code */ + o_stream_close(cctx->output); + } + i_stream_unref(&ctx->input); + o_stream_unref(&ctx->output); + + if (ctx->replicator_notify && _ctx->exit_code == 0) + dsync_replicator_notify(ctx, sync_type, str_c(state_str)); + return _ctx->exit_code == 0 ? 0 : -1; +} + +static bool +cmd_mailbox_dsync_server_parse_arg(struct doveadm_mail_cmd_context *_ctx, int c) +{ + struct dsync_cmd_context *ctx = (struct dsync_cmd_context *)_ctx; + + switch (c) { + case 'E': + /* dsync wrapper detection flag */ + legacy_dsync = TRUE; + break; + case 'r': + ctx->rawlog_path = optarg; + break; + case 'T': + if (str_to_uint(optarg, &ctx->io_timeout_secs) < 0) + i_fatal("Invalid -T parameter: %s", optarg); + break; + case 'U': + ctx->replicator_notify = TRUE; + break; + default: + return FALSE; + } + return TRUE; +} + +static struct doveadm_mail_cmd_context *cmd_dsync_server_alloc(void) +{ + struct dsync_cmd_context *ctx; + + ctx = doveadm_mail_cmd_alloc(struct dsync_cmd_context); + ctx->io_timeout_secs = DSYNC_DEFAULT_IO_STREAM_TIMEOUT_SECS; + ctx->ctx.getopt_args = "Er:T:U"; + ctx->ctx.v.parse_arg = cmd_mailbox_dsync_server_parse_arg; + ctx->ctx.v.run = cmd_dsync_server_run; + ctx->sync_type = DSYNC_BRAIN_SYNC_TYPE_CHANGED; + + ctx->fd_in = STDIN_FILENO; + ctx->fd_out = STDOUT_FILENO; + return &ctx->ctx; +} + +#define DSYNC_COMMON_PARAMS \ +DOVEADM_CMD_MAIL_COMMON \ +DOVEADM_CMD_PARAM('f', "full-sync", CMD_PARAM_BOOL, 0) \ +DOVEADM_CMD_PARAM('P', "purge-remote", CMD_PARAM_BOOL, 0) \ +DOVEADM_CMD_PARAM('R', "reverse-sync", CMD_PARAM_BOOL, 0) \ +DOVEADM_CMD_PARAM('U', "replicator-notify", CMD_PARAM_BOOL, 0) \ +DOVEADM_CMD_PARAM('l', "lock-timeout", CMD_PARAM_INT64, 0) \ +DOVEADM_CMD_PARAM('r', "rawlog", CMD_PARAM_STR, 0) \ +DOVEADM_CMD_PARAM('m', "mailbox", CMD_PARAM_STR, 0) \ +DOVEADM_CMD_PARAM('g', "mailbox-guid", CMD_PARAM_STR, 0) \ +DOVEADM_CMD_PARAM('n', "namespace", CMD_PARAM_ARRAY, 0) \ +DOVEADM_CMD_PARAM('N', "all-namespaces", CMD_PARAM_BOOL, 0) \ +DOVEADM_CMD_PARAM('x', "exclude-mailbox", CMD_PARAM_ARRAY, 0) \ +DOVEADM_CMD_PARAM('a', "all-mailbox", CMD_PARAM_STR, 0) \ +DOVEADM_CMD_PARAM('s', "state", CMD_PARAM_STR, 0) \ +DOVEADM_CMD_PARAM('t', "sync-since-time", CMD_PARAM_STR, 0) \ +DOVEADM_CMD_PARAM('e', "sync-until-time", CMD_PARAM_STR, 0) \ +DOVEADM_CMD_PARAM('O', "sync-flags", CMD_PARAM_STR, 0) \ +DOVEADM_CMD_PARAM('I', "sync-max-size", CMD_PARAM_STR, 0) \ +DOVEADM_CMD_PARAM('T', "timeout", CMD_PARAM_INT64, 0) \ +DOVEADM_CMD_PARAM('d', "default-destination", CMD_PARAM_BOOL, 0) \ +DOVEADM_CMD_PARAM('E', "legacy-dsync", CMD_PARAM_BOOL, 0) \ +DOVEADM_CMD_PARAM('\0', "destination", CMD_PARAM_ARRAY, CMD_PARAM_FLAG_POSITIONAL) + +#define DSYNC_COMMON_USAGE \ + "[-l <secs>] [-r <rawlog path>] " \ + "[-m <mailbox>] [-g <mailbox guid>] [-n <namespace> | -N] " \ + "[-x <exclude>] [-a <all mailbox>] [-s <state>] [-T <secs>] " \ + "[-t <start date>] [-e <end date>] [-O <sync flag>] [-I <max size>] " \ + "-d|<dest>" + +struct doveadm_cmd_ver2 doveadm_cmd_dsync_mirror = { + .mail_cmd = cmd_dsync_alloc, + .name = "sync", + .usage = "[-1fPRU] "DSYNC_COMMON_USAGE, + .flags = CMD_FLAG_NO_UNORDERED_OPTIONS, +DOVEADM_CMD_PARAMS_START +DSYNC_COMMON_PARAMS +DOVEADM_CMD_PARAM('1', "oneway-sync", CMD_PARAM_BOOL, 0) +DOVEADM_CMD_PARAMS_END +}; +struct doveadm_cmd_ver2 doveadm_cmd_dsync_backup = { + .mail_cmd = cmd_dsync_backup_alloc, + .name = "backup", + .usage = "[-fPRU] "DSYNC_COMMON_USAGE, + .flags = CMD_FLAG_NO_UNORDERED_OPTIONS, +DOVEADM_CMD_PARAMS_START +DSYNC_COMMON_PARAMS +DOVEADM_CMD_PARAMS_END +}; +struct doveadm_cmd_ver2 doveadm_cmd_dsync_server = { + .mail_cmd = cmd_dsync_server_alloc, + .name = "dsync-server", + .usage = "[-E] [-r <rawlog path>] [-T <timeout secs>] [-U]", +DOVEADM_CMD_PARAMS_START +DOVEADM_CMD_MAIL_COMMON +DOVEADM_CMD_PARAM('E', "legacy-dsync", CMD_PARAM_BOOL, 0) +DOVEADM_CMD_PARAM('r', "rawlog", CMD_PARAM_STR, 0) +DOVEADM_CMD_PARAM('T', "timeout", CMD_PARAM_INT64, 0) +DOVEADM_CMD_PARAM('U', "replicator-notify", CMD_PARAM_BOOL, 0) +/* previously dsync-server could have been added twice to the parameters */ +DOVEADM_CMD_PARAM('\0', "ignore-arg", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) +DOVEADM_CMD_PARAMS_END +}; + +void doveadm_dsync_main(int *_argc, char **_argv[]) +{ + int argc = *_argc; + const char *getopt_str; + char **argv = *_argv; + char **new_argv, *mailbox = NULL, *alt_char = NULL, *username = NULL; + char *p, *dup, new_flags[6]; + int max_argc, src, dest, i, j; + bool flag_f = FALSE, flag_R = FALSE, flag_m, flag_u, flag_C, has_arg; + bool dsync_server = FALSE; + + p = strrchr(argv[0], '/'); + if (p == NULL) p = argv[0]; + if (strstr(p, "dsync") == NULL) + return; + + /* @UNSAFE: this is called when the "doveadm" binary is called as + "dsync" (for backwards compatibility) */ + max_argc = argc + 7; + new_argv = t_new(char *, max_argc); + new_argv[0] = argv[0]; + dest = 1; + getopt_str = master_service_getopt_string(); + + /* add global doveadm flags */ + for (src = 1; src < argc; src++) { + if (argv[src][0] != '-') + break; + + flag_m = FALSE; flag_C = FALSE; has_arg = FALSE; flag_u = FALSE; + dup = t_strdup_noconst(argv[src]); + for (i = j = 1; argv[src][i] != '\0'; i++) { + switch (argv[src][i]) { + case 'C': + flag_C = TRUE; + break; + case 'f': + flag_f = TRUE; + break; + case 'R': + flag_R = TRUE; + break; + case 'm': + flag_m = TRUE; + break; + case 'u': + flag_u = TRUE; + break; + default: + p = strchr(getopt_str, argv[src][i]); + if (p != NULL && p[1] == ':') + has_arg = TRUE; + dup[j++] = argv[src][i]; + break; + } + } + if (j > 1) { + dup[j++] = '\0'; + new_argv[dest++] = dup; + if (has_arg && src+1 < argc) + new_argv[dest++] = argv[++src]; + } + if (flag_m) { + if (src+1 == argc) + i_fatal("-m missing parameter"); + mailbox = argv[++src]; + } + if (flag_u) { + if (src+1 == argc) + i_fatal("-u missing parameter"); + username = argv[++src]; + } + if (flag_C) { + if (src+1 == argc) + i_fatal("-C missing parameter"); + alt_char = argv[++src]; + } + } + if (alt_char != NULL) { + new_argv[dest++] = "-o"; + new_argv[dest++] = + p_strconcat(pool_datastack_create(), + "dsync_alt_char=", alt_char, NULL); + } + + /* mirror|backup|server */ + if (src == argc) + i_fatal("Missing mirror or backup parameter"); + if (strcmp(argv[src], "sync") == 0 || + strcmp(argv[src], "dsync-server") == 0) { + /* we're re-executing dsync due to doveconf. + "backup" re-exec detection is later. */ + return; + } + if (strcmp(argv[src], "mirror") == 0) + new_argv[dest] = "sync"; + else if (strcmp(argv[src], "backup") == 0) + new_argv[dest] = "backup"; + else if (strcmp(argv[src], "server") == 0) { + new_argv[dest] = "dsync-server"; + dsync_server = TRUE; + } else + i_fatal("Invalid parameter: %s", argv[src]); + src++; dest++; + + if (src < argc && str_begins(argv[src], "-E")) { + /* we're re-executing dsync due to doveconf */ + return; + } + + /* dsync flags */ + new_flags[0] = '-'; + new_flags[1] = 'E'; i = 2; + if (!dsync_server) { + if (flag_f) + new_flags[i++] = 'f'; + if (flag_R) + new_flags[i++] = 'R'; + if (mailbox != NULL) + new_flags[i++] = 'm'; + } + i_assert((unsigned int)i < sizeof(new_flags)); + new_flags[i] = '\0'; + + if (i > 1) { + new_argv[dest++] = strdup(new_flags); + if (mailbox != NULL) + new_argv[dest++] = mailbox; + } + if (username != NULL) { + new_argv[dest++] = "-u"; + new_argv[dest++] = username; + } + + /* rest of the parameters */ + for (; src < argc; src++) + new_argv[dest++] = argv[src]; + i_assert(dest < max_argc); + new_argv[dest] = NULL; + + legacy_dsync = TRUE; + *_argc = dest; + *_argv = new_argv; + i_getopt_reset(); +} |