]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
lib-master: Add admin-client API
authorTimo Sirainen <timo.sirainen@open-xchange.com>
Thu, 13 Jan 2022 15:32:09 +0000 (17:32 +0200)
committerTimo Sirainen <timo.sirainen@open-xchange.com>
Tue, 8 Feb 2022 09:48:24 +0000 (10:48 +0100)
The admin-clients are automatically created for %{pid} socket listeners.
They are not reported to master process as being actual connections to
the process, or counted towards service_count.

src/lib-master/Makefile.am
src/lib-master/master-admin-client.c [new file with mode: 0644]
src/lib-master/master-admin-client.h [new file with mode: 0644]
src/lib-master/master-service-private.h
src/lib-master/master-service.c

index 8133c05361c6a16e4e22b122f2edb8a9a37f32e7..8893d18ddf2adff73590b2870012be9e35c540c0 100644 (file)
@@ -18,6 +18,7 @@ libmaster_la_SOURCES = \
        anvil-client.c \
        ipc-client.c \
        ipc-server.c \
+       master-admin-client.c \
        master-auth.c \
        master-instance.c \
        master-login.c \
@@ -35,6 +36,7 @@ headers = \
        anvil-client.h \
        ipc-client.h \
        ipc-server.h \
+       master-admin-client.h \
        master-auth.h \
        master-instance.h \
        master-interface.h \
diff --git a/src/lib-master/master-admin-client.c b/src/lib-master/master-admin-client.c
new file mode 100644 (file)
index 0000000..f11acaf
--- /dev/null
@@ -0,0 +1,145 @@
+/* Copyright (c) 2022 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "connection.h"
+#include "ostream.h"
+#include "str.h"
+#include "master-service-private.h"
+#include "master-admin-client.h"
+
+struct master_admin_client {
+       struct connection conn;
+
+       bool reply_pending;
+};
+
+static struct connection_list *master_admin_clients = NULL;
+static struct master_admin_client_callback master_admin_client_callbacks;
+
+static void
+cmd_kick_user(struct master_admin_client *client, const char *const *args)
+{
+       const char *user = args[0];
+       if (user == NULL) {
+               master_admin_client_send_reply(client, "-Missing parameter");
+               return;
+       }
+       guid_128_t conn_guid;
+       if (args[1] == NULL)
+               guid_128_empty(conn_guid);
+       else if (guid_128_from_string(args[1], conn_guid) < 0) {
+               master_admin_client_send_reply(client,
+                                              "-Invalid conn-guid parameter");
+               return;
+       } else if (args[2] != NULL) {
+               master_admin_client_send_reply(client, "-Extra parameters");
+               return;
+       }
+
+       master_admin_client_send_reply(client, t_strdup_printf("+%u",
+               master_admin_client_callbacks.cmd_kick_user(user, conn_guid)));
+}
+
+static int
+master_admin_client_input_args(struct connection *conn, const char *const *args)
+{
+       struct master_admin_client *client =
+               container_of(conn, struct master_admin_client, conn);
+
+       if (client->reply_pending) {
+               /* Delay handling the command until the previous command is
+                  replied to. */
+               connection_input_halt(conn);
+               return 0;
+       }
+       const char *cmd = args[0];
+       args++;
+
+       client->reply_pending = TRUE;
+       if (strcmp(cmd, "KICK-USER") == 0 &&
+           master_admin_client_callbacks.cmd_kick_user != NULL)
+               cmd_kick_user(client, args);
+       else if (master_admin_client_callbacks.cmd == NULL ||
+                !master_admin_client_callbacks.cmd(client, cmd, args)) {
+               client->reply_pending = FALSE;
+               o_stream_nsend_str(conn->output, "-Unknown command\n");
+       }
+       return 1;
+}
+
+void master_admin_client_send_reply(struct master_admin_client *client,
+                                   const char *reply)
+{
+       i_assert(client->reply_pending);
+       client->reply_pending = FALSE;
+
+       struct const_iovec iov[] = {
+               { reply, strlen(reply) },
+               { "\n", 1 }
+       };
+       if (client->conn.output != NULL) {
+               o_stream_nsendv(client->conn.output, iov, N_ELEMENTS(iov));
+               connection_input_resume(&client->conn);
+       } else {
+               /* client already disconnected */
+               i_free(client);
+       }
+}
+
+static void master_admin_client_destroy(struct connection *conn)
+{
+       struct master_admin_client *client =
+               container_of(conn, struct master_admin_client, conn);
+
+       connection_deinit(conn);
+        /* if reply is pending, delay freeing the client until reply is sent */
+       if (!client->reply_pending)
+               i_free(client);
+}
+
+static const struct connection_settings master_admin_conn_set = {
+       .service_name_in = "master-admin-client",
+       .service_name_out = "master-admin-server",
+       .major_version = 1,
+       .minor_version = 0,
+
+       .input_max_size = 1024,
+       .output_max_size = SIZE_MAX,
+       .client = FALSE
+};
+
+static const struct connection_vfuncs master_admin_conn_vfuncs = {
+       .destroy = master_admin_client_destroy,
+       .input_args = master_admin_client_input_args
+};
+
+void master_admin_client_create(struct master_service_connection *master_conn)
+{
+       struct master_admin_client *client;
+
+       if (master_admin_clients == NULL) {
+               master_admin_clients =
+                       connection_list_init(&master_admin_conn_set,
+                                            &master_admin_conn_vfuncs);
+       }
+
+       client = i_new(struct master_admin_client, 1);
+       connection_init_server(master_admin_clients, &client->conn, master_conn->name,
+                              master_conn->fd, master_conn->fd);
+}
+
+bool master_admin_client_can_accept(const char *name)
+{
+       return name != NULL && strcmp(name, "%{pid}") == 0;
+}
+
+void master_admin_clients_init(const struct master_admin_client_callback *callbacks)
+{
+       master_admin_client_callbacks = *callbacks;
+}
+
+void master_admin_clients_deinit(void)
+{
+       if (master_admin_clients != NULL)
+               connection_list_deinit(&master_admin_clients);
+}
diff --git a/src/lib-master/master-admin-client.h b/src/lib-master/master-admin-client.h
new file mode 100644 (file)
index 0000000..d42df69
--- /dev/null
@@ -0,0 +1,36 @@
+#ifndef MASTER_ADMIN_CLIENT_H
+#define MASTER_ADMIN_CLIENT_H
+
+#include "guid.h"
+
+struct master_service_connection;
+struct master_admin_client;
+
+struct master_admin_client_callback {
+       /* Handle a command sent to admin socket. Send the reply with
+          master_admin_client_send_reply(). The command can be processed
+          asynchronously. Returns TRUE if the command was handled, FALSE if
+          the command was unknown. */
+       bool (*cmd)(struct master_admin_client *client,
+                   const char *cmd, const char *const *args);
+
+       /* Standard commands implemented by multiple processes: */
+
+       /* Kick user's connections and return the number of connections kicked.
+          If conn_guid is not empty, only the specific connection is kicked. */
+       unsigned int (*cmd_kick_user)(const char *user,
+                                     const guid_128_t conn_guid);
+};
+
+void master_admin_client_create(struct master_service_connection *master_conn);
+
+/* Send reply to admin command from admin_client_command_t. */
+void master_admin_client_send_reply(struct master_admin_client *client,
+                                   const char *reply);
+
+/* Returns TRUE if service name points to admin socket. */
+bool master_admin_client_can_accept(const char *name);
+
+void master_admin_clients_init(const struct master_admin_client_callback *callbacks);
+
+#endif
index 429f2eb71360c7c18f6468ca8b655b5ca1500ed3..7844864b2850515b2be5c4b21ae19577a37518cf 100644 (file)
@@ -106,4 +106,6 @@ void master_service_haproxy_new(struct master_service *service,
                                struct master_service_connection *conn);
 void master_service_haproxy_abort(struct master_service *service);
 
+void master_admin_clients_deinit(void);
+
 #endif
index a92c0a8d66763874c24ccf5fc45e4e561f83bc60..5410aa488b7bb71b705873977d34f48651b614db 100644 (file)
@@ -17,6 +17,7 @@
 #include "settings-parser.h"
 #include "syslog-util.h"
 #include "stats-client.h"
+#include "master-admin-client.h"
 #include "master-instance.h"
 #include "master-login.h"
 #include "master-service-ssl.h"
@@ -628,6 +629,10 @@ bool master_service_parse_option(struct master_service *service,
 
 static void master_service_error(struct master_service *service)
 {
+       /* Close all master-admin connections from anvil. This way they won't
+          block stopping the process quickly. */
+       master_admin_clients_deinit();
+
        master_service_stop_new_connections(service);
        if (service->master_status.available_count ==
            service->total_available_count || service->die_with_master) {
@@ -1115,9 +1120,11 @@ static void master_service_deinit_real(struct master_service **_service)
                if (!t_pop(&service->datastack_frame_id))
                        i_panic("Leaked t_pop() call");
        }
+       master_admin_clients_deinit();
        master_service_haproxy_abort(service);
 
-       master_service_io_listeners_remove(service);
+       for (unsigned int i = 0; i < service->socket_count; i++)
+               io_remove(&service->listeners[i].io);
        master_service_ssl_ctx_deinit(service);
 
        if (service->stats_client != NULL)
@@ -1280,8 +1287,13 @@ static void master_service_listen(struct master_service_listener *l)
 {
        struct master_service *service = l->service;
        struct master_service_connection conn;
+       const char *conn_name;
+       bool master_admin_conn;
 
-       if (service->master_status.available_count == 0) {
+       conn_name = master_service_get_socket_name(service, l->fd);
+       master_admin_conn = master_admin_client_can_accept(conn_name);
+
+       if (service->master_status.available_count == 0 && !master_admin_conn) {
                if (master_service_full(service)) {
                        /* Stop the listener until a client has disconnected or
                           overflow callback has killed one. */
@@ -1335,6 +1347,10 @@ static void master_service_listen(struct master_service_listener *l)
 
        net_set_nonblock(conn.fd, TRUE);
 
+       if (master_admin_conn) {
+               master_admin_client_create(&conn);
+               return;
+       }
        master_service_client_connection_created(service);
        if (l->haproxy)
                master_service_haproxy_new(service, &conn);
@@ -1368,7 +1384,8 @@ void master_service_io_listeners_remove(struct master_service *service)
        unsigned int i;
 
        for (i = 0; i < service->socket_count; i++) {
-               io_remove(&service->listeners[i].io);
+               if (!master_admin_client_can_accept(service->listeners[i].name))
+                       io_remove(&service->listeners[i].io);
        }
 }
 
@@ -1390,7 +1407,8 @@ static void master_service_io_listeners_close(struct master_service *service)
        /* close via listeners. some fds might be pipes that are
           currently handled as clients. we don't want to close them. */
        for (i = 0; i < service->socket_count; i++) {
-               if (service->listeners[i].fd != -1) {
+               if (service->listeners[i].fd != -1 &&
+                   !master_admin_client_can_accept(service->listeners[i].name)) {
                        if (close(service->listeners[i].fd) < 0) {
                                i_error("close(listener %d) failed: %m",
                                        service->listeners[i].fd);