summaryrefslogtreecommitdiffstats
path: root/src/doveadm/server-connection.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 09:51:24 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 09:51:24 +0000
commitf7548d6d28c313cf80e6f3ef89aed16a19815df1 (patch)
treea3f6f2a3f247293bee59ecd28e8cd8ceb6ca064a /src/doveadm/server-connection.c
parentInitial commit. (diff)
downloaddovecot-f7548d6d28c313cf80e6f3ef89aed16a19815df1.tar.xz
dovecot-f7548d6d28c313cf80e6f3ef89aed16a19815df1.zip
Adding upstream version 1:2.3.19.1+dfsg1.upstream/1%2.3.19.1+dfsg1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/doveadm/server-connection.c')
-rw-r--r--src/doveadm/server-connection.c691
1 files changed, 691 insertions, 0 deletions
diff --git a/src/doveadm/server-connection.c b/src/doveadm/server-connection.c
new file mode 100644
index 0000000..a0e6500
--- /dev/null
+++ b/src/doveadm/server-connection.c
@@ -0,0 +1,691 @@
+/* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "base64.h"
+#include "ioloop.h"
+#include "net.h"
+#include "istream.h"
+#include "istream-multiplex.h"
+#include "ostream.h"
+#include "ostream-dot.h"
+#include "str.h"
+#include "strescape.h"
+#include "iostream-ssl.h"
+#include "master-service.h"
+#include "master-service-settings.h"
+#include "settings-parser.h"
+#include "doveadm.h"
+#include "doveadm-print.h"
+#include "doveadm-util.h"
+#include "doveadm-server.h"
+#include "doveadm-settings.h"
+#include "server-connection.h"
+
+#include <sysexits.h>
+#include <unistd.h>
+
+#define DOVEADM_LOG_CHANNEL_ID 'L'
+
+#define MAX_INBUF_SIZE (1024*32)
+
+enum server_reply_state {
+ SERVER_REPLY_STATE_DONE = 0,
+ SERVER_REPLY_STATE_PRINT,
+ SERVER_REPLY_STATE_RET
+};
+
+struct server_connection {
+ struct doveadm_server *server;
+
+ pool_t pool;
+ struct doveadm_settings *set;
+
+ int fd;
+ unsigned int minor;
+
+ struct io *io;
+ struct io *io_log;
+ struct istream *input;
+ struct istream *log_input;
+ struct ostream *output;
+ struct ssl_iostream *ssl_iostream;
+
+ struct istream *cmd_input;
+ struct ostream *cmd_output;
+ const char *delayed_cmd;
+ server_cmd_callback_t *callback;
+ void *context;
+
+ enum server_reply_state state;
+
+ bool version_received:1;
+ bool authenticate_sent:1;
+ bool authenticated:1;
+ bool streaming:1;
+ bool ssl_done:1;
+};
+
+static struct server_connection *printing_conn = NULL;
+static ARRAY(struct doveadm_server *) print_pending_servers = ARRAY_INIT;
+
+static void server_connection_input(struct server_connection *conn);
+static bool server_connection_input_one(struct server_connection *conn);
+static int server_connection_init_ssl(struct server_connection *conn,
+ const char **error_r);
+
+static void server_set_print_pending(struct doveadm_server *server)
+{
+ struct doveadm_server *pending_server;
+
+ if (!array_is_created(&print_pending_servers))
+ i_array_init(&print_pending_servers, 16);
+ array_foreach_elem(&print_pending_servers, pending_server) {
+ if (pending_server == server)
+ return;
+ }
+ array_push_back(&print_pending_servers, &server);
+}
+
+static void server_print_connection_released(struct doveadm_server *server)
+{
+ struct server_connection *const *conns;
+ unsigned int i, count;
+
+ conns = array_get(&server->connections, &count);
+ for (i = 0; i < count; i++) {
+ if (conns[i]->io != NULL)
+ continue;
+
+ conns[i]->io = io_add(conns[i]->fd, IO_READ,
+ server_connection_input, conns[i]);
+ io_set_pending(conns[i]->io);
+ }
+}
+
+static void print_connection_released(void)
+{
+ struct doveadm_server *server;
+
+ printing_conn = NULL;
+ if (!array_is_created(&print_pending_servers))
+ return;
+
+ array_foreach_elem(&print_pending_servers, server)
+ server_print_connection_released(server);
+ array_free(&print_pending_servers);
+}
+
+static int server_connection_send_cmd_input_more(struct server_connection *conn)
+{
+ enum ostream_send_istream_result res;
+ int ret = -1;
+
+ /* ostream-dot writes only up to max buffer size, so keep it non-zero */
+ o_stream_set_max_buffer_size(conn->cmd_output, IO_BLOCK_SIZE);
+ res = o_stream_send_istream(conn->cmd_output, conn->cmd_input);
+ o_stream_set_max_buffer_size(conn->cmd_output, SIZE_MAX);
+
+ switch (res) {
+ case OSTREAM_SEND_ISTREAM_RESULT_FINISHED:
+ break;
+ case OSTREAM_SEND_ISTREAM_RESULT_WAIT_INPUT:
+ return 1;
+ case OSTREAM_SEND_ISTREAM_RESULT_WAIT_OUTPUT:
+ return 0;
+ case OSTREAM_SEND_ISTREAM_RESULT_ERROR_INPUT:
+ i_error("read(%s) failed: %s",
+ i_stream_get_name(conn->cmd_input),
+ i_stream_get_error(conn->cmd_input));
+ break;
+ case OSTREAM_SEND_ISTREAM_RESULT_ERROR_OUTPUT:
+ i_error("write(%s) failed: %s",
+ o_stream_get_name(conn->cmd_output),
+ o_stream_get_error(conn->cmd_output));
+ break;
+ }
+ if (res == OSTREAM_SEND_ISTREAM_RESULT_FINISHED) {
+ if ((ret = o_stream_finish(conn->cmd_output)) == 0)
+ return 0;
+ else if (ret < 0) {
+ i_error("write(%s) failed: %s",
+ o_stream_get_name(conn->cmd_output),
+ o_stream_get_error(conn->cmd_output));
+ }
+ }
+
+ i_stream_destroy(&conn->cmd_input);
+ o_stream_destroy(&conn->cmd_output);
+ return ret;
+}
+
+static void server_connection_send_cmd_input(struct server_connection *conn)
+{
+ if (conn->cmd_input == NULL)
+ return;
+
+ conn->cmd_output = o_stream_create_dot(conn->output, TRUE);
+ (void)server_connection_send_cmd_input_more(conn);
+}
+
+static int server_connection_output(struct server_connection *conn)
+{
+ int ret;
+
+ ret = o_stream_flush(conn->output);
+ if (ret > 0 && conn->cmd_input != NULL && conn->delayed_cmd == NULL)
+ ret = server_connection_send_cmd_input_more(conn);
+ if (ret < 0)
+ server_connection_destroy(&conn);
+ return ret;
+}
+
+static void
+server_connection_callback(struct server_connection *conn,
+ int exit_code, const char *error)
+{
+ server_cmd_callback_t *callback = conn->callback;
+
+ conn->callback = NULL;
+ callback(exit_code, error, conn->context);
+}
+
+static void stream_data(string_t *str, const unsigned char *data, size_t size)
+{
+ str_truncate(str, 0);
+ str_append_tabunescaped(str, data, size);
+ doveadm_print_stream(str->data, str->used);
+}
+
+static void server_flush_field(struct server_connection *conn, string_t *str,
+ const unsigned char *data, size_t size)
+{
+ if (conn->streaming) {
+ conn->streaming = FALSE;
+ if (size > 0)
+ stream_data(str, data, size);
+ doveadm_print_stream("", 0);
+ } else {
+ str_truncate(str, 0);
+ str_append_tabunescaped(str, data, size);
+ doveadm_print(str_c(str));
+ }
+}
+
+static void
+server_handle_input(struct server_connection *conn,
+ const unsigned char *data, size_t size)
+{
+ string_t *str;
+ size_t i, start;
+
+ if (printing_conn == conn) {
+ /* continue printing */
+ } else if (printing_conn == NULL) {
+ printing_conn = conn;
+ } else {
+ /* someone else is printing. don't continue until it
+ goes away */
+ server_set_print_pending(conn->server);
+ io_remove(&conn->io);
+ return;
+ }
+
+ if (data[size-1] == '\001') {
+ /* last character is an escape */
+ size--;
+ }
+
+ str = t_str_new(128);
+ for (i = start = 0; i < size; i++) {
+ if (data[i] == '\n') {
+ if (i != start) {
+ i_error("doveadm server sent broken print input");
+ server_connection_destroy(&conn);
+ return;
+ }
+ conn->state = SERVER_REPLY_STATE_RET;
+ i_stream_skip(conn->input, i + 1);
+
+ print_connection_released();
+ return;
+ }
+ if (data[i] == '\t') {
+ server_flush_field(conn, str, data + start, i - start);
+ start = i + 1;
+ }
+ }
+ if (start != size) {
+ conn->streaming = TRUE;
+ stream_data(str, data + start, size - start);
+ }
+ i_stream_skip(conn->input, size);
+}
+
+static void server_connection_authenticated(struct server_connection *conn)
+{
+ conn->authenticated = TRUE;
+ if (conn->delayed_cmd != NULL) {
+ o_stream_nsend_str(conn->output, conn->delayed_cmd);
+ conn->delayed_cmd = NULL;
+ server_connection_send_cmd_input(conn);
+ }
+}
+
+static int
+server_connection_authenticate(struct server_connection *conn)
+{
+ string_t *plain = t_str_new(128);
+ string_t *cmd = t_str_new(128);
+
+ if (*conn->set->doveadm_password == '\0') {
+ i_error("doveadm_password not set, "
+ "can't authenticate to remote server");
+ return -1;
+ }
+
+ str_append_c(plain, '\0');
+ str_append(plain, conn->set->doveadm_username);
+ str_append_c(plain, '\0');
+ str_append(plain, conn->set->doveadm_password);
+
+ str_append(cmd, "PLAIN\t");
+ base64_encode(plain->data, plain->used, cmd);
+ str_append_c(cmd, '\n');
+
+ o_stream_nsend(conn->output, cmd->data, cmd->used);
+ conn->authenticate_sent = TRUE;
+ return 0;
+}
+
+static void server_log_disconnect_error(struct server_connection *conn)
+{
+ const char *error;
+
+ error = conn->ssl_iostream == NULL ? NULL :
+ ssl_iostream_get_last_error(conn->ssl_iostream);
+ if (error == NULL) {
+ error = conn->input->stream_errno == 0 ? "EOF" :
+ strerror(conn->input->stream_errno);
+ }
+ i_error("doveadm server disconnected before handshake: %s", error);
+}
+
+static void server_connection_print_log(struct server_connection *conn)
+{
+ const char *line;
+ struct failure_context ctx;
+ i_zero(&ctx);
+
+ while((line = i_stream_read_next_line(conn->log_input))!=NULL) {
+ /* skip empty lines */
+ if (*line == '\0') continue;
+
+ if (!doveadm_log_type_from_char(line[0], &ctx.type))
+ i_warning("Doveadm server sent invalid log type 0x%02x",
+ line[0]);
+ line++;
+ i_log_type(&ctx, "remote(%s): %s", conn->server->name, line);
+ }
+}
+
+static void server_connection_start_multiplex(struct server_connection *conn)
+{
+ struct istream *is = conn->input;
+ conn->input = i_stream_create_multiplex(is, MAX_INBUF_SIZE);
+ i_stream_unref(&is);
+ io_remove(&conn->io);
+ conn->io = io_add_istream(conn->input, server_connection_input, conn);
+ conn->log_input = i_stream_multiplex_add_channel(conn->input, DOVEADM_LOG_CHANNEL_ID);
+ conn->io_log = io_add_istream(conn->log_input, server_connection_print_log, conn);
+ i_stream_set_return_partial_line(conn->log_input, TRUE);
+}
+
+static void server_connection_input(struct server_connection *conn)
+{
+ const char *line;
+ const char *error;
+
+ if (i_stream_read(conn->input) < 0) {
+ /* disconnected */
+ server_log_disconnect_error(conn);
+ server_connection_destroy(&conn);
+ return;
+ }
+
+ while (!conn->authenticated) {
+ if ((line = i_stream_next_line(conn->input)) == NULL) {
+ if (conn->input->eof) {
+ /* we'll also get here if the line is too long */
+ server_log_disconnect_error(conn);
+ server_connection_destroy(&conn);
+ }
+ return;
+ }
+ /* Allow VERSION before or after the "+" or "-" line,
+ because v2.2.33 sent the version after and newer
+ versions send before. */
+ if (!conn->version_received &&
+ str_begins(line, "VERSION\t")) {
+ if (!version_string_verify_full(line, "doveadm-client",
+ DOVEADM_SERVER_PROTOCOL_VERSION_MAJOR,
+ &conn->minor)) {
+ i_error("doveadm server not compatible with this client"
+ "(mixed old and new binaries?)");
+ server_connection_destroy(&conn);
+ return;
+ }
+ conn->version_received = TRUE;
+ } else if (strcmp(line, "+") == 0) {
+ if (conn->minor > 0)
+ server_connection_start_multiplex(conn);
+ server_connection_authenticated(conn);
+ } else if (strcmp(line, "-") == 0) {
+ if (conn->authenticate_sent) {
+ i_error("doveadm authentication failed (%s)",
+ line+1);
+ server_connection_destroy(&conn);
+ return;
+ }
+ if (!conn->ssl_done &&
+ (conn->server->ssl_flags & PROXY_SSL_FLAG_STARTTLS) != 0) {
+ io_remove(&conn->io);
+ if (conn->minor < 2) {
+ i_error("doveadm STARTTLS failed: Server does not support it");
+ server_connection_destroy(&conn);
+ return;
+ }
+ /* send STARTTLS */
+ o_stream_nsend_str(conn->output, "STARTTLS\n");
+ if (server_connection_init_ssl(conn, &error) < 0) {
+ i_error("doveadm STARTTLS failed: %s", error);
+ server_connection_destroy(&conn);
+ return;
+ }
+ conn->ssl_done = TRUE;
+ conn->io = io_add_istream(conn->input, server_connection_input, conn);
+ }
+ if (server_connection_authenticate(conn) < 0) {
+ server_connection_destroy(&conn);
+ return;
+ }
+ } else {
+ i_error("doveadm server sent invalid handshake: %s",
+ line);
+ server_connection_destroy(&conn);
+ return;
+ }
+ }
+
+ while (server_connection_input_one(conn)) ;
+}
+
+static bool server_connection_input_one(struct server_connection *conn)
+{
+ const unsigned char *data;
+ size_t size;
+ const char *line;
+ int exit_code;
+
+ /* check logs - NOTE: must be before i_stream_get_data() since checking
+ for logs may add data to our channel. */
+ if (conn->log_input != NULL)
+ (void)server_connection_print_log(conn);
+
+ data = i_stream_get_data(conn->input, &size);
+ if (size == 0)
+ return FALSE;
+
+ switch (conn->state) {
+ case SERVER_REPLY_STATE_DONE:
+ i_error("doveadm server sent unexpected input");
+ server_connection_destroy(&conn);
+ return FALSE;
+ case SERVER_REPLY_STATE_PRINT:
+ server_handle_input(conn, data, size);
+ if (conn->state != SERVER_REPLY_STATE_RET)
+ return FALSE;
+ /* fall through */
+ case SERVER_REPLY_STATE_RET:
+ line = i_stream_next_line(conn->input);
+ if (line == NULL)
+ return FALSE;
+ if (line[0] == '+')
+ server_connection_callback(conn, 0, "");
+ else if (line[0] == '-') {
+ line++;
+ exit_code = doveadm_str_to_exit_code(line);
+ if (exit_code == DOVEADM_EX_UNKNOWN &&
+ str_to_int(line, &exit_code) < 0) {
+ /* old doveadm-server */
+ exit_code = EX_TEMPFAIL;
+ }
+ server_connection_callback(conn, exit_code, line);
+ } else {
+ i_error("doveadm server sent broken input "
+ "(expected cmd reply): %s", line);
+ server_connection_destroy(&conn);
+ return FALSE;
+ }
+ if (conn->callback == NULL) {
+ /* we're finished, close the connection */
+ server_connection_destroy(&conn);
+ return FALSE;
+ }
+ return TRUE;
+ }
+ i_unreached();
+}
+
+static int server_connection_read_settings(struct server_connection *conn,
+ const char **error_r)
+{
+ const struct setting_parser_info *set_roots[] = {
+ &doveadm_setting_parser_info,
+ NULL
+ };
+ struct master_service_settings_input input;
+ struct master_service_settings_output output;
+ const char *error;
+ in_port_t port;
+ void *set;
+
+ i_zero(&input);
+ input.roots = set_roots;
+ input.service = "doveadm";
+
+ (void)net_getsockname(conn->fd, &input.local_ip, &port);
+ (void)net_getpeername(conn->fd, &input.remote_ip, &port);
+
+ if (master_service_settings_read(master_service, &input,
+ &output, &error) < 0) {
+ *error_r = t_strdup_printf(
+ "Error reading configuration: %s", error);
+ return -1;
+ }
+ set = master_service_settings_get_others(master_service)[0];
+ conn->set = settings_dup(&doveadm_setting_parser_info, set, conn->pool);
+ return 0;
+}
+
+static int server_connection_init_ssl(struct server_connection *conn,
+ const char **error_r)
+{
+ struct ssl_iostream_settings ssl_set;
+ const char *error;
+
+ if (conn->server->ssl_flags == 0)
+ return 0;
+
+ doveadm_get_ssl_settings(&ssl_set, pool_datastack_create());
+
+ if ((conn->server->ssl_flags & PROXY_SSL_FLAG_ANY_CERT) != 0)
+ ssl_set.allow_invalid_cert = TRUE;
+ if (ssl_set.allow_invalid_cert)
+ ssl_set.verbose_invalid_cert = TRUE;
+
+ if (conn->server->ssl_ctx == NULL &&
+ ssl_iostream_client_context_cache_get(&ssl_set,
+ &conn->server->ssl_ctx,
+ &error) < 0) {
+ *error_r = t_strdup_printf(
+ "Couldn't initialize SSL client: %s", error);
+ return -1;
+ }
+
+ if (io_stream_create_ssl_client(conn->server->ssl_ctx,
+ conn->server->hostname, &ssl_set,
+ &conn->input, &conn->output,
+ &conn->ssl_iostream, &error) < 0) {
+ *error_r = t_strdup_printf(
+ "Couldn't initialize SSL client: %s", error);
+ return -1;
+ }
+ if (ssl_iostream_handshake(conn->ssl_iostream) < 0) {
+ *error_r = t_strdup_printf(
+ "SSL handshake failed: %s",
+ ssl_iostream_get_last_error(conn->ssl_iostream));
+ return -1;
+ }
+ return 0;
+}
+
+int server_connection_create(struct doveadm_server *server,
+ struct server_connection **conn_r,
+ const char **error_r)
+{
+ const char *target;
+ struct server_connection *conn;
+ pool_t pool;
+
+ pool = pool_alloconly_create("doveadm server connection", 1024*16);
+ conn = p_new(pool, struct server_connection, 1);
+ conn->pool = pool;
+ conn->server = server;
+ if (server->ip.family != 0) {
+ (void)net_ipport2str(&server->ip, server->port, &target);
+ } else {
+ target = server->name;
+ }
+ conn->fd = doveadm_connect_with_default_port(target,
+ doveadm_settings->doveadm_port);
+ net_set_nonblock(conn->fd, TRUE);
+ conn->input = i_stream_create_fd(conn->fd, MAX_INBUF_SIZE);
+ conn->output = o_stream_create_fd(conn->fd, SIZE_MAX);
+ o_stream_set_flush_callback(conn->output, server_connection_output, conn);
+ o_stream_set_no_error_handling(conn->output, TRUE);
+
+ i_stream_set_name(conn->input, server->name);
+ o_stream_set_name(conn->output, server->name);
+
+ array_push_back(&conn->server->connections, &conn);
+
+ if (server_connection_read_settings(conn, error_r) < 0 ||
+ ((server->ssl_flags & PROXY_SSL_FLAG_STARTTLS) == 0 &&
+ server_connection_init_ssl(conn, error_r) < 0)) {
+ server_connection_destroy(&conn);
+ return -1;
+ }
+ conn->io = io_add_istream(conn->input, server_connection_input, conn);
+
+ conn->state = SERVER_REPLY_STATE_DONE;
+ o_stream_nsend_str(conn->output, DOVEADM_SERVER_PROTOCOL_VERSION_LINE"\n");
+
+ *conn_r = conn;
+ return 0;
+}
+
+void server_connection_destroy(struct server_connection **_conn)
+{
+ struct server_connection *conn = *_conn;
+ struct server_connection *const *conns;
+ const char *error;
+ unsigned int i, count;
+
+ *_conn = NULL;
+
+ conns = array_get(&conn->server->connections, &count);
+ for (i = 0; i < count; i++) {
+ if (conns[i] == conn) {
+ array_delete(&conn->server->connections, i, 1);
+ break;
+ }
+ }
+
+ if (conn->callback != NULL) {
+ error = conn->ssl_iostream == NULL ? NULL :
+ ssl_iostream_get_last_error(conn->ssl_iostream);
+ if (error == NULL) {
+ error = conn->input->stream_errno == 0 ? "EOF" :
+ strerror(conn->input->stream_errno);
+ }
+ server_connection_callback(conn, SERVER_EXIT_CODE_DISCONNECTED,
+ error);
+ }
+ if (printing_conn == conn)
+ print_connection_released();
+
+ i_stream_destroy(&conn->input);
+ o_stream_destroy(&conn->output);
+ i_stream_destroy(&conn->cmd_input);
+ /* close cmd_output after its parent, so the "." isn't sent */
+ o_stream_destroy(&conn->cmd_output);
+ ssl_iostream_destroy(&conn->ssl_iostream);
+ io_remove(&conn->io_log);
+ /* make sure all logs got consumed */
+ if (conn->log_input != NULL)
+ server_connection_print_log(conn);
+ i_stream_unref(&conn->log_input);
+ io_remove(&conn->io);
+ i_close_fd(&conn->fd);
+ pool_unref(&conn->pool);
+}
+
+struct doveadm_server *
+server_connection_get_server(struct server_connection *conn)
+{
+ return conn->server;
+}
+
+void server_connection_cmd(struct server_connection *conn, const char *line,
+ struct istream *cmd_input,
+ server_cmd_callback_t *callback, void *context)
+{
+ i_assert(conn->delayed_cmd == NULL);
+
+ conn->state = SERVER_REPLY_STATE_PRINT;
+ if (cmd_input != NULL) {
+ i_assert(conn->cmd_input == NULL);
+ i_stream_ref(cmd_input);
+ conn->cmd_input = cmd_input;
+ }
+ if (!conn->authenticated)
+ conn->delayed_cmd = p_strdup(conn->pool, line);
+ else {
+ o_stream_nsend_str(conn->output, line);
+ server_connection_send_cmd_input(conn);
+ }
+ conn->callback = callback;
+ conn->context = context;
+}
+
+bool server_connection_is_idle(struct server_connection *conn)
+{
+ return conn->callback == NULL;
+}
+
+void server_connection_extract(struct server_connection *conn,
+ struct istream **istream_r,
+ struct ostream **ostream_r,
+ struct ssl_iostream **ssl_iostream_r)
+{
+ *istream_r = conn->input;
+ *ostream_r = conn->output;
+ *ssl_iostream_r = conn->ssl_iostream;
+
+ conn->input = NULL;
+ conn->output = NULL;
+ conn->ssl_iostream = NULL;
+ io_remove(&conn->io);
+ conn->fd = -1;
+}