From: Timo Sirainen Date: Thu, 13 Jan 2022 15:32:09 +0000 (+0200) Subject: lib-master: Add admin-client API X-Git-Tag: 2.4.0~4552 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=518b2c8df233e92d7a7eb128713be2193debc604;p=thirdparty%2Fdovecot%2Fcore.git lib-master: Add admin-client API 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. --- diff --git a/src/lib-master/Makefile.am b/src/lib-master/Makefile.am index 8133c05361..8893d18ddf 100644 --- a/src/lib-master/Makefile.am +++ b/src/lib-master/Makefile.am @@ -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 index 0000000000..f11acaf3f3 --- /dev/null +++ b/src/lib-master/master-admin-client.c @@ -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 index 0000000000..d42df69a03 --- /dev/null +++ b/src/lib-master/master-admin-client.h @@ -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 diff --git a/src/lib-master/master-service-private.h b/src/lib-master/master-service-private.h index 429f2eb713..7844864b28 100644 --- a/src/lib-master/master-service-private.h +++ b/src/lib-master/master-service-private.h @@ -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 diff --git a/src/lib-master/master-service.c b/src/lib-master/master-service.c index a92c0a8d66..5410aa488b 100644 --- a/src/lib-master/master-service.c +++ b/src/lib-master/master-service.c @@ -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);