c25356d5978632df6203437e1953bcb29e0c736fTimo Sirainen/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */
c25356d5978632df6203437e1953bcb29e0c736fTimo Sirainen
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen#include "lib.h"
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen#include "array.h"
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen#include "net.h"
87ca2e468841829b44c09d618ac02f61a30b7a49Timo Sirainen#include "istream.h"
8d80659e504ffb34bb0c6a633184fece35751b18Timo Sirainen#include "wildcard-match.h"
87ca2e468841829b44c09d618ac02f61a30b7a49Timo Sirainen#include "hash.h"
8d80659e504ffb34bb0c6a633184fece35751b18Timo Sirainen#include "str.h"
8d80659e504ffb34bb0c6a633184fece35751b18Timo Sirainen#include "strescape.h"
87ca2e468841829b44c09d618ac02f61a30b7a49Timo Sirainen#include "doveadm.h"
8d80659e504ffb34bb0c6a633184fece35751b18Timo Sirainen#include "doveadm-print.h"
d6badc27cd6e8d3398877b6766cb0aaeef3a7800Timo Sirainen#include "doveadm-who.h"
24fc71a693331ffe77e2b6d81c70aca6fa055e47Timo Sirainen
df4018ae2f0a95be602f724ca70df7e0e3bd6a7dTimo Sirainen#include <stdio.h>
b20fb5b1df9d604a7541f5118fc5b4b466d211efTimo Sirainen#include <unistd.h>
b20fb5b1df9d604a7541f5118fc5b4b466d211efTimo Sirainen
73b50eecfc31750a312e2f940023f522eb07178cTimo Sirainenstruct who_user {
73b50eecfc31750a312e2f940023f522eb07178cTimo Sirainen const char *username;
73b50eecfc31750a312e2f940023f522eb07178cTimo Sirainen const char *service;
88187ee880b4829443e0d55ea7d145d9d5880217Timo Sirainen ARRAY(struct ip_addr) ips;
88187ee880b4829443e0d55ea7d145d9d5880217Timo Sirainen ARRAY(pid_t) pids;
3da614c39dd29f536c485089e67839b4cf89fed3Timo Sirainen unsigned int connection_count;
88187ee880b4829443e0d55ea7d145d9d5880217Timo Sirainen};
88187ee880b4829443e0d55ea7d145d9d5880217Timo Sirainen
88187ee880b4829443e0d55ea7d145d9d5880217Timo Sirainenstatic void who_user_ip(const struct who_user *user, struct ip_addr *ip_r)
88187ee880b4829443e0d55ea7d145d9d5880217Timo Sirainen{
88187ee880b4829443e0d55ea7d145d9d5880217Timo Sirainen if (array_count(&user->ips) == 0)
3fe9483b2b412a14493e3120751b0e99ecfe9388Timo Sirainen i_zero(ip_r);
3fe9483b2b412a14493e3120751b0e99ecfe9388Timo Sirainen else {
73b50eecfc31750a312e2f940023f522eb07178cTimo Sirainen const struct ip_addr *ip = array_idx(&user->ips, 0);
73b50eecfc31750a312e2f940023f522eb07178cTimo Sirainen *ip_r = *ip;
73b50eecfc31750a312e2f940023f522eb07178cTimo Sirainen }
88187ee880b4829443e0d55ea7d145d9d5880217Timo Sirainen}
88187ee880b4829443e0d55ea7d145d9d5880217Timo Sirainen
88187ee880b4829443e0d55ea7d145d9d5880217Timo Sirainenstatic unsigned int who_user_hash(const struct who_user *user)
5278c93bd7105c32ac7ec37f36015d5950f6cbcaTimo Sirainen{
44c5e644cb413a6559bf2d4179cbe48f9a82f366Timo Sirainen struct ip_addr ip;
5278c93bd7105c32ac7ec37f36015d5950f6cbcaTimo Sirainen unsigned int hash = str_hash(user->service);
24fc71a693331ffe77e2b6d81c70aca6fa055e47Timo Sirainen
24fc71a693331ffe77e2b6d81c70aca6fa055e47Timo Sirainen if (user->username[0] != '\0')
46c31f64b9f0949f00b7819f45b22f2d64b2ea27Timo Sirainen hash += str_hash(user->username);
46c31f64b9f0949f00b7819f45b22f2d64b2ea27Timo Sirainen else {
46c31f64b9f0949f00b7819f45b22f2d64b2ea27Timo Sirainen who_user_ip(user, &ip);
46c31f64b9f0949f00b7819f45b22f2d64b2ea27Timo Sirainen hash += net_ip_hash(&ip);
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen }
ef4d0eafab4d26bba047551db1e23ceff8aa9404Timo Sirainen return hash;
1b3bb8d39686ed24730cbc31cc9a33dc62c8c6c3Timo Sirainen}
1b3bb8d39686ed24730cbc31cc9a33dc62c8c6c3Timo Sirainen
d6badc27cd6e8d3398877b6766cb0aaeef3a7800Timo Sirainenstatic int who_user_cmp(const struct who_user *user1,
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen const struct who_user *user2)
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen{
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen if (strcmp(user1->username, user2->username) != 0)
5ea06115cb60413b62ffb58ffdd62786fec6a316Timo Sirainen return 1;
5ea06115cb60413b62ffb58ffdd62786fec6a316Timo Sirainen if (strcmp(user1->service, user2->service) != 0)
5ea06115cb60413b62ffb58ffdd62786fec6a316Timo Sirainen return 1;
3b80595fcf2001cf7b2fcc6290823e38f4a142fcTimo Sirainen
3b80595fcf2001cf7b2fcc6290823e38f4a142fcTimo Sirainen if (user1->username[0] == '\0') {
b6612c334604eeb27e1ca2bd804ac66dcbc2eaadTimo Sirainen /* tracking only IP addresses, not usernames */
3b80595fcf2001cf7b2fcc6290823e38f4a142fcTimo Sirainen struct ip_addr ip1, ip2;
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen
8afec4d1a32b78f540257a27769b372aad753384Timo Sirainen who_user_ip(user1, &ip1);
8afec4d1a32b78f540257a27769b372aad753384Timo Sirainen who_user_ip(user2, &ip2);
4ee00532a265bdfb38539d811fcd12d51210ac35Timo Sirainen return net_ip_cmp(&ip1, &ip2);
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen }
cc6ed00c61fda24799c905e403b94a2a8c39ae5cTimo Sirainen return 0;
cc6ed00c61fda24799c905e403b94a2a8c39ae5cTimo Sirainen}
cc6ed00c61fda24799c905e403b94a2a8c39ae5cTimo Sirainen
0d70a702dec63d22535684fec6a7247c5f153208Timo Sirainenstatic bool
03739a8eaad2d8b34b9d87dbbe5b13c5d5dfa11aTimo Sirainenwho_user_has_ip(const struct who_user *user, const struct ip_addr *ip)
87ca2e468841829b44c09d618ac02f61a30b7a49Timo Sirainen{
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen const struct ip_addr *ex_ip;
46c31f64b9f0949f00b7819f45b22f2d64b2ea27Timo Sirainen
4ee00532a265bdfb38539d811fcd12d51210ac35Timo Sirainen array_foreach(&user->ips, ex_ip) {
d6badc27cd6e8d3398877b6766cb0aaeef3a7800Timo Sirainen if (net_ip_compare(ex_ip, ip))
ef4d0eafab4d26bba047551db1e23ceff8aa9404Timo Sirainen return TRUE;
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen }
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen return FALSE;
0dffa25d211be541ee3c953b23566a1a990789dfTimo Sirainen}
687bb904e1bb76c21a6e392f60c990486b298ea4Timo Sirainen
0dffa25d211be541ee3c953b23566a1a990789dfTimo Sirainenstatic int who_parse_line(const char *line, struct who_line *line_r)
687bb904e1bb76c21a6e392f60c990486b298ea4Timo Sirainen{
0dffa25d211be541ee3c953b23566a1a990789dfTimo Sirainen const char *const *args = t_strsplit_tabescaped(line);
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen const char *ident = args[0];
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen const char *pid_str = args[1];
2a6af811ea3de3cf9e2f15e446674dd21b0705f3Timo Sirainen const char *refcount_str = args[2];
14c474d9f4591c397ed0b5206af6537c7b52c924Timo Sirainen const char *p, *ip_str;
14c474d9f4591c397ed0b5206af6537c7b52c924Timo Sirainen
24fc71a693331ffe77e2b6d81c70aca6fa055e47Timo Sirainen i_zero(line_r);
24fc71a693331ffe77e2b6d81c70aca6fa055e47Timo Sirainen
afd0d073a0afd1c8cf6473b4ae76919586eaa1faTimo Sirainen /* ident = service/ip/username (imap, pop3)
afd0d073a0afd1c8cf6473b4ae76919586eaa1faTimo Sirainen or service/username (lmtp) */
1b3bb8d39686ed24730cbc31cc9a33dc62c8c6c3Timo Sirainen p = strchr(ident, '/');
7c5b51bdf43a98e12c654ad437e0b258c5fffbc1Timo Sirainen if (p == NULL)
03739a8eaad2d8b34b9d87dbbe5b13c5d5dfa11aTimo Sirainen return -1;
03739a8eaad2d8b34b9d87dbbe5b13c5d5dfa11aTimo Sirainen if (str_to_pid(pid_str, &line_r->pid) < 0)
87ca2e468841829b44c09d618ac02f61a30b7a49Timo Sirainen return -1;
87ca2e468841829b44c09d618ac02f61a30b7a49Timo Sirainen line_r->service = t_strdup_until(ident, p++);
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen line_r->username = strchr(p, '/');
2ddd140bcecd9f8b7f2686b419cdfaa5d5d3a9e8Timo Sirainen if (line_r->username == NULL) {
e8acc691a14a6d0884c5ca9aa4d8507f1e082040Timo Sirainen /* no IP */
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen line_r->username = p;
} else {
ip_str = t_strdup_until(p, line_r->username++);
(void)net_addr2ip(ip_str, &line_r->ip);
}
if (str_to_uint(refcount_str, &line_r->refcount) < 0)
return -1;
return 0;
}
static bool who_user_has_pid(struct who_user *user, pid_t pid)
{
const pid_t *ex_pid;
array_foreach(&user->pids, ex_pid) {
if (*ex_pid == pid)
return TRUE;
}
return FALSE;
}
static void who_aggregate_line(struct who_context *ctx,
const struct who_line *line)
{
struct who_user *user, lookup_user;
lookup_user.username = line->username;
lookup_user.service = line->service;
user = hash_table_lookup(ctx->users, &lookup_user);
if (user == NULL) {
user = p_new(ctx->pool, struct who_user, 1);
user->username = p_strdup(ctx->pool, line->username);
user->service = p_strdup(ctx->pool, line->service);
p_array_init(&user->ips, ctx->pool, 3);
p_array_init(&user->pids, ctx->pool, 8);
hash_table_insert(ctx->users, user, user);
}
user->connection_count += line->refcount;
if (line->ip.family != 0 && !who_user_has_ip(user, &line->ip))
array_append(&user->ips, &line->ip, 1);
if (!who_user_has_pid(user, line->pid))
array_append(&user->pids, &line->pid, 1);
}
int who_parse_args(struct who_context *ctx, const char *const *masks)
{
struct ip_addr net_ip;
unsigned int i, net_bits;
for (i = 0; masks[i] != NULL; i++) {
if (net_parse_range(masks[i], &net_ip, &net_bits) == 0) {
if (ctx->filter.net_bits != 0) {
i_error("Multiple network masks not supported");
doveadm_exit_code = EX_USAGE;
return -1;
}
ctx->filter.net_ip = net_ip;
ctx->filter.net_bits = net_bits;
} else {
if (ctx->filter.username != NULL) {
i_error("Multiple username masks not supported");
doveadm_exit_code = EX_USAGE;
return -1;
}
ctx->filter.username = masks[i];
}
}
return 0;
}
void who_lookup(struct who_context *ctx, who_callback_t *callback)
{
#define ANVIL_HANDSHAKE "VERSION\tanvil\t1\t0\n"
#define ANVIL_CMD ANVIL_HANDSHAKE"CONNECT-DUMP\n"
struct istream *input;
const char *line;
int fd;
fd = doveadm_connect(ctx->anvil_path);
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: %s", ctx->anvil_path,
i_stream_get_error(input));
}
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(struct doveadm_cmd_context *cctx)
{
const char *const *masks;
struct who_context ctx;
bool separate_connections = FALSE;
i_zero(&ctx);
if (!doveadm_cmd_param_str(cctx, "socket-path", &(ctx.anvil_path)))
ctx.anvil_path = t_strconcat(doveadm_settings->base_dir, "/anvil", NULL);
(void)doveadm_cmd_param_bool(cctx, "separate-connections", &separate_connections);
ctx.pool = pool_alloconly_create("who users", 10240);
hash_table_create(&ctx.users, ctx.pool, 0, who_user_hash, who_user_cmp);
if (doveadm_cmd_param_array(cctx, "mask", &masks)) {
if (who_parse_args(&ctx, masks) != 0) {
hash_table_destroy(&ctx.users);
pool_unref(&ctx.pool);
return;
}
}
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_ver2 doveadm_cmd_who_ver2 = {
.name = "who",
.cmd = cmd_who,
.usage = "[-a <anvil socket path>] [-1] [<user mask>] [<ip/bits>]",
DOVEADM_CMD_PARAMS_START
DOVEADM_CMD_PARAM('a',"socket-path", CMD_PARAM_STR, 0)
DOVEADM_CMD_PARAM('1',"separate-connections", CMD_PARAM_BOOL, 0)
DOVEADM_CMD_PARAM('\0',"mask", CMD_PARAM_ARRAY, CMD_PARAM_FLAG_POSITIONAL)
DOVEADM_CMD_PARAMS_END
};