bcb4e51a409d94ae670de96afb8483a4f7855294Stephan Bosch/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */
00e7c3010f7da4a49881a7feb05e413af353af0aTimo Sirainen This program accepts incoming unauthenticated IMAP connections from
00e7c3010f7da4a49881a7feb05e413af353af0aTimo Sirainen port 14300. If the same user is connecting to multiple different local IPs,
00e7c3010f7da4a49881a7feb05e413af353af0aTimo Sirainen it logs an error (i.e. director is not working right then).
7ca63fa4166f89fee900b7c14d87d53fbac47242Timo Sirainen This program also accepts incoming director connections on port 9091 and
7ca63fa4166f89fee900b7c14d87d53fbac47242Timo Sirainen forwards them to local_ip:9090. To make this work properly, director
7ca63fa4166f89fee900b7c14d87d53fbac47242Timo Sirainen executable must be given -t 9091 parameter. The idea is that this test tool
7ca63fa4166f89fee900b7c14d87d53fbac47242Timo Sirainen hooks between all director connections and can then add delays or break the
00e7c3010f7da4a49881a7feb05e413af353af0aTimo Sirainen Finally, this program connects to director-admin socket where it adds
00e7c3010f7da4a49881a7feb05e413af353af0aTimo Sirainen and removes mail hosts.
c9b3bbfb605ca19fbd39d083984241b2419e9fe1Timo Sirainen#define USER_TIMEOUT_MSECS (1000*10) /* FIXME: this should be based on director_user_expire */
00e7c3010f7da4a49881a7feb05e413af353af0aTimo Sirainenstatic struct director_connection *director_connections;
678d0463849ba777106eb7875f27db07a5d8e3dfTimo Sirainenstatic HASH_TABLE(char *, struct user *) users;
678d0463849ba777106eb7875f27db07a5d8e3dfTimo Sirainenstatic HASH_TABLE(struct ip_addr *, struct host *) hosts;
00e7c3010f7da4a49881a7feb05e413af353af0aTimo Sirainenstatic void imap_client_destroy(struct imap_client **client);
7ca63fa4166f89fee900b7c14d87d53fbac47242Timo Sirainenstatic void director_connection_destroy(struct director_connection **conn);
7ca63fa4166f89fee900b7c14d87d53fbac47242Timo Sirainenstatic void director_connection_timeout(struct director_connection *conn);
00e7c3010f7da4a49881a7feb05e413af353af0aTimo Sirainenstatic void client_username_check(struct imap_client *client)
00e7c3010f7da4a49881a7feb05e413af353af0aTimo Sirainen if (net_getsockname(client->fd, &local_ip, NULL) < 0)
00e7c3010f7da4a49881a7feb05e413af353af0aTimo Sirainen user = hash_table_lookup(users, client->username);
00e7c3010f7da4a49881a7feb05e413af353af0aTimo Sirainen hash_table_insert(users, user->username, user);
00e7c3010f7da4a49881a7feb05e413af353af0aTimo Sirainen i_error("user %s: old connection from %s, new from %s. "
00e7c3010f7da4a49881a7feb05e413af353af0aTimo Sirainen "%u old connections, last was %u secs ago",
00e7c3010f7da4a49881a7feb05e413af353af0aTimo Sirainen (unsigned int)(ioloop_time - user->last_seen));
00e7c3010f7da4a49881a7feb05e413af353af0aTimo Sirainenstatic int imap_client_parse_input(struct imap_client *client)
00e7c3010f7da4a49881a7feb05e413af353af0aTimo Sirainen ret = imap_parser_read_args(client->parser, 0, 0, &args);
00e7c3010f7da4a49881a7feb05e413af353af0aTimo Sirainen t_strconcat(tag, " OK Logged in.\r\n", NULL));
e2a88d59c0d47d63ce1ad5b1fd95e487124a3fd4Timo Sirainen o_stream_nsend_str(client->output, t_strconcat(
00e7c3010f7da4a49881a7feb05e413af353af0aTimo Sirainen "* BYE Out.\r\n",
00e7c3010f7da4a49881a7feb05e413af353af0aTimo Sirainen } else if (strcasecmp(cmd, "capability") == 0) {
00e7c3010f7da4a49881a7feb05e413af353af0aTimo Sirainen t_strconcat(tag, " BAD Not supported.\r\n", NULL));
00e7c3010f7da4a49881a7feb05e413af353af0aTimo Sirainen (void)i_stream_read_next_line(client->input); /* eat away LF */
00e7c3010f7da4a49881a7feb05e413af353af0aTimo Sirainenstatic void imap_client_input(struct imap_client *client)
00e7c3010f7da4a49881a7feb05e413af353af0aTimo Sirainen while ((ret = imap_client_parse_input(client)) > 0) ;
e93184a9055c2530366dfe617e07199603c399ddMartti Rannanjärvi client->input = i_stream_create_fd(fd, 4096);
e93184a9055c2530366dfe617e07199603c399ddMartti Rannanjärvi client->output = o_stream_create_fd(fd, (size_t)-1);
e2a88d59c0d47d63ce1ad5b1fd95e487124a3fd4Timo Sirainen o_stream_set_no_error_handling(client->output, TRUE);
00e7c3010f7da4a49881a7feb05e413af353af0aTimo Sirainen client->io = io_add(fd, IO_READ, imap_client_input, client);
00e7c3010f7da4a49881a7feb05e413af353af0aTimo Sirainen imap_parser_create(client->input, client->output, 4096);
00e7c3010f7da4a49881a7feb05e413af353af0aTimo Sirainen "* OK [CAPABILITY IMAP4rev1] director-test ready.\r\n");
00e7c3010f7da4a49881a7feb05e413af353af0aTimo Sirainenstatic void imap_client_destroy(struct imap_client **_client)
00e7c3010f7da4a49881a7feb05e413af353af0aTimo Sirainen user->to = timeout_add(USER_TIMEOUT_MSECS, user_free,
00e7c3010f7da4a49881a7feb05e413af353af0aTimo Sirainen master_service_client_connection_destroyed(master_service);
00e7c3010f7da4a49881a7feb05e413af353af0aTimo Sirainendirector_connection_input(struct director_connection *conn,
00e7c3010f7da4a49881a7feb05e413af353af0aTimo Sirainen struct istream *input, struct ostream *output)
3858a7a5da361c35f1e6e50c8e3214dc0cf379d6Phil Carmody if (i_stream_read_more(input, &data, &size) == -1) {
191153d1a5b0eb0c129139570e3aa5212f28d2acJosef 'Jeff' Sipek if (i_rand_limit(3) == 0 && conn->to_delay == NULL) {
191153d1a5b0eb0c129139570e3aa5212f28d2acJosef 'Jeff' Sipek timeout_add(i_rand_limit(DIRECTOR_CONN_MAX_DELAY_MSECS),
00e7c3010f7da4a49881a7feb05e413af353af0aTimo Sirainenstatic void director_connection_in_input(struct director_connection *conn)
00e7c3010f7da4a49881a7feb05e413af353af0aTimo Sirainen director_connection_input(conn, conn->in_input, conn->out_output);
00e7c3010f7da4a49881a7feb05e413af353af0aTimo Sirainenstatic void director_connection_out_input(struct director_connection *conn)
00e7c3010f7da4a49881a7feb05e413af353af0aTimo Sirainen director_connection_input(conn, conn->out_input, conn->in_output);
7ca63fa4166f89fee900b7c14d87d53fbac47242Timo Sirainenstatic void director_connection_timeout(struct director_connection *conn)
c9b3bbfb605ca19fbd39d083984241b2419e9fe1Timo Sirainendirector_connection_create(int in_fd, const struct ip_addr *local_ip,
c9b3bbfb605ca19fbd39d083984241b2419e9fe1Timo Sirainen out_fd = net_connect_ip(local_ip, DIRECTOR_OUT_PORT, remote_ip);
e93184a9055c2530366dfe617e07199603c399ddMartti Rannanjärvi conn->in_input = i_stream_create_fd(conn->in_fd, (size_t)-1);
e93184a9055c2530366dfe617e07199603c399ddMartti Rannanjärvi conn->in_output = o_stream_create_fd(conn->in_fd, (size_t)-1);
e2a88d59c0d47d63ce1ad5b1fd95e487124a3fd4Timo Sirainen o_stream_set_no_error_handling(conn->in_output, TRUE);
e93184a9055c2530366dfe617e07199603c399ddMartti Rannanjärvi conn->out_input = i_stream_create_fd(conn->out_fd, (size_t)-1);
e93184a9055c2530366dfe617e07199603c399ddMartti Rannanjärvi conn->out_output = o_stream_create_fd(conn->out_fd, (size_t)-1);
e2a88d59c0d47d63ce1ad5b1fd95e487124a3fd4Timo Sirainen o_stream_set_no_error_handling(conn->out_output, TRUE);
00e7c3010f7da4a49881a7feb05e413af353af0aTimo Sirainenstatic void director_connection_destroy(struct director_connection **_conn)
00e7c3010f7da4a49881a7feb05e413af353af0aTimo Sirainenstatic void client_connected(struct master_service_connection *conn)
00e7c3010f7da4a49881a7feb05e413af353af0aTimo Sirainen if (net_getsockname(conn->fd, &local_ip, &local_port) < 0)
c9b3bbfb605ca19fbd39d083984241b2419e9fe1Timo Sirainen if (net_getpeername(conn->fd, &remote_ip, NULL) < 0)
c9b3bbfb605ca19fbd39d083984241b2419e9fe1Timo Sirainen director_connection_create(conn->fd, &local_ip, &remote_ip);
00e7c3010f7da4a49881a7feb05e413af353af0aTimo Sirainen i_error("Connection to unknown port %u", local_port);
00e7c3010f7da4a49881a7feb05e413af353af0aTimo Sirainen master_service_client_connection_accept(conn);
00e7c3010f7da4a49881a7feb05e413af353af0aTimo Sirainenadmin_send(struct admin_connection *conn, const char *data)
00e7c3010f7da4a49881a7feb05e413af353af0aTimo Sirainen if (write_full(i_stream_get_fd(conn->input), data, strlen(data)) < 0)
7ca63fa4166f89fee900b7c14d87d53fbac47242Timo Sirainenstatic void admin_input(struct admin_connection *conn)
7ca63fa4166f89fee900b7c14d87d53fbac47242Timo Sirainen while ((line = i_stream_read_next_line(conn->input)) != NULL) {
7ca63fa4166f89fee900b7c14d87d53fbac47242Timo Sirainen i_error("director-doveadm: Unexpected input: %s", line);
7ca63fa4166f89fee900b7c14d87d53fbac47242Timo Sirainen if (conn->input->stream_errno != 0 || conn->input->eof)
7ca63fa4166f89fee900b7c14d87d53fbac47242Timo Sirainenstatic void admin_random_action(struct admin_connection *conn)
191153d1a5b0eb0c129139570e3aa5212f28d2acJosef 'Jeff' Sipek hosts[i]->vhost_count = i_rand_limit(20) * 10;
7ca63fa4166f89fee900b7c14d87d53fbac47242Timo Sirainen admin_send(conn, t_strdup_printf("HOST-SET\t%s\t%u\n",
7ca63fa4166f89fee900b7c14d87d53fbac47242Timo Sirainen net_ip2addr(&hosts[i]->ip), hosts[i]->vhost_count));
00e7c3010f7da4a49881a7feb05e413af353af0aTimo Sirainenstatic struct admin_connection *admin_connect(const char *path)
00e7c3010f7da4a49881a7feb05e413af353af0aTimo Sirainen#define DIRECTOR_ADMIN_HANDSHAKE "VERSION\tdirector-doveadm\t1\t0\n"
00e7c3010f7da4a49881a7feb05e413af353af0aTimo Sirainen i_fatal("net_connect_unix(%s) failed: %m", path);
7ca63fa4166f89fee900b7c14d87d53fbac47242Timo Sirainen conn->io = io_add(conn->fd, IO_READ, admin_input, conn);
4b335788eb41dec2de5f78459d96387fcc710010Timo Sirainen conn->to_random = timeout_add_short(ADMIN_RANDOM_TIMEOUT_MSECS,
e93184a9055c2530366dfe617e07199603c399ddMartti Rannanjärvi conn->input = i_stream_create_fd(conn->fd, (size_t)-1);
00e7c3010f7da4a49881a7feb05e413af353af0aTimo Sirainen if (!version_string_verify(line, "director-doveadm", 1)) {
00e7c3010f7da4a49881a7feb05e413af353af0aTimo Sirainen i_fatal("%s not a compatible director-doveadm socket",
00e7c3010f7da4a49881a7feb05e413af353af0aTimo Sirainenstatic void admin_disconnect(struct admin_connection **_conn)
7ca63fa4166f89fee900b7c14d87d53fbac47242Timo Sirainenstatic void admin_read_hosts(struct admin_connection *conn)
7ca63fa4166f89fee900b7c14d87d53fbac47242Timo Sirainen while ((line = i_stream_read_next_line(conn->input)) != NULL) {
7ca63fa4166f89fee900b7c14d87d53fbac47242Timo Sirainen /* ip vhost-count user-count */
7a60e1dc9e93ef3f7c7fe1af6385a0bfa1e31bc3Timo Sirainen const char *const *args = t_strsplit_tabescaped(line);
0a53eb0283d7ec28c6105f61e118b96fce8ecb95Timo Sirainendirector_connection_disconnect_timeout(void *context ATTR_UNUSED)
0a53eb0283d7ec28c6105f61e118b96fce8ecb95Timo Sirainen for (conn = director_connections; conn != NULL; conn = conn->next)
6c961309a97ee0c86b85fa336f5f51662bd3d515Timo Sirainen for (conn = director_connections; i < count; conn = conn->next) {
678d0463849ba777106eb7875f27db07a5d8e3dfTimo Sirainen hash_table_create(&users, default_pool, 0, str_hash, strcmp);
678d0463849ba777106eb7875f27db07a5d8e3dfTimo Sirainen hash_table_create(&hosts, default_pool, 0, net_ip_hash, net_ip_cmp);
191153d1a5b0eb0c129139570e3aa5212f28d2acJosef 'Jeff' Sipek timeout_add(1000 * i_rand_minmax(5, 5 + DIRECTOR_DISCONNECT_TIMEOUT_SECS - 1),
677b75f90d81eafe742896d6570a2f63ce501d05Josef 'Jeff' Sipek director_connection_disconnect_timeout, NULL);
00e7c3010f7da4a49881a7feb05e413af353af0aTimo Sirainen struct director_connection *conn = director_connections;
a75d470c9223a75801418fcdda258885c36317e0Timo Sirainen while (hash_table_iterate(iter, users, &username, &user))
a75d470c9223a75801418fcdda258885c36317e0Timo Sirainen while (hash_table_iterate(iter, hosts, &ip, &host))
00e7c3010f7da4a49881a7feb05e413af353af0aTimo Sirainen master_service = master_service_init("director-test", 0,
7ca63fa4166f89fee900b7c14d87d53fbac47242Timo Sirainen i_fatal("director-doveadm socket path missing");
00e7c3010f7da4a49881a7feb05e413af353af0aTimo Sirainen master_service_init_log(master_service, "director-test: ");