view src/doveadm/dsync/doveadm-dsync.c @ 14133:ba770cba5598

Updated copyright notices to include year 2012.
author Timo Sirainen <tss@iki.fi>
date Sun, 12 Feb 2012 18:55:28 +0200
parents 1a722c7676bb
children 0a5951b08478
line wrap: on
line source

/* Copyright (c) 2009-2012 Dovecot authors, see the included COPYING file */

#include "lib.h"
#include "lib-signals.h"
#include "array.h"
#include "execv-const.h"
#include "settings-parser.h"
#include "master-service.h"
#include "mail-storage-service.h"
#include "mail-user.h"
#include "mail-namespace.h"
#include "doveadm-settings.h"
#include "doveadm-mail.h"
#include "dsync-brain.h"
#include "dsync-worker.h"
#include "dsync-proxy-server.h"
#include "doveadm-dsync.h"

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <ctype.h>

struct dsync_cmd_context {
	struct doveadm_mail_cmd_context ctx;
	enum dsync_brain_flags brain_flags;
	const char *mailbox;

	const char *const *remote_cmd_args;
	const char *local_location;

	int fd_in, fd_out;

	unsigned int reverse_workers:1;
};

static const char *ssh_cmd = "ssh";

static void run_cmd(const char *const *args, int *fd_in_r, int *fd_out_r)
{
	int fd_in[2], fd_out[2];

	if (pipe(fd_in) < 0 || pipe(fd_out) < 0)
		i_fatal("pipe() failed: %m");

	switch (fork()) {
	case -1:
		i_fatal("fork() failed: %m");
		break;
	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)
			i_fatal("dup2() failed: %m");

		(void)close(fd_in[0]);
		(void)close(fd_in[1]);
		(void)close(fd_out[0]);
		(void)close(fd_out[1]);

		execvp_const(args[0], args);
		break;
	default:
		/* parent */
		(void)close(fd_in[0]);
		(void)close(fd_out[1]);
		*fd_in_r = fd_out[0];
		*fd_out_r = fd_in[1];
		break;
	}
}

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_append(&cmd_args, &p, 1);
	}

	p = strchr(argv[0], '/');
	if (p == NULL) p = argv[0];
	if (strstr(p, "dsync") != NULL) {
		/* we're executing dsync */
		p = "server";
	} else {
		/* we're executing doveadm */
		p = "dsync-server";
	}
	array_append(&cmd_args, &p, 1);
	(void)array_append_space(&cmd_args);
	*cmd_args_r = array_idx(&cmd_args, 0);
}

static bool mirror_get_remote_cmd(const char *const *argv, const char *user,
				  const char *const **cmd_args_r)
{
	ARRAY_TYPE(const_string) cmd_args;
	const char *p, *host;

	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. */
	t_array_init(&cmd_args, 8);
	array_append(&cmd_args, &ssh_cmd, 1);
	array_append(&cmd_args, &host, 1);
	p = "doveadm"; array_append(&cmd_args, &p, 1);
	p = "dsync-server"; array_append(&cmd_args, &p, 1);
	if (*user != '\0') {
		p = "-u"; array_append(&cmd_args, &p, 1);
		array_append(&cmd_args, &user, 1);
	}
	(void)array_append_space(&cmd_args);
	*cmd_args_r = array_idx(&cmd_args, 0);
	return TRUE;
}

static struct dsync_worker *
cmd_dsync_run_local(struct dsync_cmd_context *ctx, struct mail_user *user)
{
	struct mail_user *user2;
	struct dsync_worker *worker2;
	struct setting_parser_context *set_parser;
	const char *set_line, *path1, *path2;

	i_assert(ctx->local_location != NULL);

	ctx->brain_flags |= DSYNC_BRAIN_FLAG_LOCAL;
	i_set_failure_prefix(t_strdup_printf("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=", ctx->local_location, NULL);
	if (settings_parse_line(set_parser, set_line) < 0)
		i_unreached();
	if (mail_storage_service_next(ctx->ctx.storage_service,
				      ctx->ctx.cur_service_user, &user2) < 0)
		i_fatal("User init failed");
	user2->admin = TRUE;

	if (mail_namespaces_get_root_sep(user->namespaces) !=
	    mail_namespaces_get_root_sep(user2->namespaces)) {
		i_fatal("Mail locations must use the same "
			"virtual mailbox hierarchy separator "
			"(specify separator for the default namespace)");
	}
	path1 = mailbox_list_get_path(user->namespaces->list, NULL,
				      MAILBOX_LIST_PATH_TYPE_MAILBOX);
	path2 = mailbox_list_get_path(user2->namespaces->list, NULL,
				      MAILBOX_LIST_PATH_TYPE_MAILBOX);
	if (path1 != NULL && path2 != NULL &&
	    strcmp(path1, path2) == 0) {
		i_fatal("Both source and destination mail_location "
			"points to same directory: %s", path1);
	}

	worker2 = dsync_worker_init_local(user2, *ctx->ctx.set->dsync_alt_char);
	mail_user_unref(&user2);
	return worker2;
}

static struct dsync_worker *
cmd_dsync_run_remote(struct dsync_cmd_context *ctx, struct mail_user *user)
{
	i_set_failure_prefix(t_strdup_printf("dsync-local(%s): ",
					     user->username));
	return dsync_worker_init_proxy_client(ctx->fd_in, ctx->fd_out);
}

static void
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_worker *worker1, *worker2, *workertmp;
	struct dsync_brain *brain;

	user->admin = TRUE;

	/* create workers */
	worker1 = dsync_worker_init_local(user, *_ctx->set->dsync_alt_char);
	if (ctx->remote_cmd_args == NULL)
		worker2 = cmd_dsync_run_local(ctx, user);
	else
		worker2 = cmd_dsync_run_remote(ctx, user);
	if (ctx->reverse_workers) {
		workertmp = worker1;
		worker1 = worker2;
		worker2 = workertmp;
	}

	/* create and run the brain */
	brain = dsync_brain_init(worker1, worker2, ctx->mailbox,
				 ctx->brain_flags);
	if (ctx->remote_cmd_args == NULL)
		dsync_brain_sync_all(brain);
	else {
		dsync_brain_sync(brain);
		if (!dsync_brain_has_failed(brain))
			io_loop_run(current_ioloop);
	}
	/* deinit */
	if (dsync_brain_has_unexpected_changes(brain)) {
		i_warning("Mailbox changes caused a desync. "
			  "You may want to run dsync again.");
		_ctx->exit_code = 2;
	}
	if (dsync_brain_deinit(&brain) < 0)
		_ctx->exit_code = 1;

	dsync_worker_deinit(&worker1);
	dsync_worker_deinit(&worker2);
}

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;
	const char *username = "";

	if (args[0] == NULL)
		doveadm_mail_help_name(_ctx->cmd->name);

	lib_signals_ignore(SIGHUP, TRUE);

	if (doveadm_debug || doveadm_verbose)
		ctx->brain_flags |= DSYNC_BRAIN_FLAG_VERBOSE;

	/* if we're executing remotely, give -u parameter if we also
	   did a userdb lookup. this works only when we're handling a
	   single user */
	if ((_ctx->service_flags & MAIL_STORAGE_SERVICE_FLAG_USERDB_LOOKUP) != 0 &&
	    _ctx->cur_username != NULL)
		username = _ctx->cur_username;
	if (!mirror_get_remote_cmd(args, username, &ctx->remote_cmd_args)) {
		/* it's a mail_location */
		if (args[1] != NULL)
			doveadm_mail_help_name(_ctx->cmd->name);
		ctx->local_location = args[0];
	}

	if (ctx->remote_cmd_args != NULL) {
		/* do this before mail_storage_service_next() in case it
		   drops process privileges */
		run_cmd(ctx->remote_cmd_args, &ctx->fd_in, &ctx->fd_out);
	} else {
		ctx->fd_in = STDIN_FILENO;
		ctx->fd_out = STDOUT_FILENO;
	}
}

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;

	switch (c) {
	case 'E':
		/* dsync backup wrapper detection flag */
		break;
	case 'f':
		ctx->brain_flags |= DSYNC_BRAIN_FLAG_FULL_SYNC;
		break;
	case 'm':
		ctx->mailbox = optarg;
		break;
	case 'R':
		ctx->reverse_workers = 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->ctx.getopt_args = "EfRm:";
	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.run = cmd_dsync_run;
	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->brain_flags |= DSYNC_BRAIN_FLAG_BACKUP;
	return _ctx;
}

static void
cmd_dsync_server_run(struct doveadm_mail_cmd_context *ctx,
		     struct mail_user *user)
{
	struct dsync_proxy_server *server;
	struct dsync_worker *worker;

	user->admin = TRUE;

	i_set_failure_prefix(t_strdup_printf("dsync-remote(%s): ",
					     user->username));
	worker = dsync_worker_init_local(user, *ctx->set->dsync_alt_char);
	server = dsync_proxy_server_init(STDIN_FILENO, STDOUT_FILENO, worker);

	io_loop_run(current_ioloop);

	dsync_proxy_server_deinit(&server);
	dsync_worker_deinit(&worker);
}

static struct doveadm_mail_cmd_context *cmd_dsync_server_alloc(void)
{
	struct doveadm_mail_cmd_context *ctx;

	ctx = doveadm_mail_cmd_alloc(struct doveadm_mail_cmd_context);
	ctx->v.run = cmd_dsync_server_run;
	return ctx;
}

struct doveadm_mail_cmd cmd_dsync_mirror = {
	cmd_dsync_alloc, "sync", "[-fR] [-m <mailbox>] <dest>"
};
struct doveadm_mail_cmd cmd_dsync_backup = {
	cmd_dsync_backup_alloc, "backup",
	"[-fR] [-m <mailbox>] <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 backup_flag = 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 = calloc(sizeof(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 = strdup(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) {
		backup_flag = TRUE;
		new_argv[dest] = "backup";
	} else if (strcmp(argv[src], "server") == 0)
		new_argv[dest] = "dsync-server";
	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] = '-'; i = 1;
	if (backup_flag)
		new_flags[i++] = 'E';
	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;

	*_argc = dest;
	*_argv = new_argv;
	optind = 1;
}