/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "llist.h" #include "ioloop.h" #include "istream.h" #include "ostream.h" #include "strescape.h" #include "master-service.h" #include "mail-command.h" #include "mail-session.h" #include "mail-user.h" #include "mail-domain.h" #include "mail-ip.h" #include "client-export.h" #include "client-reset.h" #include "client.h" #include #define CLIENT_MAX_SIMULTANEOUS_ITER_COUNT 1000 #define MAX_INBUF_SIZE 1024 #define OUTBUF_THROTTLE_SIZE (1024*64) static struct client *clients; bool client_is_busy(struct client *client) { client->iter_count++; if (client->iter_count % CLIENT_MAX_SIMULTANEOUS_ITER_COUNT == 0) return TRUE; if (o_stream_get_buffer_used_size(client->output) < OUTBUF_THROTTLE_SIZE) return FALSE; if (o_stream_flush(client->output) < 0) return TRUE; return o_stream_get_buffer_used_size(client->output) >= OUTBUF_THROTTLE_SIZE; } static int client_handle_request(struct client *client, const char *const *args, const char **error_r) { const char *cmd = args[0]; if (cmd == NULL) { *error_r = "Missing command"; return -1; } args++; if (strcmp(cmd, "EXPORT") == 0) return client_export(client, args, error_r); if (strcmp(cmd, "RESET") == 0) return client_stats_reset(client, args, error_r); *error_r = "Unknown command"; return -1; } static const char *const* client_read_next_line(struct client *client) { const char *line; line = i_stream_next_line(client->input); if (line == NULL) return NULL; return t_strsplit_tabescaped(line); } static void client_input(struct client *client) { const char *const *args, *error; int ret; timeout_remove(&client->to_pending); switch (i_stream_read(client->input)) { case -2: i_error("BUG: Stats client sent too much data"); client_destroy(&client); return; case -1: client_destroy(&client); return; } o_stream_cork(client->output); while ((args = client_read_next_line(client)) != NULL) { ret = client_handle_request(client, args, &error); if (ret < 0) { i_error("Stats client input error: %s", error); client_destroy(&client); return; } if (ret == 0) { o_stream_set_flush_pending(client->output, TRUE); io_remove(&client->io); break; } client->cmd_more = NULL; } o_stream_uncork(client->output); } static int client_output(struct client *client) { int ret = 1; if (o_stream_flush(client->output) < 0) { client_destroy(&client); return 1; } if (client->cmd_more != NULL) ret = client->cmd_more(client); if (ret > 0) { client->cmd_more = NULL; if (client->io == NULL) client_enable_io(client); } return ret; } void client_enable_io(struct client *client) { i_assert(client->io == NULL); client->io = io_add(client->fd, IO_READ, client_input, client); if (client->to_pending == NULL) client->to_pending = timeout_add(0, client_input, client); } struct client *client_create(int fd) { struct client *client; client = i_new(struct client, 1); client->fd = fd; client->io = io_add(fd, IO_READ, client_input, client); client->input = i_stream_create_fd(fd, MAX_INBUF_SIZE); client->output = o_stream_create_fd(fd, SIZE_MAX); o_stream_set_no_error_handling(client->output, TRUE); o_stream_set_flush_callback(client->output, client_output, client); client->cmd_pool = pool_alloconly_create("cmd pool", 1024); DLLIST_PREPEND(&clients, client); return client; } static void client_unref_iters(struct client *client) { if (client->mail_cmd_iter != NULL) mail_command_unref(&client->mail_cmd_iter); if (client->mail_session_iter != NULL) mail_session_unref(&client->mail_session_iter); if (client->mail_user_iter != NULL) mail_user_unref(&client->mail_user_iter); if (client->mail_domain_iter != NULL) mail_domain_unref(&client->mail_domain_iter); if (client->mail_ip_iter != NULL) mail_ip_unref(&client->mail_ip_iter); } void client_destroy(struct client **_client) { struct client *client = *_client; *_client = NULL; DLLIST_REMOVE(&clients, client); io_remove(&client->io); i_stream_destroy(&client->input); o_stream_destroy(&client->output); if (close(client->fd) < 0) i_error("close(client) failed: %m"); client_unref_iters(client); pool_unref(&client->cmd_pool); i_free(client); master_service_client_connection_destroyed(master_service); } void clients_destroy_all(void) { while (clients != NULL) { struct client *client = clients; client_destroy(&client); } }