diff options
Diffstat (limited to 'src/old-stats/client-export.c')
-rw-r--r-- | src/old-stats/client-export.c | 657 |
1 files changed, 657 insertions, 0 deletions
diff --git a/src/old-stats/client-export.c b/src/old-stats/client-export.c new file mode 100644 index 0000000..e020d13 --- /dev/null +++ b/src/old-stats/client-export.c @@ -0,0 +1,657 @@ +/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "net.h" +#include "ostream.h" +#include "str.h" +#include "strescape.h" +#include "wildcard-match.h" +#include "mail-stats.h" +#include "mail-command.h" +#include "mail-session.h" +#include "mail-user.h" +#include "mail-domain.h" +#include "mail-ip.h" +#include "client.h" +#include "client-export.h" + +enum mail_export_level { + MAIL_EXPORT_LEVEL_COMMAND, + MAIL_EXPORT_LEVEL_SESSION, + MAIL_EXPORT_LEVEL_USER, + MAIL_EXPORT_LEVEL_DOMAIN, + MAIL_EXPORT_LEVEL_IP, + MAIL_EXPORT_LEVEL_GLOBAL +}; +static const char *mail_export_level_names[] = { + "command", "session", "user", "domain", "ip", "global" +}; + +struct mail_export_filter { + const char *user, *domain, *session; + struct ip_addr ip; + unsigned int ip_bits; + time_t since; + bool connected; +}; + +struct client_export_cmd { + enum mail_export_level level; + struct mail_export_filter filter; + string_t *str; + int (*export_iter)(struct client *client); + bool header_sent; +}; + +static int +mail_export_level_parse(const char *str, enum mail_export_level *level_r) +{ + unsigned int i; + + for (i = 0; i < N_ELEMENTS(mail_export_level_names); i++) { + if (strcmp(mail_export_level_names[i], str) == 0) { + *level_r = (enum mail_export_level)i; + return 0; + } + } + return -1; +} + +static int +mail_export_parse_filter(const char *const *args, pool_t pool, + struct mail_export_filter *filter_r, + const char **error_r) +{ + unsigned long l; + + /* filters: + user=<wildcard> | domain=<wildcard> | session=<str> + ip=<ip>[/<mask>] + since=<timestamp> + connected + */ + i_zero(filter_r); + for (; *args != NULL; args++) { + if (str_begins(*args, "user=")) + filter_r->user = p_strdup(pool, *args + 5); + else if (str_begins(*args, "domain=")) + filter_r->domain = p_strdup(pool, *args + 7); + else if (str_begins(*args, "session=")) + filter_r->session = p_strdup(pool, *args + 8); + else if (str_begins(*args, "ip=")) { + if (net_parse_range(*args + 3, &filter_r->ip, + &filter_r->ip_bits) < 0) { + *error_r = "Invalid ip filter"; + return -1; + } + } else if (str_begins(*args, "since=")) { + if (str_to_ulong(*args + 6, &l) < 0) { + *error_r = "Invalid since filter"; + return -1; + } + filter_r->since = (time_t)l; + } else if (strcmp(*args, "connected") == 0) { + filter_r->connected = TRUE; + } + } + return 0; +} + +static void +client_export_stats_headers(struct client *client) +{ + unsigned int i, count = stats_field_count(); + string_t *str = t_str_new(128); + + i_assert(count > 0); + + str_append(str, stats_field_name(0)); + for (i = 1; i < count; i++) { + str_append_c(str, '\t'); + str_append(str, stats_field_name(i)); + } + str_append_c(str, '\n'); + o_stream_nsend(client->output, str_data(str), str_len(str)); +} + +static void +client_export_stats(string_t *str, const struct stats *stats) +{ + unsigned int i, count = stats_field_count(); + + i_assert(count > 0); + + stats_field_value(str, stats, 0); + for (i = 1; i < count; i++) { + str_append_c(str, '\t'); + stats_field_value(str, stats, i); + } +} + +static bool +mail_export_filter_match_session(const struct mail_export_filter *filter, + const struct mail_session *session) +{ + if (filter->connected && session->disconnected) + return FALSE; + if (filter->since > session->last_update.tv_sec) + return FALSE; + if (filter->session != NULL && + strcmp(session->id, filter->session) != 0) + return FALSE; + if (filter->user != NULL && + !wildcard_match(session->user->name, filter->user)) + return FALSE; + if (filter->domain != NULL && + !wildcard_match(session->user->domain->name, filter->domain)) + return FALSE; + if (filter->ip_bits > 0 && + !net_is_in_network(&session->ip->ip, &filter->ip, filter->ip_bits)) + return FALSE; + return TRUE; +} + +static bool +mail_export_filter_match_user_common(const struct mail_export_filter *filter, + const struct mail_user *user) +{ + struct mail_session *s; + bool connected = FALSE, ip_ok = FALSE; + + if (filter->user != NULL && + !wildcard_match(user->name, filter->user)) + return FALSE; + + if (filter->connected || filter->ip_bits > 0) { + for (s = user->sessions; s != NULL; s = s->user_next) { + if (!s->disconnected) + connected = TRUE; + if (filter->ip_bits > 0 && + net_is_in_network(&s->ip->ip, &filter->ip, + filter->ip_bits)) + ip_ok = TRUE; + + } + if (filter->connected && !connected) + return FALSE; + if (filter->ip_bits > 0 && !ip_ok) + return FALSE; + } + return TRUE; +} + +static bool +mail_export_filter_match_user(const struct mail_export_filter *filter, + const struct mail_user *user) +{ + if (filter->since > user->last_update.tv_sec) + return FALSE; + if (filter->domain != NULL && + !wildcard_match(user->domain->name, filter->domain)) + return FALSE; + return mail_export_filter_match_user_common(filter, user); +} + +static bool +mail_export_filter_match_domain(const struct mail_export_filter *filter, + const struct mail_domain *domain) +{ + struct mail_user *user; + + if (filter->since > domain->last_update.tv_sec) + return FALSE; + if (filter->domain != NULL && + !wildcard_match(domain->name, filter->domain)) + return FALSE; + + if (filter->user != NULL || filter->connected || filter->ip_bits > 0) { + for (user = domain->users; user != NULL; user = user->domain_next) { + if (mail_export_filter_match_user_common(filter, user)) + break; + } + if (user == NULL) + return FALSE; + } + return TRUE; +} + +static bool +mail_export_filter_match_ip(const struct mail_export_filter *filter, + const struct mail_ip *ip) +{ + struct mail_session *s; + bool connected = FALSE, user_ok = FALSE, domain_ok = FALSE; + + if (filter->connected || filter->ip_bits > 0) { + for (s = ip->sessions; s != NULL; s = s->ip_next) { + if (!s->disconnected) + connected = TRUE; + if (filter->user != NULL && + wildcard_match(s->user->name, filter->user)) + user_ok = TRUE; + if (filter->domain != NULL && + wildcard_match(s->user->domain->name, filter->domain)) + domain_ok = TRUE; + } + if (filter->connected && !connected) + return FALSE; + if (filter->user != NULL && !user_ok) + return FALSE; + if (filter->domain != NULL && !domain_ok) + return FALSE; + } + if (filter->since > ip->last_update.tv_sec) + return FALSE; + if (filter->ip_bits > 0 && + !net_is_in_network(&ip->ip, &filter->ip, filter->ip_bits)) + return FALSE; + return TRUE; +} + +static void client_export_timeval(string_t *str, const struct timeval *tv) +{ + str_printfa(str, "\t%ld.%06u", (long)tv->tv_sec, + (unsigned int)tv->tv_usec); +} + +static int client_export_iter_command(struct client *client) +{ + struct client_export_cmd *cmd = client->cmd_export; + struct mail_command *command = client->mail_cmd_iter; + + i_assert(cmd->level == MAIL_EXPORT_LEVEL_COMMAND); + mail_command_unref(&client->mail_cmd_iter); + + if (!cmd->header_sent) { + o_stream_nsend_str(client->output, + "cmd\targs\tsession\tuser\tlast_update\t"); + client_export_stats_headers(client); + cmd->header_sent = TRUE; + } + + for (; command != NULL; command = command->stable_next) { + if (client_is_busy(client)) + break; + if (!mail_export_filter_match_session(&cmd->filter, + command->session)) + continue; + + str_truncate(cmd->str, 0); + str_append_tabescaped(cmd->str, command->name); + str_append_c(cmd->str, '\t'); + str_append_tabescaped(cmd->str, command->args); + str_append_c(cmd->str, '\t'); + str_append(cmd->str, command->session->id); + str_append_c(cmd->str, '\t'); + str_append_tabescaped(cmd->str, + command->session->user->name); + client_export_timeval(cmd->str, &command->last_update); + str_append_c(cmd->str, '\t'); + client_export_stats(cmd->str, command->stats); + str_append_c(cmd->str, '\n'); + o_stream_nsend(client->output, str_data(cmd->str), + str_len(cmd->str)); + } + + if (command != NULL) { + client->mail_cmd_iter = command; + mail_command_ref(command); + return 0; + } + return 1; +} + +static int client_export_iter_session(struct client *client) +{ + struct client_export_cmd *cmd = client->cmd_export; + struct mail_session *session = client->mail_session_iter; + + i_assert(cmd->level == MAIL_EXPORT_LEVEL_SESSION); + mail_session_unref(&client->mail_session_iter); + + if (!cmd->header_sent) { + o_stream_nsend_str(client->output, + "session\tuser\tip\tservice\tpid\tconnected" + "\tlast_update\tnum_cmds\t"); + client_export_stats_headers(client); + cmd->header_sent = TRUE; + } + + for (; session != NULL; session = session->stable_next) { + if (client_is_busy(client)) + break; + if (!mail_export_filter_match_session(&cmd->filter, session)) + continue; + + str_truncate(cmd->str, 0); + str_append(cmd->str, session->id); + str_append_c(cmd->str, '\t'); + str_append_tabescaped(cmd->str, session->user->name); + str_append_c(cmd->str, '\t'); + if (session->ip != NULL) T_BEGIN { + str_append(cmd->str, net_ip2addr(&session->ip->ip)); + } T_END; + str_append_c(cmd->str, '\t'); + str_append_tabescaped(cmd->str, session->service); + str_printfa(cmd->str, "\t%ld", (long)session->pid); + str_printfa(cmd->str, "\t%d", !session->disconnected); + client_export_timeval(cmd->str, &session->last_update); + str_printfa(cmd->str, "\t%u\t", session->num_cmds); + client_export_stats(cmd->str, session->stats); + str_append_c(cmd->str, '\n'); + o_stream_nsend(client->output, str_data(cmd->str), + str_len(cmd->str)); + } + + if (session != NULL) { + client->mail_session_iter = session; + mail_session_ref(session); + return 0; + } + return 1; +} + +static int client_export_iter_user(struct client *client) +{ + struct client_export_cmd *cmd = client->cmd_export; + struct mail_user *user = client->mail_user_iter; + + i_assert(cmd->level == MAIL_EXPORT_LEVEL_USER); + mail_user_unref(&client->mail_user_iter); + + if (!cmd->header_sent) { + o_stream_nsend_str(client->output, + "user\treset_timestamp\tlast_update" + "\tnum_logins\tnum_cmds\t"); + client_export_stats_headers(client); + cmd->header_sent = TRUE; + } + + for (; user != NULL; user = user->stable_next) { + if (client_is_busy(client)) + break; + if (!mail_export_filter_match_user(&cmd->filter, user)) + continue; + + str_truncate(cmd->str, 0); + str_append_tabescaped(cmd->str, user->name); + str_printfa(cmd->str, "\t%ld", (long)user->reset_timestamp); + client_export_timeval(cmd->str, &user->last_update); + str_printfa(cmd->str, "\t%u\t%u\t", + user->num_logins, user->num_cmds); + client_export_stats(cmd->str, user->stats); + str_append_c(cmd->str, '\n'); + o_stream_nsend(client->output, str_data(cmd->str), + str_len(cmd->str)); + } + + if (user != NULL) { + client->mail_user_iter = user; + mail_user_ref(user); + return 0; + } + return 1; +} + +static int client_export_iter_domain(struct client *client) +{ + struct client_export_cmd *cmd = client->cmd_export; + struct mail_domain *domain = client->mail_domain_iter; + + i_assert(cmd->level == MAIL_EXPORT_LEVEL_DOMAIN); + mail_domain_unref(&client->mail_domain_iter); + + if (!cmd->header_sent) { + o_stream_nsend_str(client->output, + "domain\treset_timestamp\tlast_update" + "\tnum_logins\tnum_cmds\tnum_connected_sessions\t"); + client_export_stats_headers(client); + cmd->header_sent = TRUE; + } + + for (; domain != NULL; domain = domain->stable_next) { + if (client_is_busy(client)) + break; + if (!mail_export_filter_match_domain(&cmd->filter, domain)) + continue; + + str_truncate(cmd->str, 0); + str_append_tabescaped(cmd->str, domain->name); + str_printfa(cmd->str, "\t%ld", (long)domain->reset_timestamp); + client_export_timeval(cmd->str, &domain->last_update); + str_printfa(cmd->str, "\t%u\t%u\t%u\t", + domain->num_logins, domain->num_cmds, + domain->num_connected_sessions); + client_export_stats(cmd->str, domain->stats); + str_append_c(cmd->str, '\n'); + o_stream_nsend(client->output, str_data(cmd->str), + str_len(cmd->str)); + } + + if (domain != NULL) { + client->mail_domain_iter = domain; + mail_domain_ref(domain); + return 0; + } + return 1; +} + +static int client_export_iter_ip(struct client *client) +{ + struct client_export_cmd *cmd = client->cmd_export; + struct mail_ip *ip = client->mail_ip_iter; + + i_assert(cmd->level == MAIL_EXPORT_LEVEL_IP); + mail_ip_unref(&client->mail_ip_iter); + + if (!cmd->header_sent) { + o_stream_nsend_str(client->output, + "ip\treset_timestamp\tlast_update" + "\tnum_logins\tnum_cmds\tnum_connected_sessions\t"); + client_export_stats_headers(client); + cmd->header_sent = TRUE; + } + + for (; ip != NULL; ip = ip->stable_next) { + if (client_is_busy(client)) + break; + if (!mail_export_filter_match_ip(&cmd->filter, ip)) + continue; + + str_truncate(cmd->str, 0); + T_BEGIN { + str_append(cmd->str, net_ip2addr(&ip->ip)); + } T_END; + str_printfa(cmd->str, "\t%ld", (long)ip->reset_timestamp); + client_export_timeval(cmd->str, &ip->last_update); + str_printfa(cmd->str, "\t%u\t%u\t%u\t", + ip->num_logins, ip->num_cmds, ip->num_connected_sessions); + client_export_stats(cmd->str, ip->stats); + str_append_c(cmd->str, '\n'); + o_stream_nsend(client->output, str_data(cmd->str), + str_len(cmd->str)); + } + + if (ip != NULL) { + client->mail_ip_iter = ip; + mail_ip_ref(ip); + return 0; + } + return 1; +} + +static int client_export_iter_global(struct client *client) +{ + struct client_export_cmd *cmd = client->cmd_export; + struct mail_global *g = &mail_global_stats; + + i_assert(cmd->level == MAIL_EXPORT_LEVEL_GLOBAL); + + if (!cmd->header_sent) { + o_stream_nsend_str(client->output, + "reset_timestamp\tlast_update" + "\tnum_logins\tnum_cmds\tnum_connected_sessions\t"); + client_export_stats_headers(client); + cmd->header_sent = TRUE; + } + + str_truncate(cmd->str, 0); + str_printfa(cmd->str, "%ld", (long)g->reset_timestamp); + client_export_timeval(cmd->str, &g->last_update); + str_printfa(cmd->str, "\t%u\t%u\t%u\t", + g->num_logins, g->num_cmds, g->num_connected_sessions); + client_export_stats(cmd->str, g->stats); + str_append_c(cmd->str, '\n'); + o_stream_nsend(client->output, str_data(cmd->str), + str_len(cmd->str)); + return 1; +} + +static int client_export_more(struct client *client) +{ + if (client->cmd_export->export_iter(client) == 0) + return 0; + o_stream_nsend_str(client->output, "\n"); + return 1; +} + +static bool client_export_iter_init(struct client *client) +{ + struct client_export_cmd *cmd = client->cmd_export; + + if (cmd->filter.user != NULL && strchr(cmd->filter.user, '*') == NULL && + (cmd->level == MAIL_EXPORT_LEVEL_USER || + cmd->level == MAIL_EXPORT_LEVEL_SESSION)) { + /* exact user */ + struct mail_user *user = mail_user_lookup(cmd->filter.user); + if (user == NULL) + return FALSE; + if (cmd->level == MAIL_EXPORT_LEVEL_SESSION) { + client->mail_session_iter = user->sessions; + if (client->mail_session_iter == NULL) + return FALSE; + mail_session_ref(client->mail_session_iter); + cmd->export_iter = client_export_iter_session; + } else { + client->mail_user_iter = user; + mail_user_ref(user); + cmd->export_iter = client_export_iter_user; + } + return TRUE; + } + if (cmd->filter.ip_bits == IPADDR_BITS(&cmd->filter.ip) && + (cmd->level == MAIL_EXPORT_LEVEL_IP || + cmd->level == MAIL_EXPORT_LEVEL_SESSION)) { + /* exact IP address */ + struct mail_ip *ip = mail_ip_lookup(&cmd->filter.ip); + if (ip == NULL) + return FALSE; + if (cmd->level == MAIL_EXPORT_LEVEL_SESSION) { + client->mail_session_iter = ip->sessions; + if (client->mail_session_iter == NULL) + return FALSE; + mail_session_ref(client->mail_session_iter); + cmd->export_iter = client_export_iter_session; + } else { + client->mail_ip_iter = ip; + mail_ip_ref(ip); + cmd->export_iter = client_export_iter_ip; + } + return TRUE; + } + if (cmd->filter.domain != NULL && + strchr(cmd->filter.domain, '*') == NULL && + (cmd->level == MAIL_EXPORT_LEVEL_DOMAIN || + cmd->level == MAIL_EXPORT_LEVEL_USER)) { + /* exact domain */ + struct mail_domain *domain = + mail_domain_lookup(cmd->filter.domain); + if (domain == NULL) + return FALSE; + if (cmd->level == MAIL_EXPORT_LEVEL_USER) { + client->mail_user_iter = domain->users; + mail_user_ref(client->mail_user_iter); + cmd->export_iter = client_export_iter_user; + } else { + client->mail_domain_iter = domain; + mail_domain_ref(domain); + cmd->export_iter = client_export_iter_domain; + } + return TRUE; + } + + switch (cmd->level) { + case MAIL_EXPORT_LEVEL_COMMAND: + client->mail_cmd_iter = stable_mail_commands_head; + if (client->mail_cmd_iter == NULL) + return FALSE; + mail_command_ref(client->mail_cmd_iter); + cmd->export_iter = client_export_iter_command; + break; + case MAIL_EXPORT_LEVEL_SESSION: + client->mail_session_iter = stable_mail_sessions; + if (client->mail_session_iter == NULL) + return FALSE; + mail_session_ref(client->mail_session_iter); + cmd->export_iter = client_export_iter_session; + break; + case MAIL_EXPORT_LEVEL_USER: + client->mail_user_iter = stable_mail_users; + if (client->mail_user_iter == NULL) + return FALSE; + mail_user_ref(client->mail_user_iter); + cmd->export_iter = client_export_iter_user; + break; + case MAIL_EXPORT_LEVEL_DOMAIN: + client->mail_domain_iter = stable_mail_domains; + if (client->mail_domain_iter == NULL) + return FALSE; + mail_domain_ref(client->mail_domain_iter); + cmd->export_iter = client_export_iter_domain; + break; + case MAIL_EXPORT_LEVEL_IP: + client->mail_ip_iter = stable_mail_ips; + if (client->mail_ip_iter == NULL) + return FALSE; + mail_ip_ref(client->mail_ip_iter); + cmd->export_iter = client_export_iter_ip; + break; + case MAIL_EXPORT_LEVEL_GLOBAL: + cmd->export_iter = client_export_iter_global; + break; + } + i_assert(cmd->export_iter != NULL); + return TRUE; +} + +int client_export(struct client *client, const char *const *args, + const char **error_r) +{ + const char *level_str = args[0]; + struct client_export_cmd *cmd; + + p_clear(client->cmd_pool); + cmd = p_new(client->cmd_pool, struct client_export_cmd, 1); + cmd->str = str_new(client->cmd_pool, 256); + + if (level_str == NULL) { + *error_r = "Missing level parameter"; + return -1; + } + if (mail_export_level_parse(level_str, &cmd->level) < 0) { + *error_r = "Invalid level"; + return -1; + } + if (mail_export_parse_filter(args + 1, client->cmd_pool, + &cmd->filter, error_r) < 0) + return -1; + + client->cmd_export = cmd; + if (!client_export_iter_init(client)) { + /* nothing to export */ + o_stream_nsend_str(client->output, "\n"); + return 1; + } + client->cmd_more = client_export_more; + return client_export_more(client); +} |