/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "buffer.h" #include "base64.h" #include "ioloop.h" #include "llist.h" #include "global-memory.h" #include "stats-settings.h" #include "mail-stats.h" #include "mail-session.h" #include "mail-command.h" #define MAIL_COMMAND_TIMEOUT_SECS (60*15) /* commands are sorted by their last_update timestamp, oldest first */ struct mail_command *stable_mail_commands_head; struct mail_command *stable_mail_commands_tail; static size_t mail_command_memsize(const struct mail_command *cmd) { return sizeof(*cmd) + strlen(cmd->name) + 1 + strlen(cmd->args) + 1; } static struct mail_command * mail_command_find(struct mail_session *session, unsigned int id) { struct mail_command *cmd; i_assert(id != 0); if (id > session->highest_cmd_id) { /* fast path for new commands */ return NULL; } for (cmd = session->commands; cmd != NULL; cmd = cmd->session_next) { if (cmd->id == id) return cmd; } /* expired */ return NULL; } static struct mail_command * mail_command_add(struct mail_session *session, const char *name, const char *args) { struct mail_command *cmd; cmd = i_malloc(MALLOC_ADD(sizeof(struct mail_command), stats_alloc_size())); cmd->stats = (void *)(cmd + 1); cmd->refcount = 1; /* unrefed at "done" */ cmd->session = session; cmd->name = i_strdup(name); cmd->args = i_strdup(args); cmd->last_update = ioloop_timeval; DLLIST2_APPEND_FULL(&stable_mail_commands_head, &stable_mail_commands_tail, cmd, stable_prev, stable_next); DLLIST_PREPEND_FULL(&session->commands, cmd, session_prev, session_next); mail_session_ref(cmd->session); global_memory_alloc(mail_command_memsize(cmd)); return cmd; } static void mail_command_free(struct mail_command *cmd) { i_assert(cmd->refcount == 0); global_memory_free(mail_command_memsize(cmd)); DLLIST2_REMOVE_FULL(&stable_mail_commands_head, &stable_mail_commands_tail, cmd, stable_prev, stable_next); DLLIST_REMOVE_FULL(&cmd->session->commands, cmd, session_prev, session_next); mail_session_unref(&cmd->session); i_free(cmd->name); i_free(cmd->args); i_free(cmd); } void mail_command_ref(struct mail_command *cmd) { cmd->refcount++; } void mail_command_unref(struct mail_command **_cmd) { struct mail_command *cmd = *_cmd; i_assert(cmd->refcount > 0); cmd->refcount--; *_cmd = NULL; } int mail_command_update_parse(const char *const *args, const char **error_r) { struct mail_session *session; struct mail_command *cmd; struct stats *new_stats, *diff_stats; buffer_t *buf; const char *error; unsigned int i, cmd_id; bool done = FALSE, continued = FALSE; /* [d] c[d] */ if (str_array_length(args) < 3) { *error_r = "UPDATE-CMD: Too few parameters"; return -1; } if (mail_session_get(args[0], &session, error_r) < 0) return -1; if (str_to_uint(args[1], &cmd_id) < 0 || cmd_id == 0) { *error_r = "UPDATE-CMD: Invalid command id"; return -1; } for (i = 0; args[2][i] != '\0'; i++) { switch (args[2][i]) { case 'd': done = TRUE; break; case 'c': continued = TRUE; break; default: *error_r = "UPDATE-CMD: Invalid flags parameter"; return -1; } } cmd = mail_command_find(session, cmd_id); if (!continued) { /* new command */ if (cmd != NULL) { *error_r = "UPDATE-CMD: Duplicate new command id"; return -1; } if (str_array_length(args) < 5) { *error_r = "UPDATE-CMD: Too few parameters"; return -1; } cmd = mail_command_add(session, args[3], args[4]); cmd->id = cmd_id; session->highest_cmd_id = I_MAX(session->highest_cmd_id, cmd_id); session->num_cmds++; session->user->num_cmds++; session->user->domain->num_cmds++; if (session->ip != NULL) session->ip->num_cmds++; mail_global_stats.num_cmds++; args += 5; } else { if (cmd == NULL) { /* already expired command, ignore */ i_warning("UPDATE-CMD: Already expired"); return 0; } args += 3; cmd->last_update = ioloop_timeval; } buf = t_buffer_create(256); if (args[0] == NULL || base64_decode(args[0], strlen(args[0]), NULL, buf) < 0) { *error_r = t_strdup_printf("UPDATE-CMD: Invalid base64 input"); return -1; } new_stats = stats_alloc(pool_datastack_create()); diff_stats = stats_alloc(pool_datastack_create()); if (!stats_import(buf->data, buf->used, cmd->stats, new_stats, &error)) { *error_r = t_strdup_printf("UPDATE-CMD: %s", error); return -1; } if (!stats_diff(cmd->stats, new_stats, diff_stats, &error)) { *error_r = t_strdup_printf("UPDATE-CMD: stats shrank: %s", error); return -1; } stats_add(cmd->stats, diff_stats); if (done) { cmd->id = 0; mail_command_unref(&cmd); } mail_session_refresh(session, NULL); return 0; } static bool mail_command_is_timed_out(struct mail_command *cmd) { /* some commands like IDLE can run forever */ return ioloop_time - cmd->last_update.tv_sec > MAIL_COMMAND_TIMEOUT_SECS; } void mail_commands_free_memory(void) { unsigned int diff; while (stable_mail_commands_head != NULL) { struct mail_command *cmd = stable_mail_commands_head; if (cmd->refcount == 0) i_assert(cmd->id == 0); else if (cmd->refcount == 1 && (cmd->session->disconnected || mail_command_is_timed_out(cmd))) { /* session was probably lost */ mail_command_unref(&cmd); } else { break; } mail_command_free(stable_mail_commands_head); if (global_used_memory < stats_settings->memory_limit || stable_mail_commands_head == NULL) break; diff = ioloop_time - stable_mail_commands_head->last_update.tv_sec; if (diff < stats_settings->command_min_time) break; } } void mail_commands_init(void) { } void mail_commands_deinit(void) { while (stable_mail_commands_head != NULL) { struct mail_command *cmd = stable_mail_commands_head; if (cmd->id != 0) mail_command_unref(&cmd); mail_command_free(stable_mail_commands_head); } }