diff options
Diffstat (limited to 'src/doveadm/doveadm-kick.c')
-rw-r--r-- | src/doveadm/doveadm-kick.c | 235 |
1 files changed, 235 insertions, 0 deletions
diff --git a/src/doveadm/doveadm-kick.c b/src/doveadm/doveadm-kick.c new file mode 100644 index 0000000..a0b06f3 --- /dev/null +++ b/src/doveadm/doveadm-kick.c @@ -0,0 +1,235 @@ +/* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "net.h" +#include "hash.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 { + 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; +}; + +struct kick_context { + struct who_context who; + HASH_TABLE(void *, struct kick_pid *) pids; + enum doveadm_client_type conn_type; + bool force_kick; + 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; +} + +static void +kick_print_kicked(struct kick_context *ctx, const bool show_warning) +{ + unsigned int i, count; + const char *const *users; + bool cli = (ctx->conn_type == DOVEADM_CONNECTION_TYPE_CLI); + + if (array_count(&ctx->kicked_users) == 0) { + if (cli) + printf("no users kicked\n"); + doveadm_exit_code = DOVEADM_EX_NOTFOUND; + 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"); + } + } + + array_sort(&ctx->kicked_users, i_strcmp_p); + users = array_get(&ctx->kicked_users, &count); + doveadm_print(users[0]); + for (i = 1; i < count; i++) { + if (strcmp(users[i-1], users[i]) != 0) + doveadm_print(users[i]); + } + + doveadm_print_flush(); + + if (cli) + printf("\n"); + + if (show_warning) + printf("Use the '-f' option to enforce the disconnect.\n"); +} + +static void kick_users(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; + } + hash_table_iterate_deinit(&iter); + + if (show_enforce_warning) { + kick_print_kicked(ctx, show_enforce_warning); + 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); + } + } + } + hash_table_iterate_deinit(&iter); + + kick_print_kicked(ctx, show_enforce_warning); +} + +static void cmd_kick(struct doveadm_cmd_context *cctx) +{ + const char *const *masks; + struct kick_context ctx; + + 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; + } + + doveadm_print_init(DOVEADM_PRINT_TYPE_FORMATTED); + doveadm_print_formatted_set_format("%{result} "); + doveadm_print_header_simple("result"); + + who_lookup(&ctx.who, kick_aggregate_line); + kick_users(&ctx); + + hash_table_destroy(&ctx.pids); + pool_unref(&ctx.who.pool); +} + +struct doveadm_cmd_ver2 doveadm_cmd_kick_ver2 = { + .name = "kick", + .cmd = cmd_kick, + .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 +}; |