diff options
Diffstat (limited to 'src/doveadm/doveadm-replicator.c')
-rw-r--r-- | src/doveadm/doveadm-replicator.c | 372 |
1 files changed, 372 insertions, 0 deletions
diff --git a/src/doveadm/doveadm-replicator.c b/src/doveadm/doveadm-replicator.c new file mode 100644 index 0000000..17af8b9 --- /dev/null +++ b/src/doveadm/doveadm-replicator.c @@ -0,0 +1,372 @@ +/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "ioloop.h" +#include "str.h" +#include "strescape.h" +#include "istream.h" +#include "write-full.h" +#include "master-service.h" +#include "doveadm.h" +#include "doveadm-print.h" + +#include <stdio.h> +#include <unistd.h> +#include <fcntl.h> + +struct replicator_context { + const char *socket_path; + const char *priority; + const char *user_mask, *username; + struct istream *input; + bool full_sync; +}; + +extern struct doveadm_cmd_ver2 doveadm_cmd_replicator[]; + +static void replicator_cmd_help(const struct doveadm_cmd_ver2 *cmd) ATTR_NORETURN; + +static void +replicator_send(struct replicator_context *ctx, const char *data) +{ + if (write_full(i_stream_get_fd(ctx->input), data, strlen(data)) < 0) + i_fatal("write(%s) failed: %m", ctx->socket_path); +} + +static void replicator_connect(struct replicator_context *ctx) +{ +#define REPLICATOR_HANDSHAKE "VERSION\treplicator-doveadm-client\t1\t0\n" + const char *line; + int fd; + + fd = doveadm_connect(ctx->socket_path); + net_set_nonblock(fd, FALSE); + + ctx->input = i_stream_create_fd_autoclose(&fd, SIZE_MAX); + replicator_send(ctx, REPLICATOR_HANDSHAKE); + + alarm(5); + line = i_stream_read_next_line(ctx->input); + alarm(0); + if (line == NULL) { + if (ctx->input->stream_errno != 0) { + i_fatal("read(%s) failed: %s", ctx->socket_path, + i_stream_get_error(ctx->input)); + } else if (ctx->input->eof) + i_fatal("%s disconnected", ctx->socket_path); + else + i_fatal("read(%s) timed out", ctx->socket_path); + } + if (!version_string_verify(line, "replicator-doveadm-server", 1)) { + i_fatal_status(EX_PROTOCOL, + "%s not a compatible replicator-doveadm socket", + ctx->socket_path); + } +} + +static void replicator_disconnect(struct replicator_context *ctx) +{ + if (ctx->input->stream_errno != 0) { + i_fatal("read(%s) failed: %s", ctx->socket_path, + i_stream_get_error(ctx->input)); + } + i_stream_destroy(&ctx->input); +} + +static struct replicator_context * +cmd_replicator_init(struct doveadm_cmd_context *cctx) +{ + struct replicator_context *ctx; + + ctx = t_new(struct replicator_context, 1); + ctx->socket_path = t_strconcat(doveadm_settings->base_dir, + "/replicator-doveadm", NULL); + + (void)doveadm_cmd_param_str(cctx, "socket-path", &ctx->socket_path); + (void)doveadm_cmd_param_bool(cctx, "full-sync", &ctx->full_sync); + (void)doveadm_cmd_param_str(cctx, "priority", &ctx->priority); + (void)doveadm_cmd_param_str(cctx, "user-mask", &ctx->user_mask); + (void)doveadm_cmd_param_str(cctx, "user", &ctx->username); + + replicator_connect(ctx); + return ctx; +} + +static const char *time_ago(time_t t) +{ + int diff = ioloop_time - t; + + if (t == 0) + return "-"; + return t_strdup_printf("%02d:%02d:%02d", diff/3600, + (diff/60)%60, diff%60); +} + +static void cmd_replicator_status_overview(struct replicator_context *ctx) +{ + char *line, *value; + + doveadm_print_init(DOVEADM_PRINT_TYPE_TABLE); + doveadm_print_header("field", "field", + DOVEADM_PRINT_HEADER_FLAG_HIDE_TITLE); + doveadm_print_header("value", "value", + DOVEADM_PRINT_HEADER_FLAG_HIDE_TITLE); + + replicator_send(ctx, "STATUS\n"); + while ((line = i_stream_read_next_line(ctx->input)) != NULL) { + if (*line == '\0') + break; + value = strchr(line, '\t'); + if (value != NULL) + *value++ = '\0'; + else + value = ""; + doveadm_print(line); + doveadm_print(value); + } + replicator_disconnect(ctx); +} + +static void cmd_replicator_status(struct doveadm_cmd_context *cctx) +{ + struct replicator_context *ctx; + const char *line, *const *args; + time_t last_fast, last_full, last_success; + + ctx = cmd_replicator_init(cctx); + if (ctx->user_mask == NULL) { + cmd_replicator_status_overview(ctx); + return; + } + + doveadm_print_init(DOVEADM_PRINT_TYPE_TABLE); + doveadm_print_header("username", "username", + DOVEADM_PRINT_HEADER_FLAG_EXPAND); + doveadm_print_header_simple("priority"); + doveadm_print_header_simple("fast sync"); + doveadm_print_header_simple("full sync"); + doveadm_print_header_simple("success sync"); + doveadm_print_header_simple("failed"); + + replicator_send(ctx, t_strdup_printf("STATUS\t%s\n", + str_tabescape(ctx->user_mask))); + while ((line = i_stream_read_next_line(ctx->input)) != NULL) { + if (*line == '\0') + break; + T_BEGIN { + args = t_strsplit_tabescaped(line); + if (str_array_length(args) >= 5 && + str_to_time(args[2], &last_fast) == 0 && + str_to_time(args[3], &last_full) == 0 && + str_to_time(args[5], &last_success) == 0) { + doveadm_print(args[0]); + doveadm_print(args[1]); + doveadm_print(time_ago(last_fast)); + doveadm_print(time_ago(last_full)); + doveadm_print(time_ago(last_success)); + doveadm_print(args[4][0] == '0' ? "-" : "y"); + } + } T_END; + } + if (line == NULL) { + i_error("Replicator disconnected unexpectedly"); + doveadm_exit_code = EX_TEMPFAIL; + } + replicator_disconnect(ctx); +} + +static void cmd_replicator_dsync_status(struct doveadm_cmd_context *cctx) +{ + struct replicator_context *ctx; + const char *line; + unsigned int i; + + ctx = cmd_replicator_init(cctx); + + doveadm_print_init(DOVEADM_PRINT_TYPE_TABLE); + doveadm_print_header("username", "username", + DOVEADM_PRINT_HEADER_FLAG_EXPAND); + doveadm_print_header_simple("type"); + doveadm_print_header_simple("status"); + + replicator_send(ctx, "STATUS-DSYNC\n"); + while ((line = i_stream_read_next_line(ctx->input)) != NULL) { + if (*line == '\0') + break; + T_BEGIN { + const char *const *args = t_strsplit_tabescaped(line); + + for (i = 0; i < 3; i++) { + if (args[i] == NULL) + break; + doveadm_print(args[i]); + } + for (; i < 3; i++) + doveadm_print(""); + } T_END; + } + replicator_disconnect(ctx); +} + +static void cmd_replicator_replicate(struct doveadm_cmd_context *cctx) +{ + struct replicator_context *ctx; + string_t *str; + const char *line; + + ctx = cmd_replicator_init(cctx); + if (ctx->user_mask == NULL) + replicator_cmd_help(cctx->cmd); + + str = t_str_new(128); + str_append(str, "REPLICATE\t"); + if (ctx->priority == NULL) + str_append_tabescaped(str, "low"); + else + str_append_tabescaped(str, ctx->priority); + str_append_c(str, '\t'); + if (ctx->full_sync) + str_append_c(str, 'f'); + str_append_c(str, '\t'); + str_append_tabescaped(str, ctx->user_mask); + str_append_c(str, '\n'); + replicator_send(ctx, str_c(str)); + + doveadm_print_init(DOVEADM_PRINT_TYPE_FLOW); + doveadm_print_header("result", "result", + DOVEADM_PRINT_HEADER_FLAG_HIDE_TITLE); + + line = i_stream_read_next_line(ctx->input); + if (line == NULL) { + i_error("Replicator disconnected unexpectedly"); + doveadm_exit_code = EX_TEMPFAIL; + } else if (line[0] != '+') { + i_error("Replicator failed: %s", line+1); + doveadm_exit_code = EX_USAGE; + } else { + doveadm_print(t_strdup_printf("%s users updated", line+1)); + } + replicator_disconnect(ctx); +} + +static void cmd_replicator_add(struct doveadm_cmd_context *cctx) +{ + struct replicator_context *ctx; + string_t *str; + const char *line; + + ctx = cmd_replicator_init(cctx); + if (ctx->user_mask == NULL) + replicator_cmd_help(cctx->cmd); + + str = t_str_new(128); + str_append(str, "ADD\t"); + str_append_tabescaped(str, ctx->user_mask); + str_append_c(str, '\n'); + replicator_send(ctx, str_c(str)); + + line = i_stream_read_next_line(ctx->input); + if (line == NULL) { + i_error("Replicator disconnected unexpectedly"); + doveadm_exit_code = EX_TEMPFAIL; + } else if (line[0] != '+') { + i_error("Replicator failed: %s", line+1); + doveadm_exit_code = EX_USAGE; + } + replicator_disconnect(ctx); +} + +static void cmd_replicator_remove(struct doveadm_cmd_context *cctx) +{ + struct replicator_context *ctx; + string_t *str; + const char *line; + + ctx = cmd_replicator_init(cctx); + if (ctx->username == NULL) + replicator_cmd_help(cctx->cmd); + + str = t_str_new(128); + str_append(str, "REMOVE\t"); + str_append_tabescaped(str, ctx->username); + str_append_c(str, '\n'); + replicator_send(ctx, str_c(str)); + + line = i_stream_read_next_line(ctx->input); + if (line == NULL) { + i_error("Replicator disconnected unexpectedly"); + doveadm_exit_code = EX_TEMPFAIL; + } else if (line[0] != '+') { + i_error("Replicator failed: %s", line+1); + doveadm_exit_code = EX_USAGE; + } + replicator_disconnect(ctx); +} + +struct doveadm_cmd_ver2 doveadm_cmd_replicator[] = { +{ + .name = "replicator status", + .cmd = cmd_replicator_status, + .usage = "[-a <replicator socket path>] [<user mask>]", +DOVEADM_CMD_PARAMS_START +DOVEADM_CMD_PARAM('a', "socket-path", CMD_PARAM_STR, 0) +DOVEADM_CMD_PARAM('\0', "user-mask", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) +DOVEADM_CMD_PARAMS_END +}, +{ + .name = "replicator dsync-status", + .cmd = cmd_replicator_dsync_status, + .usage = "[-a <replicator socket path>]", +DOVEADM_CMD_PARAMS_START +DOVEADM_CMD_PARAM('a', "socket-path", CMD_PARAM_STR, 0) +DOVEADM_CMD_PARAMS_END +}, +{ + .name = "replicator replicate", + .cmd = cmd_replicator_replicate, + .usage = "[-a <replicator socket path>] [-f] [-p <priority>] <user mask>", +DOVEADM_CMD_PARAMS_START +DOVEADM_CMD_PARAM('a', "socket-path", CMD_PARAM_STR, 0) +DOVEADM_CMD_PARAM('f', "full-sync", CMD_PARAM_BOOL, 0) +DOVEADM_CMD_PARAM('p', "priority", CMD_PARAM_STR, 0) +DOVEADM_CMD_PARAM('\0', "user-mask", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) +DOVEADM_CMD_PARAMS_END +}, +{ + .name = "replicator add", + .cmd = cmd_replicator_add, + .usage = "[-a <replicator socket path>] <user mask>", +DOVEADM_CMD_PARAMS_START +DOVEADM_CMD_PARAM('a', "socket-path", CMD_PARAM_STR, 0) +DOVEADM_CMD_PARAM('\0', "user-mask", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) +DOVEADM_CMD_PARAMS_END +}, +{ + .name = "replicator remove", + .cmd = cmd_replicator_remove, + .usage = "[-a <replicator socket path>] <username>", +DOVEADM_CMD_PARAMS_START +DOVEADM_CMD_PARAM('a', "socket-path", CMD_PARAM_STR, 0) +DOVEADM_CMD_PARAM('\0', "user", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) +DOVEADM_CMD_PARAMS_END +}, +}; + +static void replicator_cmd_help(const struct doveadm_cmd_ver2 *cmd) +{ + unsigned int i; + + for (i = 0; i < N_ELEMENTS(doveadm_cmd_replicator); i++) { + if (doveadm_cmd_replicator[i].cmd == cmd->cmd) + help_ver2(&doveadm_cmd_replicator[i]); + } + i_unreached(); +} + +void doveadm_register_replicator_commands(void) +{ + unsigned int i; + + for (i = 0; i < N_ELEMENTS(doveadm_cmd_replicator); i++) + doveadm_cmd_register_ver2(&doveadm_cmd_replicator[i]); +} |