summaryrefslogtreecommitdiffstats
path: root/src/doveadm/doveadm-who.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/doveadm/doveadm-who.c')
-rw-r--r--src/doveadm/doveadm-who.c364
1 files changed, 364 insertions, 0 deletions
diff --git a/src/doveadm/doveadm-who.c b/src/doveadm/doveadm-who.c
new file mode 100644
index 0000000..b60e515
--- /dev/null
+++ b/src/doveadm/doveadm-who.c
@@ -0,0 +1,364 @@
+/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "net.h"
+#include "istream.h"
+#include "wildcard-match.h"
+#include "hash.h"
+#include "str.h"
+#include "strescape.h"
+#include "doveadm.h"
+#include "doveadm-print.h"
+#include "doveadm-who.h"
+
+#include <stdio.h>
+#include <unistd.h>
+
+struct who_user {
+ const char *username;
+ const char *service;
+ ARRAY(struct ip_addr) ips;
+ ARRAY(pid_t) pids;
+ unsigned int connection_count;
+};
+
+static void who_user_ip(const struct who_user *user, struct ip_addr *ip_r)
+{
+ if (array_count(&user->ips) == 0)
+ i_zero(ip_r);
+ else {
+ const struct ip_addr *ip = array_front(&user->ips);
+ *ip_r = *ip;
+ }
+}
+
+static unsigned int who_user_hash(const struct who_user *user)
+{
+ struct ip_addr ip;
+ unsigned int hash = str_hash(user->service);
+
+ if (user->username[0] != '\0')
+ hash += str_hash(user->username);
+ else {
+ who_user_ip(user, &ip);
+ hash += net_ip_hash(&ip);
+ }
+ return hash;
+}
+
+static int who_user_cmp(const struct who_user *user1,
+ const struct who_user *user2)
+{
+ if (strcmp(user1->username, user2->username) != 0)
+ return 1;
+ if (strcmp(user1->service, user2->service) != 0)
+ return 1;
+
+ if (user1->username[0] == '\0') {
+ /* tracking only IP addresses, not usernames */
+ struct ip_addr ip1, ip2;
+
+ who_user_ip(user1, &ip1);
+ who_user_ip(user2, &ip2);
+ return net_ip_cmp(&ip1, &ip2);
+ }
+ return 0;
+}
+
+static bool
+who_user_has_ip(const struct who_user *user, const struct ip_addr *ip)
+{
+ const struct ip_addr *ex_ip;
+
+ array_foreach(&user->ips, ex_ip) {
+ if (net_ip_compare(ex_ip, ip))
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static int who_parse_line(const char *line, struct who_line *line_r)
+{
+ const char *const *args = t_strsplit_tabescaped(line);
+ const char *ident = args[0];
+ const char *pid_str = args[1];
+ const char *refcount_str = args[2];
+ const char *p, *ip_str;
+
+ i_zero(line_r);
+
+ /* ident = service/ip/username (imap, pop3)
+ or service/username (lmtp) */
+ p = strchr(ident, '/');
+ if (p == NULL)
+ return -1;
+ if (str_to_pid(pid_str, &line_r->pid) < 0)
+ return -1;
+ line_r->service = t_strdup_until(ident, p++);
+ line_r->username = strchr(p, '/');
+ if (line_r->username == NULL) {
+ /* no IP */
+ 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)
+{
+ pid_t ex_pid;
+
+ array_foreach_elem(&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_push_back(&user->ips, &line->ip);
+
+ if (!who_user_has_pid(user, line->pid))
+ array_push_back(&user->pids, &line->pid);
+}
+
+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 (!str_is_numeric(masks[i], '\0') &&
+ 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_MAX);
+ 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;
+ 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_elem(&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));
+
+ doveadm_print_flush();
+}
+
+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
+};