diff options
Diffstat (limited to 'src/config/config-connection.c')
-rw-r--r-- | src/config/config-connection.c | 267 |
1 files changed, 267 insertions, 0 deletions
diff --git a/src/config/config-connection.c b/src/config/config-connection.c new file mode 100644 index 0000000..bd3db86 --- /dev/null +++ b/src/config/config-connection.c @@ -0,0 +1,267 @@ +/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "llist.h" +#include "istream.h" +#include "ostream.h" +#include "strescape.h" +#include "settings-parser.h" +#include "master-service.h" +#include "master-service-settings.h" +#include "config-request.h" +#include "config-parser.h" +#include "config-connection.h" + +#include <unistd.h> + +#define MAX_INBUF_SIZE 1024 + +#define CONFIG_CLIENT_PROTOCOL_MAJOR_VERSION 2 +#define CONFIG_CLIENT_PROTOCOL_MINOR_VERSION 0 + +struct config_connection { + struct config_connection *prev, *next; + + int fd; + struct istream *input; + struct ostream *output; + struct io *io; + + bool version_received:1; + bool handshaked:1; +}; + +static struct config_connection *config_connections = NULL; + +static const char *const * +config_connection_next_line(struct config_connection *conn) +{ + const char *line; + + line = i_stream_next_line(conn->input); + if (line == NULL) + return NULL; + + return t_strsplit_tabescaped(line); +} + +static void +config_request_output(const char *key, const char *value, + enum config_key_type type ATTR_UNUSED, void *context) +{ + struct ostream *output = context; + const char *p; + + o_stream_nsend_str(output, key); + o_stream_nsend_str(output, "="); + while ((p = strchr(value, '\n')) != NULL) { + o_stream_nsend(output, value, p-value); + o_stream_nsend(output, SETTING_STREAM_LF_CHAR, 1); + value = p+1; + } + o_stream_nsend_str(output, value); + o_stream_nsend_str(output, "\n"); +} + +static int config_connection_request(struct config_connection *conn, + const char *const *args) +{ + struct config_export_context *ctx; + struct master_service_settings_output output; + struct config_filter filter; + const char *path, *error, *module, *const *wanted_modules; + ARRAY(const char *) modules; + ARRAY(const char *) exclude_settings; + bool is_master = FALSE; + + /* [<args>] */ + t_array_init(&modules, 4); + t_array_init(&exclude_settings, 4); + i_zero(&filter); + for (; *args != NULL; args++) { + if (str_begins(*args, "service=")) + filter.service = *args + 8; + else if (str_begins(*args, "module=")) { + module = *args + 7; + if (strcmp(module, "master") == 0) + is_master = TRUE; + array_push_back(&modules, &module); + } else if (str_begins(*args, "exclude=")) { + const char *value = *args + 8; + array_push_back(&exclude_settings, &value); + } else if (str_begins(*args, "lname=")) + filter.local_name = *args + 6; + else if (str_begins(*args, "lip=")) { + if (net_addr2ip(*args + 4, &filter.local_net) == 0) { + filter.local_bits = + IPADDR_IS_V4(&filter.local_net) ? + 32 : 128; + } + } else if (str_begins(*args, "rip=")) { + if (net_addr2ip(*args + 4, &filter.remote_net) == 0) { + filter.remote_bits = + IPADDR_IS_V4(&filter.remote_net) ? + 32 : 128; + } + } + } + array_append_zero(&modules); + wanted_modules = array_count(&modules) == 1 ? NULL : + array_front(&modules); + array_append_zero(&exclude_settings); + + if (is_master) { + /* master reads configuration only when reloading settings */ + path = master_service_get_config_path(master_service); + if (config_parse_file(path, TRUE, NULL, &error) <= 0) { + o_stream_nsend_str(conn->output, + t_strconcat("\nERROR ", error, "\n", NULL)); + config_connection_destroy(conn); + return -1; + } + } + + o_stream_cork(conn->output); + + ctx = config_export_init(wanted_modules, + array_count(&exclude_settings) == 1 ? NULL : + array_front(&exclude_settings), + CONFIG_DUMP_SCOPE_SET, 0, + config_request_output, conn->output); + config_export_by_filter(ctx, &filter); + config_export_get_output(ctx, &output); + + if (output.specific_services != NULL) { + const char *const *s; + + for (s = output.specific_services; *s != NULL; s++) { + o_stream_nsend_str(conn->output, + t_strdup_printf("service=%s\t", *s)); + } + } + if (output.service_uses_local) + o_stream_nsend_str(conn->output, "service-uses-local\t"); + if (output.service_uses_remote) + o_stream_nsend_str(conn->output, "service-uses-remote\t"); + if (output.used_local) + o_stream_nsend_str(conn->output, "used-local\t"); + if (output.used_remote) + o_stream_nsend_str(conn->output, "used-remote\t"); + o_stream_nsend_str(conn->output, "\n"); + + if (config_export_finish(&ctx) < 0) { + config_connection_destroy(conn); + return -1; + } + o_stream_nsend_str(conn->output, "\n"); + o_stream_uncork(conn->output); + return 0; +} + +static int config_filters_request(struct config_connection *conn) +{ + struct config_filter_parser *const *filters = config_filter_get_all(config_filter); + o_stream_cork(conn->output); + while(*filters != NULL) { + const struct config_filter *filter = &(*filters)->filter; + o_stream_nsend_str(conn->output, "FILTER"); + if (filter->service != NULL) + o_stream_nsend_str(conn->output, t_strdup_printf("\tservice=%s", + str_tabescape(filter->service))); + if (filter->local_name != NULL) + o_stream_nsend_str(conn->output, t_strdup_printf("\tlocal-name=%s", + str_tabescape(filter->local_name))); + if (filter->local_bits > 0) + o_stream_nsend_str(conn->output, t_strdup_printf("\tlocal-net=%s/%u", + net_ip2addr(&filter->local_net), + filter->local_bits)); + if (filter->remote_bits > 0) + o_stream_nsend_str(conn->output, t_strdup_printf("\tremote-net=%s/%u", + net_ip2addr(&filter->remote_net), + filter->remote_bits)); + o_stream_nsend_str(conn->output, "\n"); + filters++; + } + o_stream_nsend_str(conn->output, "\n"); + o_stream_uncork(conn->output); + return 0; +} + + +static void config_connection_input(struct config_connection *conn) +{ + const char *const *args, *line; + + switch (i_stream_read(conn->input)) { + case -2: + i_error("BUG: Config client connection sent too much data"); + config_connection_destroy(conn); + return; + case -1: + config_connection_destroy(conn); + return; + } + + if (!conn->version_received) { + line = i_stream_next_line(conn->input); + if (line == NULL) + return; + + if (!version_string_verify(line, "config", + CONFIG_CLIENT_PROTOCOL_MAJOR_VERSION)) { + i_error("Config client not compatible with this server " + "(mixed old and new binaries?)"); + config_connection_destroy(conn); + return; + } + conn->version_received = TRUE; + } + + while ((args = config_connection_next_line(conn)) != NULL) { + if (args[0] == NULL) + continue; + if (strcmp(args[0], "REQ") == 0) { + if (config_connection_request(conn, args + 1) < 0) + break; + } + if (strcmp(args[0], "FILTERS") == 0) { + if (config_filters_request(conn) < 0) + break; + } + } +} + +struct config_connection *config_connection_create(int fd) +{ + struct config_connection *conn; + + conn = i_new(struct config_connection, 1); + conn->fd = fd; + conn->input = i_stream_create_fd(fd, MAX_INBUF_SIZE); + 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, config_connection_input, conn); + DLLIST_PREPEND(&config_connections, conn); + return conn; +} + +void config_connection_destroy(struct config_connection *conn) +{ + DLLIST_REMOVE(&config_connections, conn); + + io_remove(&conn->io); + i_stream_destroy(&conn->input); + o_stream_destroy(&conn->output); + if (close(conn->fd) < 0) + i_error("close(config conn) failed: %m"); + i_free(conn); + + master_service_client_connection_destroyed(master_service); +} + +void config_connections_destroy_all(void) +{ + while (config_connections != NULL) + config_connection_destroy(config_connections); +} |