doveadm-dsync.c revision f538498d8ad0c7781c7aef6c08483b3ea1bea2cd
7cb128dc4cae2a03a742f63ba7afee23c78e3af0Phil Carmody/* Copyright (c) 2009-2016 Dovecot authors, see the included COPYING file */
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen#include "lib.h"
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen#include "lib-signals.h"
bdd36cfdba3ff66d25570a9ff568d69e1eb543cfTimo Sirainen#include "array.h"
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen#include "execv-const.h"
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen#include "fd-set-nonblock.h"
fe8af34153615d9007f2238fca87df11ff32d614Timo Sirainen#include "child-wait.h"
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen#include "istream.h"
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen#include "ostream.h"
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen#include "iostream-ssl.h"
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen#include "iostream-rawlog.h"
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen#include "write-full.h"
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen#include "str.h"
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen#include "strescape.h"
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen#include "var-expand.h"
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen#include "settings-parser.h"
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen#include "imap-util.h"
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen#include "master-service.h"
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen#include "master-service-settings.h"
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen#include "master-service-ssl-settings.h"
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen#include "mail-storage.h"
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen#include "mail-storage-service.h"
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen#include "mail-user.h"
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen#include "mail-namespace.h"
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen#include "mailbox-list-private.h"
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen#include "doveadm-settings.h"
b4ddb5b3c3722620a8fef387dd8c47bb411a5643Timo Sirainen#include "doveadm-mail.h"
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen#include "doveadm-print.h"
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen#include "doveadm-server.h"
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen#include "client-connection.h"
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen#include "server-connection.h"
152db3f90f298b7fb2dbbd4276f0fc30a9bc30f6Timo Sirainen#include "dsync/dsync-brain.h"
0dc72981f5286d60ca9233f6ac7c444d393d24fbTimo Sirainen#include "dsync/dsync-ibc.h"
0dc72981f5286d60ca9233f6ac7c444d393d24fbTimo Sirainen#include "doveadm-dsync.h"
0dc72981f5286d60ca9233f6ac7c444d393d24fbTimo Sirainen
0dc72981f5286d60ca9233f6ac7c444d393d24fbTimo Sirainen#include <stdio.h>
0dc72981f5286d60ca9233f6ac7c444d393d24fbTimo Sirainen#include <unistd.h>
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen#include <ctype.h>
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen#include <sys/wait.h>
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen#define DSYNC_COMMON_GETOPT_ARGS "+1a:dDEfg:I:l:m:n:NO:Pr:Rs:t:T:Ux:"
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen#define DSYNC_REMOTE_CMD_EXIT_WAIT_SECS 30
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen/* The broken_char is mainly set to get a proper error message when trying to
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen convert a mailbox with a name that can't be used properly translated between
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen vname/storage_name and would otherwise be mixed up with a normal "mailbox
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen doesn't exist" error message. This could be any control character, since
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen none of them are allowed to be created in regular mailbox names. */
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen#define DSYNC_LIST_BROKEN_CHAR '\003'
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen#define DSYNC_DEFAULT_IO_STREAM_TIMEOUT_SECS (60*10)
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainenenum dsync_run_type {
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen DSYNC_RUN_TYPE_LOCAL,
df6c7293d54523f5e7d5899af5a6b02893ff7fbdTimo Sirainen DSYNC_RUN_TYPE_STREAM,
df6c7293d54523f5e7d5899af5a6b02893ff7fbdTimo Sirainen DSYNC_RUN_TYPE_CMD
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen};
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainenstruct dsync_cmd_context {
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen struct doveadm_mail_cmd_context ctx;
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen enum dsync_brain_sync_type sync_type;
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen const char *mailbox;
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen const char *sync_flags;
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen const char *virtual_all_box;
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen guid_128_t mailbox_guid;
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen const char *state_input, *rawlog_path;
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen ARRAY_TYPE(const_string) exclude_mailboxes;
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen ARRAY_TYPE(const_string) namespace_prefixes;
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen time_t sync_since_timestamp;
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen uoff_t sync_max_size;
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen unsigned int io_timeout_secs;
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen const char *remote_name;
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen const char *local_location;
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen pid_t remote_pid;
e2a88d59c0d47d63ce1ad5b1fd95e487124a3fd4Timo Sirainen const char *const *remote_cmd_args;
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen struct child_wait *child_wait;
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen int exit_status;
0dc72981f5286d60ca9233f6ac7c444d393d24fbTimo Sirainen
0dc72981f5286d60ca9233f6ac7c444d393d24fbTimo Sirainen int fd_in, fd_out, fd_err;
0dc72981f5286d60ca9233f6ac7c444d393d24fbTimo Sirainen struct io *io_err;
0dc72981f5286d60ca9233f6ac7c444d393d24fbTimo Sirainen struct istream *input, *err_stream;
0dc72981f5286d60ca9233f6ac7c444d393d24fbTimo Sirainen struct ostream *output;
0dc72981f5286d60ca9233f6ac7c444d393d24fbTimo Sirainen size_t input_orig_bufsize, output_orig_bufsize;
0dc72981f5286d60ca9233f6ac7c444d393d24fbTimo Sirainen
0dc72981f5286d60ca9233f6ac7c444d393d24fbTimo Sirainen struct ssl_iostream_context *ssl_ctx;
0dc72981f5286d60ca9233f6ac7c444d393d24fbTimo Sirainen struct ssl_iostream *ssl_iostream;
0dc72981f5286d60ca9233f6ac7c444d393d24fbTimo Sirainen
0dc72981f5286d60ca9233f6ac7c444d393d24fbTimo Sirainen enum dsync_run_type run_type;
152db3f90f298b7fb2dbbd4276f0fc30a9bc30f6Timo Sirainen struct server_connection *tcp_conn;
152db3f90f298b7fb2dbbd4276f0fc30a9bc30f6Timo Sirainen const char *error;
152db3f90f298b7fb2dbbd4276f0fc30a9bc30f6Timo Sirainen
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen unsigned int lock_timeout;
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen
300e4e43ed1ca46d0614459161ca2fb460ef661aTimo Sirainen bool lock:1;
44875c83e0fa7111571262686818d8de43a57b2fTimo Sirainen bool purge_remote:1;
300e4e43ed1ca46d0614459161ca2fb460ef661aTimo Sirainen bool sync_visible_namespaces:1;
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen bool default_replica_location:1;
0dc72981f5286d60ca9233f6ac7c444d393d24fbTimo Sirainen bool oneway:1;
44875c83e0fa7111571262686818d8de43a57b2fTimo Sirainen bool backup:1;
44875c83e0fa7111571262686818d8de43a57b2fTimo Sirainen bool reverse_backup:1;
44875c83e0fa7111571262686818d8de43a57b2fTimo Sirainen bool remote_user_prefix:1;
44875c83e0fa7111571262686818d8de43a57b2fTimo Sirainen bool no_mail_sync:1;
44875c83e0fa7111571262686818d8de43a57b2fTimo Sirainen bool no_mailbox_renames:1;
44875c83e0fa7111571262686818d8de43a57b2fTimo Sirainen bool local_location_from_arg:1;
152db3f90f298b7fb2dbbd4276f0fc30a9bc30f6Timo Sirainen bool replicator_notify:1;
152db3f90f298b7fb2dbbd4276f0fc30a9bc30f6Timo Sirainen bool exited:1;
152db3f90f298b7fb2dbbd4276f0fc30a9bc30f6Timo Sirainen bool empty_hdr_workaround:1;
44875c83e0fa7111571262686818d8de43a57b2fTimo Sirainen};
152db3f90f298b7fb2dbbd4276f0fc30a9bc30f6Timo Sirainen
0dc72981f5286d60ca9233f6ac7c444d393d24fbTimo Sirainenstatic bool legacy_dsync = FALSE;
0dc72981f5286d60ca9233f6ac7c444d393d24fbTimo Sirainen
0dc72981f5286d60ca9233f6ac7c444d393d24fbTimo Sirainenstatic void remote_error_input(struct dsync_cmd_context *ctx)
0dc72981f5286d60ca9233f6ac7c444d393d24fbTimo Sirainen{
0dc72981f5286d60ca9233f6ac7c444d393d24fbTimo Sirainen const unsigned char *data;
0dc72981f5286d60ca9233f6ac7c444d393d24fbTimo Sirainen size_t size;
0dc72981f5286d60ca9233f6ac7c444d393d24fbTimo Sirainen const char *line;
152db3f90f298b7fb2dbbd4276f0fc30a9bc30f6Timo Sirainen
152db3f90f298b7fb2dbbd4276f0fc30a9bc30f6Timo Sirainen switch (i_stream_read(ctx->err_stream)) {
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen case -2:
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen data = i_stream_get_data(ctx->err_stream, &size);
152db3f90f298b7fb2dbbd4276f0fc30a9bc30f6Timo Sirainen fprintf(stderr, "%.*s", (int)size, data);
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen i_stream_skip(ctx->err_stream, size);
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen break;
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen case -1:
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen if (ctx->io_err != NULL)
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen io_remove(&ctx->io_err);
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen break;
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen default:
0dc72981f5286d60ca9233f6ac7c444d393d24fbTimo Sirainen while ((line = i_stream_next_line(ctx->err_stream)) != NULL)
09060303d565e15d54e42b4ef722f9d3c26f5336Timo Sirainen fprintf(stderr, "%s\n", line);
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen break;
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen }
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen}
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainenstatic void
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainenrun_cmd(struct dsync_cmd_context *ctx, const char *const *args)
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen{
b4ddb5b3c3722620a8fef387dd8c47bb411a5643Timo Sirainen int fd_in[2], fd_out[2], fd_err[2];
b4ddb5b3c3722620a8fef387dd8c47bb411a5643Timo Sirainen
b4ddb5b3c3722620a8fef387dd8c47bb411a5643Timo Sirainen ctx->remote_cmd_args = p_strarray_dup(ctx->ctx.pool, args);
b4ddb5b3c3722620a8fef387dd8c47bb411a5643Timo Sirainen
b4ddb5b3c3722620a8fef387dd8c47bb411a5643Timo Sirainen if (pipe(fd_in) < 0 || pipe(fd_out) < 0 || pipe(fd_err) < 0)
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen i_fatal("pipe() failed: %m");
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen ctx->remote_pid = fork();
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen switch (ctx->remote_pid) {
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen case -1:
3c296d819c54e21ce05c3d2eeeedc79be42ac593Timo Sirainen i_fatal("fork() failed: %m");
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen case 0:
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen /* child, which will execute the proxy server. stdin/stdout
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen goes to pipes which we'll pass to proxy client. */
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen if (dup2(fd_in[0], STDIN_FILENO) < 0 ||
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen dup2(fd_out[1], STDOUT_FILENO) < 0 ||
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen dup2(fd_err[1], STDERR_FILENO) < 0)
0dc72981f5286d60ca9233f6ac7c444d393d24fbTimo Sirainen i_fatal("dup2() failed: %m");
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen i_close_fd(&fd_in[0]);
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen i_close_fd(&fd_in[1]);
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen i_close_fd(&fd_out[0]);
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen i_close_fd(&fd_out[1]);
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen i_close_fd(&fd_err[0]);
0dc72981f5286d60ca9233f6ac7c444d393d24fbTimo Sirainen i_close_fd(&fd_err[1]);
0dc72981f5286d60ca9233f6ac7c444d393d24fbTimo Sirainen
0dc72981f5286d60ca9233f6ac7c444d393d24fbTimo Sirainen execvp_const(args[0], args);
0dc72981f5286d60ca9233f6ac7c444d393d24fbTimo Sirainen default:
0dc72981f5286d60ca9233f6ac7c444d393d24fbTimo Sirainen /* parent */
0dc72981f5286d60ca9233f6ac7c444d393d24fbTimo Sirainen break;
0dc72981f5286d60ca9233f6ac7c444d393d24fbTimo Sirainen }
0dc72981f5286d60ca9233f6ac7c444d393d24fbTimo Sirainen
0dc72981f5286d60ca9233f6ac7c444d393d24fbTimo Sirainen i_close_fd(&fd_in[0]);
0dc72981f5286d60ca9233f6ac7c444d393d24fbTimo Sirainen i_close_fd(&fd_out[1]);
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen i_close_fd(&fd_err[1]);
09060303d565e15d54e42b4ef722f9d3c26f5336Timo Sirainen ctx->fd_in = fd_out[0];
09060303d565e15d54e42b4ef722f9d3c26f5336Timo Sirainen ctx->fd_out = fd_in[1];
0dc72981f5286d60ca9233f6ac7c444d393d24fbTimo Sirainen ctx->fd_err = fd_err[0];
0dc72981f5286d60ca9233f6ac7c444d393d24fbTimo Sirainen
0dc72981f5286d60ca9233f6ac7c444d393d24fbTimo Sirainen if (ctx->remote_user_prefix) {
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen const char *prefix =
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen t_strdup_printf("%s\n", ctx->ctx.cur_username);
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen if (write_full(ctx->fd_out, prefix, strlen(prefix)) < 0)
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen i_fatal("write(remote out) failed: %m");
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen }
0dc72981f5286d60ca9233f6ac7c444d393d24fbTimo Sirainen
0dc72981f5286d60ca9233f6ac7c444d393d24fbTimo Sirainen fd_set_nonblock(ctx->fd_err, TRUE);
456f3c79aafea8f6d009fab3d94946f38047dcd0Timo Sirainen ctx->err_stream = i_stream_create_fd(ctx->fd_err, IO_BLOCK_SIZE);
456f3c79aafea8f6d009fab3d94946f38047dcd0Timo Sirainen i_stream_set_return_partial_line(ctx->err_stream, TRUE);
456f3c79aafea8f6d009fab3d94946f38047dcd0Timo Sirainen}
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainenstatic void
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainenmirror_get_remote_cmd_line(const char *const *argv,
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen const char *const **cmd_args_r)
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen{
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen ARRAY_TYPE(const_string) cmd_args;
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen unsigned int i;
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen const char *p;
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen
0dc72981f5286d60ca9233f6ac7c444d393d24fbTimo Sirainen i_assert(argv[0] != NULL);
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen t_array_init(&cmd_args, 16);
152db3f90f298b7fb2dbbd4276f0fc30a9bc30f6Timo Sirainen for (i = 0; argv[i] != NULL; i++) {
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen p = argv[i];
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen array_append(&cmd_args, &p, 1);
09060303d565e15d54e42b4ef722f9d3c26f5336Timo Sirainen }
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen if (legacy_dsync) {
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen /* we're executing dsync */
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen p = "server";
b4ddb5b3c3722620a8fef387dd8c47bb411a5643Timo Sirainen } else {
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen /* we're executing doveadm */
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen p = "dsync-server";
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen }
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen array_append(&cmd_args, &p, 1);
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen array_append_zero(&cmd_args);
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen *cmd_args_r = array_idx(&cmd_args, 0);
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen}
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainenstatic const char *const *
e2a88d59c0d47d63ce1ad5b1fd95e487124a3fd4Timo Sirainenget_ssh_cmd_args(const char *host, const char *login, const char *mail_user)
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen{
b4ddb5b3c3722620a8fef387dd8c47bb411a5643Timo Sirainen static struct var_expand_table static_tab[] = {
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen { 'u', NULL, "user" },
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen { '\0', NULL, "login" },
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen { '\0', NULL, "host" },
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen { '\0', NULL, NULL }
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen };
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen struct var_expand_table *tab;
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen ARRAY_TYPE(const_string) cmd_args;
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen string_t *str, *str2;
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen const char *value, *const *args;
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen tab = t_malloc_no0(sizeof(static_tab));
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen memcpy(tab, static_tab, sizeof(static_tab));
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen tab[0].value = mail_user;
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen tab[1].value = login;
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen tab[2].value = host;
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen t_array_init(&cmd_args, 8);
e2a88d59c0d47d63ce1ad5b1fd95e487124a3fd4Timo Sirainen str = t_str_new(128);
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen str2 = t_str_new(128);
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen args = t_strsplit(doveadm_settings->dsync_remote_cmd, " ");
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen for (; *args != NULL; args++) {
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen if (strchr(*args, '%') == NULL)
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen value = *args;
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen else {
fe8af34153615d9007f2238fca87df11ff32d614Timo Sirainen /* some automation: if parameter's all %variables
fe8af34153615d9007f2238fca87df11ff32d614Timo Sirainen expand to empty, but the %variable isn't the only
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen text in the parameter, skip it. */
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen str_truncate(str, 0);
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen str_truncate(str2, 0);
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen var_expand(str, *args, tab);
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen var_expand(str2, *args, static_tab);
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen if (strcmp(str_c(str), str_c(str2)) == 0 &&
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen str_len(str) > 0)
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen continue;
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen value = t_strdup(str_c(str));
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen }
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen array_append(&cmd_args, &value, 1);
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen }
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen array_append_zero(&cmd_args);
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen return array_idx(&cmd_args, 0);
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen}
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainenstatic bool mirror_get_remote_cmd(struct dsync_cmd_context *ctx,
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen const char *user,
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen const char *const **cmd_args_r)
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen{
2670cd577aa57eb9f915a4f4220ae48c9b4fc5fbTimo Sirainen 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;
user->dsyncing = TRUE;
for (ns = user->namespaces; ns != NULL; ns = ns->next)
ns->list->set.broken_char = DSYNC_LIST_BROKEN_CHAR;
}
static bool paths_are_equal(struct mail_user *user1, struct mail_user *user2,
enum mailbox_list_path_type type)
{
const char *path1, *path2;
i_assert(user1->namespaces != NULL);
i_assert(user2->namespaces != NULL);
return mailbox_list_get_root_path(user1->namespaces->list, type, &path1) &&
mailbox_list_get_root_path(user2->namespaces->list, type, &path2) &&
strcmp(path1, path2) == 0;
}
static int
cmd_dsync_run_local(struct dsync_cmd_context *ctx, struct mail_user *user,
struct dsync_brain *brain, struct dsync_ibc *ibc2,
bool *changes_during_sync_r, enum mail_error *mail_error_r)
{
struct dsync_brain *brain2;
struct mail_user *user2;
struct setting_parser_context *set_parser;
const char *set_line, *location, *error;
bool brain1_running, brain2_running, changed1, changed2;
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);
set_line = t_strconcat("mail_location=", location, NULL);
if (settings_parse_line(set_parser, set_line) < 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 (mail_namespaces_get_root_sep(user->namespaces) !=
mail_namespaces_get_root_sep(user2->namespaces)) {
i_error("Mail locations must use the same "
"virtual mailbox hierarchy separator "
"(specify separator for the default namespace)");
ctx->ctx.exit_code = EX_CONFIG;
mail_user_unref(&user2);
return -1;
}
if (paths_are_equal(user, user2, MAILBOX_LIST_PATH_TYPE_MAILBOX) &&
paths_are_equal(user, user2, MAILBOX_LIST_PATH_TYPE_INDEX)) {
i_error("Both source and destination mail_location "
"points to same directory: %s",
mailbox_list_get_root_forced(user->namespaces->list,
MAILBOX_LIST_PATH_TYPE_MAILBOX));
ctx->ctx.exit_code = EX_CONFIG;
mail_user_unref(&user2);
return -1;
}
brain2 = dsync_brain_slave_init(user2, ibc2, TRUE, "");
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 = dsync_brain_has_unexpected_changes(brain2);
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);
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 = strchr(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_icb_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_t)-1);
ctx->output = o_stream_create_fd(ctx->fd_out, (size_t)-1);
} else {
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_t)-1);
o_stream_set_max_buffer_size(ctx->output, (size_t)-1);
}
if (ctx->rawlog_path != NULL) {
iostream_rawlog_create_path(ctx->rawlog_path,
&ctx->input, &ctx->output);
}
i_stream_ref(ctx->input);
o_stream_ref(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 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 changes_during_sync = FALSE;
int ret = 0;
memset(&set, 0, sizeof(set));
if (_ctx->cur_client_ip.family != 0) {
/* include the doveadm client's IP address in the ps output */
set.process_title_prefix = t_strdup_printf(
"%s ", net_ip2addr(&_ctx->cur_client_ip));
}
set.sync_since_timestamp = ctx->sync_since_timestamp;
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.state = ctx->state_input;
set.mailbox_alt_char = doveadm_settings->dsync_alt_char[0];
if (array_count(&ctx->exclude_mailboxes) > 0) {
/* array is NULL-terminated in init() */
set.exclude_mailboxes = array_idx(&ctx->exclude_mailboxes, 0);
}
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_append(&set.sync_namespaces, &ns, 1);
}
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_icb_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->no_mailbox_renames)
brain_flags |= DSYNC_BRAIN_FLAG_NO_MAILBOX_RENAMES;
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_sync, &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);
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));
}
if (dsync_brain_has_unexpected_changes(brain) || changes_during_sync) {
/* don't log a warning when running via doveadm server
(e.g. called by replicator) */
if (ctx->ctx.conn == NULL) {
i_warning("Mailbox changes caused a desync. "
"You may want to run dsync again.");
}
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);
if (ctx->ssl_iostream != NULL)
ssl_iostream_destroy(&ctx->ssl_iostream);
if (ctx->ssl_ctx != NULL)
ssl_iostream_context_deinit(&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);
}
if (ctx->io_err != NULL)
io_remove(&ctx->io_err);
if (ctx->fd_err != -1)
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;
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 mail_storage_settings *mail_set,
const char **error_r)
{
struct ssl_iostream_settings ssl_set;
if (ctx->ssl_ctx != NULL)
return 0;
memset(&ssl_set, 0, sizeof(ssl_set));
ssl_set.ca_dir = mail_set->ssl_client_ca_dir;
ssl_set.ca_file = mail_set->ssl_client_ca_file;
ssl_set.verify_remote_cert = TRUE;
ssl_set.crypto_device = mail_set->ssl_crypto_device;
return ssl_iostream_context_init_client(&ssl_set, &ctx->ssl_ctx, error_r);
}
static int
dsync_connect_tcp(struct dsync_cmd_context *ctx,
const struct mail_storage_settings *mail_set,
const char *target, bool ssl, const char **error_r)
{
struct doveadm_server *server;
struct server_connection *conn;
struct ioloop *ioloop;
string_t *cmd;
const char *error;
server = p_new(ctx->ctx.pool, struct doveadm_server, 1);
server->name = p_strdup(ctx->ctx.pool, target);
if (ssl) {
if (dsync_init_ssl_ctx(ctx, mail_set, &error) < 0) {
*error_r = t_strdup_printf(
"Couldn't initialize SSL context: %s", error);
return -1;
}
server->ssl_ctx = ctx->ssl_ctx;
}
p_array_init(&server->connections, ctx->ctx.pool, 1);
p_array_init(&server->queue, ctx->ctx.pool, 1);
ioloop = io_loop_create();
if (server_connection_create(server, &conn) < 0) {
*error_r = "Couldn't create server connection";
return -1;
}
/* <flags> <username> <command> [<args>] */
cmd = t_str_new(256);
if (doveadm_debug)
str_append_c(cmd, 'D');
str_append_c(cmd, '\t');
str_append_tabescaped(cmd, ctx->ctx.cur_username);
str_append(cmd, "\tdsync-server\t-u");
str_append_tabescaped(cmd, ctx->ctx.cur_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(ioloop);
ctx->tcp_conn = NULL;
if (array_count(&server->connections) > 0)
server_connection_destroy(&conn);
io_loop_destroy(&ioloop);
if (ctx->error != NULL) {
*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 mail_storage_settings *mail_set,
const char *location,
const char *const **remote_cmd_args_r, const char **error_r)
{
if (strncmp(location, "tcp:", 4) == 0) {
/* TCP connection to remote dsync */
ctx->remote_name = location+4;
return dsync_connect_tcp(ctx, mail_set, ctx->remote_name,
FALSE, error_r);
}
if (strncmp(location, "tcps:", 5) == 0) {
/* TCP+SSL connection to remote dsync */
ctx->remote_name = location+5;
return dsync_connect_tcp(ctx, mail_set, ctx->remote_name,
TRUE, error_r);
}
if (strncmp(location, "remote:", 7) == 0) {
/* this is a remote (ssh) command */
ctx->remote_name = location+7;
} else if (strncmp(location, "remoteprefix:", 13) == 0) {
/* 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, ctx->ctx.cur_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;
const char *const *remote_cmd_args = NULL;
const struct mail_user_settings *user_set;
const struct mail_storage_settings *mail_set;
const char *username = "";
user_set = mail_storage_service_user_get_set(service_user)[0];
mail_set = mail_storage_service_user_get_mail_set(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 = _ctx->cur_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, mail_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;
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 'D':
ctx->no_mailbox_renames = 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_append(&ctx->exclude_mailboxes, &str, 1);
break;
case 'n':
str = optarg;
array_append(&ctx->namespace_prefixes, &str, 1);
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) < 0)
i_fatal("Invalid -t 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;
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 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 (_ctx->conn != NULL) {
/* doveadm-server connection. start with a success reply.
after that follows the regular dsync protocol. */
ctx->fd_in = ctx->fd_out = -1;
ctx->input = _ctx->conn->input;
ctx->output = _ctx->conn->output;
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 (_ctx->cur_client_ip.family != 0) {
/* include the doveadm client's IP address in the ps output */
process_title_prefix = t_strdup_printf(
"%s ", net_ip2addr(&_ctx->cur_client_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_icb_stream_init(ctx, name, str_c(temp_prefix));
brain = dsync_brain_slave_init(user, ibc, FALSE, process_title_prefix);
io_loop_run(current_ioloop);
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 (_ctx->conn != NULL) {
/* make sure nothing more is written by the generic doveadm
connection code */
o_stream_close(_ctx->conn->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;
}
struct doveadm_mail_cmd cmd_dsync_mirror = {
cmd_dsync_alloc, "sync",
"[-1fPRU] [-l <secs>] [-r <rawlog path>] [-m <mailbox>] [-g <mailbox_guid>] [-n <namespace> | -N] [-x <exclude>] [-s <state>] -d|<dest>"
};
struct doveadm_mail_cmd cmd_dsync_backup = {
cmd_dsync_backup_alloc, "backup",
"[-fPRU] [-l <secs>] [-r <rawlog path>] [-m <mailbox>] [-g <mailbox_guid>] [-n <namespace> | -N] [-x <exclude>] [-s <state>] -d|<dest>"
};
struct doveadm_mail_cmd cmd_dsync_server = {
cmd_dsync_server_alloc, "dsync-server", &doveadm_mail_cmd_hide
};
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 && strncmp(argv[src], "-E", 2) == 0) {
/* 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();
}