diff options
Diffstat (limited to '')
-rw-r--r-- | src/old-stats/mail-session.c | 343 |
1 files changed, 343 insertions, 0 deletions
diff --git a/src/old-stats/mail-session.c b/src/old-stats/mail-session.c new file mode 100644 index 0000000..db202b3 --- /dev/null +++ b/src/old-stats/mail-session.c @@ -0,0 +1,343 @@ +/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "buffer.h" +#include "base64.h" +#include "ioloop.h" +#include "hash.h" +#include "llist.h" +#include "str-table.h" +#include "global-memory.h" +#include "stats-settings.h" +#include "mail-stats.h" +#include "mail-user.h" +#include "mail-ip.h" +#include "mail-session.h" +#include "mail-domain.h" + +/* If session doesn't receive any updates for this long, assume that the + process associated with it has crashed, and forcibly disconnect the + session. Must be larger than SESSION_STATS_FORCE_REFRESH_SECS in + stats plugin */ +#define MAIL_SESSION_IDLE_TIMEOUT_MSECS (1000*60*15) +/* If stats process crashes/restarts, existing processes keep sending status + updates to it, but this process doesn't know their session IDs. If these + missing IDs are found within this many seconds of starting the stats process, + don't log a warning about them. (On a larger installation this avoids + flooding the error log with hundreds of warnings.) */ +#define SESSION_ID_WARN_HIDE_SECS (60*5) + +static HASH_TABLE(char *, struct mail_session *) mail_sessions_hash; +/* sessions are sorted by their last_update timestamp, oldest first */ +static struct mail_session *mail_sessions_head, *mail_sessions_tail; +static time_t session_id_warn_hide_until; +static bool session_id_hide_warned = FALSE; +static struct str_table *services; + +struct mail_session *stable_mail_sessions; + +static size_t mail_session_memsize(const struct mail_session *session) +{ + return sizeof(*session) + strlen(session->id) + 1; +} + +static void mail_session_disconnect(struct mail_session *session) +{ + i_assert(!session->disconnected); + + mail_user_disconnected(session->user); + if (session->ip != NULL) + mail_ip_disconnected(session->ip); + + hash_table_remove(mail_sessions_hash, session->id); + session->disconnected = TRUE; + timeout_remove(&session->to_idle); + mail_session_unref(&session); +} + +static void mail_session_idle_timeout(struct mail_session *session) +{ + /* user="" service="" pid=0 is used for incoming sessions that were + received after we detected a stats process crash/restart. there's + no point in logging anything about them, since they contain no + useful information. */ + if (session->user->name[0] == '\0' && session->service[0] != '\0' && + session->pid == 0) { + i_warning("Session %s (user %s, service %s) " + "appears to have crashed, disconnecting it", + session->id, session->user->name, session->service); + } + mail_session_disconnect(session); +} + +int mail_session_connect_parse(const char *const *args, const char **error_r) +{ + struct mail_session *session; + const char *session_id; + pid_t pid; + struct ip_addr ip; + unsigned int i; + + /* <session id> <username> <service> <pid> [key=value ..] */ + if (str_array_length(args) < 4) { + *error_r = "CONNECT: Too few parameters"; + return -1; + } + session_id = args[0]; + if (str_to_pid(args[3], &pid) < 0) { + *error_r = t_strdup_printf("CONNECT: Invalid pid %s for session ID %s", + args[3], session_id); + return -1; + } + + session = hash_table_lookup(mail_sessions_hash, session_id); + if (session != NULL) { + *error_r = t_strdup_printf( + "CONNECT: Duplicate session ID %s for user %s service %s (old PID %ld, new PID %ld)", + session_id, args[1], args[2], (long)session->pid, (long)pid); + return -1; + } + session = i_malloc(MALLOC_ADD(sizeof(struct mail_session), stats_alloc_size())); + session->stats = (void *)(session + 1); + session->refcount = 1; /* unrefed at disconnect */ + session->id = i_strdup(session_id); + session->service = str_table_ref(services, args[2]); + session->pid = pid; + session->last_update = ioloop_timeval; + session->to_idle = timeout_add(MAIL_SESSION_IDLE_TIMEOUT_MSECS, + mail_session_idle_timeout, session); + + session->user = mail_user_login(args[1]); + session->user->num_logins++; + mail_domain_login(session->user->domain); + + for (i = 3; args[i] != NULL; i++) { + if (str_begins(args[i], "rip=") && + net_addr2ip(args[i] + 4, &ip) == 0) + session->ip = mail_ip_login(&ip); + } + + hash_table_insert(mail_sessions_hash, session->id, session); + DLLIST_PREPEND_FULL(&stable_mail_sessions, session, + stable_prev, stable_next); + DLLIST2_APPEND_FULL(&mail_sessions_head, &mail_sessions_tail, session, + sorted_prev, sorted_next); + DLLIST_PREPEND_FULL(&session->user->sessions, session, + user_prev, user_next); + mail_user_ref(session->user); + if (session->ip != NULL) { + DLLIST_PREPEND_FULL(&session->ip->sessions, session, + ip_prev, ip_next); + mail_ip_ref(session->ip); + } + global_memory_alloc(mail_session_memsize(session)); + + mail_global_login(); + return 0; +} + +void mail_session_ref(struct mail_session *session) +{ + session->refcount++; +} + +void mail_session_unref(struct mail_session **_session) +{ + struct mail_session *session = *_session; + + i_assert(session->refcount > 0); + session->refcount--; + + *_session = NULL; +} + +static void mail_session_free(struct mail_session *session) +{ + i_assert(session->refcount == 0); + + global_memory_free(mail_session_memsize(session)); + + timeout_remove(&session->to_idle); + if (!session->disconnected) + hash_table_remove(mail_sessions_hash, session->id); + DLLIST_REMOVE_FULL(&stable_mail_sessions, session, + stable_prev, stable_next); + DLLIST2_REMOVE_FULL(&mail_sessions_head, &mail_sessions_tail, session, + sorted_prev, sorted_next); + DLLIST_REMOVE_FULL(&session->user->sessions, session, + user_prev, user_next); + mail_user_unref(&session->user); + if (session->ip != NULL) { + DLLIST_REMOVE_FULL(&session->ip->sessions, session, + ip_prev, ip_next); + mail_ip_unref(&session->ip); + } + + str_table_unref(services, &session->service); + i_free(session->id); + i_free(session); +} + +static void mail_session_id_lost(const char *session_id) +{ + if (ioloop_time < session_id_warn_hide_until) { + if (session_id_hide_warned) + return; + session_id_hide_warned = TRUE; + i_warning("stats process appears to have crashed/restarted, " + "hiding missing session ID warnings for %d seconds", + (int)(session_id_warn_hide_until - ioloop_time)); + return; + } + i_warning("Couldn't find session ID: %s", session_id); +} + +int mail_session_lookup(const char *id, struct mail_session **session_r, + const char **error_r) +{ + if (id == NULL) { + *error_r = "Too few parameters"; + return -1; + } + *session_r = hash_table_lookup(mail_sessions_hash, id); + if (*session_r == NULL) { + mail_session_id_lost(id); + return 0; + } + return 1; +} + +int mail_session_get(const char *id, struct mail_session **session_r, + const char **error_r) +{ + const char *new_args[5]; + int ret; + + if ((ret = mail_session_lookup(id, session_r, error_r)) != 0) + return ret; + + /* Create a new dummy session to avoid repeated warnings */ + new_args[0] = id; + new_args[1] = ""; /* username */ + new_args[2] = ""; /* service */ + new_args[3] = "0"; /* pid */ + new_args[4] = NULL; + if (mail_session_connect_parse(new_args, error_r) < 0) + i_unreached(); + if (mail_session_lookup(id, session_r, error_r) != 1) + i_unreached(); + return 0; +} + +int mail_session_disconnect_parse(const char *const *args, const char **error_r) +{ + struct mail_session *session; + int ret; + + /* <session id> */ + if ((ret = mail_session_lookup(args[0], &session, error_r)) <= 0) + return ret; + + if (!session->disconnected) + mail_session_disconnect(session); + return 0; +} + +void mail_session_refresh(struct mail_session *session, + const struct stats *diff_stats) +{ + timeout_reset(session->to_idle); + + if (diff_stats != NULL) + stats_add(session->stats, diff_stats); + session->last_update = ioloop_timeval; + DLLIST2_REMOVE_FULL(&mail_sessions_head, &mail_sessions_tail, session, + sorted_prev, sorted_next); + DLLIST2_APPEND_FULL(&mail_sessions_head, &mail_sessions_tail, session, + sorted_prev, sorted_next); + + mail_user_refresh(session->user, diff_stats); + if (session->ip != NULL) + mail_ip_refresh(session->ip, diff_stats); +} + +int mail_session_update_parse(const char *const *args, const char **error_r) +{ + struct mail_session *session; + struct stats *new_stats, *diff_stats; + buffer_t *buf; + const char *error; + + /* <session id> <stats> */ + if (mail_session_get(args[0], &session, error_r) < 0) + return -1; + + buf = t_buffer_create(256); + if (args[1] == NULL || + base64_decode(args[1], strlen(args[1]), NULL, buf) < 0) { + *error_r = t_strdup_printf("UPDATE-SESSION %s %s %s: Invalid base64 input", + session->user->name, + session->service, session->id); + return -1; + } + + new_stats = stats_alloc(pool_datastack_create()); + diff_stats = stats_alloc(pool_datastack_create()); + + if (!stats_import(buf->data, buf->used, session->stats, new_stats, &error)) { + *error_r = t_strdup_printf("UPDATE-SESSION %s %s %s: %s", + session->user->name, + session->service, session->id, error); + return -1; + } + + if (!stats_diff(session->stats, new_stats, diff_stats, &error)) { + *error_r = t_strdup_printf("UPDATE-SESSION %s %s %s: stats shrank: %s", + session->user->name, + session->service, session->id, error); + return -1; + } + mail_session_refresh(session, diff_stats); + return 0; +} + +void mail_sessions_free_memory(void) +{ + unsigned int diff; + + while (mail_sessions_head != NULL && + mail_sessions_head->refcount == 0) { + i_assert(mail_sessions_head->disconnected); + mail_session_free(mail_sessions_head); + + if (global_used_memory < stats_settings->memory_limit || + mail_sessions_head == NULL) + break; + + diff = ioloop_time - mail_sessions_head->last_update.tv_sec; + if (diff < stats_settings->session_min_time) + break; + } +} + +void mail_sessions_init(void) +{ + session_id_warn_hide_until = + ioloop_time + SESSION_ID_WARN_HIDE_SECS; + hash_table_create(&mail_sessions_hash, default_pool, 0, + str_hash, strcmp); + services = str_table_init(); +} + +void mail_sessions_deinit(void) +{ + while (mail_sessions_head != NULL) { + struct mail_session *session = mail_sessions_head; + + if (!session->disconnected) + mail_session_unref(&session); + mail_session_free(mail_sessions_head); + } + hash_table_destroy(&mail_sessions_hash); + str_table_deinit(&services); +} |