1 /* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */
2
3 #include "lib.h"
4 #include "array.h"
5 #include "net.h"
6 #include "istream.h"
7 #include "wildcard-match.h"
8 #include "hash.h"
9 #include "str.h"
10 #include "strescape.h"
11 #include "doveadm.h"
12 #include "doveadm-print.h"
13 #include "doveadm-who.h"
14
15 #include <stdio.h>
16 #include <unistd.h>
17
18 struct who_user {
19 const char *username;
20 const char *service;
21 ARRAY(struct ip_addr) ips;
22 ARRAY(pid_t) pids;
23 unsigned int connection_count;
24 };
25
who_user_ip(const struct who_user * user,struct ip_addr * ip_r)26 static void who_user_ip(const struct who_user *user, struct ip_addr *ip_r)
27 {
28 if (array_count(&user->ips) == 0)
29 i_zero(ip_r);
30 else {
31 const struct ip_addr *ip = array_front(&user->ips);
32 *ip_r = *ip;
33 }
34 }
35
who_user_hash(const struct who_user * user)36 static unsigned int who_user_hash(const struct who_user *user)
37 {
38 struct ip_addr ip;
39 unsigned int hash = str_hash(user->service);
40
41 if (user->username[0] != '\0')
42 hash += str_hash(user->username);
43 else {
44 who_user_ip(user, &ip);
45 hash += net_ip_hash(&ip);
46 }
47 return hash;
48 }
49
who_user_cmp(const struct who_user * user1,const struct who_user * user2)50 static int who_user_cmp(const struct who_user *user1,
51 const struct who_user *user2)
52 {
53 if (strcmp(user1->username, user2->username) != 0)
54 return 1;
55 if (strcmp(user1->service, user2->service) != 0)
56 return 1;
57
58 if (user1->username[0] == '\0') {
59 /* tracking only IP addresses, not usernames */
60 struct ip_addr ip1, ip2;
61
62 who_user_ip(user1, &ip1);
63 who_user_ip(user2, &ip2);
64 return net_ip_cmp(&ip1, &ip2);
65 }
66 return 0;
67 }
68
69 static bool
who_user_has_ip(const struct who_user * user,const struct ip_addr * ip)70 who_user_has_ip(const struct who_user *user, const struct ip_addr *ip)
71 {
72 const struct ip_addr *ex_ip;
73
74 array_foreach(&user->ips, ex_ip) {
75 if (net_ip_compare(ex_ip, ip))
76 return TRUE;
77 }
78 return FALSE;
79 }
80
who_parse_line(const char * line,struct who_line * line_r)81 static int who_parse_line(const char *line, struct who_line *line_r)
82 {
83 const char *const *args = t_strsplit_tabescaped(line);
84 const char *ident = args[0];
85 const char *pid_str = args[1];
86 const char *refcount_str = args[2];
87 const char *p, *ip_str;
88
89 i_zero(line_r);
90
91 /* ident = service/ip/username (imap, pop3)
92 or service/username (lmtp) */
93 p = strchr(ident, '/');
94 if (p == NULL)
95 return -1;
96 if (str_to_pid(pid_str, &line_r->pid) < 0)
97 return -1;
98 line_r->service = t_strdup_until(ident, p++);
99 line_r->username = strchr(p, '/');
100 if (line_r->username == NULL) {
101 /* no IP */
102 line_r->username = p;
103 } else {
104 ip_str = t_strdup_until(p, line_r->username++);
105 (void)net_addr2ip(ip_str, &line_r->ip);
106 }
107 if (str_to_uint(refcount_str, &line_r->refcount) < 0)
108 return -1;
109 return 0;
110 }
111
who_user_has_pid(struct who_user * user,pid_t pid)112 static bool who_user_has_pid(struct who_user *user, pid_t pid)
113 {
114 pid_t ex_pid;
115
116 array_foreach_elem(&user->pids, ex_pid) {
117 if (ex_pid == pid)
118 return TRUE;
119 }
120 return FALSE;
121 }
122
who_aggregate_line(struct who_context * ctx,const struct who_line * line)123 static void who_aggregate_line(struct who_context *ctx,
124 const struct who_line *line)
125 {
126 struct who_user *user, lookup_user;
127
128 lookup_user.username = line->username;
129 lookup_user.service = line->service;
130
131 user = hash_table_lookup(ctx->users, &lookup_user);
132 if (user == NULL) {
133 user = p_new(ctx->pool, struct who_user, 1);
134 user->username = p_strdup(ctx->pool, line->username);
135 user->service = p_strdup(ctx->pool, line->service);
136 p_array_init(&user->ips, ctx->pool, 3);
137 p_array_init(&user->pids, ctx->pool, 8);
138 hash_table_insert(ctx->users, user, user);
139 }
140 user->connection_count += line->refcount;
141
142 if (line->ip.family != 0 && !who_user_has_ip(user, &line->ip))
143 array_push_back(&user->ips, &line->ip);
144
145 if (!who_user_has_pid(user, line->pid))
146 array_push_back(&user->pids, &line->pid);
147 }
148
who_parse_args(struct who_context * ctx,const char * const * masks)149 int who_parse_args(struct who_context *ctx, const char *const *masks)
150 {
151 struct ip_addr net_ip;
152 unsigned int i, net_bits;
153
154 for (i = 0; masks[i] != NULL; i++) {
155 if (net_parse_range(masks[i], &net_ip, &net_bits) == 0) {
156 if (ctx->filter.net_bits != 0) {
157 i_error("Multiple network masks not supported");
158 doveadm_exit_code = EX_USAGE;
159 return -1;
160 }
161 ctx->filter.net_ip = net_ip;
162 ctx->filter.net_bits = net_bits;
163 } else {
164 if (ctx->filter.username != NULL) {
165 i_error("Multiple username masks not supported");
166 doveadm_exit_code = EX_USAGE;
167 return -1;
168 }
169 ctx->filter.username = masks[i];
170 }
171 }
172 return 0;
173 }
174
who_lookup(struct who_context * ctx,who_callback_t * callback)175 void who_lookup(struct who_context *ctx, who_callback_t *callback)
176 {
177 #define ANVIL_HANDSHAKE "VERSION\tanvil\t1\t0\n"
178 #define ANVIL_CMD ANVIL_HANDSHAKE"CONNECT-DUMP\n"
179 struct istream *input;
180 const char *line;
181 int fd;
182
183 fd = doveadm_connect(ctx->anvil_path);
184 net_set_nonblock(fd, FALSE);
185 if (write(fd, ANVIL_CMD, strlen(ANVIL_CMD)) < 0)
186 i_fatal("write(%s) failed: %m", ctx->anvil_path);
187
188 input = i_stream_create_fd_autoclose(&fd, SIZE_MAX);
189 while ((line = i_stream_read_next_line(input)) != NULL) {
190 if (*line == '\0')
191 break;
192 T_BEGIN {
193 struct who_line who_line;
194
195 if (who_parse_line(line, &who_line) < 0)
196 i_error("Invalid input: %s", line);
197 else
198 callback(ctx, &who_line);
199 } T_END;
200 }
201 if (input->stream_errno != 0) {
202 i_fatal("read(%s) failed: %s", ctx->anvil_path,
203 i_stream_get_error(input));
204 }
205
206 i_stream_destroy(&input);
207 }
208
who_user_filter_match(const struct who_user * user,const struct who_filter * filter)209 static bool who_user_filter_match(const struct who_user *user,
210 const struct who_filter *filter)
211 {
212 if (filter->username != NULL) {
213 if (!wildcard_match_icase(user->username, filter->username))
214 return FALSE;
215 }
216 if (filter->net_bits > 0) {
217 const struct ip_addr *ip;
218 bool ret = FALSE;
219
220 array_foreach(&user->ips, ip) {
221 if (net_is_in_network(ip, &filter->net_ip,
222 filter->net_bits)) {
223 ret = TRUE;
224 break;
225 }
226 }
227 if (!ret)
228 return FALSE;
229 }
230 return TRUE;
231 }
232
who_print_user(const struct who_user * user)233 static void who_print_user(const struct who_user *user)
234 {
235 const struct ip_addr *ip;
236 pid_t pid;
237 string_t *str = t_str_new(256);
238
239 doveadm_print(user->username);
240 doveadm_print(dec2str(user->connection_count));
241 doveadm_print(user->service);
242
243 str_append_c(str, '(');
244 array_foreach_elem(&user->pids, pid)
245 str_printfa(str, "%ld ", (long)pid);
246 if (str_len(str) > 1)
247 str_truncate(str, str_len(str)-1);
248 str_append_c(str, ')');
249 doveadm_print(str_c(str));
250
251 str_truncate(str, 0);
252 str_append_c(str, '(');
253 array_foreach(&user->ips, ip)
254 str_printfa(str, "%s ", net_ip2addr(ip));
255 if (str_len(str) > 1)
256 str_truncate(str, str_len(str)-1);
257 str_append_c(str, ')');
258 doveadm_print(str_c(str));
259
260 doveadm_print_flush();
261 }
262
who_print(struct who_context * ctx)263 static void who_print(struct who_context *ctx)
264 {
265 struct hash_iterate_context *iter;
266 struct who_user *user;
267
268 doveadm_print_header("username", "username", 0);
269 doveadm_print_header("connections", "#",
270 DOVEADM_PRINT_HEADER_FLAG_RIGHT_JUSTIFY);
271 doveadm_print_header("service", "proto", 0);
272 doveadm_print_header("pids", "(pids)", 0);
273 doveadm_print_header("ips", "(ips)", 0);
274
275 iter = hash_table_iterate_init(ctx->users);
276 while (hash_table_iterate(iter, ctx->users, &user, &user)) {
277 if (who_user_filter_match(user, &ctx->filter)) T_BEGIN {
278 who_print_user(user);
279 } T_END;
280 }
281 hash_table_iterate_deinit(&iter);
282 }
283
who_line_filter_match(const struct who_line * line,const struct who_filter * filter)284 bool who_line_filter_match(const struct who_line *line,
285 const struct who_filter *filter)
286 {
287 if (filter->username != NULL) {
288 if (!wildcard_match_icase(line->username, filter->username))
289 return FALSE;
290 }
291 if (filter->net_bits > 0) {
292 if (!net_is_in_network(&line->ip, &filter->net_ip,
293 filter->net_bits))
294 return FALSE;
295 }
296 return TRUE;
297 }
298
who_print_line(struct who_context * ctx,const struct who_line * line)299 static void who_print_line(struct who_context *ctx,
300 const struct who_line *line)
301 {
302 unsigned int i;
303
304 if (!who_line_filter_match(line, &ctx->filter))
305 return;
306
307 for (i = 0; i < line->refcount; i++) T_BEGIN {
308 doveadm_print(line->username);
309 doveadm_print(line->service);
310 doveadm_print(dec2str(line->pid));
311 doveadm_print(net_ip2addr(&line->ip));
312 } T_END;
313 }
314
cmd_who(struct doveadm_cmd_context * cctx)315 static void cmd_who(struct doveadm_cmd_context *cctx)
316 {
317 const char *const *masks;
318 struct who_context ctx;
319 bool separate_connections = FALSE;
320
321 i_zero(&ctx);
322 if (!doveadm_cmd_param_str(cctx, "socket-path", &(ctx.anvil_path)))
323 ctx.anvil_path = t_strconcat(doveadm_settings->base_dir, "/anvil", NULL);
324 (void)doveadm_cmd_param_bool(cctx, "separate-connections", &separate_connections);
325
326 ctx.pool = pool_alloconly_create("who users", 10240);
327 hash_table_create(&ctx.users, ctx.pool, 0, who_user_hash, who_user_cmp);
328
329 if (doveadm_cmd_param_array(cctx, "mask", &masks)) {
330 if (who_parse_args(&ctx, masks) != 0) {
331 hash_table_destroy(&ctx.users);
332 pool_unref(&ctx.pool);
333 return;
334 }
335 }
336
337 doveadm_print_init(DOVEADM_PRINT_TYPE_TABLE);
338 if (!separate_connections) {
339 who_lookup(&ctx, who_aggregate_line);
340 who_print(&ctx);
341 } else {
342 doveadm_print_header("username", "username",
343 DOVEADM_PRINT_HEADER_FLAG_EXPAND);
344 doveadm_print_header("service", "proto", 0);
345 doveadm_print_header_simple("pid");
346 doveadm_print_header_simple("ip");
347 who_lookup(&ctx, who_print_line);
348 }
349
350 hash_table_destroy(&ctx.users);
351 pool_unref(&ctx.pool);
352 }
353
354 struct doveadm_cmd_ver2 doveadm_cmd_who_ver2 = {
355 .name = "who",
356 .cmd = cmd_who,
357 .usage = "[-a <anvil socket path>] [-1] [<user mask>] [<ip/bits>]",
358 DOVEADM_CMD_PARAMS_START
359 DOVEADM_CMD_PARAM('a',"socket-path", CMD_PARAM_STR, 0)
360 DOVEADM_CMD_PARAM('1',"separate-connections", CMD_PARAM_BOOL, 0)
361 DOVEADM_CMD_PARAM('\0',"mask", CMD_PARAM_ARRAY, CMD_PARAM_FLAG_POSITIONAL)
362 DOVEADM_CMD_PARAMS_END
363 };
364