1 /* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */
2
3 #include "lib.h"
4 #include "net.h"
5 #include "ostream.h"
6 #include "str.h"
7 #include "strescape.h"
8 #include "wildcard-match.h"
9 #include "mail-stats.h"
10 #include "mail-command.h"
11 #include "mail-session.h"
12 #include "mail-user.h"
13 #include "mail-domain.h"
14 #include "mail-ip.h"
15 #include "client.h"
16 #include "client-export.h"
17
18 enum mail_export_level {
19 MAIL_EXPORT_LEVEL_COMMAND,
20 MAIL_EXPORT_LEVEL_SESSION,
21 MAIL_EXPORT_LEVEL_USER,
22 MAIL_EXPORT_LEVEL_DOMAIN,
23 MAIL_EXPORT_LEVEL_IP,
24 MAIL_EXPORT_LEVEL_GLOBAL
25 };
26 static const char *mail_export_level_names[] = {
27 "command", "session", "user", "domain", "ip", "global"
28 };
29
30 struct mail_export_filter {
31 const char *user, *domain, *session;
32 struct ip_addr ip;
33 unsigned int ip_bits;
34 time_t since;
35 bool connected;
36 };
37
38 struct client_export_cmd {
39 enum mail_export_level level;
40 struct mail_export_filter filter;
41 string_t *str;
42 int (*export_iter)(struct client *client);
43 bool header_sent;
44 };
45
46 static int
mail_export_level_parse(const char * str,enum mail_export_level * level_r)47 mail_export_level_parse(const char *str, enum mail_export_level *level_r)
48 {
49 unsigned int i;
50
51 for (i = 0; i < N_ELEMENTS(mail_export_level_names); i++) {
52 if (strcmp(mail_export_level_names[i], str) == 0) {
53 *level_r = (enum mail_export_level)i;
54 return 0;
55 }
56 }
57 return -1;
58 }
59
60 static int
mail_export_parse_filter(const char * const * args,pool_t pool,struct mail_export_filter * filter_r,const char ** error_r)61 mail_export_parse_filter(const char *const *args, pool_t pool,
62 struct mail_export_filter *filter_r,
63 const char **error_r)
64 {
65 unsigned long l;
66
67 /* filters:
68 user=<wildcard> | domain=<wildcard> | session=<str>
69 ip=<ip>[/<mask>]
70 since=<timestamp>
71 connected
72 */
73 i_zero(filter_r);
74 for (; *args != NULL; args++) {
75 if (str_begins(*args, "user="))
76 filter_r->user = p_strdup(pool, *args + 5);
77 else if (str_begins(*args, "domain="))
78 filter_r->domain = p_strdup(pool, *args + 7);
79 else if (str_begins(*args, "session="))
80 filter_r->session = p_strdup(pool, *args + 8);
81 else if (str_begins(*args, "ip=")) {
82 if (net_parse_range(*args + 3, &filter_r->ip,
83 &filter_r->ip_bits) < 0) {
84 *error_r = "Invalid ip filter";
85 return -1;
86 }
87 } else if (str_begins(*args, "since=")) {
88 if (str_to_ulong(*args + 6, &l) < 0) {
89 *error_r = "Invalid since filter";
90 return -1;
91 }
92 filter_r->since = (time_t)l;
93 } else if (strcmp(*args, "connected") == 0) {
94 filter_r->connected = TRUE;
95 }
96 }
97 return 0;
98 }
99
100 static void
client_export_stats_headers(struct client * client)101 client_export_stats_headers(struct client *client)
102 {
103 unsigned int i, count = stats_field_count();
104 string_t *str = t_str_new(128);
105
106 i_assert(count > 0);
107
108 str_append(str, stats_field_name(0));
109 for (i = 1; i < count; i++) {
110 str_append_c(str, '\t');
111 str_append(str, stats_field_name(i));
112 }
113 str_append_c(str, '\n');
114 o_stream_nsend(client->output, str_data(str), str_len(str));
115 }
116
117 static void
client_export_stats(string_t * str,const struct stats * stats)118 client_export_stats(string_t *str, const struct stats *stats)
119 {
120 unsigned int i, count = stats_field_count();
121
122 i_assert(count > 0);
123
124 stats_field_value(str, stats, 0);
125 for (i = 1; i < count; i++) {
126 str_append_c(str, '\t');
127 stats_field_value(str, stats, i);
128 }
129 }
130
131 static bool
mail_export_filter_match_session(const struct mail_export_filter * filter,const struct mail_session * session)132 mail_export_filter_match_session(const struct mail_export_filter *filter,
133 const struct mail_session *session)
134 {
135 if (filter->connected && session->disconnected)
136 return FALSE;
137 if (filter->since > session->last_update.tv_sec)
138 return FALSE;
139 if (filter->session != NULL &&
140 strcmp(session->id, filter->session) != 0)
141 return FALSE;
142 if (filter->user != NULL &&
143 !wildcard_match(session->user->name, filter->user))
144 return FALSE;
145 if (filter->domain != NULL &&
146 !wildcard_match(session->user->domain->name, filter->domain))
147 return FALSE;
148 if (filter->ip_bits > 0 &&
149 !net_is_in_network(&session->ip->ip, &filter->ip, filter->ip_bits))
150 return FALSE;
151 return TRUE;
152 }
153
154 static bool
mail_export_filter_match_user_common(const struct mail_export_filter * filter,const struct mail_user * user)155 mail_export_filter_match_user_common(const struct mail_export_filter *filter,
156 const struct mail_user *user)
157 {
158 struct mail_session *s;
159 bool connected = FALSE, ip_ok = FALSE;
160
161 if (filter->user != NULL &&
162 !wildcard_match(user->name, filter->user))
163 return FALSE;
164
165 if (filter->connected || filter->ip_bits > 0) {
166 for (s = user->sessions; s != NULL; s = s->user_next) {
167 if (!s->disconnected)
168 connected = TRUE;
169 if (filter->ip_bits > 0 &&
170 net_is_in_network(&s->ip->ip, &filter->ip,
171 filter->ip_bits))
172 ip_ok = TRUE;
173
174 }
175 if (filter->connected && !connected)
176 return FALSE;
177 if (filter->ip_bits > 0 && !ip_ok)
178 return FALSE;
179 }
180 return TRUE;
181 }
182
183 static bool
mail_export_filter_match_user(const struct mail_export_filter * filter,const struct mail_user * user)184 mail_export_filter_match_user(const struct mail_export_filter *filter,
185 const struct mail_user *user)
186 {
187 if (filter->since > user->last_update.tv_sec)
188 return FALSE;
189 if (filter->domain != NULL &&
190 !wildcard_match(user->domain->name, filter->domain))
191 return FALSE;
192 return mail_export_filter_match_user_common(filter, user);
193 }
194
195 static bool
mail_export_filter_match_domain(const struct mail_export_filter * filter,const struct mail_domain * domain)196 mail_export_filter_match_domain(const struct mail_export_filter *filter,
197 const struct mail_domain *domain)
198 {
199 struct mail_user *user;
200
201 if (filter->since > domain->last_update.tv_sec)
202 return FALSE;
203 if (filter->domain != NULL &&
204 !wildcard_match(domain->name, filter->domain))
205 return FALSE;
206
207 if (filter->user != NULL || filter->connected || filter->ip_bits > 0) {
208 for (user = domain->users; user != NULL; user = user->domain_next) {
209 if (mail_export_filter_match_user_common(filter, user))
210 break;
211 }
212 if (user == NULL)
213 return FALSE;
214 }
215 return TRUE;
216 }
217
218 static bool
mail_export_filter_match_ip(const struct mail_export_filter * filter,const struct mail_ip * ip)219 mail_export_filter_match_ip(const struct mail_export_filter *filter,
220 const struct mail_ip *ip)
221 {
222 struct mail_session *s;
223 bool connected = FALSE, user_ok = FALSE, domain_ok = FALSE;
224
225 if (filter->connected || filter->ip_bits > 0) {
226 for (s = ip->sessions; s != NULL; s = s->ip_next) {
227 if (!s->disconnected)
228 connected = TRUE;
229 if (filter->user != NULL &&
230 wildcard_match(s->user->name, filter->user))
231 user_ok = TRUE;
232 if (filter->domain != NULL &&
233 wildcard_match(s->user->domain->name, filter->domain))
234 domain_ok = TRUE;
235 }
236 if (filter->connected && !connected)
237 return FALSE;
238 if (filter->user != NULL && !user_ok)
239 return FALSE;
240 if (filter->domain != NULL && !domain_ok)
241 return FALSE;
242 }
243 if (filter->since > ip->last_update.tv_sec)
244 return FALSE;
245 if (filter->ip_bits > 0 &&
246 !net_is_in_network(&ip->ip, &filter->ip, filter->ip_bits))
247 return FALSE;
248 return TRUE;
249 }
250
client_export_timeval(string_t * str,const struct timeval * tv)251 static void client_export_timeval(string_t *str, const struct timeval *tv)
252 {
253 str_printfa(str, "\t%ld.%06u", (long)tv->tv_sec,
254 (unsigned int)tv->tv_usec);
255 }
256
client_export_iter_command(struct client * client)257 static int client_export_iter_command(struct client *client)
258 {
259 struct client_export_cmd *cmd = client->cmd_export;
260 struct mail_command *command = client->mail_cmd_iter;
261
262 i_assert(cmd->level == MAIL_EXPORT_LEVEL_COMMAND);
263 mail_command_unref(&client->mail_cmd_iter);
264
265 if (!cmd->header_sent) {
266 o_stream_nsend_str(client->output,
267 "cmd\targs\tsession\tuser\tlast_update\t");
268 client_export_stats_headers(client);
269 cmd->header_sent = TRUE;
270 }
271
272 for (; command != NULL; command = command->stable_next) {
273 if (client_is_busy(client))
274 break;
275 if (!mail_export_filter_match_session(&cmd->filter,
276 command->session))
277 continue;
278
279 str_truncate(cmd->str, 0);
280 str_append_tabescaped(cmd->str, command->name);
281 str_append_c(cmd->str, '\t');
282 str_append_tabescaped(cmd->str, command->args);
283 str_append_c(cmd->str, '\t');
284 str_append(cmd->str, command->session->id);
285 str_append_c(cmd->str, '\t');
286 str_append_tabescaped(cmd->str,
287 command->session->user->name);
288 client_export_timeval(cmd->str, &command->last_update);
289 str_append_c(cmd->str, '\t');
290 client_export_stats(cmd->str, command->stats);
291 str_append_c(cmd->str, '\n');
292 o_stream_nsend(client->output, str_data(cmd->str),
293 str_len(cmd->str));
294 }
295
296 if (command != NULL) {
297 client->mail_cmd_iter = command;
298 mail_command_ref(command);
299 return 0;
300 }
301 return 1;
302 }
303
client_export_iter_session(struct client * client)304 static int client_export_iter_session(struct client *client)
305 {
306 struct client_export_cmd *cmd = client->cmd_export;
307 struct mail_session *session = client->mail_session_iter;
308
309 i_assert(cmd->level == MAIL_EXPORT_LEVEL_SESSION);
310 mail_session_unref(&client->mail_session_iter);
311
312 if (!cmd->header_sent) {
313 o_stream_nsend_str(client->output,
314 "session\tuser\tip\tservice\tpid\tconnected"
315 "\tlast_update\tnum_cmds\t");
316 client_export_stats_headers(client);
317 cmd->header_sent = TRUE;
318 }
319
320 for (; session != NULL; session = session->stable_next) {
321 if (client_is_busy(client))
322 break;
323 if (!mail_export_filter_match_session(&cmd->filter, session))
324 continue;
325
326 str_truncate(cmd->str, 0);
327 str_append(cmd->str, session->id);
328 str_append_c(cmd->str, '\t');
329 str_append_tabescaped(cmd->str, session->user->name);
330 str_append_c(cmd->str, '\t');
331 if (session->ip != NULL) T_BEGIN {
332 str_append(cmd->str, net_ip2addr(&session->ip->ip));
333 } T_END;
334 str_append_c(cmd->str, '\t');
335 str_append_tabescaped(cmd->str, session->service);
336 str_printfa(cmd->str, "\t%ld", (long)session->pid);
337 str_printfa(cmd->str, "\t%d", !session->disconnected);
338 client_export_timeval(cmd->str, &session->last_update);
339 str_printfa(cmd->str, "\t%u\t", session->num_cmds);
340 client_export_stats(cmd->str, session->stats);
341 str_append_c(cmd->str, '\n');
342 o_stream_nsend(client->output, str_data(cmd->str),
343 str_len(cmd->str));
344 }
345
346 if (session != NULL) {
347 client->mail_session_iter = session;
348 mail_session_ref(session);
349 return 0;
350 }
351 return 1;
352 }
353
client_export_iter_user(struct client * client)354 static int client_export_iter_user(struct client *client)
355 {
356 struct client_export_cmd *cmd = client->cmd_export;
357 struct mail_user *user = client->mail_user_iter;
358
359 i_assert(cmd->level == MAIL_EXPORT_LEVEL_USER);
360 mail_user_unref(&client->mail_user_iter);
361
362 if (!cmd->header_sent) {
363 o_stream_nsend_str(client->output,
364 "user\treset_timestamp\tlast_update"
365 "\tnum_logins\tnum_cmds\t");
366 client_export_stats_headers(client);
367 cmd->header_sent = TRUE;
368 }
369
370 for (; user != NULL; user = user->stable_next) {
371 if (client_is_busy(client))
372 break;
373 if (!mail_export_filter_match_user(&cmd->filter, user))
374 continue;
375
376 str_truncate(cmd->str, 0);
377 str_append_tabescaped(cmd->str, user->name);
378 str_printfa(cmd->str, "\t%ld", (long)user->reset_timestamp);
379 client_export_timeval(cmd->str, &user->last_update);
380 str_printfa(cmd->str, "\t%u\t%u\t",
381 user->num_logins, user->num_cmds);
382 client_export_stats(cmd->str, user->stats);
383 str_append_c(cmd->str, '\n');
384 o_stream_nsend(client->output, str_data(cmd->str),
385 str_len(cmd->str));
386 }
387
388 if (user != NULL) {
389 client->mail_user_iter = user;
390 mail_user_ref(user);
391 return 0;
392 }
393 return 1;
394 }
395
client_export_iter_domain(struct client * client)396 static int client_export_iter_domain(struct client *client)
397 {
398 struct client_export_cmd *cmd = client->cmd_export;
399 struct mail_domain *domain = client->mail_domain_iter;
400
401 i_assert(cmd->level == MAIL_EXPORT_LEVEL_DOMAIN);
402 mail_domain_unref(&client->mail_domain_iter);
403
404 if (!cmd->header_sent) {
405 o_stream_nsend_str(client->output,
406 "domain\treset_timestamp\tlast_update"
407 "\tnum_logins\tnum_cmds\tnum_connected_sessions\t");
408 client_export_stats_headers(client);
409 cmd->header_sent = TRUE;
410 }
411
412 for (; domain != NULL; domain = domain->stable_next) {
413 if (client_is_busy(client))
414 break;
415 if (!mail_export_filter_match_domain(&cmd->filter, domain))
416 continue;
417
418 str_truncate(cmd->str, 0);
419 str_append_tabescaped(cmd->str, domain->name);
420 str_printfa(cmd->str, "\t%ld", (long)domain->reset_timestamp);
421 client_export_timeval(cmd->str, &domain->last_update);
422 str_printfa(cmd->str, "\t%u\t%u\t%u\t",
423 domain->num_logins, domain->num_cmds,
424 domain->num_connected_sessions);
425 client_export_stats(cmd->str, domain->stats);
426 str_append_c(cmd->str, '\n');
427 o_stream_nsend(client->output, str_data(cmd->str),
428 str_len(cmd->str));
429 }
430
431 if (domain != NULL) {
432 client->mail_domain_iter = domain;
433 mail_domain_ref(domain);
434 return 0;
435 }
436 return 1;
437 }
438
client_export_iter_ip(struct client * client)439 static int client_export_iter_ip(struct client *client)
440 {
441 struct client_export_cmd *cmd = client->cmd_export;
442 struct mail_ip *ip = client->mail_ip_iter;
443
444 i_assert(cmd->level == MAIL_EXPORT_LEVEL_IP);
445 mail_ip_unref(&client->mail_ip_iter);
446
447 if (!cmd->header_sent) {
448 o_stream_nsend_str(client->output,
449 "ip\treset_timestamp\tlast_update"
450 "\tnum_logins\tnum_cmds\tnum_connected_sessions\t");
451 client_export_stats_headers(client);
452 cmd->header_sent = TRUE;
453 }
454
455 for (; ip != NULL; ip = ip->stable_next) {
456 if (client_is_busy(client))
457 break;
458 if (!mail_export_filter_match_ip(&cmd->filter, ip))
459 continue;
460
461 str_truncate(cmd->str, 0);
462 T_BEGIN {
463 str_append(cmd->str, net_ip2addr(&ip->ip));
464 } T_END;
465 str_printfa(cmd->str, "\t%ld", (long)ip->reset_timestamp);
466 client_export_timeval(cmd->str, &ip->last_update);
467 str_printfa(cmd->str, "\t%u\t%u\t%u\t",
468 ip->num_logins, ip->num_cmds, ip->num_connected_sessions);
469 client_export_stats(cmd->str, ip->stats);
470 str_append_c(cmd->str, '\n');
471 o_stream_nsend(client->output, str_data(cmd->str),
472 str_len(cmd->str));
473 }
474
475 if (ip != NULL) {
476 client->mail_ip_iter = ip;
477 mail_ip_ref(ip);
478 return 0;
479 }
480 return 1;
481 }
482
client_export_iter_global(struct client * client)483 static int client_export_iter_global(struct client *client)
484 {
485 struct client_export_cmd *cmd = client->cmd_export;
486 struct mail_global *g = &mail_global_stats;
487
488 i_assert(cmd->level == MAIL_EXPORT_LEVEL_GLOBAL);
489
490 if (!cmd->header_sent) {
491 o_stream_nsend_str(client->output,
492 "reset_timestamp\tlast_update"
493 "\tnum_logins\tnum_cmds\tnum_connected_sessions\t");
494 client_export_stats_headers(client);
495 cmd->header_sent = TRUE;
496 }
497
498 str_truncate(cmd->str, 0);
499 str_printfa(cmd->str, "%ld", (long)g->reset_timestamp);
500 client_export_timeval(cmd->str, &g->last_update);
501 str_printfa(cmd->str, "\t%u\t%u\t%u\t",
502 g->num_logins, g->num_cmds, g->num_connected_sessions);
503 client_export_stats(cmd->str, g->stats);
504 str_append_c(cmd->str, '\n');
505 o_stream_nsend(client->output, str_data(cmd->str),
506 str_len(cmd->str));
507 return 1;
508 }
509
client_export_more(struct client * client)510 static int client_export_more(struct client *client)
511 {
512 if (client->cmd_export->export_iter(client) == 0)
513 return 0;
514 o_stream_nsend_str(client->output, "\n");
515 return 1;
516 }
517
client_export_iter_init(struct client * client)518 static bool client_export_iter_init(struct client *client)
519 {
520 struct client_export_cmd *cmd = client->cmd_export;
521
522 if (cmd->filter.user != NULL && strchr(cmd->filter.user, '*') == NULL &&
523 (cmd->level == MAIL_EXPORT_LEVEL_USER ||
524 cmd->level == MAIL_EXPORT_LEVEL_SESSION)) {
525 /* exact user */
526 struct mail_user *user = mail_user_lookup(cmd->filter.user);
527 if (user == NULL)
528 return FALSE;
529 if (cmd->level == MAIL_EXPORT_LEVEL_SESSION) {
530 client->mail_session_iter = user->sessions;
531 if (client->mail_session_iter == NULL)
532 return FALSE;
533 mail_session_ref(client->mail_session_iter);
534 cmd->export_iter = client_export_iter_session;
535 } else {
536 client->mail_user_iter = user;
537 mail_user_ref(user);
538 cmd->export_iter = client_export_iter_user;
539 }
540 return TRUE;
541 }
542 if (cmd->filter.ip_bits == IPADDR_BITS(&cmd->filter.ip) &&
543 (cmd->level == MAIL_EXPORT_LEVEL_IP ||
544 cmd->level == MAIL_EXPORT_LEVEL_SESSION)) {
545 /* exact IP address */
546 struct mail_ip *ip = mail_ip_lookup(&cmd->filter.ip);
547 if (ip == NULL)
548 return FALSE;
549 if (cmd->level == MAIL_EXPORT_LEVEL_SESSION) {
550 client->mail_session_iter = ip->sessions;
551 if (client->mail_session_iter == NULL)
552 return FALSE;
553 mail_session_ref(client->mail_session_iter);
554 cmd->export_iter = client_export_iter_session;
555 } else {
556 client->mail_ip_iter = ip;
557 mail_ip_ref(ip);
558 cmd->export_iter = client_export_iter_ip;
559 }
560 return TRUE;
561 }
562 if (cmd->filter.domain != NULL &&
563 strchr(cmd->filter.domain, '*') == NULL &&
564 (cmd->level == MAIL_EXPORT_LEVEL_DOMAIN ||
565 cmd->level == MAIL_EXPORT_LEVEL_USER)) {
566 /* exact domain */
567 struct mail_domain *domain =
568 mail_domain_lookup(cmd->filter.domain);
569 if (domain == NULL)
570 return FALSE;
571 if (cmd->level == MAIL_EXPORT_LEVEL_USER) {
572 client->mail_user_iter = domain->users;
573 mail_user_ref(client->mail_user_iter);
574 cmd->export_iter = client_export_iter_user;
575 } else {
576 client->mail_domain_iter = domain;
577 mail_domain_ref(domain);
578 cmd->export_iter = client_export_iter_domain;
579 }
580 return TRUE;
581 }
582
583 switch (cmd->level) {
584 case MAIL_EXPORT_LEVEL_COMMAND:
585 client->mail_cmd_iter = stable_mail_commands_head;
586 if (client->mail_cmd_iter == NULL)
587 return FALSE;
588 mail_command_ref(client->mail_cmd_iter);
589 cmd->export_iter = client_export_iter_command;
590 break;
591 case MAIL_EXPORT_LEVEL_SESSION:
592 client->mail_session_iter = stable_mail_sessions;
593 if (client->mail_session_iter == NULL)
594 return FALSE;
595 mail_session_ref(client->mail_session_iter);
596 cmd->export_iter = client_export_iter_session;
597 break;
598 case MAIL_EXPORT_LEVEL_USER:
599 client->mail_user_iter = stable_mail_users;
600 if (client->mail_user_iter == NULL)
601 return FALSE;
602 mail_user_ref(client->mail_user_iter);
603 cmd->export_iter = client_export_iter_user;
604 break;
605 case MAIL_EXPORT_LEVEL_DOMAIN:
606 client->mail_domain_iter = stable_mail_domains;
607 if (client->mail_domain_iter == NULL)
608 return FALSE;
609 mail_domain_ref(client->mail_domain_iter);
610 cmd->export_iter = client_export_iter_domain;
611 break;
612 case MAIL_EXPORT_LEVEL_IP:
613 client->mail_ip_iter = stable_mail_ips;
614 if (client->mail_ip_iter == NULL)
615 return FALSE;
616 mail_ip_ref(client->mail_ip_iter);
617 cmd->export_iter = client_export_iter_ip;
618 break;
619 case MAIL_EXPORT_LEVEL_GLOBAL:
620 cmd->export_iter = client_export_iter_global;
621 break;
622 }
623 i_assert(cmd->export_iter != NULL);
624 return TRUE;
625 }
626
client_export(struct client * client,const char * const * args,const char ** error_r)627 int client_export(struct client *client, const char *const *args,
628 const char **error_r)
629 {
630 const char *level_str = args[0];
631 struct client_export_cmd *cmd;
632
633 p_clear(client->cmd_pool);
634 cmd = p_new(client->cmd_pool, struct client_export_cmd, 1);
635 cmd->str = str_new(client->cmd_pool, 256);
636
637 if (level_str == NULL) {
638 *error_r = "Missing level parameter";
639 return -1;
640 }
641 if (mail_export_level_parse(level_str, &cmd->level) < 0) {
642 *error_r = "Invalid level";
643 return -1;
644 }
645 if (mail_export_parse_filter(args + 1, client->cmd_pool,
646 &cmd->filter, error_r) < 0)
647 return -1;
648
649 client->cmd_export = cmd;
650 if (!client_export_iter_init(client)) {
651 /* nothing to export */
652 o_stream_nsend_str(client->output, "\n");
653 return 1;
654 }
655 client->cmd_more = client_export_more;
656 return client_export_more(client);
657 }
658