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