]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
anvil: Add support for sending commands to anvil client connections
authorTimo Sirainen <timo.sirainen@open-xchange.com>
Mon, 3 Jan 2022 16:43:04 +0000 (18:43 +0200)
committerTimo Sirainen <timo.sirainen@open-xchange.com>
Tue, 8 Feb 2022 09:48:24 +0000 (10:48 +0100)
src/anvil/anvil-connection.c
src/anvil/anvil-connection.h

index e4b4e3566083a1b9b59e602b313409c07b3de6a8..6ae9fb1ddf3eedc1a987bc80eb67da2122c9ecf7 100644 (file)
@@ -1,6 +1,7 @@
 /* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */
 
 #include "common.h"
+#include "array.h"
 #include "hash.h"
 #include "llist.h"
 #include "istream.h"
 #define ANVIL_CLIENT_PROTOCOL_MAJOR_VERSION 2
 #define ANVIL_CLIENT_PROTOCOL_MINOR_VERSION 0
 
+#define ANVIL_CMD_CHANNEL_ID 1
+
 struct anvil_connection_key {
        const char *service;
        pid_t pid;
 };
 
+struct anvil_connection_command {
+       char *cmdline;
+       anvil_connection_cmd_callback_t *callback;
+       void *context;
+};
+
 struct anvil_connection {
        struct connection conn;
+       ARRAY(struct anvil_connection_command) commands;
+
+       struct istream *cmd_input;
+       struct ostream *cmd_output;
+       struct io *cmd_io;
 
        char *service;
        bool master:1;
@@ -94,6 +108,36 @@ static int str_to_kick_type(const char *str, enum kick_type *kick_type_r)
        return str[1] == '\0' ? 0 : -1;
 }
 
+static void
+anvil_connection_cmd_reply(struct anvil_connection *conn,
+                          const char *reply, const char *error)
+{
+       struct anvil_connection_command *cmd;
+
+       cmd = array_idx_modifiable(&conn->commands, 0);
+       cmd->callback(reply, error, cmd->context);
+       i_free(cmd->cmdline);
+       array_pop_front(&conn->commands);
+}
+
+static void anvil_cmd_input(struct anvil_connection *conn)
+{
+       const char *line;
+
+       if (connection_input_read_stream(&conn->conn, conn->cmd_input) < 0)
+               return;
+
+       while ((line = i_stream_next_line(conn->cmd_input)) != NULL) {
+               if (array_count(&conn->commands) == 0) {
+                       e_error(conn->conn.event,
+                               "Unexpected input from command channel: %s",
+                               line);
+               } else {
+                       anvil_connection_cmd_reply(conn, line, NULL);
+               }
+       }
+}
+
 static int
 anvil_connection_request(struct anvil_connection *conn,
                         const char *const *args, const char **error_r)
@@ -282,6 +326,13 @@ anvil_connection_handshake(struct anvil_connection *conn,
 
        connection_streams_changed(&conn->conn);
 
+       /* add a separate channel for handling admin commands */
+       conn->cmd_input = i_stream_multiplex_add_channel(conn->conn.input,
+                                                        ANVIL_CMD_CHANNEL_ID);
+       conn->cmd_io = io_add_istream(conn->cmd_input, anvil_cmd_input, conn);
+       conn->cmd_output = o_stream_multiplex_add_channel(conn->conn.output,
+                                                         ANVIL_CMD_CHANNEL_ID);
+
        struct anvil_connection_key *hash_key, key = {
                .service = conn->service,
                .pid = conn->conn.remote_pid,
@@ -363,6 +414,7 @@ void anvil_connection_create(int fd, bool master, bool fifo)
        }
        conn->master = master;
        conn->fifo = fifo;
+       i_array_init(&conn->commands, 8);
 }
 
 static void anvil_connection_destroy(struct connection *_conn)
@@ -371,6 +423,11 @@ static void anvil_connection_destroy(struct connection *_conn)
                container_of(_conn, struct anvil_connection, conn);
        bool fifo = conn->fifo;
 
+       while (array_count(&conn->commands) > 0) {
+               anvil_connection_cmd_reply(conn, NULL,
+                       connection_disconnect_reason(_conn));
+       }
+       array_free(&conn->commands);
        connection_deinit(&conn->conn);
 
        if (conn->added_to_hash) {
@@ -388,6 +445,9 @@ static void anvil_connection_destroy(struct connection *_conn)
        }
 
        o_stream_destroy(&conn->conn.output);
+       io_remove(&conn->cmd_io);
+       i_stream_destroy(&conn->cmd_input);
+       o_stream_destroy(&conn->cmd_output);
        i_free(conn->service);
        i_free(conn);
 
@@ -404,6 +464,25 @@ struct anvil_connection *anvil_connection_find(const char *service, pid_t pid)
        return hash_table_lookup(anvil_connections_hash, &key);
 }
 
+void anvil_connection_send_cmd(struct anvil_connection *conn,
+                              const char *cmdline,
+                              anvil_connection_cmd_callback_t *callback,
+                              void *context)
+{
+       struct anvil_connection_command *cmd;
+
+       const struct const_iovec iov[] = {
+               { cmdline, strlen(cmdline) },
+               { "\n", 1 }
+       };
+       o_stream_nsendv(conn->cmd_output, iov, N_ELEMENTS(iov));
+
+       cmd = array_append_space(&conn->commands);
+       cmd->cmdline = i_strdup(cmdline);
+       cmd->callback = callback;
+       cmd->context = context;
+}
+
 static struct connection_settings anvil_connections_set = {
        .dont_send_version = TRUE,
        .input_max_size = MAX_INBUF_SIZE,
index 6b3f426800e012bf12e63a58a9bfa617f7b56add..f9244b8621e4c21685763f46fd6c4c16af2edb5e 100644 (file)
@@ -1,11 +1,21 @@
 #ifndef ANVIL_CONNECTION_H
 #define ANVIL_CONNECTION_H
 
+/* Error is set and reply=NULL on internal errors. */
+typedef void
+anvil_connection_cmd_callback_t(const char *reply, const char *error,
+                               void *context);
+
 void anvil_connection_create(int fd, bool master, bool fifo);
 
 /* Find an existing anvil connection from the specified process. */
 struct anvil_connection *anvil_connection_find(const char *service, pid_t pid);
 
+void anvil_connection_send_cmd(struct anvil_connection *conn,
+                              const char *cmdline,
+                              anvil_connection_cmd_callback_t *callback,
+                              void *context);
+
 void anvil_connections_init(void);
 void anvil_connections_deinit(void);