doveadm-who.c revision bbd2991d36c5d1d5e34eb274d1e52f0f799e2ee4
45312f52ff3a3d4c137447be4c7556500c2f8bf2Timo Sirainen/* Copyright (c) 2009 Dovecot authors, see the included COPYING file */
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen
08d6658a4e2ec8104cd1307f6baa75fdb07a24f8Mark Washenberger#include "lib.h"
d5abbb932a0a598f002da39a8b3326643b1b5efcTimo Sirainen#include "array.h"
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen#include "network.h"
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen#include "istream.h"
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen#include "hash.h"
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen#include "doveadm.h"
40eb305d9b12cf48400fe3806a8a15ad6d372952Timo Sirainen
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen#include <stdio.h>
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen#include <stdlib.h>
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen#include <unistd.h>
c5383a0ed56a188a7d5efaaa4c6f8243af432d65Timo Sirainen
c5383a0ed56a188a7d5efaaa4c6f8243af432d65Timo Sirainenstruct who_line {
40eb305d9b12cf48400fe3806a8a15ad6d372952Timo Sirainen const char *username;
40eb305d9b12cf48400fe3806a8a15ad6d372952Timo Sirainen const char *service;
40eb305d9b12cf48400fe3806a8a15ad6d372952Timo Sirainen struct ip_addr ip;
40eb305d9b12cf48400fe3806a8a15ad6d372952Timo Sirainen pid_t pid;
40eb305d9b12cf48400fe3806a8a15ad6d372952Timo Sirainen unsigned int refcount;
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen};
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainenstruct who_user {
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen const char *username;
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen const char *service;
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen ARRAY_DEFINE(ips, struct ip_addr);
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen ARRAY_DEFINE(pids, pid_t);
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen unsigned int connection_count;
3b94ff5951db4d4eddb7a80ed4e3f61207202635Timo Sirainen};
66d2db642fe24d555d113ba463e446b038d476efTimo Sirainen
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainenstruct who_filter {
40eb305d9b12cf48400fe3806a8a15ad6d372952Timo Sirainen const char *username;
40eb305d9b12cf48400fe3806a8a15ad6d372952Timo Sirainen struct ip_addr net_ip;
40eb305d9b12cf48400fe3806a8a15ad6d372952Timo Sirainen unsigned int net_bits;
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen};
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen
00bde9ae9eab9e720462bf6ec9a4dd85e88c3bbfTimo Sirainenstruct who_context {
00bde9ae9eab9e720462bf6ec9a4dd85e88c3bbfTimo Sirainen const char *anvil_path;
3b94ff5951db4d4eddb7a80ed4e3f61207202635Timo Sirainen struct who_filter filter;
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen
49e513d090753ccbf95560b2f3a21f081a5b6c51Timo Sirainen pool_t pool;
6fabfb7bbfd88d0c1de66981e52850f26067623bTimo Sirainen struct hash_table *users; /* username -> who_user */
22535a9e685e29214082878e37a267157044618eTimo Sirainen};
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainentypedef void who_callback_t(struct who_context *ctx,
6fabfb7bbfd88d0c1de66981e52850f26067623bTimo Sirainen const struct who_line *line);
137ea7ca34005345aa2304a940149b7f3774d727Timo Sirainen
6fabfb7bbfd88d0c1de66981e52850f26067623bTimo Sirainenstatic unsigned int who_user_hash(const void *p)
40eb305d9b12cf48400fe3806a8a15ad6d372952Timo Sirainen{
40eb305d9b12cf48400fe3806a8a15ad6d372952Timo Sirainen const struct who_user *user = p;
312d3129573fc63ba0d39105b68daccfab9f0c8fTimo Sirainen
312d3129573fc63ba0d39105b68daccfab9f0c8fTimo Sirainen return str_hash(user->username) + str_hash(user->service);
312d3129573fc63ba0d39105b68daccfab9f0c8fTimo Sirainen}
40eb305d9b12cf48400fe3806a8a15ad6d372952Timo Sirainen
312d3129573fc63ba0d39105b68daccfab9f0c8fTimo Sirainenstatic int who_user_cmp(const void *p1, const void *p2)
312d3129573fc63ba0d39105b68daccfab9f0c8fTimo Sirainen{
40eb305d9b12cf48400fe3806a8a15ad6d372952Timo Sirainen const struct who_user *user1 = p1, *user2 = p2;
40eb305d9b12cf48400fe3806a8a15ad6d372952Timo Sirainen
40eb305d9b12cf48400fe3806a8a15ad6d372952Timo Sirainen if (strcmp(user1->username, user2->username) != 0)
40eb305d9b12cf48400fe3806a8a15ad6d372952Timo Sirainen return 1;
40eb305d9b12cf48400fe3806a8a15ad6d372952Timo Sirainen if (strcmp(user1->service, user2->service) != 0)
40eb305d9b12cf48400fe3806a8a15ad6d372952Timo Sirainen return 1;
40eb305d9b12cf48400fe3806a8a15ad6d372952Timo Sirainen return 0;
40eb305d9b12cf48400fe3806a8a15ad6d372952Timo Sirainen}
40eb305d9b12cf48400fe3806a8a15ad6d372952Timo Sirainen
40eb305d9b12cf48400fe3806a8a15ad6d372952Timo Sirainenstatic bool
40eb305d9b12cf48400fe3806a8a15ad6d372952Timo Sirainenwho_user_has_ip(const struct who_user *user, const struct ip_addr *ip)
6fabfb7bbfd88d0c1de66981e52850f26067623bTimo Sirainen{
6fabfb7bbfd88d0c1de66981e52850f26067623bTimo Sirainen const struct ip_addr *ex_ip;
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen array_foreach(&user->ips, ex_ip) {
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen if (net_ip_compare(ex_ip, ip))
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen return TRUE;
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen }
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen return FALSE;
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen}
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainenstatic void who_parse_line(const char *line, struct who_line *line_r)
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen{
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen const char *const *args = t_strsplit(line, "\t");
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen const char *ident = args[0];
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen const char *pid_str = args[1];
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen const char *refcount_str = args[2];
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen const char *p, *ip_str;
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen memset(line_r, 0, sizeof(*line_r));
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen p = strchr(ident, '/');
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen line_r->pid = strtoul(pid_str, NULL, 10);
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen line_r->service = t_strdup_until(ident, p++);
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen line_r->username = strchr(p, '/');
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen line_r->refcount = atoi(refcount_str);
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen ip_str = t_strdup_until(p, line_r->username++);
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen (void)net_addr2ip(ip_str, &line_r->ip);
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen}
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainenstatic void who_aggregate_line(struct who_context *ctx,
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen const struct who_line *line)
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen{
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen struct who_user *user, lookup_user;
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen const pid_t *ex_pid;
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen lookup_user.username = line->username;
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen lookup_user.service = line->service;
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen user = hash_table_lookup(ctx->users, &lookup_user);
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen if (user == NULL) {
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen user = p_new(ctx->pool, struct who_user, 1);
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen user->username = p_strdup(ctx->pool, line->username);
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen user->service = p_strdup(ctx->pool, line->service);
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen p_array_init(&user->ips, ctx->pool, 3);
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen p_array_init(&user->pids, ctx->pool, 8);
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen hash_table_insert(ctx->users, user, user);
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen }
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen user->connection_count += line->refcount;
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen if (line->ip.family != 0 && !who_user_has_ip(user, &line->ip))
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen array_append(&user->ips, &line->ip, 1);
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen array_foreach(&user->pids, ex_pid) {
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen if (*ex_pid == line->pid)
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen break;
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen }
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen if (*ex_pid != line->pid)
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen array_append(&user->pids, &line->pid, 1);
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen}
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainenstatic void who_lookup(struct who_context *ctx, who_callback_t *callback)
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen{
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen#define ANVIL_HANDSHAKE "VERSION\tanvil\t1\t0\n"
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen#define ANVIL_CMD ANVIL_HANDSHAKE"CONNECT-DUMP\n"
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen struct istream *input;
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen const char *line;
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen int fd;
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen fd = net_connect_unix(ctx->anvil_path);
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen if (fd == -1)
40eb305d9b12cf48400fe3806a8a15ad6d372952Timo Sirainen i_fatal("net_connect_unix(%s) failed: %m", ctx->anvil_path);
40eb305d9b12cf48400fe3806a8a15ad6d372952Timo Sirainen net_set_nonblock(fd, FALSE);
c5383a0ed56a188a7d5efaaa4c6f8243af432d65Timo Sirainen
40eb305d9b12cf48400fe3806a8a15ad6d372952Timo Sirainen input = i_stream_create_fd(fd, (size_t)-1, TRUE);
40eb305d9b12cf48400fe3806a8a15ad6d372952Timo Sirainen if (write(fd, ANVIL_CMD, strlen(ANVIL_CMD)) < 0)
40eb305d9b12cf48400fe3806a8a15ad6d372952Timo Sirainen i_fatal("write(%s) failed: %m", ctx->anvil_path);
40eb305d9b12cf48400fe3806a8a15ad6d372952Timo Sirainen while ((line = i_stream_read_next_line(input)) != NULL) {
40eb305d9b12cf48400fe3806a8a15ad6d372952Timo Sirainen if (*line == '\0')
40eb305d9b12cf48400fe3806a8a15ad6d372952Timo Sirainen break;
40eb305d9b12cf48400fe3806a8a15ad6d372952Timo Sirainen T_BEGIN {
40eb305d9b12cf48400fe3806a8a15ad6d372952Timo Sirainen struct who_line who_line;
40eb305d9b12cf48400fe3806a8a15ad6d372952Timo Sirainen
40eb305d9b12cf48400fe3806a8a15ad6d372952Timo Sirainen who_parse_line(line, &who_line);
40eb305d9b12cf48400fe3806a8a15ad6d372952Timo Sirainen callback(ctx, &who_line);
40eb305d9b12cf48400fe3806a8a15ad6d372952Timo Sirainen } T_END;
40eb305d9b12cf48400fe3806a8a15ad6d372952Timo Sirainen }
40eb305d9b12cf48400fe3806a8a15ad6d372952Timo Sirainen if (input->stream_errno != 0)
c5383a0ed56a188a7d5efaaa4c6f8243af432d65Timo Sirainen i_fatal("read(%s) failed: %m", ctx->anvil_path);
c5383a0ed56a188a7d5efaaa4c6f8243af432d65Timo Sirainen
b42f37ae6f65ed986315b6885568d32115e589b1Timo Sirainen i_stream_destroy(&input);
1f1e81aab38d833d1c9cdc244c91fd762e0080d4Timo Sirainen}
1f1e81aab38d833d1c9cdc244c91fd762e0080d4Timo Sirainen
40eb305d9b12cf48400fe3806a8a15ad6d372952Timo Sirainenstatic bool who_user_filter_match(const struct who_user *user,
c5383a0ed56a188a7d5efaaa4c6f8243af432d65Timo Sirainen const struct who_filter *filter)
c5383a0ed56a188a7d5efaaa4c6f8243af432d65Timo Sirainen{
c5383a0ed56a188a7d5efaaa4c6f8243af432d65Timo Sirainen if (filter->username != NULL) {
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen if (strstr(user->username, filter->username) == NULL)
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen return FALSE;
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen }
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen if (filter->net_bits > 0) {
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen const struct ip_addr *ip;
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen bool ret = FALSE;
d5abbb932a0a598f002da39a8b3326643b1b5efcTimo Sirainen
d5abbb932a0a598f002da39a8b3326643b1b5efcTimo Sirainen array_foreach(&user->ips, ip) {
d5abbb932a0a598f002da39a8b3326643b1b5efcTimo Sirainen if (net_is_in_network(ip, &filter->net_ip,
d5abbb932a0a598f002da39a8b3326643b1b5efcTimo Sirainen filter->net_bits)) {
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen ret = TRUE;
break;
}
}
if (!ret)
return FALSE;
}
return TRUE;
}
static void who_print(struct who_context *ctx)
{
struct hash_iterate_context *iter;
void *key, *value;
fprintf(stderr, "%-30s # proto (ips) (pids)\n", "username");
iter = hash_table_iterate_init(ctx->users);
while (hash_table_iterate(iter, &key, &value)) {
struct who_user *user = value;
const struct ip_addr *ip;
const pid_t *pid;
bool first = TRUE;
if (!who_user_filter_match(user, &ctx->filter))
continue;
printf("%-30s %2u %-5s ", user->username,
user->connection_count, user->service);
printf("(");
array_foreach(&user->ips, ip) T_BEGIN {
if (first)
first = FALSE;
else
printf(" ");
printf("%s", net_ip2addr(ip));
} T_END;
printf(") (");
first = TRUE;
array_foreach(&user->pids, pid) T_BEGIN {
if (first)
first = FALSE;
else
printf(" ");
printf("%ld", (long)*pid);
} T_END;
printf(")\n");
};
hash_table_iterate_deinit(&iter);
}
static bool who_line_filter_match(const struct who_line *line,
const struct who_filter *filter)
{
if (filter->username != NULL) {
if (strstr(line->username, filter->username) == NULL)
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 {
printf("%-30s %-15s %-5s %ld\n", line->username,
net_ip2addr(&line->ip), line->service, (long)line->pid);
} T_END;
}
static void cmd_who(int argc, char *argv[])
{
struct who_context ctx;
struct ip_addr net_ip;
unsigned int net_bits;
bool separate_connections = FALSE;
int c;
memset(&ctx, 0, sizeof(ctx));
ctx.anvil_path = PKG_RUNDIR"/anvil";
ctx.pool = pool_alloconly_create("who users", 10240);
ctx.users = hash_table_create(default_pool, 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;
while (argv[1] != NULL) {
if (net_parse_range(argv[1], &net_ip, &net_bits) == 0) {
if (ctx.filter.net_bits != 0)
usage();
ctx.filter.net_ip = net_ip;
ctx.filter.net_bits = net_bits;
} else {
if (ctx.filter.username != NULL)
usage();
ctx.filter.username = argv[1];
}
argv++;
}
if (!separate_connections) {
who_lookup(&ctx, who_aggregate_line);
who_print(&ctx);
} else {
fprintf(stderr, "%-30s %-15s proto pid\n", "username", "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>] [<ip/bits>]", NULL
};