#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;
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);
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)
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_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);
}
.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
};