diff options
Diffstat (limited to 'src/anvil/anvil-connection.c')
-rw-r--r-- | src/anvil/anvil-connection.c | 226 |
1 files changed, 226 insertions, 0 deletions
diff --git a/src/anvil/anvil-connection.c b/src/anvil/anvil-connection.c new file mode 100644 index 0000000..20e859b --- /dev/null +++ b/src/anvil/anvil-connection.c @@ -0,0 +1,226 @@ +/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */ + +#include "common.h" +#include "llist.h" +#include "istream.h" +#include "ostream.h" +#include "strescape.h" +#include "master-service.h" +#include "master-interface.h" +#include "connect-limit.h" +#include "penalty.h" +#include "anvil-connection.h" + +#include <unistd.h> + +#define MAX_INBUF_SIZE 1024 + +#define ANVIL_CLIENT_PROTOCOL_MAJOR_VERSION 1 +#define ANVIL_CLIENT_PROTOCOL_MINOR_VERSION 0 + +struct anvil_connection { + struct anvil_connection *prev, *next; + + int fd; + struct istream *input; + struct ostream *output; + struct io *io; + + bool version_received:1; + bool handshaked:1; + bool master:1; + bool fifo:1; +}; + +static struct anvil_connection *anvil_connections = NULL; + +static const char *const * +anvil_connection_next_line(struct anvil_connection *conn) +{ + const char *line; + + line = i_stream_next_line(conn->input); + return line == NULL ? NULL : t_strsplit_tabescaped(line); +} + +static int +anvil_connection_request(struct anvil_connection *conn, + const char *const *args, const char **error_r) +{ + const char *cmd = args[0]; + unsigned int value, checksum; + time_t stamp; + pid_t pid; + + args++; + if (strcmp(cmd, "CONNECT") == 0) { + if (args[0] == NULL || args[1] == NULL) { + *error_r = "CONNECT: Not enough parameters"; + return -1; + } + if (str_to_pid(args[0], &pid) < 0) { + *error_r = "CONNECT: Invalid pid"; + return -1; + } + connect_limit_connect(connect_limit, pid, args[1]); + } else if (strcmp(cmd, "DISCONNECT") == 0) { + if (args[0] == NULL || args[1] == NULL) { + *error_r = "DISCONNECT: Not enough parameters"; + return -1; + } + if (str_to_pid(args[0], &pid) < 0) { + *error_r = "DISCONNECT: Invalid pid"; + return -1; + } + connect_limit_disconnect(connect_limit, pid, args[1]); + } else if (strcmp(cmd, "CONNECT-DUMP") == 0) { + connect_limit_dump(connect_limit, conn->output); + } else if (strcmp(cmd, "KILL") == 0) { + if (args[0] == NULL) { + *error_r = "KILL: Not enough parameters"; + return -1; + } + if (!conn->master) { + *error_r = "KILL sent by a non-master connection"; + return -1; + } + if (str_to_pid(args[0], &pid) < 0) { + *error_r = "KILL: Invalid pid"; + return -1; + } + connect_limit_disconnect_pid(connect_limit, pid); + } else if (strcmp(cmd, "LOOKUP") == 0) { + if (args[0] == NULL) { + *error_r = "LOOKUP: Not enough parameters"; + return -1; + } + if (conn->output == NULL) { + *error_r = "LOOKUP on a FIFO, can't send reply"; + return -1; + } + value = connect_limit_lookup(connect_limit, args[0]); + o_stream_nsend_str(conn->output, + t_strdup_printf("%u\n", value)); + } else if (strcmp(cmd, "PENALTY-GET") == 0) { + if (args[0] == NULL) { + *error_r = "PENALTY-GET: Not enough parameters"; + return -1; + } + value = penalty_get(penalty, args[0], &stamp); + o_stream_nsend_str(conn->output, + t_strdup_printf("%u %s\n", value, dec2str(stamp))); + } else if (strcmp(cmd, "PENALTY-INC") == 0) { + if (args[0] == NULL || args[1] == NULL || args[2] == NULL) { + *error_r = "PENALTY-INC: Not enough parameters"; + return -1; + } + if (str_to_uint(args[1], &checksum) < 0 || + str_to_uint(args[2], &value) < 0 || + value > PENALTY_MAX_VALUE || + (value == 0 && checksum != 0)) { + *error_r = "PENALTY-INC: Invalid parameters"; + return -1; + } + penalty_inc(penalty, args[0], checksum, value); + } else if (strcmp(cmd, "PENALTY-SET-EXPIRE-SECS") == 0) { + if (args[0] == NULL || str_to_uint(args[0], &value) < 0) { + *error_r = "PENALTY-SET-EXPIRE-SECS: " + "Invalid parameters"; + return -1; + } + penalty_set_expire_secs(penalty, value); + } else if (strcmp(cmd, "PENALTY-DUMP") == 0) { + penalty_dump(penalty, conn->output); + } else { + *error_r = t_strconcat("Unknown command: ", cmd, NULL); + return -1; + } + return 0; +} + +static void anvil_connection_input(struct anvil_connection *conn) +{ + const char *line, *const *args, *error; + + switch (i_stream_read(conn->input)) { + case -2: + i_error("BUG: Anvil client connection sent too much data"); + anvil_connection_destroy(conn); + return; + case -1: + anvil_connection_destroy(conn); + return; + } + + if (!conn->version_received) { + if ((line = i_stream_next_line(conn->input)) == NULL) + return; + + if (!version_string_verify(line, "anvil", + ANVIL_CLIENT_PROTOCOL_MAJOR_VERSION)) { + if (anvil_restarted && (conn->master || conn->fifo)) { + /* old pending data. ignore input until we get + the handshake. */ + anvil_connection_input(conn); + return; + } + i_error("Anvil client not compatible with this server " + "(mixed old and new binaries?) %s", line); + anvil_connection_destroy(conn); + return; + } + conn->version_received = TRUE; + } + + while ((args = anvil_connection_next_line(conn)) != NULL) { + if (args[0] != NULL) { + if (anvil_connection_request(conn, args, &error) < 0) { + i_error("Anvil client input error: %s", error); + anvil_connection_destroy(conn); + break; + } + } + } +} + +struct anvil_connection * +anvil_connection_create(int fd, bool master, bool fifo) +{ + struct anvil_connection *conn; + + conn = i_new(struct anvil_connection, 1); + conn->fd = fd; + conn->input = i_stream_create_fd(fd, MAX_INBUF_SIZE); + if (!fifo) { + conn->output = o_stream_create_fd(fd, SIZE_MAX); + o_stream_set_no_error_handling(conn->output, TRUE); + } + conn->io = io_add(fd, IO_READ, anvil_connection_input, conn); + conn->master = master; + conn->fifo = fifo; + DLLIST_PREPEND(&anvil_connections, conn); + return conn; +} + +void anvil_connection_destroy(struct anvil_connection *conn) +{ + bool fifo = conn->fifo; + + DLLIST_REMOVE(&anvil_connections, conn); + + io_remove(&conn->io); + i_stream_destroy(&conn->input); + o_stream_destroy(&conn->output); + if (close(conn->fd) < 0) + i_error("close(anvil conn) failed: %m"); + i_free(conn); + + if (!fifo) + master_service_client_connection_destroyed(master_service); +} + +void anvil_connections_destroy_all(void) +{ + while (anvil_connections != NULL) + anvil_connection_destroy(anvil_connections); +} |