<commit_before>/* Copyright (c) 2005-2010 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 "imap-parser.h"
#include "master-service.h"
#include "master-service-settings.h"
#include "director-settings.h"

#include <stdlib.h>
#include <unistd.h>

#define IMAP_PORT 14300
#define DIRECTOR_IN_PORT 9091
#define DIRECTOR_OUT_PORT 9090
#define USER_TIMEOUT_MSECS (1000*60)
#define ADMIN_RANDOM_TIMEOUT_MSECS 500
#define DIRECTOR_CONN_MAX_DELAY_MSECS 1000

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;
};

static struct imap_client *imap_clients;
static struct director_connection *director_connections;
static struct hash_table *users;
static struct hash_table *hosts;
static ARRAY_DEFINE(hosts_array, struct host *);
static struct admin_connection *admin;

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++;
		host->ip = local_ip;
		host->vhost_count = 100;
		hash_table_insert(hosts, &host->ip, host);
		array_append(&hosts_array, &host, 1);
	}

	user = hash_table_lookup(users, client->username);
	if (user == NULL) {
		user = i_new(struct user, 1);
		user->username = i_strdup(client->username);
		user->host = host;
		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));
		return;
	}
	client->user = user;
	user->connections++;
	user->last_seen = ioloop_time;
	user->host->refcount++;

	if (user->to != NULL)
		timeout_remove(&user->to);
}

static void user_free(struct user *user)
{
	host_unref(&user->host);
	if (user->to != NULL)
		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_send_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_send_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_send_str(client->output,
			t_strconcat("* CAPABILITY IMAP4rev1\r\n",
				    tag, " OK Done.\r\n", NULL));
	} else {
		o_stream_send_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, FALSE);
	client->output = o_stream_create_fd(fd, (size_t)-1, FALSE);
	client->io = io_add(fd, IO_READ, imap_client_input, client);
	client->parser =
		imap_parser_create(client->input, client->output, 4096);
	o_stream_send_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_destroy(&client->parser);
	io_remove(&client->io);
	i_stream_unref(&client->input);
	o_stream_unref(&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_data(input, &data, &size, 0) == -1) {
		director_connection_destroy(&conn);
		return;
	}

	o_stream_send(output, data, size);
	i_stream_skip(input, size);

	if (rand() % 3 == 0 && conn->to_delay == NULL) {
		conn->to_delay =
			timeout_add(rand() % 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);

	director_connection_in_input(conn);
	director_connection_out_input(conn);
}<commit_msg>director-test: Some fixes<commit_after>/* Copyright (c) 2005-2010 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 "imap-parser.h"
#include "master-service.h"
#include "master-service-settings.h"
#include "director-settings.h"

#include <stdlib.h>
#include <unistd.h>

#define IMAP_PORT 14300
#define DIRECTOR_IN_PORT 9091
#define DIRECTOR_OUT_PORT 9090
#define USER_TIMEOUT_MSECS (1000*60)
#define ADMIN_RANDOM_TIMEOUT_MSECS 500
#define DIRECTOR_CONN_MAX_DELAY_MSECS 100

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;
};

static struct imap_client *imap_clients;
static struct director_connection *director_connections;
static struct hash_table *users;
static struct hash_table *hosts;
static ARRAY_DEFINE(hosts_array, struct host *);
static struct admin_connection *admin;

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++;
		host->ip = local_ip;
		host->vhost_count = 100;
		hash_table_insert(hosts, &host->ip, host);
		array_append(&hosts_array, &host, 1);
	}

	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++;

	if (user->to != NULL)
		timeout_remove(&user->to);
}

static void user_free(struct user *user)
{
	host_unref(&user->host);
	if (user->to != NULL)
		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_send_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_send_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_send_str(client->output,
			t_strconcat("* CAPABILITY IMAP4rev1\r\n",
				    tag, " OK Done.\r\n", NULL));
	} else {
		o_stream_send_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, FALSE);
	client->output = o_stream_create_fd(fd, (size_t)-1, FALSE);
	client->io = io_add(fd, IO_READ, imap_client_input, client);
	client->parser =
		imap_parser_create(client->input, client->output, 4096);
	o_stream_send_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_destroy(&client->parser);
	io_remove(&client->io);
	i_stream_unref(&client->input);
	o_stream_unref(&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_data(input, &data, &size, 0) == -1) {
		director_connection_destroy(&conn);
		return;
	}

	o_stream_send(output, data, size);
	i_stream_skip(input, size);

	if (rand() % 3 == 0 && conn->to_delay == NULL) {
		conn->to_delay =
			timeout_add(rand() % 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);
}
{'fraction_non_alphanumeric': 0.1304770586447888, 'fraction_numerical': 0.012336675782436949, 'mean_word_length': 4.908797127468581, 'pattern_counts': {'":': 0, '<': 13, '<?xml version=': 0, '>': 164, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5498090', 'n_tokens_mistral': 6388, 'n_tokens_neox': 5907, 'n_words': 1432}