changeset 11321:5f350b5ff6d9 HEAD

Added initial implementation of a director process (for NFS users). There are still some unimplemented features and bugs. Also changing mail server list doesn't yet make sure that other directors won't assign the same user to a different server at the same time.
author Timo Sirainen <tss@iki.fi>
date Wed, 19 May 2010 09:56:49 +0200
parents 1753fe048fee
children 12316378922e
files .hgignore configure.in doc/example-config/conf.d/10-director.conf src/Makefile.am src/director/Makefile.am src/director/auth-connection.c src/director/auth-connection.h src/director/director-connection.c src/director/director-connection.h src/director/director-host.c src/director/director-host.h src/director/director-request.c src/director/director-request.h src/director/director-settings.c src/director/director-settings.h src/director/director.c src/director/director.h src/director/doveadm-connection.c src/director/doveadm-connection.h src/director/login-connection.c src/director/login-connection.h src/director/mail-host.c src/director/mail-host.h src/director/main.c src/director/user-directory.c src/director/user-directory.h src/doveadm/Makefile.am src/doveadm/doveadm-director.c src/doveadm/doveadm.c src/doveadm/doveadm.h
diffstat 30 files changed, 2949 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- a/.hgignore	Wed May 19 09:52:36 2010 +0200
+++ b/.hgignore	Wed May 19 09:56:49 2010 +0200
@@ -61,6 +61,7 @@
 src/config/doveconf
 src/lda/dovecot-lda
 src/dict/dict
+src/director/director
 src/dns/dns-client
 src/doveadm/doveadm
 src/dsync/dsync
--- a/configure.in	Wed May 19 09:52:36 2010 +0200
+++ b/configure.in	Wed May 19 09:56:49 2010 +0200
@@ -2659,6 +2659,7 @@
 src/log/Makefile
 src/lmtp/Makefile
 src/dict/Makefile
+src/director/Makefile
 src/dns/Makefile
 src/imap/Makefile
 src/imap-login/Makefile
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/example-config/conf.d/10-director.conf	Wed May 19 09:56:49 2010 +0200
@@ -0,0 +1,41 @@
+##
+## Director-specific settings.
+##
+
+# Director can be used by Dovecot proxy to keep a temporary user -> mail server
+# mapping. As long as user has simultaneous connections, the user is always
+# redirected to the same server. Each proxy server is running its own director
+# process, and the directors are communicating the state to each others.
+# Directors are mainly useful with NFS-like setups.
+
+# List of IPs or hostnames to all director servers, including ourself.
+# Ports can be specified as ip:port. The default port is the same as
+# what director service's inet_listener is using.
+director_servers = 
+
+# List of IPs or hostnames to all backend mail servers. Ranges are allowed
+# too, like 10.0.0.10-10.0.0.30.
+director_mail_servers = 
+
+# How long to redirect users to a specific server after it no longer has
+# any connections.
+director_user_expire = 15 min
+
+# To enable director service, uncomment the mode and assign a port.
+service director {
+  unix_listener login/director {
+    #mode = 0666
+  }
+  inet_listener {
+    #port = 
+  }
+}
+
+# Enable director for the wanted login services by telling them to
+# connect to director socket instead of the default login socket:
+service imap-login {
+  #executable = imap-login director
+}
+service pop3-login {
+  #executable = pop3-login director
+}
--- a/src/Makefile.am	Wed May 19 09:52:36 2010 +0200
+++ b/src/Makefile.am	Wed May 19 09:56:49 2010 +0200
@@ -33,6 +33,7 @@
 	lmtp \
 	log \
 	config \
+	director \
 	util \
 	doveadm \
 	dsync \
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/director/Makefile.am	Wed May 19 09:56:49 2010 +0200
@@ -0,0 +1,38 @@
+pkglibexecdir = $(libexecdir)/dovecot
+
+pkglibexec_PROGRAMS = director
+
+AM_CPPFLAGS = \
+	-I$(top_srcdir)/src/lib \
+	-I$(top_srcdir)/src/lib-auth \
+	-I$(top_srcdir)/src/lib-settings \
+	-I$(top_srcdir)/src/lib-master \
+	-DPKG_RUNDIR=\""$(rundir)"\"
+
+director_LDADD = $(LIBDOVECOT)
+director_DEPENDENCIES = $(LIBDOVECOT_DEPS)
+
+director_SOURCES = \
+	main.c \
+	auth-connection.c \
+	director.c \
+	director-connection.c \
+	director-host.c \
+	director-request.c \
+	director-settings.c \
+	doveadm-connection.c \
+	login-connection.c \
+	mail-host.c \
+	user-directory.c
+
+noinst_HEADERS = \
+	auth-connection.h \
+	director.h \
+	director-connection.h \
+	director-host.h \
+	director-request.h \
+	director-settings.h \
+	doveadm-connection.h \
+	login-connection.h \
+	mail-host.h \
+	user-directory.h
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/director/auth-connection.c	Wed May 19 09:56:49 2010 +0200
@@ -0,0 +1,127 @@
+/* Copyright (c) 2010 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "istream.h"
+#include "ostream.h"
+#include "network.h"
+#include "llist.h"
+#include "safe-memset.h"
+#include "auth-client-interface.h"
+#include "auth-connection.h"
+
+#include <unistd.h>
+
+struct auth_connection {
+	struct auth_connection *prev, *next;
+
+	char *path;
+	int fd;
+	struct io *io;
+	struct istream *input;
+	struct ostream *output;
+
+	auth_input_callback *callback;
+	void *context;
+};
+
+static struct auth_connection *auth_connections;
+
+static void auth_connection_input(struct auth_connection *conn)
+{
+	char *line;
+
+	switch (i_stream_read(conn->input)) {
+	case 0:
+		return;
+	case -1:
+		/* disconnected */
+		auth_connection_deinit(&conn);
+		return;
+	case -2:
+		/* buffer full */
+		i_error("BUG: Auth server sent us more than %d bytes",
+			(int)AUTH_CLIENT_MAX_LINE_LENGTH);
+		auth_connection_deinit(&conn);
+		return;
+	}
+
+	while ((line = i_stream_next_line(conn->input)) != NULL) {
+		T_BEGIN {
+			conn->callback(line, conn->context);
+			safe_memset(line, 0, strlen(line));
+		} T_END;
+	}
+}
+
+struct auth_connection *auth_connection_init(const char *path)
+{
+	struct auth_connection *conn;
+ 
+	conn = i_new(struct auth_connection, 1);
+	conn->fd = -1;
+	conn->path = i_strdup(path);
+	DLLIST_PREPEND(&auth_connections, conn);
+	return conn;
+}
+
+void auth_connection_set_callback(struct auth_connection *conn,
+				  auth_input_callback *callback, void *context)
+{
+	conn->callback = callback;
+	conn->context = context;
+}
+
+int auth_connection_connect(struct auth_connection *conn)
+{
+	i_assert(conn->fd == -1);
+
+	conn->fd = net_connect_unix_with_retries(conn->path, 1000);
+	if (conn->fd == -1) {
+		i_error("connect(%s) failed: %m", conn->path);
+		return -1;
+	}
+
+	conn->input = i_stream_create_fd(conn->fd, AUTH_CLIENT_MAX_LINE_LENGTH,
+					 FALSE);
+	conn->output = o_stream_create_fd(conn->fd, (size_t)-1, FALSE);
+	conn->io = io_add(conn->fd, IO_READ, auth_connection_input, conn);
+	return 0;
+}
+
+void auth_connection_deinit(struct auth_connection **_conn)
+{
+	struct auth_connection *conn = *_conn;
+
+	*_conn = NULL;
+
+	DLLIST_REMOVE(&auth_connections, conn);
+	if (conn->fd != -1) {
+		io_remove(&conn->io);
+		i_stream_unref(&conn->input);
+		o_stream_unref(&conn->output);
+
+		if (close(conn->fd) < 0)
+			i_error("close(auth connection) failed: %m");
+		conn->callback(NULL, conn->context);
+	}
+	i_free(conn->path);
+	i_free(conn);
+}
+
+void auth_connection_send(struct auth_connection *conn,
+			  const void *data, size_t size)
+{
+	i_assert(conn->fd != -1);
+
+	(void)o_stream_send(conn->output, data, size);
+}
+
+void auth_connections_deinit(void)
+{
+	while (auth_connections != NULL) {
+		struct auth_connection *conn = auth_connections;
+
+		auth_connection_deinit(&conn);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/director/auth-connection.h	Wed May 19 09:56:49 2010 +0200
@@ -0,0 +1,22 @@
+#ifndef AUTH_CONNECTION_H
+#define AUTH_CONNECTION_H
+
+/* Called for each input line. This is also called with line=NULL if
+   connection gets disonnected. */
+typedef void auth_input_callback(const char *line, void *context);
+
+struct auth_connection *auth_connection_init(const char *path);
+void auth_connection_deinit(struct auth_connection **conn);
+
+void auth_connection_set_callback(struct auth_connection *conn,
+				  auth_input_callback *callback, void *context);
+
+/* Start connecting. Returns 0 if ok, -1 if connect failed. */
+int auth_connection_connect(struct auth_connection *conn);
+/* Send data to auth connection. */
+void auth_connection_send(struct auth_connection *conn,
+			  const void *data, size_t size);
+
+void auth_connections_deinit(void);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/director/director-connection.c	Wed May 19 09:56:49 2010 +0200
@@ -0,0 +1,735 @@
+/* Copyright (c) 2010 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "array.h"
+#include "network.h"
+#include "istream.h"
+#include "ostream.h"
+#include "str.h"
+#include "mail-host.h"
+#include "director.h"
+#include "director-host.h"
+#include "director-request.h"
+#include "user-directory.h"
+#include "director-connection.h"
+
+#include <stdlib.h>
+#include <unistd.h>
+
+#define DIRECTOR_VERSION_NAME "director"
+#define DIRECTOR_VERSION_MAJOR 1
+#define DIRECTOR_VERSION_MINOR 0
+
+#define MAX_INBUF_SIZE 1024
+#define MAX_OUTBUF_SIZE (1024*1024*10)
+#define OUTBUF_FLUSH_THRESHOLD (1024*128)
+#define DIRECTOR_CONNECTION_PING_TIMEOUT_MSECS (2*1000)
+
+struct director_connection {
+	struct director *dir;
+	const char *name;
+
+	/* for incoming connections the director host isn't known until
+	   ME-line is received */
+	struct director_host *host;
+
+	int fd;
+	struct io *io;
+	struct istream *input;
+	struct ostream *output;
+	struct timeout *to, *to_ping;
+
+	struct user_directory_iter *user_iter;
+
+	unsigned int in:1;
+	unsigned int connected:1;
+	unsigned int version_received:1;
+	unsigned int me_received:1;
+	unsigned int handshake_received:1;
+};
+
+static void director_connection_ping(struct director_connection *conn);
+
+static bool
+director_args_parse_ip_port(struct director_connection *conn,
+			    const char *const *args,
+			    struct ip_addr *ip_r, unsigned int *port_r)
+{
+	if (net_addr2ip(args[0], ip_r) < 0) {
+		i_error("director(%s): Command has invalid IP address: %s",
+			conn->name, args[0]);
+		return FALSE;
+	}
+	if (str_to_uint(args[1], port_r) < 0) {
+		i_error("director(%s): Command has invalid port: %s",
+			conn->name, args[1]);
+		return FALSE;
+	}
+	return TRUE;
+}
+
+static bool director_cmd_me(struct director_connection *conn,
+			    const char *const *args)
+{
+	struct director *dir = conn->dir;
+	struct director_host *host;
+	const char *connect_str;
+	struct ip_addr ip;
+	unsigned int port;
+
+	if (!director_args_parse_ip_port(conn, args, &ip, &port))
+		return FALSE;
+
+	if (!conn->in && (!net_ip_compare(&conn->host->ip, &ip) ||
+			  conn->host->port != port)) {
+		i_error("Remote director thinks it's someone else "
+			"(connected to %s:%u, remote says it's %s:%u)",
+			net_ip2addr(&conn->host->ip), conn->host->port,
+			net_ip2addr(&ip), port);
+		return FALSE;
+	}
+	host = director_host_get(dir, &ip, port);
+	conn->me_received = TRUE;
+
+	if (!conn->in)
+		return TRUE;
+
+	conn->host = host;
+	connect_str = t_strdup_printf("CONNECT\t%s\t%u\n",
+				      net_ip2addr(&host->ip), host->port);
+	/* make sure this is the correct incoming connection */
+	if (host->self) {
+		/* probably we're trying to find our own ip. it's no */
+		i_error("director(%s): Connection from self, dropping",
+			host->name);
+		return FALSE;
+	} else if (dir->left == NULL) {
+		/* no conflicts yet */
+	} else if (dir->left->host == host) {
+		i_warning("director(%s): Dropping existing connection "
+			  "in favor of its new connection", host->name);
+		director_connection_deinit(&dir->left);
+	} else {
+		if (director_host_cmp_to_self(dir->left->host, host,
+					      dir->self_host) > 0) {
+			/* the old connection is the correct one.
+			   refer the client there. */
+			director_connection_send(conn, t_strdup_printf(
+				"CONNECT\t%s\t%u\n",
+				net_ip2addr(&dir->left->host->ip),
+				dir->left->host->port));
+			/* also make sure that the connection is alive */
+			director_connection_ping(dir->left);
+			return FALSE;
+		}
+
+		/* this new connection is the correct one. disconnect the old
+		   one, but before that tell it to connect to the new one.
+		   that message might not reach it, so also send the same
+		   message to right side. */
+		director_connection_send(dir->left, connect_str);
+		(void)o_stream_flush(dir->left->output);
+		director_connection_deinit(&dir->left);
+	}
+	dir->left = conn;
+
+	/* tell the ring's right side to connect to this new director. */
+	if (dir->right != NULL) {
+		if (dir->left->host != dir->right->host)
+			director_connection_send(dir->right, connect_str);
+		else {
+			/* there are only two directors */
+		}
+	} else {
+		/* looks like we're the right side. */
+		(void)director_connect_host(dir, host);
+	}
+	return TRUE;
+}
+
+static bool
+director_user_refresh(struct director *dir, unsigned int username_hash,
+		      struct mail_host *host, time_t timestamp,
+		      struct user **user_r)
+{
+	struct user *user;
+	bool ret = FALSE;
+
+	user = user_directory_lookup(dir->users, username_hash);
+	if (user == NULL) {
+		*user_r = user_directory_add(dir->users, username_hash,
+					     host, timestamp);
+		return TRUE;
+	}
+	if (timestamp == ioloop_time && user->timestamp != timestamp) {
+		user_directory_refresh(dir->users, user);
+		ret = TRUE;
+	}
+
+	if (user->host != host) {
+		i_error("User hash %u is being redirected to two hosts: "
+			"%s and %s", username_hash,
+			net_ip2addr(&user->host->ip),
+			net_ip2addr(&host->ip));
+		user->host = host;
+		ret = TRUE;
+	}
+	*user_r = user;
+	return ret;
+}
+
+static bool
+director_handshake_cmd_user(struct director_connection *conn,
+			    const char *const *args)
+{
+	unsigned int username_hash, timestamp;
+	struct ip_addr ip;
+	struct mail_host *host;
+	struct user *user;
+
+	if (str_array_length(args) != 3 ||
+	    str_to_uint(args[0], &username_hash) < 0 ||
+	    net_addr2ip(args[1], &ip) < 0 ||
+	    str_to_uint(args[2], &timestamp) < 0) {
+		i_error("director(%s): Invalid USER handshake args",
+			conn->name);
+		return FALSE;
+	}
+
+	host = mail_host_lookup(&ip);
+	if (host == NULL) {
+		i_error("director(%s): USER used unknown host %s in handshake",
+			conn->name, args[1]);
+		return FALSE;
+	}
+
+	director_user_refresh(conn->dir, username_hash, host, timestamp, &user);
+	return TRUE;
+}
+
+static bool director_cmd_director(struct director_connection *conn,
+				  const char *const *args)
+{
+	struct director_host *host;
+	struct ip_addr ip;
+	unsigned int port;
+
+	if (!director_args_parse_ip_port(conn, args, &ip, &port))
+		return FALSE;
+
+	host = director_host_lookup(conn->dir, &ip, port);
+	if (host != NULL) {
+		/* already have this, skip */
+		return TRUE;
+	}
+
+	/* save the director and forward it */
+	director_host_add(conn->dir, &ip, port);
+	director_connection_send(conn->dir->right,
+		t_strdup_printf("DIRECTOR\t%s\t%u\n", net_ip2addr(&ip), port));
+	return TRUE;
+}
+
+static bool
+director_cmd_host(struct director_connection *conn, const char *const *args)
+{
+	struct mail_host *host;
+	struct ip_addr ip;
+	unsigned int vhost_count;
+	bool update;
+
+	if (str_array_length(args) != 2 ||
+	    net_addr2ip(args[0], &ip) < 0 ||
+	    str_to_uint(args[1], &vhost_count) < 0) {
+		i_error("director(%s): Invalid HOST args", conn->name);
+		return FALSE;
+	}
+
+	host = mail_host_lookup(&ip);
+	if (host == NULL) {
+		host = mail_host_add_ip(&ip);
+		update = TRUE;
+	} else {
+		update = host->vhost_count != vhost_count;
+	}
+
+	if (update) {
+		/* FIXME: 1) shouldn't be unconditional, 2) if we're not
+		   handshaking, we should do SYNC before making it visible */
+		host->vhost_count = vhost_count;
+		director_update_host(conn->dir, conn->host, host);
+	}
+	return TRUE;
+}
+
+static bool
+director_cmd_host_remove(struct director_connection *conn,
+			 const char *const *args)
+{
+	struct mail_host *host;
+	struct ip_addr ip;
+
+	if (str_array_length(args) != 1 ||
+	    net_addr2ip(args[0], &ip) < 0) {
+		i_error("director(%s): Invalid HOST-REMOVE args", conn->name);
+		return FALSE;
+	}
+
+	host = mail_host_lookup(&ip);
+	if (host != NULL)
+		director_remove_host(conn->dir, conn->host, host);
+	return TRUE;
+}
+
+static void director_handshake_cmd_done(struct director_connection *conn)
+{
+	struct director *dir = conn->dir;
+
+	conn->handshake_received = TRUE;
+	if (conn->in) {
+		/* handshaked to left side. tell it we've received the
+		   whole handshake. */
+		director_connection_send(conn, "DONE\n");
+
+		/* tell the right director about the left one */
+		if (dir->right != NULL) {
+			director_connection_send(dir->right,
+				t_strdup_printf("DIRECTOR\t%s\t%u\n",
+						net_ip2addr(&conn->host->ip),
+						conn->host->port));
+		}
+	}
+
+	if (dir->left != NULL && dir->right != NULL) {
+		/* we're connected to both directors. see if the ring is
+		   finished by sending a SYNC. if we get it back, it's done. */
+		dir->sync_seq = ++dir->self_host->last_seq;
+		director_connection_send(dir->right,
+			t_strdup_printf("SYNC\t%s\t%u\t%u\n",
+					net_ip2addr(&dir->self_ip),
+					dir->self_port, dir->sync_seq));
+	}
+}
+
+static bool
+director_connection_handle_handshake(struct director_connection *conn,
+				     const char *cmd, const char *const *args)
+{
+	struct director_host *host;
+	struct ip_addr ip;
+	unsigned int port;
+
+	/* both incoming and outgoing connections get VERSION and ME */
+	if (strcmp(cmd, "VERSION") == 0 && str_array_length(args) >= 3) {
+		if (strcmp(args[0], DIRECTOR_VERSION_NAME) != 0) {
+			i_error("director(%s): Wrong protocol in socket "
+				"(%s vs %s)",
+				conn->name, args[0], DIRECTOR_VERSION_NAME);
+			return FALSE;
+		} else if (atoi(args[1]) != DIRECTOR_VERSION_MAJOR) {
+			i_error("director(%s): Incompatible protocol version: "
+				"%u vs %u", conn->name, atoi(args[1]),
+				DIRECTOR_VERSION_MAJOR);
+			return FALSE;
+		}
+		conn->version_received = TRUE;
+		return TRUE;
+	}
+	if (!conn->version_received) {
+		i_error("director(%s): Incompatible protocol", conn->name);
+		return FALSE;
+	}
+
+	if (strcmp(cmd, "ME") == 0 && !conn->me_received &&
+	    str_array_length(args) == 2)
+		return director_cmd_me(conn, args);
+
+	/* only outgoing connections get a CONNECT reference */
+	if (!conn->in && strcmp(cmd, "CONNECT") == 0 &&
+	    str_array_length(args) == 2) {
+		/* remote wants us to connect elsewhere */
+		if (!director_args_parse_ip_port(conn, args, &ip, &port))
+			return FALSE;
+
+		conn->dir->right = NULL;
+		host = director_host_get(conn->dir, &ip, port);
+		(void)director_connect_host(conn->dir, host);
+		return FALSE;
+	}
+	/* only incoming connections get DIRECTOR and HOST lists */
+	if (conn->in && strcmp(cmd, "DIRECTOR") == 0 && conn->me_received)
+		return director_cmd_director(conn, args);
+	if (conn->in && strcmp(cmd, "HOST") == 0 && conn->me_received)
+		return director_cmd_host(conn, args);
+	/* only incoming connections get a USER list */
+	if (conn->in && strcmp(cmd, "USER") == 0 && conn->me_received)
+		return director_handshake_cmd_user(conn, args);
+	/* both get DONE */
+	if (strcmp(cmd, "DONE") == 0 && !conn->handshake_received) {
+		director_handshake_cmd_done(conn);
+		return TRUE;
+	}
+	i_error("director(%s): Unknown command (in this state): %s",
+		conn->name, cmd);
+	return FALSE;
+}
+
+static bool
+director_cmd_user(struct director_connection *conn, const char *const *args)
+{
+	unsigned int username_hash;
+	struct ip_addr ip;
+	struct mail_host *host;
+	struct user *user;
+
+	if (str_array_length(args) != 2 ||
+	    str_to_uint(args[0], &username_hash) < 0 ||
+	    net_addr2ip(args[1], &ip) < 0) {
+		i_error("director(%s): Invalid USER args", conn->name);
+		return FALSE;
+	}
+
+	host = mail_host_lookup(&ip);
+	if (host == NULL) {
+		/* we probably just removed this host. */
+		return TRUE;
+	}
+
+	if (director_user_refresh(conn->dir, username_hash,
+				  host, ioloop_time, &user))
+		director_update_user(conn->dir, conn->host, user);
+	return TRUE;
+}
+
+static bool director_connection_sync(struct director_connection *conn,
+				     const char *const *args, const char *line)
+{
+	struct director_host *host;
+	struct ip_addr ip;
+	unsigned int port, seq;
+
+	if (str_array_length(args) != 3 ||
+	    director_args_parse_ip_port(conn, args, &ip, &port) < 0 ||
+	    str_to_uint(args[2], &seq) < 0) {
+		i_error("director(%s): Invalid SYNC args", conn->name);
+		return FALSE;
+	}
+
+	/* find the originating director. if we don't see it, it was already
+	   removed and we can ignore this sync. */
+	host = director_host_lookup(conn->dir, &ip, port);
+	if (host == NULL)
+		return TRUE;
+
+	if (host->self) {
+		if (conn->dir->sync_seq != seq) {
+			/* stale SYNC event */
+			return TRUE;
+		}
+		if (conn->dir->ring_handshaked)
+			return TRUE;
+
+		/* the ring is handshaked */
+		conn->dir->ring_handshaked = TRUE;
+		director_set_state_changed(conn->dir);
+		return TRUE;
+	}
+
+	/* forward it to the connection on right */
+	if (conn->dir->right != NULL) {
+		director_connection_send(conn->dir->right,
+					 t_strconcat(line, "\n", NULL));
+	}
+	return TRUE;
+}
+
+static bool
+director_connection_handle_line(struct director_connection *conn,
+				const char *line)
+{
+	const char *cmd, *const *args;
+
+	args = t_strsplit(line, "\t");
+	cmd = args[0]; args++;
+	if (cmd == NULL) {
+		i_error("director(%s): Received empty line", conn->name);
+		return FALSE;
+	}
+	if (!conn->handshake_received)
+		return director_connection_handle_handshake(conn, cmd, args);
+
+	if (strcmp(cmd, "USER") == 0)
+		return director_cmd_user(conn, args);
+	if (strcmp(cmd, "HOST") == 0)
+		return director_cmd_host(conn, args);
+	if (strcmp(cmd, "HOST-REMOVE") == 0)
+		return director_cmd_host_remove(conn, args);
+	if (strcmp(cmd, "DIRECTOR") == 0)
+		return director_cmd_director(conn, args);
+	if (strcmp(cmd, "SYNC") == 0)
+		return director_connection_sync(conn, args, line);
+
+	if (strcmp(cmd, "PING") == 0) {
+		director_connection_send(conn, "PONG\n");
+		return TRUE;
+	}
+	if (strcmp(cmd, "PONG") == 0) {
+		if (conn->to_ping != NULL)
+			timeout_remove(&conn->to_ping);
+		return TRUE;
+	}
+	i_error("director(%s): Unknown command (in this state): %s",
+		conn->name, cmd);
+	return FALSE;
+}
+
+static void director_connection_input(struct director_connection *conn)
+{
+	char *line;
+	bool ret;
+
+	if (conn->to_ping != NULL)
+		timeout_reset(conn->to_ping);
+	switch (i_stream_read(conn->input)) {
+	case 0:
+		return;
+	case -1:
+		/* disconnected */
+		director_connection_deinit(&conn);
+		return;
+	case -2:
+		/* buffer full */
+		i_error("BUG: Director sent us more than %d bytes",
+			MAX_INBUF_SIZE);
+		director_connection_deinit(&conn);
+		return;
+	}
+
+	while ((line = i_stream_next_line(conn->input)) != NULL) {
+		T_BEGIN {
+			ret = director_connection_handle_line(conn, line);
+		} T_END;
+
+		if (!ret) {
+			director_connection_deinit(&conn);
+			break;
+		}
+	}
+}
+
+static void director_connection_send_directors(struct director_connection *conn,
+					       string_t *str)
+{
+	struct director_host *const *hostp;
+
+	array_foreach(&conn->dir->dir_hosts, hostp) {
+		str_printfa(str, "DIRECTOR\t%s\t%u\n",
+			    net_ip2addr(&(*hostp)->ip), (*hostp)->port);
+	}
+}
+
+static void director_connection_send_hosts(string_t *str)
+{
+	struct mail_host *const *hostp;
+
+	array_foreach(mail_hosts_get(), hostp) {
+		str_printfa(str, "HOST\t%s\t%u\n",
+			    net_ip2addr(&(*hostp)->ip), (*hostp)->vhost_count);
+	}
+}
+
+static int director_connection_send_users(struct director_connection *conn)
+{
+	struct user *user;
+	int ret;
+
+	o_stream_cork(conn->output);
+	while ((user = user_directory_iter_next(conn->user_iter)) != NULL) {
+		T_BEGIN {
+			const char *line;
+
+			line = t_strdup_printf("USER\t%u\t%s\t%u\n",
+					       user->username_hash,
+					       net_ip2addr(&user->host->ip),
+					       user->timestamp);
+			director_connection_send(conn, line);
+		} T_END;
+
+		if (o_stream_get_buffer_used_size(conn->output) >= OUTBUF_FLUSH_THRESHOLD) {
+			if ((ret = o_stream_flush(conn->output)) <= 0) {
+				/* continue later */
+				return ret;
+			}
+		}
+	}
+	user_directory_iter_deinit(&conn->user_iter);
+	director_connection_send(conn, "DONE\n");
+
+	i_assert(conn->io == NULL);
+	conn->io = io_add(conn->fd, IO_READ, director_connection_input, conn);
+
+	ret = o_stream_flush(conn->output);
+	o_stream_uncork(conn->output);
+	return ret;
+}
+
+static int director_connection_output(struct director_connection *conn)
+{
+	if (conn->user_iter != NULL)
+		return director_connection_send_users(conn);
+	else
+		return o_stream_flush(conn->output);
+}
+
+static struct director_connection *
+director_connection_init_common(struct director *dir, int fd)
+{
+	struct director_connection *conn;
+
+	conn = i_new(struct director_connection, 1);
+	conn->fd = fd;
+	conn->dir = dir;
+	conn->input = i_stream_create_fd(conn->fd, MAX_INBUF_SIZE, FALSE);
+	conn->output = o_stream_create_fd(conn->fd, MAX_OUTBUF_SIZE, FALSE);
+	o_stream_set_flush_callback(conn->output,
+				    director_connection_output, conn);
+	return conn;
+}
+
+static void director_connection_send_handshake(struct director_connection *conn)
+{
+	director_connection_send(conn, t_strdup_printf(
+		"VERSION\t"DIRECTOR_VERSION_NAME"\t%u\t%u\n"
+		"ME\t%s\t%u\n",
+		DIRECTOR_VERSION_MAJOR, DIRECTOR_VERSION_MINOR,
+		net_ip2addr(&conn->dir->self_ip), conn->dir->self_port));
+}
+
+struct director_connection *
+director_connection_init_in(struct director *dir, int fd)
+{
+	struct director_connection *conn;
+
+	conn = director_connection_init_common(dir, fd);
+	conn->in = TRUE;
+	conn->connected = TRUE;
+	conn->name = "<incoming>";
+	conn->io = io_add(conn->fd, IO_READ, director_connection_input, conn);
+
+	director_connection_send_handshake(conn);
+	return conn;
+}
+
+static void director_connection_connected(struct director_connection *conn)
+{
+	string_t *str = t_str_new(1024);
+	int err;
+
+	if ((err = net_geterror(conn->fd)) != 0) {
+		i_error("director(%s): connect() failed: %s", conn->name,
+			strerror(err));
+		director_connection_deinit(&conn);
+		return;
+	}
+	conn->connected = TRUE;
+
+	io_remove(&conn->io);
+
+	director_connection_send_handshake(conn);
+	director_connection_send_directors(conn, str);
+	director_connection_send_hosts(str);
+	director_connection_send(conn, str_c(str));
+
+	conn->user_iter = user_directory_iter_init(conn->dir->users);
+	(void)director_connection_send_users(conn);
+}
+
+struct director_connection *
+director_connection_init_out(struct director *dir, int fd,
+			     struct director_host *host)
+{
+	struct director_connection *conn;
+
+	conn = director_connection_init_common(dir, fd);
+	conn->name = host->name;
+	conn->host = host;
+	conn->io = io_add(conn->fd, IO_WRITE,
+			  director_connection_connected, conn);
+	return conn;
+}
+
+void director_connection_deinit(struct director_connection **_conn)
+{
+	struct director_connection *conn = *_conn;
+
+	*_conn = NULL;
+
+	if (conn->dir->left == conn)
+		conn->dir->left = NULL;
+	if (conn->dir->right == conn)
+		conn->dir->right = NULL;
+
+	if (conn->to != NULL)
+		timeout_remove(&conn->to);
+	if (conn->to_ping != NULL)
+		timeout_remove(&conn->to_ping);
+	if (conn->io != NULL)
+		io_remove(&conn->io);
+	i_stream_unref(&conn->input);
+	o_stream_unref(&conn->output);
+	if (close(conn->fd) < 0)
+		i_error("close(director connection) failed: %m");
+	i_free(conn);
+}
+
+static void director_connection_timeout(struct director_connection *conn)
+{
+	director_connection_deinit(&conn);
+}
+
+void director_connection_send(struct director_connection *conn,
+			      const char *data)
+{
+	unsigned int len = strlen(data);
+	off_t ret;
+
+	if (conn->output->closed || !conn->connected)
+		return;
+
+	ret = o_stream_send(conn->output, data, len);
+	if (ret != (off_t)len) {
+		if (ret < 0)
+			i_error("director(%s): write() failed: %m", conn->name);
+		else {
+			i_error("director(%s): Output buffer full, "
+				"disconnecting", conn->name);
+		}
+		o_stream_close(conn->output);
+		conn->to = timeout_add(0, director_connection_timeout, conn);
+	}
+}
+
+void director_connection_send_except(struct director_connection *conn,
+				     struct director_host *skip_host,
+				     const char *data)
+{
+	if (conn->host != skip_host)
+		director_connection_send(conn, data);
+}
+
+static void director_connection_ping_timeout(struct director_connection *conn)
+{
+	i_error("director(%s): Ping timed out, disconnecting", conn->name);
+	director_connection_deinit(&conn);
+}
+
+static void director_connection_ping(struct director_connection *conn)
+{
+	if (conn->to_ping != NULL)
+		return;
+
+	conn->to_ping = timeout_add(DIRECTOR_CONNECTION_PING_TIMEOUT_MSECS,
+				    director_connection_ping_timeout, conn);
+	director_connection_send(conn, "PING\n");
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/director/director-connection.h	Wed May 19 09:56:49 2010 +0200
@@ -0,0 +1,20 @@
+#ifndef DIRECTOR_CONNECTION_H
+#define DIRECTOR_CONNECTION_H
+
+struct director_host;
+struct director;
+
+struct director_connection *
+director_connection_init_in(struct director *dir, int fd);
+struct director_connection *
+director_connection_init_out(struct director *dir, int fd,
+			     struct director_host *host);
+void director_connection_deinit(struct director_connection **conn);
+
+void director_connection_send(struct director_connection *conn,
+			      const char *data);
+void director_connection_send_except(struct director_connection *conn,
+				     struct director_host *skip_host,
+				     const char *data);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/director/director-host.c	Wed May 19 09:56:49 2010 +0200
@@ -0,0 +1,138 @@
+/* Copyright (c) 2010 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "director.h"
+#include "director-host.h"
+
+static int director_host_cmp(const struct director_host *b1,
+			     const struct director_host *b2)
+{
+	int ret;
+
+	ret = net_ip_cmp(&b1->ip, &b2->ip);
+	if (ret != 0)
+		return ret;
+	return (int)b1->port - (int)b2->port;
+}
+
+static int director_host_cmp_p(struct director_host *const *host1,
+			       struct director_host *const *host2)
+{
+	return director_host_cmp(*host1, *host2);
+}
+
+struct director_host *
+director_host_add(struct director *dir,
+		  const struct ip_addr *ip, unsigned int port)
+{
+	struct director_host *host;
+
+	host = i_new(struct director_host, 1);
+	host->ip = *ip;
+	host->port = port;
+	host->name = i_strdup_printf("%s:%u", net_ip2addr(ip), port);
+
+	array_append(&dir->dir_hosts, &host, 1);
+
+	/* there are few enough directors that sorting after each
+	   addition should be fine */
+	array_sort(&dir->dir_hosts, director_host_cmp_p);
+	return host;
+}
+
+void director_host_free(struct director_host *host)
+{
+	i_free(host->name);
+	i_free(host);
+}
+
+struct director_host *
+director_host_get(struct director *dir, const struct ip_addr *ip,
+		  unsigned int port)
+{
+	struct director_host *host;
+
+	host = director_host_lookup(dir, ip, port);
+	if (host == NULL)
+		host = director_host_add(dir, ip, port);
+	return host;
+}
+
+struct director_host *
+director_host_lookup(struct director *dir, const struct ip_addr *ip,
+		     unsigned int port)
+{
+	struct director_host *const *hostp;
+
+	array_foreach(&dir->dir_hosts, hostp) {
+		if (net_ip_compare(&(*hostp)->ip, ip) &&
+		    (*hostp)->port == port)
+			return *hostp;
+	}
+	return NULL;
+}
+
+struct director_host *
+director_host_lookup_ip(struct director *dir, const struct ip_addr *ip)
+{
+	struct director_host *const *hostp;
+
+	array_foreach(&dir->dir_hosts, hostp) {
+		if (net_ip_compare(&(*hostp)->ip, ip))
+			return *hostp;
+	}
+	return NULL;
+}
+
+int director_host_cmp_to_self(const struct director_host *b1,
+			      const struct director_host *b2,
+			      const struct director_host *self)
+{
+	if (director_host_cmp(b1, self) < 0)
+		return director_host_cmp(b1, b2);
+	else
+		return director_host_cmp(b2, b1);
+}
+
+static void director_host_add_string(struct director *dir, const char *host)
+{
+	struct ip_addr *ips;
+	unsigned int i, port, ips_count;
+	const char *p;
+
+	p = strrchr(host, ':');
+	if (p != NULL) {
+		if (str_to_uint(p + 1, &port) < 0 || port == 0 || port > 65535)
+			i_fatal("Invalid director port in %s", host);
+		host = t_strdup_until(host, p);
+	} else {
+		port = dir->self_port;
+	}
+
+	if (net_gethostbyname(host, &ips, &ips_count) < 0)
+		i_fatal("Unknown director host: %s", host);
+
+	for (i = 0; i < ips_count; i++)
+		director_host_add(dir, &ips[i], port);
+}
+
+void director_host_add_from_string(struct director *dir, const char *hosts)
+{
+	T_BEGIN {
+		const char *const *tmp;
+
+		tmp = t_strsplit_spaces(hosts, " ");
+		for (; *tmp != NULL; tmp++)
+			director_host_add_string(dir, *tmp);
+	} T_END;
+
+	if (array_count(&dir->dir_hosts) == 0) {
+		/* standalone director */
+		struct ip_addr ip;
+
+		net_addr2ip("127.0.0.1", &ip);
+		dir->self_host = director_host_add(dir, &ip, 0);
+		dir->self_host->self = TRUE;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/director/director-host.h	Wed May 19 09:56:49 2010 +0200
@@ -0,0 +1,48 @@
+#ifndef DIRECTOR_HOST_H
+#define DIRECTOR_HOST_H
+
+#include "network.h"
+
+struct director;
+
+struct director_host {
+	struct ip_addr ip;
+	unsigned int port;
+
+	/* name contains "ip:port" */
+	char *name;
+
+	/* each command between directors contains an increasing sequence.
+	   if director A gets conflicting information about director B, it can
+	   trust the one that has the highest sequence. */
+	unsigned int last_seq;
+
+	/* we are this director */
+	unsigned int self:1;
+};
+
+struct director_host *
+director_host_add(struct director *dir, const struct ip_addr *ip,
+		  unsigned int port);
+void director_host_free(struct director_host *host);
+
+struct director_host *
+director_host_get(struct director *dir, const struct ip_addr *ip,
+		  unsigned int port);
+struct director_host *
+director_host_lookup(struct director *dir, const struct ip_addr *ip,
+		     unsigned int port);
+struct director_host *
+director_host_lookup_ip(struct director *dir, const struct ip_addr *ip);
+
+/* Returns -1 if b1 is more on our left side than b2, 1 if b2 is,
+   0 if they equal. */
+int director_host_cmp_to_self(const struct director_host *b1,
+			      const struct director_host *b2,
+			      const struct director_host *self);
+
+/* Parse hosts list (e.g. "host1:port host2 host3:port") and them as
+   directors */
+void director_host_add_from_string(struct director *dir, const char *hosts);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/director/director-request.c	Wed May 19 09:56:49 2010 +0200
@@ -0,0 +1,106 @@
+/* Copyright (c) 2010 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "array.h"
+#include "mail-host.h"
+#include "user-directory.h"
+#include "director.h"
+#include "director-request.h"
+
+#define DIRECTOR_REQUEST_TIMEOUT_SECS 30
+
+struct director_request {
+	struct director *dir;
+
+	time_t create_time;
+	unsigned int username_hash;
+
+	director_request_callback *callback;
+	void *context;
+};
+
+static void director_request_timeout(struct director *dir)
+{
+	struct director_request **requestp, *request;
+
+	while (array_count(&dir->pending_requests) > 0) {
+		requestp = array_idx_modifiable(&dir->pending_requests, 0);
+		request = *requestp;
+
+		if (request->create_time +
+		    DIRECTOR_REQUEST_TIMEOUT_SECS > ioloop_time)
+			break;
+
+		array_delete(&dir->pending_requests, 0, 1);
+		request->callback(NULL, request->context);
+		i_free(request);
+	}
+
+	if (array_count(&dir->pending_requests) == 0 && dir->to_request != NULL)
+		timeout_remove(&dir->to_request);
+}
+
+void director_request(struct director *dir, const char *username,
+		      director_request_callback *callback, void *context)
+{
+	struct director_request *request;
+	unsigned int username_hash = user_directory_get_username_hash(username);
+
+	request = i_new(struct director_request, 1);
+	request->dir = dir;
+	request->create_time = ioloop_time;
+	request->username_hash = username_hash;
+	request->callback = callback;
+	request->context = context;
+
+	if (director_request_continue(request))
+		return;
+
+	/* need to queue it */
+	if (dir->to_request == NULL) {
+		dir->to_request =
+			timeout_add(DIRECTOR_REQUEST_TIMEOUT_SECS * 1000,
+				    director_request_timeout, dir);
+	}
+	array_append(&dir->pending_requests, &request, 1);
+}
+
+bool director_request_continue(struct director_request *request)
+{
+	struct director *dir = request->dir;
+	struct mail_host *host;
+	struct user *user;
+
+	if (!dir->ring_handshaked) {
+		/* delay requests until ring handshaking is complete */
+		if (!dir->ring_handshake_warning_sent) {
+			i_warning("Delaying connections until all "
+				  "directors have connected");
+			dir->ring_handshake_warning_sent = TRUE;
+		}
+		return FALSE;
+	}
+
+	user = user_directory_lookup(dir->users, request->username_hash);
+	if (user != NULL)
+		user_directory_refresh(dir->users, user);
+	else {
+		if (array_count(&dir->desynced_host_changes) != 0) {
+			/* delay adding new users until ring is again synced */
+			return FALSE;
+		}
+		host = mail_host_get_by_hash(request->username_hash);
+		if (host == NULL) {
+			/* all hosts have been removed */
+			return FALSE;
+		}
+		user = user_directory_add(dir->users, request->username_hash,
+					  host, ioloop_time);
+	}
+
+	director_update_user(dir, dir->self_host, user);
+	request->callback(&user->host->ip, request->context);
+	i_free(request);
+	return TRUE;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/director/director-request.h	Wed May 19 09:56:49 2010 +0200
@@ -0,0 +1,14 @@
+#ifndef DIRECTOR_REQUEST_H
+#define DIRECTOR_REQUEST_H
+
+struct director;
+struct director_request;
+
+typedef void
+director_request_callback(const struct ip_addr *ip, void *context);
+
+void director_request(struct director *dir, const char *username,
+		      director_request_callback *callback, void *context);
+bool director_request_continue(struct director_request *request);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/director/director-settings.c	Wed May 19 09:56:49 2010 +0200
@@ -0,0 +1,81 @@
+/* Copyright (c) 2009-2010 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "buffer.h"
+#include "settings-parser.h"
+#include "service-settings.h"
+#include "director-settings.h"
+
+/* <settings checks> */
+static struct file_listener_settings director_unix_listeners_array[] = {
+	{ "login/director", 0, "", "" },
+	{ "director-admin", 0600, "", "" }
+};
+static struct file_listener_settings *director_unix_listeners[] = {
+	&director_unix_listeners_array[0],
+	&director_unix_listeners_array[1]
+};
+static buffer_t director_unix_listeners_buf = {
+	director_unix_listeners,
+	sizeof(director_unix_listeners), { 0, }
+};
+/* </settings checks> */
+
+struct service_settings director_service_settings = {
+	.name = "director",
+	.protocol = "",
+	.type = "",
+	.executable = "director",
+	.user = "$default_internal_user",
+	.group = "",
+	.privileged_group = "",
+	.extra_groups = "",
+	.chroot = "",
+
+	.drop_priv_before_exec = FALSE,
+
+	.process_min_avail = 0,
+	.process_limit = 1,
+	.client_limit = 0,
+	.service_count = 0,
+	.vsz_limit = -1U,
+
+	.unix_listeners = { { &director_unix_listeners_buf,
+			      sizeof(director_unix_listeners[0]) } },
+	.fifo_listeners = ARRAY_INIT,
+	.inet_listeners = ARRAY_INIT
+};
+#undef DEF
+#define DEF(type, name) \
+	{ type, #name, offsetof(struct director_settings, name), NULL }
+
+static const struct setting_define director_setting_defines[] = {
+	DEF(SET_STR, base_dir),
+	DEF(SET_STR, master_user_separator),
+
+	DEF(SET_STR, director_servers),
+	DEF(SET_STR, director_mail_servers),
+	DEF(SET_TIME, director_user_expire),
+
+	SETTING_DEFINE_LIST_END
+};
+
+const struct director_settings director_default_settings = {
+	.base_dir = PKG_RUNDIR,
+	.master_user_separator = "",
+
+	.director_servers = "",
+	.director_mail_servers = "",
+	.director_user_expire = 60*15
+};
+
+const struct setting_parser_info director_setting_parser_info = {
+	.module_name = "director",
+	.defines = director_setting_defines,
+	.defaults = &director_default_settings,
+
+	.type_offset = (size_t)-1,
+	.struct_size = sizeof(struct director_settings),
+
+	.parent_offset = (size_t)-1
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/director/director-settings.h	Wed May 19 09:56:49 2010 +0200
@@ -0,0 +1,15 @@
+#ifndef DIRECTOR_SETTINGS_H
+#define DIRECTOR_SETTINGS_H
+
+struct director_settings {
+	const char *base_dir;
+	const char *master_user_separator;
+
+	const char *director_servers;
+	const char *director_mail_servers;
+	unsigned int director_user_expire;
+};
+
+extern const struct setting_parser_info director_setting_parser_info;
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/director/director.c	Wed May 19 09:56:49 2010 +0200
@@ -0,0 +1,209 @@
+/* Copyright (c) 2010 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "array.h"
+#include "str.h"
+#include "user-directory.h"
+#include "mail-host.h"
+#include "director-host.h"
+#include "director-connection.h"
+#include "director.h"
+
+static bool director_is_self_ip_set(struct director *dir)
+{
+	struct ip_addr ip;
+
+	net_get_ip_any4(&ip);
+	if (net_ip_compare(&dir->self_ip, &ip))
+		return FALSE;
+
+	net_get_ip_any6(&ip);
+	if (net_ip_compare(&dir->self_ip, &ip))
+		return FALSE;
+
+	return TRUE;
+}
+
+static void director_find_self_ip(struct director *dir)
+{
+	struct director_host *const *hosts;
+	unsigned int i, count;
+	int fd = -1;
+
+	hosts = array_get(&dir->dir_hosts, &count);
+	for (i = 0; i < count; i++) {
+		fd = net_connect_ip(&hosts[i]->ip, hosts[i]->port, NULL);
+		if (fd != -1)
+			break;
+	}
+
+	if (fd == -1) {
+		i_fatal("Couldn't connect to any servers listed in "
+			"director_servers (we should have been able to "
+			"connect at least to ourself)");
+	}
+
+	if (net_getsockname(fd, &dir->self_ip, NULL) < 0)
+		i_fatal("getsockname() failed: %m");
+	net_disconnect(fd);
+}
+
+static void director_find_self(struct director *dir)
+{
+	if (dir->self_host != NULL)
+		return;
+
+	if (!director_is_self_ip_set(dir)) {
+		/* our IP isn't known yet. have to connect to some other
+		   server before we know it. */
+		director_find_self_ip(dir);
+	}
+
+	dir->self_host = director_host_lookup(dir, &dir->self_ip,
+					      dir->self_port);
+	if (dir->self_host == NULL) {
+		i_fatal("director_servers doesn't list ourself (%s:%u)",
+			net_ip2addr(&dir->self_ip), dir->self_port);
+	}
+	dir->self_host->self = TRUE;
+}
+
+static unsigned int director_find_self_idx(struct director *dir)
+{
+	struct director_host *const *hosts;
+	unsigned int i, count;
+
+	i_assert(dir->self_host != NULL);
+
+	hosts = array_get(&dir->dir_hosts, &count);
+	for (i = 0; i < count; i++) {
+		if (hosts[i] == dir->self_host)
+			return i;
+	}
+	i_unreached();
+}
+
+int director_connect_host(struct director *dir, struct director_host *host)
+{
+	int fd;
+
+	i_assert(dir->right == NULL);
+
+	fd = net_connect_ip(&host->ip, host->port, NULL);
+	if (fd == -1) {
+		i_error("connect(%s) failed: %m", host->name);
+		return -1;
+	}
+
+	dir->right = director_connection_init_out(dir, fd, host);
+	return 1;
+}
+
+void director_connect(struct director *dir)
+{
+	struct director_host *const *hosts;
+	unsigned int i, count, self_idx;
+
+	director_find_self(dir);
+	self_idx = director_find_self_idx(dir);
+
+	/* try to connect to first working server on our right side.
+	   the left side is supposed to connect to us. */
+	hosts = array_get(&dir->dir_hosts, &count);
+	for (i = 1; i < count; i++) {
+		unsigned int idx = (self_idx + i) % count;
+
+		if (director_connect_host(dir, hosts[idx]) > 0)
+			break;
+	}
+	if (i == count) {
+		/* we're the only one */
+		dir->ring_handshaked = TRUE;
+		director_set_state_changed(dir);
+	}
+}
+
+void director_update_host(struct director *dir, struct director_host *src,
+			  struct mail_host *host)
+{
+	director_set_state_changed(dir);
+
+	director_update_send(dir, src, t_strdup_printf(
+		"HOST\t%s\t%u\n", net_ip2addr(&host->ip), host->vhost_count));
+}
+
+void director_remove_host(struct director *dir, struct director_host *src,
+			  struct mail_host *host)
+{
+	director_update_send(dir, src, t_strdup_printf(
+		"HOST-REMOVE\t%s\n", net_ip2addr(&host->ip)));
+	user_directory_remove_host(dir->users, host);
+	mail_host_remove(host);
+}
+
+void director_update_user(struct director *dir, struct director_host *src,
+			  struct user *user)
+{
+	director_update_send(dir, src, t_strdup_printf(
+		"USER\t%u\t%s\n", user->username_hash,
+		net_ip2addr(&user->host->ip)));
+}
+
+void director_set_state_changed(struct director *dir)
+{
+	dir->state_change_callback(dir);
+}
+
+void director_update_send(struct director *dir, struct director_host *src,
+			  const char *cmd)
+{
+	i_assert(src != NULL);
+
+	if (dir->left != NULL)
+		director_connection_send_except(dir->left, src, cmd);
+	if (dir->right != NULL && dir->right != dir->left)
+		director_connection_send_except(dir->right, src, cmd);
+}
+
+struct director *
+director_init(const struct director_settings *set,
+	      const struct ip_addr *listen_ip, unsigned int listen_port,
+	      director_state_change_callback_t *callback)
+{
+	struct director *dir;
+
+	dir = i_new(struct director, 1);
+	dir->set = set;
+	dir->self_port = listen_port;
+	dir->self_ip = *listen_ip;
+	dir->state_change_callback = callback;
+	i_array_init(&dir->dir_hosts, 16);
+	i_array_init(&dir->pending_requests, 16);
+	i_array_init(&dir->desynced_host_changes, 16);
+	dir->users = user_directory_init(set->director_user_expire);
+	return dir;
+}
+
+void director_deinit(struct director **_dir)
+{
+	struct director *dir = *_dir;
+	struct director_host *const *hostp;
+
+	*_dir = NULL;
+
+	if (dir->left != NULL)
+		director_connection_deinit(&dir->left);
+	if (dir->right != NULL)
+		director_connection_deinit(&dir->right);
+
+	user_directory_deinit(&dir->users);
+	if (dir->to_request != NULL)
+		timeout_remove(&dir->to_request);
+	array_foreach(&dir->dir_hosts, hostp)
+		director_host_free(*hostp);
+	array_free(&dir->desynced_host_changes);
+	array_free(&dir->pending_requests);
+	array_free(&dir->dir_hosts);
+	i_free(dir);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/director/director.h	Wed May 19 09:56:49 2010 +0200
@@ -0,0 +1,88 @@
+#ifndef DIRECTOR_H
+#define DIRECTOR_H
+
+#include "network.h"
+#include "director-settings.h"
+
+struct director;
+struct mail_host;
+struct user;
+
+typedef void director_state_change_callback_t(struct director *dir);
+
+struct director_host_change {
+	/* originating director for this change. keep ip/port here separately,
+	   because by the time its sync comes, the director itself may have
+	   already been removed. */
+	struct ip_addr ip;
+	unsigned int port;
+	/* highest change sequence from this director */
+	unsigned int seq;
+};
+
+struct director {
+	const struct director_settings *set;
+
+	/* IP and port of this director. self_host->ip/port must equal these. */
+	struct ip_addr self_ip;
+	unsigned int self_port;
+
+	struct director_host *self_host;
+	struct director_connection *left, *right;
+
+	/* temporary user -> host associations */
+	struct user_directory *users;
+
+	/* these requests are waiting for directors to be in synced */
+	ARRAY_DEFINE(pending_requests, struct director_request *);
+	struct timeout *to_request;
+
+	director_state_change_callback_t *state_change_callback;
+
+	/* director hosts are sorted by IP (and port) */
+	ARRAY_DEFINE(dir_hosts, struct director_host *);
+
+	/* this array contains host changes done by directors.
+	   while it's non-empty, new user mappings can't be added, because
+	   different directors may see different hosts. SYNC events remove
+	   these changes. */
+	ARRAY_DEFINE(desynced_host_changes, struct director_host_change);
+
+	unsigned int sync_seq;
+
+	/* director ring handshaking is complete.
+	   director can start serving clients. */
+	unsigned int ring_handshaked:1;
+	unsigned int ring_handshake_warning_sent:1;
+};
+
+/* Create a new director. If listen_ip specifies an actual IP, it's used with
+   listen_port for finding ourself from the director_servers setting.
+   listen_port is used regardless by director_host_add_from_string() for hosts
+   without specified port. */
+struct director *
+director_init(const struct director_settings *set,
+	      const struct ip_addr *listen_ip, unsigned int listen_port,
+	      director_state_change_callback_t *callback);
+void director_deinit(struct director **dir);
+
+/* Start connecting to other directors */
+void director_connect(struct director *dir);
+
+void director_set_state_changed(struct director *dir);
+
+void director_update_host(struct director *dir, struct director_host *src,
+			  struct mail_host *host);
+void director_remove_host(struct director *dir, struct director_host *src,
+			  struct mail_host *host);
+void director_update_user(struct director *dir, struct director_host *src,
+			  struct user *user);
+
+/* Send data to all directors using both left and right connections
+   (unless they're the same). */
+void director_update_send(struct director *dir, struct director_host *src,
+			  const char *data);
+
+int director_connect_host(struct director *dir, struct director_host *host);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/director/doveadm-connection.c	Wed May 19 09:56:49 2010 +0200
@@ -0,0 +1,193 @@
+/* Copyright (c) 2010 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "network.h"
+#include "istream.h"
+#include "ostream.h"
+#include "array.h"
+#include "str.h"
+#include "llist.h"
+#include "user-directory.h"
+#include "mail-host.h"
+#include "director.h"
+#include "director-host.h"
+#include "director-request.h"
+#include "doveadm-connection.h"
+
+#include <unistd.h>
+
+#define DOVEADM_HANDSHAKE_EXPECTED "VERSION\tdirector-doveadm\t1\t"
+#define DOVEADM_HANDSHAKE DOVEADM_HANDSHAKE_EXPECTED"0\n"
+
+#define MAX_VALID_VHOST_COUNT 1000
+
+struct doveadm_connection {
+	struct doveadm_connection *prev, *next;
+
+	int fd;
+	struct io *io;
+	struct istream *input;
+	struct ostream *output;
+	struct director *dir;
+
+	unsigned int handshaked:1;
+};
+
+static struct doveadm_connection *doveadm_connections;
+
+static void doveadm_connection_deinit(struct doveadm_connection **_conn);
+
+static void doveadm_cmd_host_list(struct doveadm_connection *conn)
+{
+	struct mail_host *const *hostp;
+	string_t *str = t_str_new(1024);
+
+	array_foreach(mail_hosts_get(), hostp) {
+		str_printfa(str, "%s\t%u\t%u\n",
+			    net_ip2addr(&(*hostp)->ip), (*hostp)->vhost_count,
+			    (*hostp)->user_count);
+	}
+	str_append_c(str, '\n');
+	o_stream_send(conn->output, str_data(str), str_len(str));
+}
+
+static void doveadm_cmd_director_list(struct doveadm_connection *conn)
+{
+	struct director_host *const *hostp;
+	string_t *str = t_str_new(1024);
+
+	array_foreach(&conn->dir->dir_hosts, hostp) {
+		str_printfa(str, "%s\t%u\n",
+			    net_ip2addr(&(*hostp)->ip), (*hostp)->port);
+	}
+	str_append_c(str, '\n');
+	o_stream_send(conn->output, str_data(str), str_len(str));
+}
+
+static bool
+doveadm_cmd_host_set(struct doveadm_connection *conn, const char *line)
+{
+	const char *const *args;
+	struct mail_host *host;
+	struct ip_addr ip;
+	unsigned int vhost_count = -1U;
+
+	args = t_strsplit(line, "\t");
+	if (args[0] == NULL ||
+	    net_addr2ip(args[0], &ip) < 0 ||
+	    (args[1] != NULL && str_to_uint(args[1], &vhost_count) < 0)) {
+		i_error("doveadm sent invalid HOST-SET parameters");
+		return FALSE;
+	}
+	if (vhost_count > MAX_VALID_VHOST_COUNT && vhost_count != -1U) {
+		o_stream_send_str(conn->output, "vhost count too large\n");
+		return TRUE;
+	}
+	host = mail_host_lookup(&ip);
+	if (host == NULL)
+		host = mail_host_add_ip(&ip);
+	if (vhost_count != -1U)
+		mail_host_set_vhost_count(host, vhost_count);
+	director_update_host(conn->dir, conn->dir->self_host, host);
+
+	o_stream_send(conn->output, "OK\n", 3);
+	return TRUE;
+}
+
+static bool
+doveadm_cmd_host_remove(struct doveadm_connection *conn, const char *line)
+{
+	struct mail_host *host;
+	struct ip_addr ip;
+
+	if (net_addr2ip(line, &ip) < 0) {
+		i_error("doveadm sent invalid HOST-SET parameters");
+		return FALSE;
+	}
+	host = mail_host_lookup(&ip);
+	if (host == NULL)
+		o_stream_send_str(conn->output, "NOTFOUND\n");
+	else {
+		director_remove_host(conn->dir, conn->dir->self_host, host);
+		o_stream_send(conn->output, "OK\n", 3);
+	}
+	return TRUE;
+}
+
+static void doveadm_connection_input(struct doveadm_connection *conn)
+{
+	const char *line;
+	bool ret = TRUE;
+
+	if (!conn->handshaked) {
+		if ((line = i_stream_read_next_line(conn->input)) == NULL)
+			return;
+
+		if (strncmp(line, DOVEADM_HANDSHAKE_EXPECTED,
+			    strlen(DOVEADM_HANDSHAKE_EXPECTED)) != 0) {
+			i_error("doveadm not compatible with this server "
+				"(mixed old and new binaries?)");
+			doveadm_connection_deinit(&conn);
+			return;
+		}
+		conn->handshaked = TRUE;
+	}
+
+	while ((line = i_stream_read_next_line(conn->input)) != NULL && ret) {
+		if (strcmp(line, "HOST-LIST") == 0)
+			doveadm_cmd_host_list(conn);
+		else if (strcmp(line, "DIRECTOR-LIST") == 0)
+			doveadm_cmd_director_list(conn);
+		else if (strncmp(line, "HOST-SET\t", 9) == 0)
+			ret = doveadm_cmd_host_set(conn, line + 9);
+		else if (strncmp(line, "HOST-REMOVE\t", 12) == 0)
+			ret = doveadm_cmd_host_remove(conn, line + 12);
+		else {
+			i_error("doveadm sent unknown command: %s", line);
+			ret = FALSE;
+		}
+	}
+	if (conn->input->eof || conn->input->stream_errno != 0 || !ret)
+		doveadm_connection_deinit(&conn);
+}
+
+struct doveadm_connection *
+doveadm_connection_init(struct director *dir, int fd)
+{
+	struct doveadm_connection *conn;
+
+	conn = i_new(struct doveadm_connection, 1);
+	conn->fd = fd;
+	conn->dir = dir;
+	conn->input = i_stream_create_fd(conn->fd, 1024, FALSE);
+	conn->output = o_stream_create_fd(conn->fd, (size_t)-1, FALSE);
+	conn->io = io_add(conn->fd, IO_READ, doveadm_connection_input, conn);
+	o_stream_send_str(conn->output, DOVEADM_HANDSHAKE);
+
+	DLLIST_PREPEND(&doveadm_connections, conn);
+	return conn;
+}
+
+static void doveadm_connection_deinit(struct doveadm_connection **_conn)
+{
+	struct doveadm_connection *conn = *_conn;
+
+	*_conn = NULL;
+
+	DLLIST_REMOVE(&doveadm_connections, conn);
+	io_remove(&conn->io);
+	o_stream_unref(&conn->output);
+	if (close(conn->fd) < 0)
+		i_error("close(doveadm connection) failed: %m");
+	i_free(conn);
+}
+
+void doveadm_connections_deinit(void)
+{
+	while (doveadm_connections != NULL) {
+		struct doveadm_connection *conn = doveadm_connections;
+
+		doveadm_connection_deinit(&conn);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/director/doveadm-connection.h	Wed May 19 09:56:49 2010 +0200
@@ -0,0 +1,10 @@
+#ifndef DOVEADM_CONNECTION_H
+#define DOVEADM_CONNECTION_H
+
+struct director;
+
+struct doveadm_connection *
+doveadm_connection_init(struct director *dir, int fd);
+void doveadm_connections_deinit(void);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/director/login-connection.c	Wed May 19 09:56:49 2010 +0200
@@ -0,0 +1,211 @@
+/* Copyright (c) 2010 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "network.h"
+#include "ostream.h"
+#include "llist.h"
+#include "director.h"
+#include "director-request.h"
+#include "auth-connection.h"
+#include "login-connection.h"
+
+#include <unistd.h>
+
+struct login_connection {
+	struct login_connection *prev, *next;
+
+	int refcount;
+
+	int fd;
+	struct io *io;
+	struct ostream *output;
+	struct auth_connection *auth;
+	struct director *dir;
+
+	unsigned int destroyed:1;
+};
+
+struct login_host_request {
+	struct login_connection *conn;
+	char *line;
+};
+
+static struct login_connection *login_connections;
+
+static void login_connection_unref(struct login_connection **_conn);
+
+static void login_connection_input(struct login_connection *conn)
+{
+	unsigned char buf[4096];
+	ssize_t ret;
+
+	ret = read(conn->fd, buf, sizeof(buf));
+	if (ret <= 0) {
+		if (ret < 0) {
+			if (errno == EAGAIN)
+				return;
+			i_error("read(login connection) failed: %m");
+		}
+		login_connection_deinit(&conn);
+		return;
+	}
+	auth_connection_send(conn->auth, buf, ret);
+}
+
+static void
+login_connection_send_line(struct login_connection *conn, const char *line)
+{
+	struct const_iovec iov[2];
+
+	if (conn->destroyed)
+		return;
+
+	iov[0].iov_base = line;
+	iov[0].iov_len = strlen(line);
+	iov[1].iov_base = "\n";
+	iov[1].iov_len = 1;
+	(void)o_stream_sendv(conn->output, iov, N_ELEMENTS(iov));
+}
+
+static void login_host_callback(const struct ip_addr *ip, void *context)
+{
+	struct login_host_request *request = context;
+	const char *line;
+
+	T_BEGIN {
+		if (ip != NULL) {
+			line = t_strconcat(request->line, "\thost=",
+					   net_ip2addr(ip), NULL);
+		} else {
+			i_assert(strncmp(request->line, "OK\t", 3) == 0);
+			line = t_strconcat("FAIL\t",
+					   t_strcut(request->line + 3, '\t'),
+					   NULL);
+		}
+		login_connection_send_line(request->conn, line);
+	} T_END;
+
+	login_connection_unref(&request->conn);
+	i_free(request->line);
+	i_free(request);
+}
+
+static void auth_input_line(const char *line, void *context)
+{
+	struct login_connection *conn = context;
+	struct login_host_request *request;
+	const char *const *args, *username = NULL;
+	bool proxy = FALSE, host = FALSE;
+
+	if (line == NULL) {
+		/* auth connection died -> kill also this login connection */
+		login_connection_deinit(&conn);
+		return;
+	}
+	if (strncmp(line, "OK\t", 3) != 0) {
+		login_connection_send_line(conn, line);
+		return;
+	}
+
+	/* OK <id> [<parameters>] */
+	args = t_strsplit(line + 3, "\t");
+	if (*args != NULL) {
+		/* we should always get here, but in case we don't just
+		   forward as-is and let login process handle the error. */
+		args++;
+	}
+
+	for (; *args != NULL; args++) {
+		if (strncmp(*args, "proxy", 5) == 0 &&
+		    ((*args)[5] == '=' || (*args)[5] == '\0'))
+			proxy = TRUE;
+		else if (strncmp(*args, "host=", 5) == 0)
+			host = TRUE;
+		else if (strncmp(*args, "destuser=", 9) == 0)
+			username = *args + 9;
+		else if (strncmp(*args, "user=", 5) == 0) {
+			if (username == NULL)
+				username = *args + 5;
+		}
+	}
+	if (*conn->dir->set->master_user_separator != '\0') {
+		/* with master user logins we still want to use only the
+		   login username */
+		username = t_strcut(username,
+				    *conn->dir->set->master_user_separator);
+	}
+
+	if (!proxy || host || username == NULL) {
+		login_connection_send_line(conn, line);
+		return;
+	}
+
+	/* we need to add the host. the lookup might be asynchronous */
+	request = i_new(struct login_host_request, 1);
+	request->conn = conn;
+	request->line = i_strdup(line);
+
+	conn->refcount++;
+	director_request(conn->dir, username, login_host_callback, request);
+}
+
+struct login_connection *
+login_connection_init(struct director *dir, int fd,
+		      struct auth_connection *auth)
+{
+	struct login_connection *conn;
+
+	conn = i_new(struct login_connection, 1);
+	conn->refcount = 1;
+	conn->fd = fd;
+	conn->auth = auth;
+	conn->dir = dir;
+	conn->output = o_stream_create_fd(conn->fd, (size_t)-1, FALSE);
+	conn->io = io_add(conn->fd, IO_READ, login_connection_input, conn);
+
+	auth_connection_set_callback(conn->auth, auth_input_line, conn);
+	DLLIST_PREPEND(&login_connections, conn);
+	return conn;
+}
+
+void login_connection_deinit(struct login_connection **_conn)
+{
+	struct login_connection *conn = *_conn;
+
+	*_conn = NULL;
+
+	if (conn->destroyed)
+		return;
+	conn->destroyed = TRUE;
+
+	DLLIST_REMOVE(&login_connections, conn);
+	io_remove(&conn->io);
+	o_stream_unref(&conn->output);
+	if (close(conn->fd) < 0)
+		i_error("close(login connection) failed: %m");
+	conn->fd = -1;
+
+	auth_connection_deinit(&conn->auth);
+	login_connection_unref(&conn);
+}
+
+static void login_connection_unref(struct login_connection **_conn)
+{
+	struct login_connection *conn = *_conn;
+
+	*_conn = NULL;
+
+	i_assert(conn->refcount > 0);
+	if (--conn->refcount == 0)
+		i_free(conn);
+}
+
+void login_connections_deinit(void)
+{
+	while (login_connections != NULL) {
+		struct login_connection *conn = login_connections;
+
+		login_connection_deinit(&conn);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/director/login-connection.h	Wed May 19 09:56:49 2010 +0200
@@ -0,0 +1,13 @@
+#ifndef LOGIN_CONNECTION_H
+#define LOGIN_CONNECTION_H
+
+struct director;
+
+struct login_connection *
+login_connection_init(struct director *dir, int fd,
+		      struct auth_connection *auth);
+void login_connection_deinit(struct login_connection **conn);
+
+void login_connections_deinit(void);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/director/mail-host.c	Wed May 19 09:56:49 2010 +0200
@@ -0,0 +1,182 @@
+/* Copyright (c) 2010 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "mail-host.h"
+
+#define VHOST_MULTIPLIER 100
+
+static ARRAY_TYPE(mail_host) hosts;
+static ARRAY_DEFINE(vhosts, struct mail_host *);
+static bool hosts_unsorted;
+
+static int
+mail_host_cmp(struct mail_host *const *h1, struct mail_host *const *h2)
+{
+	return net_ip_cmp(&(*h1)->ip, &(*h2)->ip);
+}
+
+static void mail_hosts_sort(void)
+{
+	struct mail_host *const *hostp;
+	unsigned int i;
+
+	array_sort(&hosts, mail_host_cmp);
+
+	/* rebuild vhosts */
+	array_clear(&vhosts);
+	array_foreach(&hosts, hostp) {
+		for (i = 0; i < (*hostp)->vhost_count; i++)
+			array_append(&vhosts, hostp, 1);
+	}
+	hosts_unsorted = FALSE;
+}
+
+struct mail_host *mail_host_add_ip(const struct ip_addr *ip)
+{
+	struct mail_host *host;
+
+	host = i_new(struct mail_host, 1);
+	host->vhost_count = VHOST_MULTIPLIER;
+	host->ip = *ip;
+	array_append(&hosts, &host, 1);
+
+	hosts_unsorted = TRUE;
+	return host;
+}
+
+static int mail_host_add(const char *host)
+{
+	struct ip_addr ip;
+
+	if (net_addr2ip(host, &ip) < 0) {
+		i_error("Invalid IP address: %s", host);
+		return -1;
+	}
+
+	mail_host_add_ip(&ip);
+	return 0;
+}
+
+static int mail_hosts_add_range(const char *host1, const char *host2)
+{
+	struct ip_addr ip1, ip2;
+
+	if (net_addr2ip(host1, &ip1) < 0) {
+		i_error("Invalid IP address: %s", host1);
+		return -1;
+	}
+	if (net_addr2ip(host2, &ip2) < 0) {
+		i_error("Invalid IP address: %s", host2);
+		return -1;
+	}
+
+	// FIXME
+
+	return 0;
+}
+
+int mail_hosts_parse_and_add(const char *hosts_list)
+{
+	int ret = 0;
+
+	T_BEGIN {
+		const char *const *tmp, *p;
+
+		tmp = t_strsplit_spaces(hosts_list, " ");
+		for (; *tmp != NULL; tmp++) {
+			p = strchr(*tmp, '-');
+			if (p == NULL) {
+				if (mail_host_add(*tmp) < 0)
+					ret = -1;
+			} else if (mail_hosts_add_range(t_strdup_until(*tmp, p),
+							p + 1) < 0)
+				ret = -1;
+		}
+	} T_END;
+
+	if (array_count(&hosts) == 0) {
+		if (ret < 0)
+			i_error("No valid servers specified");
+		else
+			i_error("Empty server list");
+		ret = -1;
+	}
+	return ret;
+}
+
+void mail_host_set_vhost_count(struct mail_host *host,
+			       unsigned int vhost_count)
+{
+	host->vhost_count = vhost_count;
+	mail_hosts_sort();
+}
+
+void mail_host_remove(struct mail_host *host)
+{
+	struct mail_host *const *h;
+	unsigned int i, count;
+
+	h = array_get(&hosts, &count);
+	for (i = 0; i < count; i++) {
+		if (h[i] == host) {
+			array_delete(&hosts, i, 1);
+			break;
+		}
+	}
+
+	i_free(host);
+	mail_hosts_sort();
+}
+
+struct mail_host *mail_host_lookup(const struct ip_addr *ip)
+{
+	struct mail_host *const *hostp;
+
+	if (hosts_unsorted)
+		mail_hosts_sort();
+
+	array_foreach(&hosts, hostp) {
+		if (net_ip_compare(&(*hostp)->ip, ip))
+			return *hostp;
+	}
+	return NULL;
+}
+
+struct mail_host *mail_host_get_by_hash(unsigned int hash)
+{
+	struct mail_host *const *v;
+	unsigned int count;
+
+	if (hosts_unsorted)
+		mail_hosts_sort();
+
+	v = array_get(&vhosts, &count);
+	if (count == 0)
+		return NULL;
+
+	return v[hash % count];
+}
+
+const ARRAY_TYPE(mail_host) *mail_hosts_get(void)
+{
+	if (hosts_unsorted)
+		mail_hosts_sort();
+	return &hosts;
+}
+
+void mail_hosts_init(void)
+{
+	i_array_init(&hosts, 16);
+	i_array_init(&vhosts, 16*VHOST_MULTIPLIER);
+}
+
+void mail_hosts_deinit(void)
+{
+	struct mail_host **hostp;
+
+	array_foreach_modifiable(&hosts, hostp)
+		i_free(*hostp);
+	array_free(&hosts);
+	array_free(&vhosts);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/director/mail-host.h	Wed May 19 09:56:49 2010 +0200
@@ -0,0 +1,28 @@
+#ifndef MAIL_HOST_H
+#define MAIL_HOST_H
+
+#include "network.h"
+
+struct mail_host {
+	unsigned int user_count;
+	unsigned int vhost_count;
+
+	struct ip_addr ip;
+};
+ARRAY_DEFINE_TYPE(mail_host, struct mail_host *);
+
+struct mail_host *mail_host_add_ip(const struct ip_addr *ip);
+struct mail_host *mail_host_lookup(const struct ip_addr *ip);
+struct mail_host *mail_host_get_by_hash(unsigned int hash);
+
+int mail_hosts_parse_and_add(const char *hosts_list);
+void mail_host_set_vhost_count(struct mail_host *host,
+			       unsigned int vhost_count);
+void mail_host_remove(struct mail_host *host);
+
+const ARRAY_TYPE(mail_host) *mail_hosts_get(void);
+
+void mail_hosts_init(void);
+void mail_hosts_deinit(void);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/director/main.c	Wed May 19 09:56:49 2010 +0200
@@ -0,0 +1,178 @@
+/* Copyright (c) 2010 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "array.h"
+#include "restrict-access.h"
+#include "master-interface.h"
+#include "master-service.h"
+#include "master-service-settings.h"
+#include "auth-connection.h"
+#include "doveadm-connection.h"
+#include "login-connection.h"
+#include "director.h"
+#include "director-host.h"
+#include "director-connection.h"
+#include "director-request.h"
+#include "mail-host.h"
+
+#include <unistd.h>
+
+#define AUTH_SOCKET_PATH "login/login"
+
+static struct director *director;
+static char *auth_socket_path;
+
+static int director_client_connected(int fd, const struct ip_addr *ip)
+{
+	if (director_host_lookup_ip(director, ip) == NULL) {
+		i_warning("Connection from %s: Server not listed in "
+			  "director_servers, dropping", net_ip2addr(ip));
+		return -1;
+	}
+
+	director_connection_init_in(director, fd);
+	return 0;
+}
+
+static void client_connected(const struct master_service_connection *conn)
+{
+	struct auth_connection *auth;
+	const char *path, *name;
+	struct ip_addr ip;
+	unsigned int port, len;
+
+	if (net_getpeername(conn->fd, &ip, &port) == 0 &&
+	    (IPADDR_IS_V4(&ip) || IPADDR_IS_V6(&ip))) {
+		/* TCP/IP connection - this is another director */
+		if (director_client_connected(conn->fd, &ip) < 0)
+			(void)close(conn->fd);
+		return;
+	}
+
+	if (net_getunixname(conn->listen_fd, &path) < 0)
+		i_fatal("getsockname(%d) failed: %m", conn->listen_fd);
+
+	name = strrchr(path, '/');
+	if (name == NULL)
+		name = path;
+	else
+		name++;
+
+	len = strlen(name);
+	if (len > 6 && strcmp(name + len - 6, "-admin") == 0) {
+		/* doveadm connection */
+		(void)doveadm_connection_init(director, conn->fd);
+	} else {
+		/* login connection */
+		auth = auth_connection_init(auth_socket_path);
+		if (auth_connection_connect(auth) == 0)
+			login_connection_init(director, conn->fd, auth);
+		else {
+			(void)close(conn->fd);
+			auth_connection_deinit(&auth);
+		}
+	}
+}
+
+static unsigned int find_inet_listener_port(struct ip_addr *ip_r)
+{
+	unsigned int i, socket_count, port;
+
+	socket_count = master_service_get_socket_count(master_service);
+	for (i = 0; i < socket_count; i++) {
+		int fd = MASTER_LISTEN_FD_FIRST + i;
+
+		if (net_getsockname(fd, ip_r, &port) == 0 && port > 0)
+			return port;
+	}
+	return 0;
+}
+
+static void director_state_changed(struct director *dir)
+{
+	struct director_request *const *requestp;
+	bool ret;
+
+	if (!dir->ring_handshaked ||
+	    array_count(&dir->desynced_host_changes) != 0 ||
+	    mail_host_get_by_hash(0) == NULL)
+		return;
+
+	/* if there are any pending client requests, finish them now */
+	array_foreach(&dir->pending_requests, requestp) {
+		ret = director_request_continue(*requestp);
+		i_assert(ret);
+	}
+	array_clear(&dir->pending_requests);
+
+	if (dir->to_request != NULL)
+		timeout_remove(&dir->to_request);
+}
+
+static void main_init(void)
+{
+	const struct director_settings *set;
+	struct ip_addr listen_ip;
+	unsigned int listen_port;
+
+	set = master_service_settings_get_others(master_service)[0];
+
+	auth_socket_path = i_strconcat(set->base_dir,
+				       "/"AUTH_SOCKET_PATH, NULL);
+
+	mail_hosts_init();
+	if (mail_hosts_parse_and_add(set->director_mail_servers) < 0)
+		i_fatal("Invalid value for director_mail_servers setting");
+
+	listen_port = find_inet_listener_port(&listen_ip);
+	if (listen_port == 0 && *set->director_servers != '\0') {
+		i_fatal("No inet_listeners defined for director service "
+			"(for standalone keep director_servers empty)");
+	}
+
+	director = director_init(set, &listen_ip, listen_port,
+				 director_state_changed);
+	director_host_add_from_string(director, set->director_servers);
+
+	director_connect(director);
+}
+
+static void main_deinit(void)
+{
+	director_deinit(&director);
+	doveadm_connections_deinit();
+	login_connections_deinit();
+	auth_connections_deinit();
+	mail_hosts_deinit();
+	i_free(auth_socket_path);
+}
+
+int main(int argc, char *argv[])
+{
+	const struct setting_parser_info *set_roots[] = {
+		&director_setting_parser_info,
+		NULL
+	};
+	const char *error;
+
+	master_service = master_service_init("director", 0, &argc, &argv, NULL);
+	if (master_getopt(master_service) > 0)
+		return FATAL_DEFAULT;
+	if (master_service_settings_read_simple(master_service, set_roots,
+						&error) < 0)
+		i_fatal("Error reading configuration: %s", error);
+
+	master_service_init_log(master_service, "director: ");
+
+	restrict_access_by_env(NULL, FALSE);
+	restrict_access_allow_coredumps(TRUE);
+	master_service_init_finish(master_service);
+
+	main_init();
+	master_service_run(master_service, client_connected);
+	main_deinit();
+
+	master_service_deinit(&master_service);
+        return 0;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/director/user-directory.c	Wed May 19 09:56:49 2010 +0200
@@ -0,0 +1,191 @@
+/* Copyright (c) 2010 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "array.h"
+#include "md5.h"
+#include "hash.h"
+#include "llist.h"
+#include "mail-host.h"
+#include "user-directory.h"
+
+#define MAX_CLOCK_DRIFT_SECS 2
+
+struct user_directory_iter {
+	struct user_directory *dir;
+	struct user *pos;
+};
+
+struct user_directory {
+	/* const char *username => struct user* */
+	struct hash_table *hash;
+	/* sorted by time */
+	struct user *head, *tail;
+
+	ARRAY_DEFINE(iters, struct user_directory_iter *);
+
+	unsigned int timeout_secs;
+};
+
+static void user_move_iters(struct user_directory *dir, struct user *user)
+{
+	struct user_directory_iter *const *iterp;
+
+	array_foreach(&dir->iters, iterp) {
+		if ((*iterp)->pos == user)
+			(*iterp)->pos = user->next;
+	}
+}
+
+static void user_free(struct user_directory *dir, struct user *user)
+{
+	i_assert(user->host->user_count > 0);
+	user->host->user_count--;
+
+	user_move_iters(dir, user);
+
+	hash_table_remove(dir->hash, POINTER_CAST(user->username_hash));
+	DLLIST2_REMOVE(&dir->head, &dir->tail, user);
+	i_free(user);
+}
+
+static void user_directory_drop_expired(struct user_directory *dir)
+{
+	while (dir->head != NULL &&
+	       ioloop_time > dir->head->timestamp + dir->timeout_secs)
+		user_free(dir, dir->head);
+}
+
+struct user *user_directory_lookup(struct user_directory *dir,
+				   unsigned int username_hash)
+{
+	user_directory_drop_expired(dir);
+
+	return hash_table_lookup(dir->hash, POINTER_CAST(username_hash));
+}
+
+struct user *
+user_directory_add(struct user_directory *dir, unsigned int username_hash,
+		   struct mail_host *host, time_t timestamp)
+{
+	struct user *user;
+
+	user = i_new(struct user, 1);
+	user->username_hash = username_hash;
+	user->host = host;
+	user->host->user_count++;
+	user->timestamp = timestamp;
+	DLLIST2_APPEND(&dir->head, &dir->tail, user);
+
+	hash_table_insert(dir->hash, POINTER_CAST(user->username_hash), user);
+	return user;
+}
+
+void user_directory_refresh(struct user_directory *dir, struct user *user)
+{
+	user_move_iters(dir, user);
+
+	user->timestamp = ioloop_time;
+	DLLIST2_REMOVE(&dir->head, &dir->tail, user);
+	DLLIST2_APPEND(&dir->head, &dir->tail, user);
+}
+
+void user_directory_remove_host(struct user_directory *dir,
+				struct mail_host *host)
+{
+	struct user *user, *next;
+
+	for (user = dir->head; user != NULL; user = next) {
+		next = user->next;
+
+		if (user->host == host)
+			user_free(dir, user);
+	}
+}
+
+unsigned int user_directory_get_username_hash(const char *username)
+{
+	unsigned char md5[MD5_RESULTLEN];
+	unsigned int i, hash = 0;
+
+	md5_get_digest(username, strlen(username), md5);
+	for (i = 0; i < sizeof(hash); i++)
+		hash = (hash << CHAR_BIT) | md5[i];
+	return hash;
+}
+
+bool user_directory_user_has_connections(struct user_directory *dir,
+					 struct user *user)
+{
+	return user->timestamp +
+		dir->timeout_secs - MAX_CLOCK_DRIFT_SECS >= ioloop_time;
+}
+
+struct user_directory *user_directory_init(unsigned int timeout_secs)
+{
+	struct user_directory *dir;
+
+	dir = i_new(struct user_directory, 1);
+	dir->timeout_secs = timeout_secs;
+	dir->hash = hash_table_create(default_pool, default_pool,
+				      0, NULL, NULL);
+	i_array_init(&dir->iters, 8);
+	return dir;
+}
+
+void user_directory_deinit(struct user_directory **_dir)
+{
+	struct user_directory *dir = *_dir;
+
+	*_dir = NULL;
+
+	i_assert(array_count(&dir->iters) == 0);
+
+	while (dir->head != NULL)
+		user_free(dir, dir->head);
+	hash_table_destroy(&dir->hash);
+	array_free(&dir->iters);
+	i_free(dir);
+}
+
+struct user_directory_iter *
+user_directory_iter_init(struct user_directory *dir)
+{
+	struct user_directory_iter *iter;
+
+	iter = i_new(struct user_directory_iter, 1);
+	iter->dir = dir;
+	iter->pos = dir->head;
+	array_append(&dir->iters, &iter, 1);
+	return iter;
+}
+
+struct user *user_directory_iter_next(struct user_directory_iter *iter)
+{
+	struct user *user;
+
+	user = iter->pos;
+	if (user == NULL)
+		return FALSE;
+
+	iter->pos = user->next;
+	return user;
+}
+
+void user_directory_iter_deinit(struct user_directory_iter **_iter)
+{
+	struct user_directory_iter *iter = *_iter;
+	struct user_directory_iter *const *iters;
+	unsigned int i, count;
+
+	*_iter = NULL;
+
+	iters = array_get(&iter->dir->iters, &count);
+	for (i = 0; i < count; i++) {
+		if (iters[i] == iter) {
+			array_delete(&iter->dir->iters, i, 1);
+			break;
+		}
+	}
+	i_free(iter);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/director/user-directory.h	Wed May 19 09:56:49 2010 +0200
@@ -0,0 +1,47 @@
+#ifndef USER_DIRECTORY_H
+#define USER_DIRECTORY_H
+
+struct user {
+	/* sorted by time */
+	struct user *prev, *next;
+
+	/* first 32 bits of MD5(username). collisions are quite unlikely, but
+	   even if they happen it doesn't matter - the users are just
+	   redirected to same server */
+	unsigned int username_hash;
+	unsigned int timestamp;
+
+	struct mail_host *host;
+};
+
+/* Create a new directory. Users are dropped if their time gets older
+   than timeout_secs. */
+struct user_directory *user_directory_init(unsigned int timeout_secs);
+void user_directory_deinit(struct user_directory **dir);
+
+/* Look up username from directory. Returns NULL if not found. */
+struct user *user_directory_lookup(struct user_directory *dir,
+				   unsigned int username_hash);
+/* Add a user to directory and return it. */
+struct user *
+user_directory_add(struct user_directory *dir, unsigned int username_hash,
+		   struct mail_host *host, time_t timestamp);
+/* Refresh user's timestamp */
+void user_directory_refresh(struct user_directory *dir, struct user *user);
+
+/* Remove all users that have pointers to given host */
+void user_directory_remove_host(struct user_directory *dir,
+				struct mail_host *host);
+
+unsigned int user_directory_get_username_hash(const char *username);
+
+/* Returns TRUE if user still potentially has connections. */
+bool user_directory_user_has_connections(struct user_directory *dir,
+					 struct user *user);
+
+struct user_directory_iter *
+user_directory_iter_init(struct user_directory *dir);
+struct user *user_directory_iter_next(struct user_directory_iter *iter);
+void user_directory_iter_deinit(struct user_directory_iter **iter);
+
+#endif
--- a/src/doveadm/Makefile.am	Wed May 19 09:52:36 2010 +0200
+++ b/src/doveadm/Makefile.am	Wed May 19 09:56:49 2010 +0200
@@ -45,6 +45,7 @@
 doveadm_SOURCES = \
 	doveadm.c \
 	doveadm-auth.c \
+	doveadm-director.c \
 	doveadm-dump.c \
 	doveadm-dump-index.c \
 	doveadm-dump-log.c \
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/doveadm/doveadm-director.c	Wed May 19 09:56:49 2010 +0200
@@ -0,0 +1,208 @@
+/* Copyright (c) 2009-2010 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "network.h"
+#include "istream.h"
+#include "doveadm.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+struct director_context {
+	const char *socket_path;
+	struct istream *input;
+};
+
+extern struct doveadm_cmd doveadm_cmd_director[];
+
+static void
+director_send(struct director_context *ctx, const char *data)
+{
+	if (write(i_stream_get_fd(ctx->input), data, strlen(data)) < 0)
+		i_fatal("write(%s) failed: %m", ctx->socket_path);
+}
+
+static void director_connect(struct director_context *ctx)
+{
+#define DIRECTOR_HANDSHAKE_EXPECTED "VERSION\tdirector-doveadm\t1\t"
+#define DIRECTOR_HANDSHAKE DIRECTOR_HANDSHAKE_EXPECTED"0\n"
+	const char *line;
+	int fd;
+
+	fd = net_connect_unix(ctx->socket_path);
+	if (fd == -1)
+		i_fatal("net_connect_unix(%s) failed: %m", ctx->socket_path);
+	net_set_nonblock(fd, FALSE);
+
+	ctx->input = i_stream_create_fd(fd, (size_t)-1, TRUE);
+	director_send(ctx, DIRECTOR_HANDSHAKE);
+
+	line = i_stream_read_next_line(ctx->input);
+	if (line == NULL)
+		i_fatal("%s disconnected", ctx->socket_path);
+	if (strncmp(line, DIRECTOR_HANDSHAKE_EXPECTED,
+		    strlen(DIRECTOR_HANDSHAKE_EXPECTED)) != 0) {
+		i_fatal("%s not a compatible director-doveadm socket",
+			ctx->socket_path);
+	}
+}
+
+static void director_disconnect(struct director_context *ctx)
+{
+	if (ctx->input->stream_errno != 0)
+		i_fatal("read(%s) failed: %m", ctx->socket_path);
+	i_stream_destroy(&ctx->input);
+}
+
+static struct director_context *
+cmd_director_init(int argc, char *argv[], unsigned int cmd_idx)
+{
+	struct director_context *ctx;
+	int c;
+
+	ctx = t_new(struct director_context, 1);
+	ctx->socket_path = PKG_RUNDIR"/director-admin";
+
+	while ((c = getopt(argc, argv, "a:")) > 0) {
+		switch (c) {
+		case 'a':
+			ctx->socket_path = optarg;
+			break;
+		default:
+			help(&doveadm_cmd_director[cmd_idx]);
+		}
+	}
+	director_connect(ctx);
+	return ctx;
+}
+
+static void cmd_director_status(int argc, char *argv[])
+{
+	struct director_context *ctx;
+	const char *line, *const *args;
+
+	ctx = cmd_director_init(argc, argv, 0);
+	fprintf(stderr, "%-20s vhosts  users\n", "mail server ip");
+	director_send(ctx, "HOST-LIST\n");
+	while ((line = i_stream_read_next_line(ctx->input)) != NULL) {
+		if (*line == '\0')
+			break;
+		T_BEGIN {
+			args = t_strsplit(line, "\t");
+			if (str_array_length(args) >= 3) {
+				printf("%-20s %6s %6s\n",
+				       args[0], args[1], args[2]);
+			}
+		} T_END;
+	}
+	director_disconnect(ctx);
+}
+
+static void cmd_director_add(int argc, char *argv[])
+{
+	struct director_context *ctx;
+	struct ip_addr *ips;
+	unsigned int i, ips_count, vhost_count = -1U;
+	struct ip_addr ip;
+	const char *host, *cmd, *line;
+
+	ctx = cmd_director_init(argc, argv, 0);
+	host = argv[optind++];
+	if (host == NULL)
+		help(&doveadm_cmd_director[1]);
+	if (argv[optind] != NULL) {
+		if (str_to_uint(argv[optind++], &vhost_count) < 0)
+			help(&doveadm_cmd_director[1]);
+	}
+	if (argv[optind] != NULL)
+		help(&doveadm_cmd_director[1]);
+
+	if (net_addr2ip(host, &ip) == 0) {
+		ips = &ip;
+		ips_count = 1;
+	} else {
+		if (net_gethostbyname(host, &ips, &ips_count) < 0)
+			i_fatal("gethostname(%s) failed: %m", host);
+	}
+
+	for (i = 0; i < ips_count; i++) {
+		cmd = vhost_count == -1U ?
+			t_strdup_printf("HOST-SET\t%s\n",
+					net_ip2addr(&ips[i])) :
+			t_strdup_printf("HOST-SET\t%s\t%u\n",
+					net_ip2addr(&ips[i]), vhost_count);
+		director_send(ctx, cmd);
+	}
+	for (i = 0; i < ips_count; i++) {
+		line = i_stream_read_next_line(ctx->input);
+		if (line == NULL || strcmp(line, "OK") != 0) {
+			fprintf(stderr, "%s: %s\n", net_ip2addr(&ips[i]),
+				line == NULL ? "failed" : line);
+		} else if (doveadm_verbose) {
+			printf("%s: OK\n", net_ip2addr(&ips[i]));
+		}
+	}
+	if (i != ips_count)
+		i_fatal("director add failed");
+	director_disconnect(ctx);
+}
+
+static void cmd_director_remove(int argc, char *argv[])
+{
+	struct director_context *ctx;
+	struct ip_addr *ips;
+	unsigned int i, ips_count;
+	struct ip_addr ip;
+	const char *host, *line;
+
+	ctx = cmd_director_init(argc, argv, 0);
+	host = argv[optind++];
+	if (host == NULL || argv[optind] != NULL)
+		help(&doveadm_cmd_director[2]);
+
+	if (net_addr2ip(host, &ip) == 0) {
+		ips = &ip;
+		ips_count = 1;
+	} else {
+		if (net_gethostbyname(host, &ips, &ips_count) < 0)
+			i_fatal("gethostname(%s) failed: %m", host);
+	}
+
+	for (i = 0; i < ips_count; i++) {
+		director_send(ctx,
+			t_strdup_printf("HOST-REMOVE\t%s\n", net_ip2addr(&ip)));
+	}
+	for (i = 0; i < ips_count; i++) {
+		line = i_stream_read_next_line(ctx->input);
+		if (line == NULL || strcmp(line, "OK") != 0) {
+			fprintf(stderr, "%s: %s\n", net_ip2addr(&ips[i]),
+				line == NULL ? "failed" :
+				(strcmp(line, "NOTFOUND") == 0 ?
+				 "doesn't exist" : line));
+		} else if (doveadm_verbose) {
+			printf("%s: removed\n", net_ip2addr(&ips[i]));
+		}
+	}
+	if (i != ips_count)
+		i_fatal("director remove failed");
+	director_disconnect(ctx);
+}
+
+struct doveadm_cmd doveadm_cmd_director[] = {
+	{ cmd_director_status, "director status",
+	  "[-a <director socket path>]", NULL },
+	{ cmd_director_add, "director add",
+	  "[-a <director socket path>] <host> [<vhost count>]", NULL },
+	{ cmd_director_remove, "director remove",
+	  "[-a <director socket path>] <host>", NULL }
+};
+
+
+void doveadm_register_director_commands(void)
+{
+	unsigned int i;
+
+	for (i = 0; i < N_ELEMENTS(doveadm_cmd_director); i++)
+		doveadm_register_cmd(&doveadm_cmd_director[i]);
+}
--- a/src/doveadm/doveadm.c	Wed May 19 09:52:36 2010 +0200
+++ b/src/doveadm/doveadm.c	Wed May 19 09:56:49 2010 +0200
@@ -209,6 +209,7 @@
 	i_array_init(&doveadm_cmds, 32);
 	for (i = 0; i < N_ELEMENTS(doveadm_commands); i++)
 		doveadm_register_cmd(doveadm_commands[i]);
+	doveadm_register_director_commands();
 	doveadm_mail_init();
 	doveadm_load_modules();
 
--- a/src/doveadm/doveadm.h	Wed May 19 09:52:36 2010 +0200
+++ b/src/doveadm/doveadm.h	Wed May 19 09:56:49 2010 +0200
@@ -29,5 +29,6 @@
 void help(const struct doveadm_cmd *cmd);
 
 const char *unixdate2str(time_t timestamp);
+void doveadm_register_director_commands(void);
 
 #endif