/* 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>
/* The broken_char is mainly set to get a proper error message when trying to
convert a mailbox with a name that can't be used properly translated between
vname/storage_name and would otherwise be mixed up with a normal "mailbox
doesn't exist" error message. This could be any control character, since
none of them are allowed to be created in regular mailbox names. */
enum dsync_run_type {
};
struct dsync_cmd_context {
const char *mailbox;
const char *sync_flags;
const char *virtual_all_box;
unsigned int io_timeout_secs;
const char *remote_name;
const char *local_location;
const char *const *remote_cmd_args;
int exit_status;
const char *error;
unsigned int lock_timeout;
unsigned int import_commit_msgs_interval;
};
{
}
{
const unsigned char *data;
const char *line;
case -2:
break;
case -1:
break;
default:
break;
}
}
static void
{
i_fatal("pipe() failed: %m");
switch (ctx->remote_pid) {
case -1:
i_fatal("fork() failed: %m");
case 0:
goes to pipes which we'll pass to proxy client. */
i_fatal("dup2() failed: %m");
i_close_fd(&fd_in[0]);
i_close_fd(&fd_out[0]);
i_close_fd(&fd_err[0]);
default:
/* parent */
break;
}
i_close_fd(&fd_in[0]);
if (ctx->remote_user_prefix) {
const char *prefix =
i_fatal("write(remote out) failed: %m");
}
}
static void
const char *const **cmd_args_r)
{
unsigned int i;
const char *p;
p = argv[i];
}
if (legacy_dsync) {
/* we're executing dsync */
p = "server";
} else {
/* we're executing doveadm */
p = "dsync-server";
}
}
static const char *const *
{
};
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);
i_error("Failed to expand dsync_remote_cmd=%s: %s",
}
continue;
}
}
}
const char *user,
const char *const **cmd_args_r)
{
/* more than one parameter, so it contains a full command
(e.g. ssh host dsync) */
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;
}
}
/* a) the whole command is in one string. this is mainly for
backwards compatibility.
return TRUE;
}
/* [user@]host */
else
/* 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. */
return TRUE;
}
{
}
}
enum mailbox_list_path_type type)
{
}
static int
const char **changes_during_sync_r,
enum mail_error *mail_error_r)
{
bool remote_only_changes;
int ret;
*mail_error_r = 0;
if (ctx->local_location_from_arg)
else {
}
/* update mail_location and create another user for the
second location. */
i_unreached();
if (ret < 0) {
return -1;
}
i_error("Mail locations must use the same "
"virtual mailbox hierarchy separator "
"(specify separator for the default namespace)");
return -1;
}
i_error("Both source and destination mail_location "
"points to same directory: %s",
return -1;
}
while (brain1_running || brain2_running) {
if (dsync_brain_has_failed(brain) ||
break;
if (doveadm_is_killed()) {
break;
}
}
*changes_during_sync_r = t_strdup(dsync_brain_get_unexpected_changes_reason(brain2, &remote_only_changes));
return -1;
return doveadm_is_killed() ? -1 : 0;
}
struct dsync_cmd_context *ctx)
{
}
{
/* 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. */
timeout_remove(&to);
i_error("Remote command process isn't dying, killing it");
i_error("kill(%ld, SIGKILL) failed: %m",
(long)ctx->remote_pid);
}
}
}
const char *const *remote_cmd_args)
{
if (status == -1)
;
else if (WIFSIGNALED(status)) {
/* remote most likely already logged the error.
don't bother logging another line about it */
} else if (WEXITSTATUS(status) != 0) {
}
}
{
}
static const char *const *
{
else {
login = "";
}
}
static struct dsync_ibc *
const char *name, const char *temp_prefix)
{
} else {
}
}
}
static void
const char *state_str)
{
const char *path;
int fd;
if (fd == -1) {
/* replicator not running on this server. ignore. */
return;
}
return;
}
if (sync_type == DSYNC_BRAIN_SYNC_TYPE_FULL)
/* we only wanted to notify replicator. we don't care enough about the
answer to wait for it. */
}
static int
{
const char *const *strp;
bool remote_only_changes;
int ret = 0;
/* include the doveadm client's IP address in the ps output */
}
i_fatal("start date is later than end date");
}
i_error("dsync_hashed_headers must not be empty");
return -1;
}
/* array is NULL-terminated in init() */
}
return -1;
}
}
else {
str_c(temp_prefix));
}
}
if (ctx->sync_visible_namespaces)
if (ctx->purge_remote)
if (ctx->no_mailbox_renames)
if (ctx->reverse_backup)
else
}
if (ctx->no_mail_sync)
if (ctx->empty_hdr_workaround)
if (doveadm_debug)
brain_flags, &set);
case DSYNC_RUN_TYPE_LOCAL:
&changes_during_sync2, &mail_error) < 0)
ret = -1;
break;
case DSYNC_RUN_TYPE_CMD:
/* fall through */
case DSYNC_RUN_TYPE_STREAM:
break;
}
}
/* don't log a warning when running via doveadm server
(e.g. called by replicator) */
i_warning("Mailbox changes caused a desync. "
"You may want to run dsync again: %s",
changes_during_sync == NULL ||
}
}
ret = -1;
if (ret < 0) {
/* tempfail is the default error. prefer to use a non-tempfail
if that exists. */
if (mail_error2 != 0 &&
}
}
}
}
/* print any final errors after the process has died. not closing
shouldn't (at least with ssh) and we need stderr to be open to be
able to print the final errors */
} else {
}
return ret;
}
void *context)
{
switch (exit_code) {
case 0:
break;
"Disconnected from remote: %s", error);
break;
case EX_NOUSER:
break;
default:
"Failed to start remote dsync-server command: "
"Remote exit_code=%u %s",
break;
}
}
const struct mail_storage_settings *mail_set,
const char **error_r)
{
return 0;
}
static int
const struct mail_storage_settings *mail_set,
{
const char *p, *error;
if (ssl) {
"Couldn't initialize SSL context: %s", error);
return -1;
}
}
ioloop = io_loop_create();
if (doveadm_verbose_proctitle) {
}
*error_r = "Couldn't create server connection";
return -1;
}
/* <flags> <username> <command> [<args>] */
if (doveadm_debug)
if (ctx->replicator_notify)
if (doveadm_verbose_proctitle) {
}
return -1;
}
return 0;
}
static int
const struct mail_storage_settings *mail_set,
const char *location,
const char *const **remote_cmd_args_r, const char **error_r)
{
/* TCP connection to remote dsync */
}
/* TCP+SSL connection to remote dsync */
}
/* this is a remote (ssh) command */
/* this is a remote (ssh) command with a "user\n"
prefix sent before dsync actually starts */
} else {
/* local with e.g. maildir:path */
return 0;
}
return 0;
}
struct mail_storage_service_user *service_user,
const char **error_r)
{
if (ctx->default_replica_location) {
*error_r = "User has no mail_replica in userdb";
return -1;
}
} else {
/* if we're executing remotely, give -u parameter if we also
did a userdb lookup. */
/* it's a mail_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 */
}
if (ctx->sync_visible_namespaces &&
i_fatal("-N parameter requires syncing with remote host");
return 0;
}
const char *const args[])
{
if (ctx->default_replica_location) {
i_error("Don't give mail location with -d parameter");
} else {
}
}
{
}
static bool
{
bool utc;
switch (c) {
case '1':
break;
case 'a':
break;
case 'd':
break;
case 'D':
break;
case 'E':
/* dsync wrapper detection flag */
legacy_dsync = TRUE;
break;
case 'f':
break;
case 'O': {
i_fatal("-O parameter doesn't support multiple flags currently");
if (str[0] == '-')
str++;
break;
}
case 'g':
if (optarg[0] == '\0')
break;
case 'l':
break;
case 'm':
if (optarg[0] == '\0')
else
break;
case 'x':
break;
case 'n':
break;
case 'N':
break;
case 'P':
break;
case 'r':
break;
case 'R':
break;
case 's':
*optarg != '\0')
break;
case 't':
break;
case 'e':
break;
case 'I':
break;
case 'T':
break;
case 'U':
break;
default:
return FALSE;
}
return TRUE;
}
{
}
{
_ctx = cmd_dsync_alloc();
return _ctx;
}
static int
{
if (!cli) {
if (ctx->replicator_notify &&
return DOVEADM_EX_NOREPLICATE;
}
/* doveadm-server connection. start with a success reply.
after that follows the regular dsync protocol. */
/* include the doveadm client's IP address in the ps output */
}
} else {
/* the log messages go via stderr to the remote dsync,
so the names are reversed */
name = "local";
}
if (ctx->replicator_notify) {
}
if (!cli) {
/* make sure nothing more is written by the generic doveadm
connection code */
}
}
static bool
{
switch (c) {
case 'E':
/* dsync wrapper detection flag */
legacy_dsync = TRUE;
break;
case 'r':
break;
case 'T':
break;
case 'U':
break;
default:
return FALSE;
}
return TRUE;
}
{
}
cmd_dsync_alloc, "sync",
"[-1fPRU] [-l <secs>] [-r <rawlog path>] [-m <mailbox>] [-g <mailbox_guid>] [-n <namespace> | -N] [-x <exclude>] [-s <state>] [-t <start date>] -d|<dest>"
};
cmd_dsync_backup_alloc, "backup",
"[-fPRU] [-l <secs>] [-r <rawlog path>] [-m <mailbox>] [-g <mailbox_guid>] [-n <namespace> | -N] [-x <exclude>] [-s <state>] [-t <start date>] -d|<dest>"
};
};
{
const char *getopt_str;
return;
/* @UNSAFE: this is called when the "doveadm" binary is called as
"dsync" (for backwards compatibility) */
dest = 1;
/* add global doveadm flags */
break;
case 'C':
break;
case 'f':
break;
case 'R':
break;
case 'm':
break;
case 'u':
break;
default:
break;
}
}
if (j > 1) {
dup[j++] = '\0';
}
if (flag_m) {
i_fatal("-m missing parameter");
}
if (flag_u) {
i_fatal("-u missing parameter");
}
if (flag_C) {
i_fatal("-C missing parameter");
}
}
}
/* mirror|backup|server */
i_fatal("Missing mirror or backup parameter");
/* we're re-executing dsync due to doveconf.
"backup" re-exec detection is later. */
return;
}
dsync_server = TRUE;
} else
/* we're re-executing dsync due to doveconf */
return;
}
/* dsync flags */
new_flags[0] = '-';
if (!dsync_server) {
if (flag_f)
new_flags[i++] = 'f';
if (flag_R)
new_flags[i++] = 'R';
new_flags[i++] = 'm';
}
new_flags[i] = '\0';
if (i > 1) {
}
}
/* rest of the parameters */
legacy_dsync = TRUE;
}