summaryrefslogtreecommitdiffstats
path: root/src/director/director-test.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/director/director-test.c')
-rw-r--r--src/director/director-test.c605
1 files changed, 605 insertions, 0 deletions
diff --git a/src/director/director-test.c b/src/director/director-test.c
new file mode 100644
index 0000000..ab632b4
--- /dev/null
+++ b/src/director/director-test.c
@@ -0,0 +1,605 @@
+/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */
+
+/*
+ This program accepts incoming unauthenticated IMAP connections from
+ port 14300. If the same user is connecting to multiple different local IPs,
+ it logs an error (i.e. director is not working right then).
+
+ This program also accepts incoming director connections on port 9091 and
+ forwards them to local_ip:9090. To make this work properly, director
+ executable must be given -t 9091 parameter. The idea is that this test tool
+ hooks between all director connections and can then add delays or break the
+ connections.
+
+ Finally, this program connects to director-admin socket where it adds
+ and removes mail hosts.
+*/
+
+#include "lib.h"
+#include "ioloop.h"
+#include "istream.h"
+#include "ostream.h"
+#include "write-full.h"
+#include "hash.h"
+#include "llist.h"
+#include "strescape.h"
+#include "imap-parser.h"
+#include "master-service.h"
+#include "master-service-settings.h"
+#include "director-settings.h"
+
+#include <unistd.h>
+
+#define IMAP_PORT 14300
+#define DIRECTOR_IN_PORT 9091
+#define DIRECTOR_OUT_PORT 9090
+#define USER_TIMEOUT_MSECS (1000*10) /* FIXME: this should be based on director_user_expire */
+#define ADMIN_RANDOM_TIMEOUT_MSECS 500
+#define DIRECTOR_CONN_MAX_DELAY_MSECS 100
+#define DIRECTOR_DISCONNECT_TIMEOUT_SECS 10
+
+struct host {
+ int refcount;
+
+ struct ip_addr ip;
+ unsigned int vhost_count;
+};
+
+struct user {
+ char *username;
+ struct host *host;
+
+ time_t last_seen;
+ unsigned int connections;
+
+ struct timeout *to;
+};
+
+struct imap_client {
+ struct imap_client *prev, *next;
+
+ int fd;
+ struct io *io;
+ struct istream *input;
+ struct ostream *output;
+ struct imap_parser *parser;
+ struct user *user;
+
+ char *username;
+};
+
+struct director_connection {
+ struct director_connection *prev, *next;
+
+ int in_fd, out_fd;
+ struct io *in_io, *out_io;
+ struct istream *in_input, *out_input;
+ struct ostream *in_output, *out_output;
+ struct timeout *to_delay;
+};
+
+struct admin_connection {
+ char *path;
+ int fd;
+ struct io *io;
+ struct istream *input;
+ struct timeout *to_random;
+ bool pending_command;
+};
+
+static struct imap_client *imap_clients;
+static struct director_connection *director_connections;
+static HASH_TABLE(char *, struct user *) users;
+static HASH_TABLE(struct ip_addr *, struct host *) hosts;
+static ARRAY(struct host *) hosts_array;
+static struct admin_connection *admin;
+static struct timeout *to_disconnect;
+
+static void imap_client_destroy(struct imap_client **client);
+static void director_connection_destroy(struct director_connection **conn);
+static void director_connection_timeout(struct director_connection *conn);
+
+static void host_unref(struct host **_host)
+{
+ struct host *host = *_host;
+
+ *_host = NULL;
+
+ i_assert(host->refcount > 0);
+ if (--host->refcount > 0)
+ return;
+
+ i_free(host);
+}
+
+static void client_username_check(struct imap_client *client)
+{
+ struct user *user;
+ struct host *host;
+ struct ip_addr local_ip;
+
+ if (net_getsockname(client->fd, &local_ip, NULL) < 0)
+ i_fatal("net_getsockname() failed: %m");
+
+ host = hash_table_lookup(hosts, &local_ip);
+ if (host == NULL) {
+ i_error("User logging into unknown host %s",
+ net_ip2addr(&local_ip));
+ host = i_new(struct host, 1);
+ host->refcount = 1;
+ host->ip = local_ip;
+ host->vhost_count = 100;
+ hash_table_insert(hosts, &host->ip, host);
+ array_push_back(&hosts_array, &host);
+ }
+
+ user = hash_table_lookup(users, client->username);
+ if (user == NULL) {
+ user = i_new(struct user, 1);
+ user->username = i_strdup(client->username);
+ hash_table_insert(users, user->username, user);
+ } else if (user->host != host) {
+ i_error("user %s: old connection from %s, new from %s. "
+ "%u old connections, last was %u secs ago",
+ user->username, net_ip2addr(&user->host->ip),
+ net_ip2addr(&host->ip), user->connections,
+ (unsigned int)(ioloop_time - user->last_seen));
+ host_unref(&user->host);
+ }
+ client->user = user;
+ user->host = host;
+ user->connections++;
+ user->last_seen = ioloop_time;
+ user->host->refcount++;
+
+ timeout_remove(&user->to);
+}
+
+static void user_free(struct user *user)
+{
+ host_unref(&user->host);
+ timeout_remove(&user->to);
+ hash_table_remove(users, user->username);
+ i_free(user->username);
+ i_free(user);
+}
+
+static int imap_client_parse_input(struct imap_client *client)
+{
+ const char *tag, *cmd, *str;
+ const struct imap_arg *args;
+ int ret;
+
+ ret = imap_parser_read_args(client->parser, 0, 0, &args);
+ if (ret < 0) {
+ if (ret == -2)
+ return 0;
+ return -1;
+ }
+
+ if (!imap_arg_get_atom(args, &tag))
+ return -1;
+ args++;
+
+ if (!imap_arg_get_atom(args, &cmd))
+ return -1;
+ args++;
+
+ if (strcasecmp(cmd, "login") == 0) {
+ if (client->username != NULL)
+ return -1;
+
+ if (!imap_arg_get_astring(args, &str))
+ return -1;
+
+ o_stream_nsend_str(client->output,
+ t_strconcat(tag, " OK Logged in.\r\n", NULL));
+ client->username = i_strdup(str);
+ client_username_check(client);
+ } else if (strcasecmp(cmd, "logout") == 0) {
+ o_stream_nsend_str(client->output, t_strconcat(
+ "* BYE Out.\r\n",
+ tag, " OK Logged out.\r\n", NULL));
+ imap_client_destroy(&client);
+ return 0;
+ } else if (strcasecmp(cmd, "capability") == 0) {
+ o_stream_nsend_str(client->output,
+ t_strconcat("* CAPABILITY IMAP4rev1\r\n",
+ tag, " OK Done.\r\n", NULL));
+ } else {
+ o_stream_nsend_str(client->output,
+ t_strconcat(tag, " BAD Not supported.\r\n", NULL));
+ }
+
+ (void)i_stream_read_next_line(client->input); /* eat away LF */
+ imap_parser_reset(client->parser);
+ return 1;
+}
+
+static void imap_client_input(struct imap_client *client)
+{
+ int ret;
+
+ switch (i_stream_read(client->input)) {
+ case -2:
+ i_error("imap: Too much input");
+ imap_client_destroy(&client);
+ return;
+ case -1:
+ imap_client_destroy(&client);
+ return;
+ default:
+ break;
+ }
+
+ while ((ret = imap_client_parse_input(client)) > 0) ;
+ if (ret < 0) {
+ i_error("imap: Invalid input");
+ imap_client_destroy(&client);
+ }
+}
+
+static void imap_client_create(int fd)
+{
+ struct imap_client *client;
+
+ client = i_new(struct imap_client, 1);
+ client->fd = fd;
+ client->input = i_stream_create_fd(fd, 4096);
+ client->output = o_stream_create_fd(fd, SIZE_MAX);
+ o_stream_set_no_error_handling(client->output, TRUE);
+ client->io = io_add(fd, IO_READ, imap_client_input, client);
+ client->parser =
+ imap_parser_create(client->input, client->output, 4096);
+ o_stream_nsend_str(client->output,
+ "* OK [CAPABILITY IMAP4rev1] director-test ready.\r\n");
+ DLLIST_PREPEND(&imap_clients, client);
+}
+
+static void imap_client_destroy(struct imap_client **_client)
+{
+ struct imap_client *client = *_client;
+ struct user *user = client->user;
+
+ *_client = NULL;
+
+ if (user != NULL) {
+ i_assert(user->connections > 0);
+ if (--user->connections == 0) {
+ i_assert(user->to == NULL);
+ user->to = timeout_add(USER_TIMEOUT_MSECS, user_free,
+ user);
+ }
+ user->last_seen = ioloop_time;
+ }
+
+ DLLIST_REMOVE(&imap_clients, client);
+ imap_parser_unref(&client->parser);
+ io_remove(&client->io);
+ i_stream_destroy(&client->input);
+ o_stream_destroy(&client->output);
+ net_disconnect(client->fd);
+ i_free(client->username);
+ i_free(client);
+
+ master_service_client_connection_destroyed(master_service);
+}
+
+static void
+director_connection_input(struct director_connection *conn,
+ struct istream *input, struct ostream *output)
+{
+ const unsigned char *data;
+ size_t size;
+
+ if (i_stream_read_more(input, &data, &size) == -1) {
+ director_connection_destroy(&conn);
+ return;
+ }
+
+ o_stream_nsend(output, data, size);
+ i_stream_skip(input, size);
+
+ if (i_rand_limit(3) == 0 && conn->to_delay == NULL) {
+ conn->to_delay =
+ timeout_add(i_rand_limit(DIRECTOR_CONN_MAX_DELAY_MSECS),
+ director_connection_timeout, conn);
+ io_remove(&conn->in_io);
+ io_remove(&conn->out_io);
+ }
+}
+
+static void director_connection_in_input(struct director_connection *conn)
+{
+ director_connection_input(conn, conn->in_input, conn->out_output);
+}
+
+static void director_connection_out_input(struct director_connection *conn)
+{
+ director_connection_input(conn, conn->out_input, conn->in_output);
+}
+
+static void director_connection_timeout(struct director_connection *conn)
+{
+ timeout_remove(&conn->to_delay);
+ conn->in_io = io_add(conn->in_fd, IO_READ,
+ director_connection_in_input, conn);
+ conn->out_io = io_add(conn->out_fd, IO_READ,
+ director_connection_out_input, conn);
+}
+
+static void
+director_connection_create(int in_fd, const struct ip_addr *local_ip,
+ const struct ip_addr *remote_ip)
+{
+ struct director_connection *conn;
+ int out_fd;
+
+ out_fd = net_connect_ip(local_ip, DIRECTOR_OUT_PORT, remote_ip);
+ if (out_fd == -1) {
+ i_close_fd(&in_fd);
+ return;
+ }
+
+ conn = i_new(struct director_connection, 1);
+ conn->in_fd = in_fd;
+ conn->in_input = i_stream_create_fd(conn->in_fd, SIZE_MAX);
+ conn->in_output = o_stream_create_fd(conn->in_fd, SIZE_MAX);
+ o_stream_set_no_error_handling(conn->in_output, TRUE);
+ conn->in_io = io_add(conn->in_fd, IO_READ,
+ director_connection_in_input, conn);
+
+ conn->out_fd = out_fd;
+ conn->out_input = i_stream_create_fd(conn->out_fd, SIZE_MAX);
+ conn->out_output = o_stream_create_fd(conn->out_fd, SIZE_MAX);
+ o_stream_set_no_error_handling(conn->out_output, TRUE);
+ conn->out_io = io_add(conn->out_fd, IO_READ,
+ director_connection_out_input, conn);
+
+ DLLIST_PREPEND(&director_connections, conn);
+}
+
+static void director_connection_destroy(struct director_connection **_conn)
+{
+ struct director_connection *conn = *_conn;
+
+ DLLIST_REMOVE(&director_connections, conn);
+
+ timeout_remove(&conn->to_delay);
+
+ io_remove(&conn->in_io);
+ i_stream_unref(&conn->in_input);
+ o_stream_unref(&conn->in_output);
+ net_disconnect(conn->in_fd);
+
+ io_remove(&conn->out_io);
+ i_stream_unref(&conn->out_input);
+ o_stream_unref(&conn->out_output);
+ net_disconnect(conn->out_fd);
+
+ i_free(conn);
+}
+
+static void client_connected(struct master_service_connection *conn)
+{
+ struct ip_addr local_ip, remote_ip;
+ in_port_t local_port;
+
+ if (net_getsockname(conn->fd, &local_ip, &local_port) < 0)
+ i_fatal("net_getsockname() failed: %m");
+ if (net_getpeername(conn->fd, &remote_ip, NULL) < 0)
+ i_fatal("net_getsockname() failed: %m");
+
+ if (local_port == IMAP_PORT)
+ imap_client_create(conn->fd);
+ else if (local_port == DIRECTOR_IN_PORT)
+ director_connection_create(conn->fd, &local_ip, &remote_ip);
+ else {
+ i_error("Connection to unknown port %u", local_port);
+ return;
+ }
+ master_service_client_connection_accept(conn);
+}
+
+static void
+admin_send(struct admin_connection *conn, const char *data)
+{
+ if (write_full(i_stream_get_fd(conn->input), data, strlen(data)) < 0)
+ i_fatal("write(%s) failed: %m", conn->path);
+}
+
+static void admin_input(struct admin_connection *conn)
+{
+ const char *line;
+
+ while ((line = i_stream_read_next_line(conn->input)) != NULL) {
+ if (strcmp(line, "OK") != 0)
+ i_error("director-doveadm: Unexpected input: %s", line);
+ conn->pending_command = FALSE;
+ }
+ if (conn->input->stream_errno != 0 || conn->input->eof)
+ i_fatal("director-doveadm: Connection lost");
+}
+
+static void admin_random_action(struct admin_connection *conn)
+{
+ struct host *const *hosts;
+ unsigned int i, count;
+
+ if (conn->pending_command)
+ return;
+
+ hosts = array_get(&hosts_array, &count);
+ i = i_rand_limit(count);
+
+ hosts[i]->vhost_count = i_rand_limit(20) * 10;
+
+ admin_send(conn, t_strdup_printf("HOST-SET\t%s\t%u\n",
+ net_ip2addr(&hosts[i]->ip), hosts[i]->vhost_count));
+ conn->pending_command = TRUE;
+}
+
+static struct admin_connection *admin_connect(const char *path)
+{
+#define DIRECTOR_ADMIN_HANDSHAKE "VERSION\tdirector-doveadm\t1\t0\n"
+ struct admin_connection *conn;
+ const char *line;
+
+ conn = i_new(struct admin_connection, 1);
+ conn->path = i_strdup(path);
+ conn->fd = net_connect_unix(path);
+ if (conn->fd == -1)
+ i_fatal("net_connect_unix(%s) failed: %m", path);
+ conn->io = io_add(conn->fd, IO_READ, admin_input, conn);
+ conn->to_random = timeout_add_short(ADMIN_RANDOM_TIMEOUT_MSECS,
+ admin_random_action, conn);
+
+ net_set_nonblock(conn->fd, FALSE);
+ conn->input = i_stream_create_fd(conn->fd, SIZE_MAX);
+ admin_send(conn, DIRECTOR_ADMIN_HANDSHAKE);
+
+ line = i_stream_read_next_line(conn->input);
+ if (line == NULL)
+ i_fatal("%s disconnected", conn->path);
+ if (!version_string_verify(line, "director-doveadm", 1)) {
+ i_fatal("%s not a compatible director-doveadm socket",
+ conn->path);
+ }
+ net_set_nonblock(conn->fd, TRUE);
+ return conn;
+}
+
+static void admin_disconnect(struct admin_connection **_conn)
+{
+ struct admin_connection *conn = *_conn;
+
+ *_conn = NULL;
+ timeout_remove(&conn->to_random);
+ i_stream_destroy(&conn->input);
+ io_remove(&conn->io);
+ net_disconnect(conn->fd);
+ i_free(conn->path);
+ i_free(conn);
+}
+
+static void admin_read_hosts(struct admin_connection *conn)
+{
+ const char *line;
+
+ net_set_nonblock(admin->fd, FALSE);
+ while ((line = i_stream_read_next_line(conn->input)) != NULL) {
+ if (*line == '\0')
+ break;
+ /* ip vhost-count user-count */
+ T_BEGIN {
+ const char *const *args = t_strsplit_tabescaped(line);
+ struct host *host;
+
+ host = i_new(struct host, 1);
+ host->refcount = 1;
+ if (net_addr2ip(args[0], &host->ip) < 0 ||
+ str_to_uint(args[1], &host->vhost_count) < 0)
+ i_fatal("host list broken");
+ hash_table_insert(hosts, &host->ip, host);
+ array_push_back(&hosts_array, &host);
+ } T_END;
+ }
+ if (line == NULL)
+ i_fatal("Couldn't read hosts list");
+ net_set_nonblock(admin->fd, TRUE);
+}
+
+static void ATTR_NULL(1)
+director_connection_disconnect_timeout(void *context ATTR_UNUSED)
+{
+ struct director_connection *conn;
+ unsigned int i, count = 0;
+
+ for (conn = director_connections; conn != NULL; conn = conn->next)
+ count++;
+
+ if (count != 0) {
+ i = 0; count = i_rand_limit(count);
+ for (conn = director_connections; i < count; conn = conn->next) {
+ i_assert(conn != NULL);
+ i++;
+ }
+ i_assert(conn != NULL);
+ director_connection_destroy(&conn);
+ }
+}
+
+static void main_init(const char *admin_path)
+{
+ hash_table_create(&users, default_pool, 0, str_hash, strcmp);
+ hash_table_create(&hosts, default_pool, 0, net_ip_hash, net_ip_cmp);
+ i_array_init(&hosts_array, 256);
+
+ admin = admin_connect(admin_path);
+ admin_send(admin, "HOST-LIST\n");
+ admin_read_hosts(admin);
+
+ to_disconnect =
+ timeout_add(1000 * i_rand_minmax(5, 5 + DIRECTOR_DISCONNECT_TIMEOUT_SECS - 1),
+ director_connection_disconnect_timeout, NULL);
+}
+
+static void main_deinit(void)
+{
+ struct hash_iterate_context *iter;
+ char *username;
+ struct ip_addr *ip;
+ struct user *user;
+ struct host *host;
+
+ while (imap_clients != NULL) {
+ struct imap_client *client = imap_clients;
+ imap_client_destroy(&client);
+ }
+
+ timeout_remove(&to_disconnect);
+ while (director_connections != NULL) {
+ struct director_connection *conn = director_connections;
+ director_connection_destroy(&conn);
+ }
+
+ iter = hash_table_iterate_init(users);
+ while (hash_table_iterate(iter, users, &username, &user))
+ user_free(user);
+ hash_table_iterate_deinit(&iter);
+ hash_table_destroy(&users);
+
+ iter = hash_table_iterate_init(hosts);
+ while (hash_table_iterate(iter, hosts, &ip, &host))
+ host_unref(&host);
+ hash_table_iterate_deinit(&iter);
+ hash_table_destroy(&hosts);
+ array_free(&hosts_array);
+
+ admin_disconnect(&admin);
+}
+
+int main(int argc, char *argv[])
+{
+ const enum master_service_flags service_flags =
+ MASTER_SERVICE_FLAG_DONT_SEND_STATS;
+ const char *admin_path;
+
+ master_service = master_service_init("director-test", service_flags,
+ &argc, &argv, "");
+ if (master_getopt(master_service) > 0)
+ return FATAL_DEFAULT;
+ admin_path = argv[optind];
+ if (admin_path == NULL)
+ i_fatal("director-doveadm socket path missing");
+
+ master_service_init_log(master_service);
+
+ main_init(admin_path);
+ master_service_init_finish(master_service);
+ master_service_run(master_service, client_connected);
+ main_deinit();
+
+ master_service_deinit(&master_service);
+ return 0;
+}