doveadm-who.c revision d4c6c0412c6835f3dffb4e023992a08743e306e5
5f5870385cff47efd2f58e7892f251cf13761528Timo Sirainen/* Copyright (c) 2009-2014 Dovecot authors, see the included COPYING file */
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen#include "lib.h"
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen#include "array.h"
bdd36cfdba3ff66d25570a9ff568d69e1eb543cfTimo Sirainen#include "net.h"
b1f37113a5760bee842c5a7678bb5fa6f5bd8b60Timo Sirainen#include "istream.h"
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen#include "wildcard-match.h"
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen#include "hash.h"
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen#include "str.h"
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen#include "doveadm.h"
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen#include "doveadm-print.h"
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen#include "doveadm-who.h"
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen#include <stdio.h>
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen#include <stdlib.h>
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen#include <unistd.h>
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainenstruct who_user {
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen const char *username;
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen const char *service;
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen ARRAY(struct ip_addr) ips;
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen ARRAY(pid_t) pids;
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen unsigned int connection_count;
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen};
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainenstatic unsigned int who_user_hash(const struct who_user *user)
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen{
bd63b5b860658b01b1f46f26d406e1e4a9dc019aTimo Sirainen return str_hash(user->username) + str_hash(user->service);
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen}
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen
70ac869db925653b57f721cd045c467612fd5ee9Timo Sirainenstatic int who_user_cmp(const struct who_user *user1,
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen const struct who_user *user2)
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen{
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen if (strcmp(user1->username, user2->username) != 0)
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen return 1;
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen if (strcmp(user1->service, user2->service) != 0)
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen return 1;
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen return 0;
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen}
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainenstatic bool
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainenwho_user_has_ip(const struct who_user *user, const struct ip_addr *ip)
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen{
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen const struct ip_addr *ex_ip;
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen array_foreach(&user->ips, ex_ip) {
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen if (net_ip_compare(ex_ip, ip))
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen return TRUE;
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen }
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen return FALSE;
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen}
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainenstatic int who_parse_line(const char *line, struct who_line *line_r)
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen{
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen const char *const *args = t_strsplit_tab(line);
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen const char *ident = args[0];
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen const char *pid_str = args[1];
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen const char *refcount_str = args[2];
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen const char *p, *ip_str;
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen memset(line_r, 0, sizeof(*line_r));
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen p = strchr(ident, '/');
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen if (p == NULL)
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen return -1;
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen line_r->pid = strtoul(pid_str, NULL, 10);
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen line_r->service = t_strdup_until(ident, p++);
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen line_r->username = strchr(p, '/');
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen if (line_r->username == NULL)
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen return -1;
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen line_r->refcount = atoi(refcount_str);
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen ip_str = t_strdup_until(p, line_r->username++);
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen (void)net_addr2ip(ip_str, &line_r->ip);
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen return 0;
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen}
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainenstatic bool who_user_has_pid(struct who_user *user, pid_t pid)
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen{
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen const pid_t *ex_pid;
b1f37113a5760bee842c5a7678bb5fa6f5bd8b60Timo Sirainen
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen array_foreach(&user->pids, ex_pid) {
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen if (*ex_pid == pid)
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen return TRUE;
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen }
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen return FALSE;
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen}
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainenstatic void who_aggregate_line(struct who_context *ctx,
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen const struct who_line *line)
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen{
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen struct who_user *user, lookup_user;
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen lookup_user.username = line->username;
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen lookup_user.service = line->service;
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen user = hash_table_lookup(ctx->users, &lookup_user);
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen if (user == NULL) {
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen user = p_new(ctx->pool, struct who_user, 1);
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen user->username = p_strdup(ctx->pool, line->username);
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen user->service = p_strdup(ctx->pool, line->service);
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen p_array_init(&user->ips, ctx->pool, 3);
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen p_array_init(&user->pids, ctx->pool, 8);
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen hash_table_insert(ctx->users, user, user);
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen }
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen user->connection_count += line->refcount;
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen if (line->ip.family != 0 && !who_user_has_ip(user, &line->ip))
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen array_append(&user->ips, &line->ip, 1);
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen if (!who_user_has_pid(user, line->pid))
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen array_append(&user->pids, &line->pid, 1);
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen}
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainenvoid who_parse_args(struct who_context *ctx, char **args)
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen{
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen struct ip_addr net_ip;
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen unsigned int net_bits;
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen while (args[1] != NULL) {
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen if (net_parse_range(args[1], &net_ip, &net_bits) == 0) {
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen if (ctx->filter.net_bits != 0)
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen help(&doveadm_cmd_who);
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen ctx->filter.net_ip = net_ip;
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen ctx->filter.net_bits = net_bits;
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen } else {
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen if (ctx->filter.username != NULL)
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen help(&doveadm_cmd_who);
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen ctx->filter.username = args[1];
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen }
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen args++;
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen }
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen}
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainenvoid who_lookup(struct who_context *ctx, who_callback_t *callback)
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen{
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen#define ANVIL_HANDSHAKE "VERSION\tanvil\t1\t0\n"
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen#define ANVIL_CMD ANVIL_HANDSHAKE"CONNECT-DUMP\n"
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen struct istream *input;
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen const char *line;
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen int fd;
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen fd = doveadm_connect(ctx->anvil_path);
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen net_set_nonblock(fd, FALSE);
if (write(fd, ANVIL_CMD, strlen(ANVIL_CMD)) < 0)
i_fatal("write(%s) failed: %m", ctx->anvil_path);
input = i_stream_create_fd_autoclose(&fd, (size_t)-1);
while ((line = i_stream_read_next_line(input)) != NULL) {
if (*line == '\0')
break;
T_BEGIN {
struct who_line who_line;
if (who_parse_line(line, &who_line) < 0)
i_error("Invalid input: %s", line);
else
callback(ctx, &who_line);
} T_END;
}
if (input->stream_errno != 0)
i_fatal("read(%s) failed: %m", ctx->anvil_path);
i_stream_destroy(&input);
}
static bool who_user_filter_match(const struct who_user *user,
const struct who_filter *filter)
{
if (filter->username != NULL) {
if (!wildcard_match_icase(user->username, filter->username))
return FALSE;
}
if (filter->net_bits > 0) {
const struct ip_addr *ip;
bool ret = FALSE;
array_foreach(&user->ips, ip) {
if (net_is_in_network(ip, &filter->net_ip,
filter->net_bits)) {
ret = TRUE;
break;
}
}
if (!ret)
return FALSE;
}
return TRUE;
}
static void who_print_user(const struct who_user *user)
{
const struct ip_addr *ip;
const pid_t *pid;
string_t *str = t_str_new(256);
doveadm_print(user->username);
doveadm_print(dec2str(user->connection_count));
doveadm_print(user->service);
str_append_c(str, '(');
array_foreach(&user->pids, pid)
str_printfa(str, "%ld ", (long)*pid);
if (str_len(str) > 1)
str_truncate(str, str_len(str)-1);
str_append_c(str, ')');
doveadm_print(str_c(str));
str_truncate(str, 0);
str_append_c(str, '(');
array_foreach(&user->ips, ip)
str_printfa(str, "%s ", net_ip2addr(ip));
if (str_len(str) > 1)
str_truncate(str, str_len(str)-1);
str_append_c(str, ')');
doveadm_print(str_c(str));
}
static void who_print(struct who_context *ctx)
{
struct hash_iterate_context *iter;
struct who_user *user;
doveadm_print_header("username", "username", 0);
doveadm_print_header("connections", "#",
DOVEADM_PRINT_HEADER_FLAG_RIGHT_JUSTIFY);
doveadm_print_header("service", "proto", 0);
doveadm_print_header("pids", "(pids)", 0);
doveadm_print_header("ips", "(ips)", 0);
iter = hash_table_iterate_init(ctx->users);
while (hash_table_iterate(iter, ctx->users, &user, &user)) {
if (who_user_filter_match(user, &ctx->filter)) T_BEGIN {
who_print_user(user);
} T_END;
}
hash_table_iterate_deinit(&iter);
}
bool who_line_filter_match(const struct who_line *line,
const struct who_filter *filter)
{
if (filter->username != NULL) {
if (!wildcard_match_icase(line->username, filter->username))
return FALSE;
}
if (filter->net_bits > 0) {
if (!net_is_in_network(&line->ip, &filter->net_ip,
filter->net_bits))
return FALSE;
}
return TRUE;
}
static void who_print_line(struct who_context *ctx,
const struct who_line *line)
{
unsigned int i;
if (!who_line_filter_match(line, &ctx->filter))
return;
for (i = 0; i < line->refcount; i++) T_BEGIN {
doveadm_print(line->username);
doveadm_print(line->service);
doveadm_print(dec2str(line->pid));
doveadm_print(net_ip2addr(&line->ip));
} T_END;
}
static void cmd_who(int argc, char *argv[])
{
struct who_context ctx;
bool separate_connections = FALSE;
int c;
memset(&ctx, 0, sizeof(ctx));
ctx.anvil_path = t_strconcat(doveadm_settings->base_dir, "/anvil", NULL);
ctx.pool = pool_alloconly_create("who users", 10240);
hash_table_create(&ctx.users, ctx.pool, 0, who_user_hash, who_user_cmp);
while ((c = getopt(argc, argv, "1a:")) > 0) {
switch (c) {
case '1':
separate_connections = TRUE;
break;
case 'a':
ctx.anvil_path = optarg;
break;
default:
help(&doveadm_cmd_who);
}
}
argv += optind - 1;
who_parse_args(&ctx, argv);
doveadm_print_init(DOVEADM_PRINT_TYPE_TABLE);
if (!separate_connections) {
who_lookup(&ctx, who_aggregate_line);
who_print(&ctx);
} else {
doveadm_print_header("username", "username",
DOVEADM_PRINT_HEADER_FLAG_EXPAND);
doveadm_print_header("service", "proto", 0);
doveadm_print_header_simple("pid");
doveadm_print_header_simple("ip");
who_lookup(&ctx, who_print_line);
}
hash_table_destroy(&ctx.users);
pool_unref(&ctx.pool);
}
struct doveadm_cmd doveadm_cmd_who = {
cmd_who, "who",
"[-a <anvil socket path>] [-1] [<user mask>] [<ip/bits>]"
};