doveadm-kick.c revision a75d470c9223a75801418fcdda258885c36317e0
/* Copyright (c) 2010-2012 Dovecot authors, see the included COPYING file */
#include "lib.h"
#include "array.h"
#include "network.h"
#include "hash.h"
#include "doveadm.h"
#include "doveadm-who.h"
#include <stdio.h>
#include <stdlib.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;
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;
memset(&new_user, 0, sizeof(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_append(&k_pid->users, &new_user, 1);
}
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_idx(&k_pid->users, 0);
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_append(&ctx->kicked_users,
&user->username, 1);
}
}
*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;
if (array_count(&ctx->kicked_users) == 0) {
printf("no users kicked\n");
doveadm_exit_code = DOVEADM_EX_NOTFOUND;
return;
}
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);
printf("%s ", users[0]);
for (i = 1; i < count; i++) {
if (strcmp(users[i-1], users[i]) != 0)
printf("%s ", users[i]);
}
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_append(&ctx->kicked_users,
&user->username, 1);
}
}
}
hash_table_iterate_deinit(&iter);
kick_print_kicked(ctx, show_enforce_warning);
}
static void cmd_kick(int argc, char *argv[])
{
struct kick_context ctx;
int c;
memset(&ctx, 0, sizeof(ctx));
ctx.who.anvil_path = t_strconcat(doveadm_settings->base_dir, "/anvil", NULL);
ctx.force_kick = FALSE;
ctx.who.pool = pool_alloconly_create("kick pids", 10240);
hash_table_create_direct(&ctx.pids, ctx.who.pool, 0);
while ((c = getopt(argc, argv, "a:f")) > 0) {
switch (c) {
case 'a':
ctx.who.anvil_path = optarg;
break;
case 'f':
ctx.force_kick = TRUE;
break;
default:
help(&doveadm_cmd_kick);
}
}
argv += optind - 1;
if (argv[1] == NULL) {
i_fatal_status(EX_USAGE,
"user and/or ip[/bits] must be specified.");
}
who_parse_args(&ctx.who, argv);
who_lookup(&ctx.who, kick_aggregate_line);
kick_users(&ctx);
hash_table_destroy(&ctx.pids);
pool_unref(&ctx.who.pool);
}
struct doveadm_cmd doveadm_cmd_kick = {
cmd_kick, "kick",
"[-a <anvil socket path>] [-f] <user mask>[|]<ip/bits>"
};