]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
doveadm kick: Rewrite to use anvil KICK-USER command
authorTimo Sirainen <timo.sirainen@open-xchange.com>
Tue, 11 Jan 2022 10:25:18 +0000 (12:25 +0200)
committerTimo Sirainen <timo.sirainen@open-xchange.com>
Tue, 8 Feb 2022 09:48:24 +0000 (10:48 +0100)
Removed the "force" parameter, because it's no longer necessary.

src/doveadm/doveadm-kick.c

index af73030effc5304b444c0f853898887801548f1a..a53e9def16f9a582c9dcc8d6502befc8ee805e3f 100644 (file)
 #include "lib.h"
 #include "array.h"
 #include "net.h"
-#include "hash.h"
+#include "str.h"
+#include "ioloop.h"
+#include "strescape.h"
+#include "anvil-client.h"
 #include "doveadm.h"
 #include "doveadm-who.h"
 #include "doveadm-print.h"
 
 #include <stdio.h>
-#include <unistd.h>
-#include <sys/types.h>
-#include <signal.h>
 
-struct kick_user {
+struct kick_session {
        const char *username;
-       bool kick_me; /* true if username and/or ip[/mask] matches.
-                        ignored when the -f switch is given. */
-};
-
-struct kick_pid {
-       pid_t pid;
-       ARRAY(struct kick_user) users;
-       bool kick;
+       guid_128_t conn_guid;
 };
 
 struct kick_context {
        struct who_context who;
-       HASH_TABLE(void *, struct kick_pid *) pids;
        enum doveadm_client_type conn_type;
-       bool force_kick;
+       ARRAY(struct kick_session) kicks;
        ARRAY(const char *) kicked_users;
-};
-
-static void
-kick_aggregate_line(struct who_context *_ctx, const struct who_line *line)
-{
-       struct kick_context *ctx = (struct kick_context *)_ctx;
-       const bool user_match = who_line_filter_match(line, &ctx->who.filter);
-       struct kick_pid *k_pid;
-       struct kick_user new_user, *user;
-
-       i_zero(&new_user);
-
-       k_pid = hash_table_lookup(ctx->pids, POINTER_CAST(line->pid));
-       if (k_pid == NULL) {
-               k_pid = p_new(ctx->who.pool, struct kick_pid, 1);
-               k_pid->pid = line->pid;
-               p_array_init(&k_pid->users, ctx->who.pool, 5);
-               hash_table_insert(ctx->pids, POINTER_CAST(line->pid), k_pid);
-       }
-
-       array_foreach_modifiable(&k_pid->users, user) {
-               if (strcmp(line->username, user->username) == 0) {
-                       if (user_match)
-                               user->kick_me = TRUE;
-                       return;
-               }
-       }
-       new_user.username = p_strdup(ctx->who.pool, line->username);
-       new_user.kick_me = user_match;
-       array_push_back(&k_pid->users, &new_user);
-}
 
-static bool
-kick_pid_want_kicked(struct kick_context *ctx, const struct kick_pid *k_pid,
-                    bool *show_warning)
-{
-       unsigned int kick_count = 0;
-       const struct kick_user *user;
-
-       if (array_count(&k_pid->users) == 1) {
-               user = array_front(&k_pid->users);
-               if (!user->kick_me)
-                       return FALSE;
-       } else {
-               array_foreach(&k_pid->users, user) {
-                       if (user->kick_me)
-                               kick_count++;
-               }
-               if (kick_count == 0)
-                       return FALSE;
-               if (kick_count < array_count(&k_pid->users) &&
-                   !ctx->force_kick) {
-                       array_foreach(&k_pid->users, user) {
-                               if (!user->kick_me) {
-                                       array_push_back(&ctx->kicked_users,
-                                                       &user->username);
-                               }
-                       }
-                       *show_warning = TRUE;
-                       return FALSE;
-               }
-       }
-       return TRUE;
-}
+       bool kicked;
+};
 
-static void
-kick_print_kicked(struct kick_context *ctx, const bool show_warning)
+static void kick_print_kicked(struct kick_context *ctx)
 {
        unsigned int i, count;
        const char *const *users;
@@ -110,14 +40,8 @@ kick_print_kicked(struct kick_context *ctx, const bool show_warning)
                return;
        }
 
-       if (cli) {
-               if (show_warning) {
-                       printf("warning: other connections would also be "
-                              "kicked from following users:\n");
-               } else {
-                       printf("kicked connections from the following users:\n");
-               }
-       }
+       if (cli)
+               printf("kicked connections from the following users:\n");
 
        array_sort(&ctx->kicked_users, i_strcmp_p);
        users = array_get(&ctx->kicked_users, &count);
@@ -131,51 +55,72 @@ kick_print_kicked(struct kick_context *ctx, const bool show_warning)
 
        if (cli)
                printf("\n");
+}
 
-       if (show_warning)
-               printf("Use the '-f' option to enforce the disconnect.\n");
+static void kick_user_anvil_callback(const char *reply, void *context)
+{
+       struct kick_context *ctx = context;
+       unsigned int count;
+
+       if (reply != NULL) {
+               if (str_to_uint(reply, &count) < 0)
+                       i_error("Unexpected reply from anvil: %s", reply);
+               else if (count > 0)
+                       ctx->kicked = TRUE;
+       }
+       io_loop_stop(current_ioloop);
 }
 
-static void kick_users(struct kick_context *ctx)
+static void kick_users_get_via_who(struct kick_context *ctx)
 {
-       bool show_enforce_warning = FALSE;
-       struct hash_iterate_context *iter;
-       void *key;
-       struct kick_pid *k_pid;
-       const struct kick_user *user;
-
-       p_array_init(&ctx->kicked_users, ctx->who.pool, 10);
-
-       iter = hash_table_iterate_init(ctx->pids);
-       while (hash_table_iterate(iter, ctx->pids, &key, &k_pid)) {
-               if (kick_pid_want_kicked(ctx, k_pid, &show_enforce_warning))
-                       k_pid->kick = TRUE;
+       /* get a list of all user+sessions matching the filter */
+       p_array_init(&ctx->kicks, ctx->who.pool, 64);
+       struct doveadm_who_iter *iter =
+               doveadm_who_iter_init(ctx->who.anvil_path);
+       struct who_line who_line;
+       while (doveadm_who_iter_next(iter, &who_line)) {
+               if (!who_line_filter_match(&who_line, &ctx->who.filter))
+                       continue;
+               struct kick_session *session = array_append_space(&ctx->kicks);
+               session->username = p_strdup(ctx->who.pool, who_line.username);
+               guid_128_copy(session->conn_guid, who_line.conn_guid);
        }
-       hash_table_iterate_deinit(&iter);
+       if (doveadm_who_iter_deinit(&iter) < 0)
+               doveadm_exit_code = EX_TEMPFAIL;
+}
+
+static void kick_users_via_anvil(struct kick_context *ctx)
+{
+       const struct kick_session *session;
+       string_t *cmd = t_str_new(128);
 
-       if (show_enforce_warning) {
-               kick_print_kicked(ctx, show_enforce_warning);
+       struct anvil_client *anvil =
+               anvil_client_init(ctx->who.anvil_path, NULL, 0);
+       if (anvil_client_connect(anvil, TRUE) < 0) {
+               doveadm_exit_code = EX_TEMPFAIL;
                return;
        }
 
-       iter = hash_table_iterate_init(ctx->pids);
-       while (hash_table_iterate(iter, ctx->pids, &key, &k_pid)) {
-               if (!k_pid->kick)
-                       continue;
-
-               if (kill(k_pid->pid, SIGTERM) < 0 && errno != ESRCH) {
-                       fprintf(stderr, "kill(%s, SIGTERM) failed: %m\n",
-                               dec2str(k_pid->pid));
-               } else {
-                       array_foreach(&k_pid->users, user) {
-                               array_push_back(&ctx->kicked_users,
-                                               &user->username);
-                       }
-               }
+       p_array_init(&ctx->kicked_users, ctx->who.pool,
+                    array_count(&ctx->kicks));
+
+       array_foreach(&ctx->kicks, session) {
+               str_truncate(cmd, 0);
+               str_append(cmd, "KICK-USER\t");
+               str_append_tabescaped(cmd, session->username);
+               str_append_c(cmd, '\t');
+               str_append_tabescaped(cmd, guid_128_to_string(session->conn_guid));
+
+               ctx->kicked = FALSE;
+               anvil_client_query(anvil, str_c(cmd),
+                                  kick_user_anvil_callback, ctx);
+               io_loop_run(current_ioloop);
+               if (ctx->kicked)
+                       array_push_back(&ctx->kicked_users, &session->username);
        }
-       hash_table_iterate_deinit(&iter);
+       anvil_client_deinit(&anvil);
 
-       kick_print_kicked(ctx, show_enforce_warning);
+       kick_print_kicked(ctx);
 }
 
 static void cmd_kick(struct doveadm_cmd_context *cctx)
@@ -186,28 +131,15 @@ static void cmd_kick(struct doveadm_cmd_context *cctx)
        i_zero(&ctx);
        if (!doveadm_cmd_param_str(cctx, "socket-path", &(ctx.who.anvil_path)))
                ctx.who.anvil_path = t_strconcat(doveadm_settings->base_dir, "/anvil", NULL);
-       (void)doveadm_cmd_param_bool(cctx, "force", &(ctx.force_kick));
        if (!doveadm_cmd_param_array(cctx, "mask", &masks)) {
                doveadm_exit_code = EX_USAGE;
                i_error("user and/or ip[/bits] must be specified.");
                return;
        }
        ctx.conn_type = cctx->conn_type;
-       if (ctx.conn_type != DOVEADM_CONNECTION_TYPE_CLI) {
-               /* force-kick is a pretty ugly option. its output can't be
-                  nicely translated to an API reply. it also wouldn't be very
-                  useful in scripts, only for preventing a new admin from
-                  accidentally kicking too many users. it's also useful only
-                  in a non-recommended setup where processes are handling
-                  multiple connections. so for now we'll preserve the option
-                  for CLI, but always do a force-kick with non-CLI. */
-               ctx.force_kick = TRUE;
-       }
        ctx.who.pool = pool_alloconly_create("kick pids", 10240);
-       hash_table_create_direct(&ctx.pids, ctx.who.pool, 0);
 
        if (who_parse_args(&ctx.who, masks)!=0) {
-               hash_table_destroy(&ctx.pids);
                pool_unref(&ctx.who.pool);
                return;
        }
@@ -216,16 +148,9 @@ static void cmd_kick(struct doveadm_cmd_context *cctx)
        doveadm_print_formatted_set_format("%{result} ");
        doveadm_print_header_simple("result");
 
-       struct doveadm_who_iter *iter =
-               doveadm_who_iter_init(ctx.who.anvil_path);
-       struct who_line who_line;
-       while (doveadm_who_iter_next(iter, &who_line))
-               kick_aggregate_line(&ctx.who, &who_line);
-       if (doveadm_who_iter_deinit(&iter) < 0)
-               doveadm_exit_code = EX_TEMPFAIL;
-       kick_users(&ctx);
+       kick_users_get_via_who(&ctx);
+       kick_users_via_anvil(&ctx);
 
-       hash_table_destroy(&ctx.pids);
        pool_unref(&ctx.who.pool);
 }
 
@@ -235,7 +160,6 @@ struct doveadm_cmd_ver2 doveadm_cmd_kick_ver2 = {
        .usage = "[-a <anvil socket path>] <user mask>[|]<ip/bits>",
 DOVEADM_CMD_PARAMS_START
 DOVEADM_CMD_PARAM('a',"socket-path",CMD_PARAM_STR,0)
-DOVEADM_CMD_PARAM('f',"force",CMD_PARAM_BOOL,0)
 DOVEADM_CMD_PARAM('\0',"mask",CMD_PARAM_ARRAY,CMD_PARAM_FLAG_POSITIONAL)
 DOVEADM_CMD_PARAMS_END
 };