doveadm-dsync.c revision d229d26d263a57a77eec8fe7cba24fbfd9509966
e59faf65ce864fe95dc00f5d52b8323cdbd0608aTimo Sirainen/* Copyright (c) 2009-2013 Dovecot authors, see the included COPYING file */
98922c5675bbbfadc84d58768bef867fe82256c2Timo Sirainen#define DSYNC_COMMON_GETOPT_ARGS "+dEfg:l:m:n:Nr:Rs:Ux:"
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen/* The broken_char is mainly set to get a proper error message when trying to
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen convert a mailbox with a name that can't be used properly translated between
b5e6f6f27c1461f0f9f202615eeb738a645188c3Timo Sirainen vname/storage_name and would otherwise be mixed up with a normal "mailbox
2cfe9983ce7a6280636ee12beccc2e865111967bTimo Sirainen doesn't exist" error message. This could be any control character, since
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen none of them are allowed to be created in regular mailbox names. */
f0a2d04321ba456e5c5ba821c0d1ed9e8e0e2e08Timo Sirainenstatic void remote_error_input(struct dsync_cmd_context *ctx)
b6a7e0a7899e7f5d60c23cdaa50e025e4c67d05fTimo Sirainen const unsigned char *data;
f0a2d04321ba456e5c5ba821c0d1ed9e8e0e2e08Timo Sirainen data = i_stream_get_data(ctx->err_stream, &size);
c60d1eda4df179d83d531647732d5e3e45064219Timo Sirainen while ((line = i_stream_next_line(ctx->err_stream)) != NULL)
c60d1eda4df179d83d531647732d5e3e45064219Timo Sirainenrun_cmd(struct dsync_cmd_context *ctx, const char *const *args)
f0a2d04321ba456e5c5ba821c0d1ed9e8e0e2e08Timo Sirainen if (pipe(fd_in) < 0 || pipe(fd_out) < 0 || pipe(fd_err) < 0)
ee1a3e217279dcd0f1cccbd3442e481b7dc90f05Timo Sirainen /* child, which will execute the proxy server. stdin/stdout
b6a7e0a7899e7f5d60c23cdaa50e025e4c67d05fTimo Sirainen goes to pipes which we'll pass to proxy client. */
b6a7e0a7899e7f5d60c23cdaa50e025e4c67d05fTimo Sirainen const char *prefix =
1eb17e61d3d38372674aa0c55caedb0185a985f5Timo Sirainen t_strdup_printf("%s\n", ctx->ctx.cur_username);
90adcaa0a00eba29b7fbd50ca66be11c8d086d6aTimo Sirainen if (write_full(ctx->fd_out, prefix, strlen(prefix)) < 0)
83bb013a99f0936995f9c7a1077822662d8fefdbTimo Sirainen ctx->err_stream = i_stream_create_fd(ctx->fd_err, IO_BLOCK_SIZE, FALSE);
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen i_stream_set_return_partial_line(ctx->err_stream, TRUE);
83bb013a99f0936995f9c7a1077822662d8fefdbTimo Sirainenmirror_get_remote_cmd_line(const char *const *argv,
90adcaa0a00eba29b7fbd50ca66be11c8d086d6aTimo Sirainen const char *const **cmd_args_r)
90adcaa0a00eba29b7fbd50ca66be11c8d086d6aTimo Sirainen unsigned int i;
90adcaa0a00eba29b7fbd50ca66be11c8d086d6aTimo Sirainen const char *p;
83bb013a99f0936995f9c7a1077822662d8fefdbTimo Sirainen /* we're executing dsync */
90adcaa0a00eba29b7fbd50ca66be11c8d086d6aTimo Sirainen p = "server";
90adcaa0a00eba29b7fbd50ca66be11c8d086d6aTimo Sirainen /* we're executing doveadm */
90adcaa0a00eba29b7fbd50ca66be11c8d086d6aTimo Sirainen p = "dsync-server";
90adcaa0a00eba29b7fbd50ca66be11c8d086d6aTimo Sirainenstatic const char *const *
ae1b268ffff743ad9927c304a1344c5cbd7f909dTimo Sirainenget_ssh_cmd_args(const char *host, const char *login, const char *mail_user)
90adcaa0a00eba29b7fbd50ca66be11c8d086d6aTimo Sirainen static struct var_expand_table static_tab[] = {
9334fbad0aabb2fed88f40b2205d0d6f80bdffa2Timo Sirainen args = t_strsplit(doveadm_settings->dsync_remote_cmd, " ");
9334fbad0aabb2fed88f40b2205d0d6f80bdffa2Timo Sirainen /* some automation: if parameter's all %variables
9334fbad0aabb2fed88f40b2205d0d6f80bdffa2Timo Sirainen expand to empty, but the %variable isn't the only
9334fbad0aabb2fed88f40b2205d0d6f80bdffa2Timo Sirainen text in the parameter, skip it. */
c36ec256c1bd1abe1c12e792cf64f0b7e3b3135aTimo Sirainenstatic bool mirror_get_remote_cmd(struct dsync_cmd_context *ctx,
c36ec256c1bd1abe1c12e792cf64f0b7e3b3135aTimo Sirainen const char *const **cmd_args_r)
c36ec256c1bd1abe1c12e792cf64f0b7e3b3135aTimo Sirainen const char *p, *host, *const *argv = ctx->ctx.args;
c36ec256c1bd1abe1c12e792cf64f0b7e3b3135aTimo Sirainen /* more than one parameter, so it contains a full command
c36ec256c1bd1abe1c12e792cf64f0b7e3b3135aTimo Sirainen (e.g. ssh host dsync) */
c36ec256c1bd1abe1c12e792cf64f0b7e3b3135aTimo Sirainen /* if it begins with /[a-z0-9]+:/, it's a mail location
c36ec256c1bd1abe1c12e792cf64f0b7e3b3135aTimo Sirainen (e.g. mdbox:~/mail) */
d22301419109ed4a38351715e6760011421dadecTimo Sirainen if (*p == ':')
97eb53ade9057e6966dbb77289ad0204c7e1657bTimo Sirainen if (strchr(argv[0], ' ') != NULL || strchr(argv[0], '/') != NULL) {
c36ec256c1bd1abe1c12e792cf64f0b7e3b3135aTimo Sirainen /* a) the whole command is in one string. this is mainly for
d22301419109ed4a38351715e6760011421dadecTimo Sirainen backwards compatibility.
c36ec256c1bd1abe1c12e792cf64f0b7e3b3135aTimo Sirainen mirror_get_remote_cmd_line(t_strsplit(argv[0], " "),
97eb53ade9057e6966dbb77289ad0204c7e1657bTimo Sirainen /* [user@]host */
c36ec256c1bd1abe1c12e792cf64f0b7e3b3135aTimo Sirainen /* we'll assume virtual users, so in user@host it really means not to
c36ec256c1bd1abe1c12e792cf64f0b7e3b3135aTimo Sirainen give ssh a username, but to give dsync -u user parameter. */
236bedf76e31651ea9fca63fbdc25be673819526Timo Sirainen *cmd_args_r = get_ssh_cmd_args(host, "", user);
c36ec256c1bd1abe1c12e792cf64f0b7e3b3135aTimo Sirainenstatic void doveadm_user_init_dsync(struct mail_user *user)
c36ec256c1bd1abe1c12e792cf64f0b7e3b3135aTimo Sirainen for (ns = user->namespaces; ns != NULL; ns = ns->next)
c36ec256c1bd1abe1c12e792cf64f0b7e3b3135aTimo Sirainen ns->list->set.broken_char = DSYNC_LIST_BROKEN_CHAR;
97eb53ade9057e6966dbb77289ad0204c7e1657bTimo Sirainenstatic bool paths_are_equal(struct mail_user *user1, struct mail_user *user2,
3eb63515855f386449c22233d1f1baf1ddfe8a2dTimo Sirainen return mailbox_list_get_root_path(user1->namespaces->list, type, &path1) &&
7ede6554e451ec039a67beec7d6ee4aff61d386eTimo Sirainen mailbox_list_get_root_path(user2->namespaces->list, type, &path2) &&
b142deb9a831c89b1bb9129ada655f3e56b9d4ccTimo Sirainencmd_dsync_run_local(struct dsync_cmd_context *ctx, struct mail_user *user,
b142deb9a831c89b1bb9129ada655f3e56b9d4ccTimo Sirainen struct dsync_brain *brain, struct dsync_ibc *ibc2,
97eb53ade9057e6966dbb77289ad0204c7e1657bTimo Sirainen bool brain1_running, brain2_running, changed1, changed2;
9261dbf0675204898c6557591c7aa376e23a52b2Timo Sirainen i_set_failure_prefix("dsync(%s): ", user->username);
ae1b268ffff743ad9927c304a1344c5cbd7f909dTimo Sirainen /* update mail_location and create another user for the
ae1b268ffff743ad9927c304a1344c5cbd7f909dTimo Sirainen second location. */
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen set_parser = mail_storage_service_user_get_settings_parser(ctx->ctx.cur_service_user);
ae1b268ffff743ad9927c304a1344c5cbd7f909dTimo Sirainen set_line = t_strconcat("mail_location=", location, NULL);
97eb53ade9057e6966dbb77289ad0204c7e1657bTimo Sirainen if (settings_parse_line(set_parser, set_line) < 0)
ae1b268ffff743ad9927c304a1344c5cbd7f909dTimo Sirainen ret = mail_storage_service_next(ctx->ctx.storage_service,
97eb53ade9057e6966dbb77289ad0204c7e1657bTimo Sirainen ctx->ctx.exit_code = ret == -1 ? EX_TEMPFAIL : EX_CONFIG;
97eb53ade9057e6966dbb77289ad0204c7e1657bTimo Sirainen if (mail_namespaces_get_root_sep(user->namespaces) !=
ae1b268ffff743ad9927c304a1344c5cbd7f909dTimo Sirainen mail_namespaces_get_root_sep(user2->namespaces)) {
55b6e3105184ad6a2f987346380966f556300055Timo Sirainen "virtual mailbox hierarchy separator "
7aeaf23f760d86aad525d831efcac9f860a55a39Timo Sirainen "(specify separator for the default namespace)");
63aaafe7e6b201d6633f8c25610ecd30c9cda99cTimo Sirainen if (paths_are_equal(user, user2, MAILBOX_LIST_PATH_TYPE_MAILBOX) &&
cf49fc07f541c0f74578ac6c3b334ddade143aa1Timo Sirainen paths_are_equal(user, user2, MAILBOX_LIST_PATH_TYPE_INDEX)) {
63aaafe7e6b201d6633f8c25610ecd30c9cda99cTimo Sirainen i_error("Both source and destination mail_location "
cf49fc07f541c0f74578ac6c3b334ddade143aa1Timo Sirainen "points to same directory: %s",
2ff23d6fb7e2ff85aa23b7f4769aeac1d0316a1bTimo Sirainen mailbox_list_get_root_forced(user->namespaces->list,
c36ec256c1bd1abe1c12e792cf64f0b7e3b3135aTimo Sirainen brain1_running = dsync_brain_run(brain, &changed1);
97eb53ade9057e6966dbb77289ad0204c7e1657bTimo Sirainen brain2_running = dsync_brain_run(brain2, &changed2);
ae1b268ffff743ad9927c304a1344c5cbd7f909dTimo Sirainen *changes_during_sync_r = dsync_brain_has_unexpected_changes(brain2);
ae1b268ffff743ad9927c304a1344c5cbd7f909dTimo Sirainenstatic void cmd_dsync_wait_remote(struct dsync_cmd_context *ctx,
ae1b268ffff743ad9927c304a1344c5cbd7f909dTimo Sirainen /* wait for the remote command to finish to see any final errors.
ae1b268ffff743ad9927c304a1344c5cbd7f909dTimo Sirainen don't wait very long though. */
ae1b268ffff743ad9927c304a1344c5cbd7f909dTimo Sirainen if (waitpid(ctx->remote_pid, status_r, 0) == -1) {
ae1b268ffff743ad9927c304a1344c5cbd7f909dTimo Sirainen i_error("Remote command process isn't dying, killing it");
ae1b268ffff743ad9927c304a1344c5cbd7f909dTimo Sirainen if (kill(ctx->remote_pid, SIGKILL) < 0 && errno != ESRCH) {
ae1b268ffff743ad9927c304a1344c5cbd7f909dTimo Sirainenstatic void cmd_dsync_log_remote_status(int status, bool remote_errors_logged)
ae1b268ffff743ad9927c304a1344c5cbd7f909dTimo Sirainen i_error("Remote command died with signal %d", WTERMSIG(status));
83bb013a99f0936995f9c7a1077822662d8fefdbTimo Sirainen i_error("Remote command failed with status %d", status);
83bb013a99f0936995f9c7a1077822662d8fefdbTimo Sirainen else if (WEXITSTATUS(status) == EX_TEMPFAIL && remote_errors_logged) {
d67fde1a8ebc1d85704c5986d8f93aae97eccef3Timo Sirainen /* remote most likely already logged the error.
d67fde1a8ebc1d85704c5986d8f93aae97eccef3Timo Sirainen don't bother logging another line about it */
97eb53ade9057e6966dbb77289ad0204c7e1657bTimo Sirainen i_error("Remote command returned error %d", WEXITSTATUS(status));
97eb53ade9057e6966dbb77289ad0204c7e1657bTimo Sirainenstatic void cmd_dsync_run_remote(struct mail_user *user)
97eb53ade9057e6966dbb77289ad0204c7e1657bTimo Sirainen i_set_failure_prefix("dsync-local(%s): ", user->username);
97eb53ade9057e6966dbb77289ad0204c7e1657bTimo Sirainenstatic const char *const *
97eb53ade9057e6966dbb77289ad0204c7e1657bTimo Sirainenparse_ssh_location(const char *location, const char *username)
3eb63515855f386449c22233d1f1baf1ddfe8a2dTimo Sirainen return get_ssh_cmd_args(host, login, username);
97eb53ade9057e6966dbb77289ad0204c7e1657bTimo Sirainenstatic struct dsync_ibc *
97eb53ade9057e6966dbb77289ad0204c7e1657bTimo Sirainencmd_dsync_icb_stream_init(struct dsync_cmd_context *ctx,
97eb53ade9057e6966dbb77289ad0204c7e1657bTimo Sirainen ctx->input = i_stream_create_fd(ctx->fd_in, (size_t)-1, FALSE);
97eb53ade9057e6966dbb77289ad0204c7e1657bTimo Sirainen ctx->output = o_stream_create_fd(ctx->fd_out, (size_t)-1, FALSE);
97eb53ade9057e6966dbb77289ad0204c7e1657bTimo Sirainen return dsync_ibc_init_stream(ctx->input, ctx->output,
83bb013a99f0936995f9c7a1077822662d8fefdbTimo Sirainendsync_replicator_notify(struct dsync_cmd_context *ctx,
d22301419109ed4a38351715e6760011421dadecTimo Sirainen#define REPLICATOR_HANDSHAKE "VERSION\treplicator-doveadm-client\t1\t0\n"
e4e7475f646d66a257d682738fbff1f206ce4924Timo Sirainen path = t_strdup_printf("%s/replicator-doveadm",
24cd47a2c8f7507e555459b7e841de771ba3c318Timo Sirainen if (errno == ECONNREFUSED || errno == ENOENT) {
24cd47a2c8f7507e555459b7e841de771ba3c318Timo Sirainen /* replicator not running on this server. ignore. */
e4e7475f646d66a257d682738fbff1f206ce4924Timo Sirainen i_error("net_connect_unix(%s) failed: %m", path);
e4e7475f646d66a257d682738fbff1f206ce4924Timo Sirainen str_append(str, REPLICATOR_HANDSHAKE"NOTIFY\t");
e4e7475f646d66a257d682738fbff1f206ce4924Timo Sirainen str_append_tabescaped(str, ctx->ctx.cur_mail_user->username);
24cd47a2c8f7507e555459b7e841de771ba3c318Timo Sirainen if (write_full(fd, str_data(str), str_len(str)) < 0)
24cd47a2c8f7507e555459b7e841de771ba3c318Timo Sirainen /* we only wanted to notify replicator. we don't care enough about the
24cd47a2c8f7507e555459b7e841de771ba3c318Timo Sirainen answer to wait for it. */
b142deb9a831c89b1bb9129ada655f3e56b9d4ccTimo Sirainencmd_dsync_run(struct doveadm_mail_cmd_context *_ctx, struct mail_user *user)
b6a7e0a7899e7f5d60c23cdaa50e025e4c67d05fTimo Sirainen struct dsync_cmd_context *ctx = (struct dsync_cmd_context *)_ctx;
89fb98e9eb7e95255a579c8e9d865383c2334a74Timo Sirainen memcpy(set.sync_box_guid, ctx->mailbox_guid, sizeof(set.sync_box_guid));
89fb98e9eb7e95255a579c8e9d865383c2334a74Timo Sirainen if (array_count(&ctx->exclude_mailboxes) > 0) {
89fb98e9eb7e95255a579c8e9d865383c2334a74Timo Sirainen /* array is NULL-terminated in init() */
89fb98e9eb7e95255a579c8e9d865383c2334a74Timo Sirainen set.exclude_mailboxes = array_idx(&ctx->exclude_mailboxes, 0);
b19a1420da0618a10edf67c2cfd13c8c8633057aTimo Sirainen set.sync_ns = mail_namespace_find(user->namespaces,
83bb013a99f0936995f9c7a1077822662d8fefdbTimo Sirainen mail_user_set_get_temp_prefix(temp_prefix, user->set);
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen ibc = cmd_dsync_icb_stream_init(ctx, ctx->remote_name,
992a13add4eea0810e4db0f042a595dddf85536aTimo Sirainen brain_flags = DSYNC_BRAIN_FLAG_SEND_MAIL_REQUESTS;
910fa4e4204a73d3d24c03f3059dd24e727ca057Timo Sirainen brain_flags |= DSYNC_BRAIN_FLAG_SYNC_VISIBLE_NAMESPACES;
225e82df5dd1e765f4e52b80c954558f00e5a7dfTimo Sirainen brain = dsync_brain_master_init(user, ibc, ctx->sync_type,
225e82df5dd1e765f4e52b80c954558f00e5a7dfTimo Sirainen if (cmd_dsync_run_local(ctx, user, brain, ibc2,
8c9e48cd6de80da0fa32b9c0dee003472c9a7c0dTimo Sirainen if (dsync_brain_has_unexpected_changes(brain) || changes_during_sync) {
8c9e48cd6de80da0fa32b9c0dee003472c9a7c0dTimo Sirainen "You may want to run dsync again.");
8c9e48cd6de80da0fa32b9c0dee003472c9a7c0dTimo Sirainen /* print any final errors after the process has died. not closing
8c9e48cd6de80da0fa32b9c0dee003472c9a7c0dTimo Sirainen stdin/stdout before wait() may cause the process to hang, but stderr
8c9e48cd6de80da0fa32b9c0dee003472c9a7c0dTimo Sirainen shouldn't (at least with ssh) and we need stderr to be open to be
8c9e48cd6de80da0fa32b9c0dee003472c9a7c0dTimo Sirainen able to print the final errors */
fdc557286bc9f92c5f3bb49096ff6e2bcec0ea79Timo Sirainen remote_errors_logged = ctx->err_stream->v_offset > 0;
a12399903f415a7e14c2816cffa2f7a09dcbb097Timo Sirainen cmd_dsync_log_remote_status(status, remote_errors_logged);
d67fde1a8ebc1d85704c5986d8f93aae97eccef3Timo Sirainenstatic void dsync_connected_callback(int exit_code, void *context)
9af6cc9ebc9986c1275ebdfa29c39e152af1557eTimo Sirainen server_connection_extract(ctx->tcp_conn, &ctx->input,
const char **error_r)
const char *error;
if (ssl) {
if (doveadm_debug)
const char *location,
const char **error_r)
const char *const args[])
const char *str;
return FALSE;
return FALSE;
return TRUE;
return _ctx;
return FALSE;
return TRUE;
const char *getopt_str;
if (flag_m) {
if (flag_u) {
if (flag_C) {
if (!dsync_server) {
if (flag_f)
if (flag_R)