src/config/doveconf
src/lda/dovecot-lda
src/dict/dict
+src/director/director
src/dns/dns-client
src/doveadm/doveadm
src/dsync/dsync
src/log/Makefile
src/lmtp/Makefile
src/dict/Makefile
+src/director/Makefile
src/dns/Makefile
src/imap/Makefile
src/imap-login/Makefile
--- /dev/null
+##
+## 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
+}
lmtp \
log \
config \
+ director \
util \
doveadm \
dsync \
--- /dev/null
+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
+/* 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
+#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
+/* 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], ×tamp) < 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
+#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
+/* 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
+#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
+/* 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
+#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
+/* 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
+#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
+/* 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
+#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
+/* 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
+#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
+/* 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
+#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
+/* 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
+#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
+/* 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
+/* 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
+#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
doveadm_SOURCES = \
doveadm.c \
doveadm-auth.c \
+ doveadm-director.c \
doveadm-dump.c \
doveadm-dump-index.c \
doveadm-dump-log.c \
--- /dev/null
+/* 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]);
+}
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();
void help(const struct doveadm_cmd *cmd);
const char *unixdate2str(time_t timestamp);
+void doveadm_register_director_commands(void);
#endif