changeset 5887:3f2eb1b9c555 HEAD

Support listening multiple sockets. SIGHUP also doesn't anymore recreate listener sockets.
author Timo Sirainen <tss@iki.fi>
date Tue, 03 Jul 2007 20:04:28 +0300
parents 86f22b84e008
children a4cd1f37d022
files dovecot-example.conf src/login-common/main.c src/master/Makefile.am src/master/dup2-array.c src/master/dup2-array.h src/master/listener.c src/master/listener.h src/master/login-process.c src/master/main.c src/master/master-login-interface.h src/master/master-settings.c src/master/master-settings.h
diffstat 12 files changed, 594 insertions(+), 326 deletions(-) [+]
line wrap: on
line diff
--- a/dovecot-example.conf	Tue Jul 03 18:34:31 2007 +0300
+++ b/dovecot-example.conf	Tue Jul 03 20:04:28 2007 +0300
@@ -22,10 +22,10 @@
 # If you only want to use dovecot-auth, you can set this to "none".
 #protocols = imap imaps
 
-# IP or host address where to listen in for connections. It's not currently
-# possible to specify multiple addresses. "*" listens in all IPv4 interfaces.
-# "[::]" listens in all IPv6 interfaces, but may also listen in all IPv4
-# interfaces depending on the operating system.
+# A space separated list of IP or host addresses where to listen in for
+# connections. "*" listens in all IPv4 interfaces. "[::]" listens in all IPv6
+# interfaces, but may also listen in all IPv4 interfaces depending on the
+# operating system.
 #
 # If you want to specify ports for each service, you will need to configure
 # these settings inside the protocol imap/pop3 { ... } section, so you can
--- a/src/login-common/main.c	Tue Jul 03 18:34:31 2007 +0300
+++ b/src/login-common/main.c	Tue Jul 03 20:04:28 2007 +0300
@@ -2,6 +2,7 @@
 
 #include "common.h"
 #include "ioloop.h"
+#include "array.h"
 #include "lib-signals.h"
 #include "randgen.h"
 #include "restrict-access.h"
@@ -29,10 +30,12 @@
 
 static const char *process_name;
 static struct ioloop *ioloop;
-static struct io *io_listen, *io_ssl_listen;
 static int main_refcount;
 static bool is_inetd, listening;
 
+static ARRAY_DEFINE(listen_ios, struct io *);
+static unsigned int listen_count, ssl_listen_count;
+
 void main_ref(void)
 {
 	main_refcount++;
@@ -71,14 +74,15 @@
 	io_loop_stop(ioloop);
 }
 
-static void login_accept(void *context __attr_unused__)
+static void login_accept(void *context)
 {
+	int listen_fd = POINTER_CAST_TO(context, int);
 	struct ip_addr remote_ip, local_ip;
 	unsigned int remote_port, local_port;
 	struct client *client;
 	int fd;
 
-	fd = net_accept(LOGIN_LISTEN_FD, &remote_ip, &remote_port);
+	fd = net_accept(listen_fd, &remote_ip, &remote_port);
 	if (fd < 0) {
 		if (fd < -1)
 			i_error("accept() failed: %m");
@@ -100,15 +104,16 @@
 	}
 }
 
-static void login_accept_ssl(void *context __attr_unused__)
+static void login_accept_ssl(void *context)
 {
+	int listen_fd = POINTER_CAST_TO(context, int);
 	struct ip_addr remote_ip, local_ip;
 	unsigned int remote_port, local_port;
 	struct client *client;
 	struct ssl_proxy *proxy;
 	int fd, fd_ssl;
 
-	fd = net_accept(LOGIN_SSL_LISTEN_FD, &remote_ip, &remote_port);
+	fd = net_accept(listen_fd, &remote_ip, &remote_port);
 	if (fd < 0) {
 		if (fd < -1)
 			i_error("accept() failed: %m");
@@ -138,7 +143,9 @@
 
 void main_listen_start(void)
 {
-	unsigned int current_count;
+	struct io *io;
+	unsigned int i, current_count;
+	int cur_fd;
 
 	if (listening)
 		return;
@@ -156,21 +163,17 @@
 		return;
 	}
 
-	if (net_getsockname(LOGIN_LISTEN_FD, NULL, NULL) == 0) {
-		io_listen = io_add(LOGIN_LISTEN_FD, IO_READ,
-				   login_accept, NULL);
+	cur_fd = LOGIN_MASTER_SOCKET_FD + 1;
+	i_array_init(&listen_ios, listen_count + ssl_listen_count);
+	for (i = 0; i < listen_count; i++, cur_fd++) {
+		io = io_add(cur_fd, IO_READ, login_accept,
+			    POINTER_CAST(cur_fd));
+		array_append(&listen_ios, &io, 1);
 	}
-
-	if (net_getsockname(LOGIN_SSL_LISTEN_FD, NULL, NULL) == 0) {
-		if (!ssl_initialized) {
-			/* this shouldn't happen, master should have
-			   disabled the ssl socket.. */
-			i_fatal("BUG: SSL initialization parameters not given "
-				"while they should have been");
-		}
-
-		io_ssl_listen = io_add(LOGIN_SSL_LISTEN_FD, IO_READ,
-				       login_accept_ssl, NULL);
+	for (i = 0; i < ssl_listen_count; i++, cur_fd++) {
+		io = io_add(cur_fd, IO_READ, login_accept_ssl,
+			    POINTER_CAST(cur_fd));
+		array_append(&listen_ios, &io, 1);
 	}
 	listening = TRUE;
 
@@ -181,30 +184,34 @@
 
 void main_listen_stop(void)
 {
+	struct io **ios;
+	unsigned int i, count;
+	int cur_fd;
+
 	if (!listening)
 		return;
 
-	listening = FALSE;
-	if (io_listen != NULL) {
-		io_remove(&io_listen);
-		if (closing_down) {
-			if (close(LOGIN_LISTEN_FD) < 0)
-				i_fatal("close(listen) failed: %m");
-		}
-	}
+	ios = array_get_modifiable(&listen_ios, &count);
+	for (i = 0; i < count; i++)
+		io_remove(&ios[i]);
+	array_free(&listen_ios);
 
-	if (io_ssl_listen != NULL) {
-		io_remove(&io_ssl_listen);
-		if (closing_down) {
-			if (close(LOGIN_SSL_LISTEN_FD) < 0)
-				i_fatal("close(ssl_listen) failed: %m");
+	if (closing_down) {
+		cur_fd = LOGIN_MASTER_SOCKET_FD + 1;
+		for (i = 0; i < count; i++, cur_fd++) {
+			if (close(cur_fd) < 0) {
+				i_fatal("close(listener %d) failed: %m",
+					cur_fd);
+			}
 		}
 	}
 
 	listening = FALSE;
-	master_notify_state_change(clients_get_count() == 0 ?
-				   LOGIN_STATE_FULL_LOGINS :
-				   LOGIN_STATE_FULL_PRELOGINS);
+	if (io_loop_is_running(ioloop)) {
+		master_notify_state_change(clients_get_count() == 0 ?
+					   LOGIN_STATE_FULL_LOGINS :
+					   LOGIN_STATE_FULL_PRELOGINS);
+	}
 }
 
 void connection_queue_add(unsigned int connection_count)
@@ -332,7 +339,18 @@
         auth_client_set_connect_notify(auth_client, auth_connect_notify, NULL);
 	clients_init();
 
-	io_listen = io_ssl_listen = NULL;
+	value = getenv("LISTEN_FDS");
+	listen_count = value == NULL ? 0 : atoi(value);
+
+	value = getenv("SSL_LISTEN_FDS");
+	ssl_listen_count = value == NULL ? 0 : atoi(value);
+
+	if (!ssl_initialized && ssl_listen_count > 0) {
+		/* this shouldn't happen, master should have
+		   disabled the ssl socket.. */
+		i_fatal("BUG: SSL initialization parameters not given "
+			"while they should have been");
+	}
 
 	if (!is_inetd) {
 		master_init(LOGIN_MASTER_SOCKET_FD);
@@ -342,8 +360,8 @@
 
 static void main_deinit(void)
 {
-	if (io_listen != NULL) io_remove(&io_listen);
-	if (io_ssl_listen != NULL) io_remove(&io_ssl_listen);
+	closing_down = TRUE;
+	main_listen_stop();
 
 	ssl_proxy_deinit();
 	login_proxy_deinit();
@@ -370,8 +388,17 @@
 	is_inetd = getenv("DOVECOT_MASTER") == NULL;
 
 #ifdef DEBUG
-	if (!is_inetd && getenv("GDB") == NULL)
-		fd_debug_verify_leaks(5, 1024);
+	if (!is_inetd && getenv("GDB") == NULL) {
+		const char *env;
+
+		i = LOGIN_MASTER_SOCKET_FD + 1;
+		env = getenv("LISTEN_FDS");
+		if (env != NULL) i += atoi(env);
+		env = getenv("SSL_LISTEN_FDS");
+		if (env != NULL) i += atoi(env);
+
+		fd_debug_verify_leaks(i + 1, 1024);
+	}
 #endif
 	/* clear all allocated memory before freeing it. this makes the login
 	   processes pretty safe to reuse for new connections since the
--- a/src/master/Makefile.am	Tue Jul 03 18:34:31 2007 +0300
+++ b/src/master/Makefile.am	Tue Jul 03 20:04:28 2007 +0300
@@ -24,6 +24,8 @@
 	capabilities-posix.c \
 	child-process.c \
 	dict-process.c \
+	dup2-array.c \
+	listener.c \
 	log.c \
 	login-process.c \
 	mail-process.c \
@@ -38,6 +40,8 @@
 	capabilities.h \
 	child-process.h \
 	dict-process.h \
+	dup2-array.h \
+	listener.h \
 	common.h \
 	log.h \
 	login-process.h \
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/master/dup2-array.c	Tue Jul 03 20:04:28 2007 +0300
@@ -0,0 +1,80 @@
+/* Copyright (C) 2007 Timo Sirainen */
+
+#include "lib.h"
+#include "array.h"
+#include "fd-close-on-exec.h"
+#include "dup2-array.h"
+
+#include <unistd.h>
+
+void dup2_append(ARRAY_TYPE(dup2) *dups, int fd_src, int fd_dest)
+{
+	struct dup2 d;
+
+	d.fd_src = fd_src;
+	d.fd_dest = fd_dest;
+	array_append(dups, &d, 1);
+}
+
+int dup2_array(ARRAY_TYPE(dup2) *dups_arr)
+{
+	struct dup2 *dups;
+	bool *moved, moves;
+	unsigned int i, j, count, conflict;
+	int fd;
+
+	dups = array_get_modifiable(dups_arr, &count);
+
+	t_push();
+	moved = t_new(bool, count);
+	for (;;) {
+		conflict = count;
+		moves = FALSE;
+		for (i = 0; i < count; i++) {
+			if (moved[i])
+				continue;
+
+			for (j = 0; j < count; j++) {
+				if (dups[j].fd_src == dups[i].fd_dest &&
+				    !moved[j]) {
+					conflict = j;
+					break;
+				}
+			}
+
+			if (j == count) {
+				/* no conflicts, move it */
+				moved[i] = TRUE;
+				moves = TRUE;
+				if (dup2(dups[i].fd_src, dups[i].fd_dest) < 0) {
+					i_error("dup2(%d, %d) failed: %m",
+						dups[i].fd_src,
+						dups[i].fd_dest);
+					t_pop();
+					return -1;
+				}
+			}
+		}
+		if (conflict == count)
+			break;
+
+		if (moves) {
+			/* it's possible that the conflicting fd was
+			   moved already. try again. */
+			continue;
+		}
+
+		/* ok, we have to dup() */
+		fd = dup(dups[conflict].fd_src);
+		if (fd == -1) {
+			i_error("dup(%d) failed: %m", dups[conflict].fd_src);
+			t_pop();
+			return -1;
+		}
+		fd_close_on_exec(fd, TRUE);
+                dups[conflict].fd_src = fd;
+	}
+	t_pop();
+	return 0;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/master/dup2-array.h	Tue Jul 03 20:04:28 2007 +0300
@@ -0,0 +1,13 @@
+#ifndef __DUP2_ARRAY_H
+#define __DUP2_ARRAY_H
+
+struct dup2 {
+	int fd_src, fd_dest;
+};
+ARRAY_DEFINE_TYPE(dup2, struct dup2);
+
+void dup2_append(ARRAY_TYPE(dup2) *dups, int fd_src, int fd_dest);
+
+int dup2_array(ARRAY_TYPE(dup2) *dups);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/master/listener.c	Tue Jul 03 20:04:28 2007 +0300
@@ -0,0 +1,366 @@
+/* Copyright (C) 2002-2007 Timo Sirainen */
+
+#include "common.h"
+#include "array.h"
+#include "ioloop.h"
+#include "fd-close-on-exec.h"
+#include "listener.h"
+
+#include <stdlib.h>
+#include <unistd.h>
+
+static void resolve_ip(const char *set_name, const char *name,
+		       struct ip_addr *ip, unsigned int *port)
+{
+	struct ip_addr *ip_list;
+	const char *p;
+	unsigned int ips_count;
+	int ret;
+
+	if (*name == '\0') {
+                /* defaults to "*" or "[::]" */
+		ip->family = 0;
+		return;
+	}
+
+	if (name[0] == '[') {
+		/* IPv6 address */
+		p = strchr(name, ']');
+		if (p == NULL) {
+			i_fatal("%s: Missing ']' in address %s",
+				set_name, name);
+		}
+		name = t_strdup_until(name+1, p);
+
+		p++;
+		if (*p == '\0')
+			p = NULL;
+		else if (*p != ':') {
+			i_fatal("%s: Invalid data after ']' in address %s",
+				set_name, name);
+		}
+	} else {
+		p = strrchr(name, ':');
+		if (p != NULL)
+			name = t_strdup_until(name, p);
+	}
+
+	if (p != NULL) {
+		if (!is_numeric(p+1, '\0')) {
+			i_fatal("%s: Invalid port in address %s",
+				set_name, name);
+		}
+		*port = atoi(p+1);
+	}
+
+	if (strcmp(name, "*") == 0) {
+		/* IPv4 any */
+		net_get_ip_any4(ip);
+		return;
+	}
+
+	if (strcmp(name, "::") == 0) {
+		/* IPv6 any */
+		net_get_ip_any6(ip);
+		return;
+	}
+
+	/* Return the first IP if there happens to be multiple. */
+	ret = net_gethostbyname(name, &ip_list, &ips_count);
+	if (ret != 0) {
+		i_fatal("%s: Can't resolve address %s: %s",
+			set_name, name, net_gethosterror(ret));
+	}
+
+	if (ips_count < 1)
+		i_fatal("%s: No IPs for address: %s", set_name, name);
+
+	*ip = ip_list[0];
+}
+
+static void
+check_conflicts_set(const struct settings *set, const struct ip_addr *ip,
+		    unsigned int port, const char *name1, const char *name2)
+{
+	const struct listener *listens = NULL;
+	unsigned int i, count;
+
+	if (array_is_created(&set->listens))
+		listens = array_get(&set->listens, &count);
+	else
+		count = 0;
+	for (i = 0; i < count; i++) {
+		if (listens[i].fd <= 0 || listens[i].port != port ||
+		    !net_ip_compare(&listens[i].ip, ip))
+			continue;
+
+		i_fatal("Protocols %s and %s are listening in same ip/port",
+			name1, name2);
+	}
+
+	if (array_is_created(&set->ssl_listens))
+		listens = array_get(&set->ssl_listens, &count);
+	else
+		count = 0;
+	for (i = 0; i < count; i++) {
+		if (listens[i].fd <= 0 || listens[i].port != port ||
+		    !net_ip_compare(&listens[i].ip, ip))
+			continue;
+
+		i_fatal("Protocols %ss and %ss are listening in same ip/port",
+			name1, name2);
+	}
+}
+
+static void check_conflicts(const struct ip_addr *ip, unsigned int port,
+			    const char *proto)
+{
+	struct server_settings *server;
+
+	for (server = settings_root; server != NULL; server = server->next) {
+		if (server->imap != NULL) {
+			check_conflicts_set(server->imap, ip, port,
+					    "imap", proto);
+		}
+		if (server->pop3 != NULL) {
+			check_conflicts_set(server->pop3, ip, port,
+					    "pop3", proto);
+		}
+	}
+}
+
+static void
+listener_init(const char *set_name, const char *listen,
+	      unsigned int default_port, ARRAY_TYPE(listener) *listens_arr)
+{
+	const char *const *tmp;
+	struct listener l, *listens;
+	unsigned int i, count;
+
+	if (!array_is_created(listens_arr))
+		i_array_init(listens_arr, 4);
+
+	listens = array_get_modifiable(listens_arr, &count);
+	for (i = 0; i < count; i++)
+		listens[i].wanted = FALSE;
+
+	memset(&l, 0, sizeof(l));
+	l.fd = -1;
+	l.wanted = TRUE;
+
+	t_push();
+	for (tmp = t_strsplit_spaces(listen, ", "); *tmp != NULL; tmp++) {
+		l.port = default_port;
+		resolve_ip(set_name, *tmp, &l.ip, &l.port);
+
+		/* see if it already exists */
+		for (i = 0; i < count; i++) {
+			if (listens[i].port == l.port &&
+			    net_ip_compare(&listens[i].ip, &l.ip)) {
+				listens[i].wanted = TRUE;
+				break;
+			}
+		}
+
+		if (i == count) {
+			array_append(listens_arr, &l, 1);
+			listens = array_get_modifiable(listens_arr, &count);
+		}
+	}
+
+	/* close unwanted fds */
+	for (i = 0; i < count; ) {
+		if (listens[i].wanted)
+			i++;
+		else {
+			if (listens[i].fd > 0) {
+				if (close(listens[i].fd) < 0)
+					i_error("close(listener) failed: %m");
+			}
+			array_delete(listens_arr, i, 1);
+			listens = array_get_modifiable(listens_arr, &count);
+		}
+	}
+	t_pop();
+}
+
+static void listener_close_fds(ARRAY_TYPE(listener) *listens_arr)
+{
+	const struct listener *listens;
+	unsigned int i, count;
+
+	if (!array_is_created(listens_arr))
+		return;
+
+	listens = array_get(listens_arr, &count);
+	for (i = 0; i < count; i++) {
+		if (listens[i].fd > 0) {
+			if (close(listens[i].fd) < 0)
+				i_error("close(listener) failed: %m");
+		}
+	}
+	array_free(listens_arr);
+}
+
+static void listen_parse_and_close_unneeded(struct settings *set)
+{
+	const char *const *proto;
+	unsigned int default_port;
+	bool listen = FALSE, ssl_listen = FALSE;
+
+	if (set == NULL)
+		return;
+
+	/* register wanted protocols */
+        proto = t_strsplit_spaces(set->protocols, " ");
+	for (; *proto != NULL; proto++) {
+		if (strcasecmp(*proto, "imap") == 0) {
+			if (set->protocol == MAIL_PROTOCOL_IMAP)
+				listen = TRUE;
+		} else if (strcasecmp(*proto, "imaps") == 0) {
+			if (set->protocol == MAIL_PROTOCOL_IMAP &&
+			    !set->ssl_disable)
+				ssl_listen = TRUE;
+		} else if (strcasecmp(*proto, "pop3") == 0) {
+			if (set->protocol == MAIL_PROTOCOL_POP3)
+				listen = TRUE;
+		} else if (strcasecmp(*proto, "pop3s") == 0) {
+			if (set->protocol == MAIL_PROTOCOL_POP3 &&
+			    !set->ssl_disable)
+				ssl_listen = TRUE;
+		}
+	}
+
+	if (!listen)
+		listener_close_fds(&set->listens);
+	else {
+		default_port = set->protocol == MAIL_PROTOCOL_IMAP ? 143 : 110;
+		listener_init("listen", set->listen, default_port,
+			      &set->listens);
+	}
+	if (!ssl_listen)
+		listener_close_fds(&set->ssl_listens);
+	else {
+		default_port = set->protocol == MAIL_PROTOCOL_IMAP ? 993 : 995;
+		listener_init("ssl_listen", set->ssl_listen, default_port,
+			      &set->ssl_listens);
+	}
+}
+
+static void listen_copy_old(struct settings *old_set, struct settings *new_set)
+{
+	if (old_set == NULL || new_set == NULL) {
+		if (old_set != NULL) {
+			listener_close_fds(&old_set->listens);
+			listener_close_fds(&old_set->ssl_listens);
+		}
+		return;
+	}
+
+	i_assert(!array_is_created(&new_set->listens));
+	i_assert(!array_is_created(&new_set->ssl_listens));
+
+	new_set->listens = old_set->listens;
+	new_set->ssl_listens = old_set->ssl_listens;
+
+	old_set->listens.arr.buffer = NULL;
+	old_set->ssl_listens.arr.buffer = NULL;
+}
+
+static void
+listener_array_listen_missing(const char *proto,
+			      ARRAY_TYPE(listener) *listens_arr, bool retry)
+{
+	struct listener *listens;
+	unsigned int i, j, count;
+
+	if (!array_is_created(listens_arr))
+		return;
+
+	listens = array_get_modifiable(listens_arr, &count);
+	for (i = 0; i < count; i++) {
+		if (listens[i].fd > 0)
+			continue;
+
+		for (j = 0; j < 10; j++) {
+			listens[i].fd =
+				net_listen(&listens[i].ip, &listens[i].port, 8);
+			if (listens[i].fd != -1)
+				break;
+
+			if (errno == EADDRINUSE) {
+				/* retry */
+			} else if (errno == EINTR &&
+				   io_loop_is_running(ioloop)) {
+				/* SIGHUPing sometimes gets us here.
+				   we don't want to die. */
+			} else {
+				/* error */
+				break;
+			}
+
+			check_conflicts(&listens[i].ip, listens[i].port, proto);
+			if (!retry)
+				break;
+
+			/* wait a while and try again. we're SIGHUPing
+			   so we most likely just closed it ourself.. */
+			sleep(1);
+		}
+
+		if (listens[i].fd == -1) {
+			i_fatal("listen(%s, %d) failed: %m",
+				net_ip2addr(&listens[i].ip), listens[i].port);
+		}
+		net_set_nonblock(listens[i].fd, TRUE);
+		fd_close_on_exec(listens[i].fd, TRUE);
+	}
+}
+
+static void
+listener_listen_missing(struct settings *set, const char *proto, bool retry)
+{
+	listener_array_listen_missing(proto, &set->listens, retry);
+	listener_array_listen_missing(t_strconcat(proto, "s", NULL),
+				      &set->ssl_listens, retry);
+}
+
+void listeners_open_fds(struct server_settings *old_set, bool retry)
+{
+	struct server_settings *server;
+
+	for (server = settings_root; server != NULL; server = server->next) {
+		if (old_set != NULL) {
+			listen_copy_old(old_set->imap, server->imap);
+			listen_copy_old(old_set->pop3, server->pop3);
+		}
+		listen_parse_and_close_unneeded(server->imap);
+		listen_parse_and_close_unneeded(server->pop3);
+
+		if (old_set != NULL)
+			old_set = old_set->next;
+	}
+
+	for (server = settings_root; server != NULL; server = server->next) {
+		if (server->imap != NULL)
+			listener_listen_missing(server->imap, "imap", retry);
+		if (server->pop3 != NULL)
+			listener_listen_missing(server->pop3, "pop3", retry);
+	}
+}
+
+void listeners_close_fds(void)
+{
+	struct server_settings *server;
+
+	for (server = settings_root; server != NULL; server = server->next) {
+		if (server->imap != NULL) {
+			listener_close_fds(&server->imap->listens);
+			listener_close_fds(&server->imap->ssl_listens);
+		}
+		if (server->pop3 != NULL) {
+			listener_close_fds(&server->pop3->listens);
+			listener_close_fds(&server->pop3->ssl_listens);
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/master/listener.h	Tue Jul 03 20:04:28 2007 +0300
@@ -0,0 +1,7 @@
+#ifndef __LISTENER_H
+#define __LISTENER_H
+
+void listeners_open_fds(struct server_settings *old_set, bool retry);
+void listeners_close_fds(void);
+
+#endif
--- a/src/master/login-process.c	Tue Jul 03 18:34:31 2007 +0300
+++ b/src/master/login-process.c	Tue Jul 03 20:04:28 2007 +0300
@@ -1,6 +1,7 @@
 /* Copyright (C) 2002 Timo Sirainen */
 
 #include "common.h"
+#include "array.h"
 #include "ioloop.h"
 #include "hash.h"
 #include "network.h"
@@ -10,6 +11,7 @@
 #include "env-util.h"
 #include "restrict-access.h"
 #include "restrict-process-size.h"
+#include "dup2-array.h"
 #include "login-process.h"
 #include "auth-process.h"
 #include "mail-process.h"
@@ -582,10 +584,13 @@
 static pid_t create_login_process(struct login_group *group)
 {
 	struct log_io *log;
+	const struct listener *listens;
 	unsigned int max_log_lines_per_sec;
 	const char *prefix;
 	pid_t pid;
-	int fd[2], log_fd;
+	ARRAY_TYPE(dup2) dups;
+	unsigned int i, listen_count = 0, ssl_listen_count = 0;
+	int fd[2], log_fd, cur_fd;
 
 	if (group->set->login_uid == 0)
 		i_fatal("Login process must not run as root");
@@ -632,36 +637,44 @@
 				 process_names[group->mail_process_type]);
 	log_set_prefix(log, prefix);
 
-	/* move the listen handle */
-	if (dup2(group->set->listen_fd, LOGIN_LISTEN_FD) < 0)
-		i_fatal("dup2(listen_fd) failed: %m");
-	fd_close_on_exec(LOGIN_LISTEN_FD, FALSE);
-
-	/* move the SSL listen handle */
-	if (dup2(group->set->ssl_listen_fd, LOGIN_SSL_LISTEN_FD) < 0)
-		i_fatal("dup2(ssl_listen_fd) failed: %m");
-	fd_close_on_exec(LOGIN_SSL_LISTEN_FD, FALSE);
-
-	/* move communication handle */
-	if (dup2(fd[1], LOGIN_MASTER_SOCKET_FD) < 0)
-		i_fatal("dup2(master) failed: %m");
-	fd_close_on_exec(LOGIN_MASTER_SOCKET_FD, FALSE);
-
-	if (dup2(log_fd, STDERR_FILENO) < 0)
-		i_fatal("dup2(stderr) failed: %m");
-	fd_close_on_exec(STDERR_FILENO, FALSE);
-
+	t_array_init(&dups, 16);
+	dup2_append(&dups, null_fd, STDIN_FILENO);
 	/* redirect writes to stdout also to error log. For example OpenSSL
 	   can be made to log its debug messages to stdout. */
-	if (dup2(log_fd, STDOUT_FILENO) < 0)
-		i_fatal("dup2(stdout) failed: %m");
-	fd_close_on_exec(STDOUT_FILENO, FALSE);
+	dup2_append(&dups, log_fd, STDOUT_FILENO);
+	dup2_append(&dups, log_fd, STDERR_FILENO);
+	dup2_append(&dups, fd[1], LOGIN_MASTER_SOCKET_FD);
+
+	/* redirect listener fds */
+	cur_fd = LOGIN_MASTER_SOCKET_FD + 1;
+	if (array_is_created(&group->set->listens)) {
+		listens = array_get(&group->set->listens, &listen_count);
+		for (i = 0; i < listen_count; i++, cur_fd++)
+			dup2_append(&dups, listens[i].fd, cur_fd);
+	}
+
+	if (array_is_created(&group->set->ssl_listens)) {
+		listens = array_get(&group->set->ssl_listens,
+				    &ssl_listen_count);
+		for (i = 0; i < ssl_listen_count; i++, cur_fd++)
+			dup2_append(&dups, listens[i].fd, cur_fd);
+	}
+
+	if (dup2_array(&dups) < 0)
+		i_fatal("Failed to dup2() fds");
+
+	/* don't close any of these */
+	while (cur_fd >= 0)
+		fd_close_on_exec(cur_fd--, FALSE);
 
 	(void)close(fd[0]);
 	(void)close(fd[1]);
 
 	login_process_init_env(group, getpid());
 
+	env_put(t_strdup_printf("LISTEN_FDS=%u", listen_count));
+	env_put(t_strdup_printf("SSL_LISTEN_FDS=%u", ssl_listen_count));
+
 	if (!group->set->login_chroot) {
 		/* no chrooting, but still change to the directory */
 		if (chdir(group->set->login_dir) < 0) {
--- a/src/master/main.c	Tue Jul 03 18:34:31 2007 +0300
+++ b/src/master/main.c	Tue Jul 03 20:04:28 2007 +0300
@@ -1,6 +1,7 @@
 /* Copyright (C) 2002 Timo Sirainen */
 
 #include "common.h"
+#include "array.h"
 #include "ioloop.h"
 #include "lib-signals.h"
 #include "network.h"
@@ -37,9 +38,6 @@
 bool gdb;
 #endif
 
-static void listen_fds_open(bool retry);
-static void listen_fds_close(struct server_settings *server);
-
 static void set_logfile(struct settings *set)
 {
 	int facility;
@@ -74,10 +72,8 @@
 	if (!master_settings_read(configfile, FALSE, FALSE))
 		i_warning("Invalid configuration, keeping old one");
 	else {
-		if (!IS_INETD()) {
-			listen_fds_close(old_set);
-			listen_fds_open(TRUE);
-		}
+		if (!IS_INETD())
+			listeners_open_fds(old_set, TRUE);
                 set_logfile(settings_root->defaults);
 	}
 }
@@ -103,248 +99,6 @@
 	set_logfile(settings_root->defaults);
 }
 
-static void resolve_ip(const char *set_name, const char *name,
-		       struct ip_addr *ip, unsigned int *port)
-{
-	struct ip_addr *ip_list;
-	const char *p;
-	unsigned int ips_count;
-	int ret;
-
-	if (*name == '\0') {
-                /* defaults to "*" or "[::]" */
-		ip->family = 0;
-		return;
-	}
-
-	if (name[0] == '[') {
-		/* IPv6 address */
-		p = strchr(name, ']');
-		if (p == NULL) {
-			i_fatal("%s: Missing ']' in address %s",
-				set_name, name);
-		}
-		name = t_strdup_until(name+1, p);
-
-		p++;
-		if (*p == '\0')
-			p = NULL;
-		else if (*p != ':') {
-			i_fatal("%s: Invalid data after ']' in address %s",
-				set_name, name);
-		}
-	} else {
-		p = strrchr(name, ':');
-		if (p != NULL)
-			name = t_strdup_until(name, p);
-	}
-
-	if (p != NULL) {
-		if (!is_numeric(p+1, '\0')) {
-			i_fatal("%s: Invalid port in address %s",
-				set_name, name);
-		}
-		*port = atoi(p+1);
-	}
-
-	if (strcmp(name, "*") == 0) {
-		/* IPv4 any */
-		net_get_ip_any4(ip);
-		return;
-	}
-
-	if (strcmp(name, "::") == 0) {
-		/* IPv6 any */
-		net_get_ip_any6(ip);
-		return;
-	}
-
-	/* Return the first IP if there happens to be multiple. */
-	ret = net_gethostbyname(name, &ip_list, &ips_count);
-	if (ret != 0) {
-		i_fatal("%s: Can't resolve address %s: %s",
-			set_name, name, net_gethosterror(ret));
-	}
-
-	if (ips_count < 1)
-		i_fatal("%s: No IPs for address: %s", set_name, name);
-
-	*ip = ip_list[0];
-}
-
-static void
-check_conflicts_set(const struct settings *set, const struct ip_addr *ip,
-		    unsigned int port, const char *name1, const char *name2)
-{
-	if (set->listen_port == port && net_ip_compare(ip, &set->listen_ip) &&
-	    set->listen_fd > 0) {
-		i_fatal("Protocols %s and %s are listening in same ip/port",
-			name1, name2);
-	}
-	if (set->ssl_listen_port == port &&
-	    net_ip_compare(ip, &set->ssl_listen_ip) && set->ssl_listen_fd > 0) {
-		i_fatal("Protocols %ss and %s are listening in same ip/port",
-			name1, name2);
-	}
-}
-
-static void check_conflicts(const struct ip_addr *ip, unsigned int port,
-			    const char *proto)
-{
-	struct server_settings *server;
-
-	for (server = settings_root; server != NULL; server = server->next) {
-		if (server->imap != NULL) {
-			check_conflicts_set(server->imap, ip, port,
-					    "imap", proto);
-		}
-		if (server->pop3 != NULL) {
-			check_conflicts_set(server->pop3, ip, port,
-					    "pop3", proto);
-		}
-	}
-}
-
-static void listen_protocols(struct settings *set, bool retry)
-{
-	struct ip_addr *ip;
-	const char *const *proto;
-	unsigned int port;
-	int *fd, i;
-
-	set->listen_port = set->protocol == MAIL_PROTOCOL_IMAP ? 143 : 110;
-#ifdef HAVE_SSL
-	set->ssl_listen_port = set->protocol == MAIL_PROTOCOL_IMAP ? 993 : 995;
-#else
-	set->ssl_listen_port = 0;
-#endif
-
-	/* resolve */
-	resolve_ip("listen", set->listen, &set->listen_ip, &set->listen_port);
-	if (!set->ssl_disable) {
-		resolve_ip("ssl_listen", set->ssl_listen, &set->ssl_listen_ip,
-			   &set->ssl_listen_port);
-	}
-
-	/* if ssl_listen wasn't explicitly set in the config file,
-	   use the non-ssl IP settings for the ssl listener, too. */
-	if (set->ssl_listen_ip.family == 0 && *set->ssl_listen == '\0')
-		set->ssl_listen_ip = set->listen_ip;
-
-	/* register wanted protocols */
-        proto = t_strsplit_spaces(set->protocols, " ");
-	for (; *proto != NULL; proto++) {
-		fd = NULL; ip = NULL; port = 0;
-		if (strcasecmp(*proto, "imap") == 0) {
-			if (set->protocol == MAIL_PROTOCOL_IMAP) {
-				fd = &set->listen_fd;
-				port = set->listen_port;
-				ip = &set->listen_ip;
-			}
-		} else if (strcasecmp(*proto, "imaps") == 0) {
-			if (set->protocol == MAIL_PROTOCOL_IMAP &&
-			    !set->ssl_disable) {
-				fd = &set->ssl_listen_fd;
-				port = set->ssl_listen_port;
-				ip = &set->ssl_listen_ip;
-			}
-		} else if (strcasecmp(*proto, "pop3") == 0) {
-			if (set->protocol == MAIL_PROTOCOL_POP3) {
-				fd = &set->listen_fd;
-				port = set->listen_port;
-				ip = &set->listen_ip;
-			}
-		} else if (strcasecmp(*proto, "pop3s") == 0) {
-			if (set->protocol == MAIL_PROTOCOL_POP3 &&
-			    !set->ssl_disable) {
-				fd = &set->ssl_listen_fd;
-				port = set->ssl_listen_port;
-				ip = &set->ssl_listen_ip;
-			}
-		} else {
-			i_fatal("Unknown protocol %s", *proto);
-		}
-
-		if (fd == NULL)
-			continue;
-
-		if (*fd != -1)
-			i_fatal("Protocol %s given more than once", *proto);
-
-		if (port == 0)
-			*fd = null_fd;
-		else {
-			for (i = 0; i < 10; i++) {
-				*fd = net_listen(ip, &port, 8);
-				if (*fd != -1)
-					break;
-				if (errno == EADDRINUSE) {
-					/* retry */
-				} else if (errno == EINTR &&
-					   io_loop_is_running(ioloop)) {
-					/* SIGHUPing sometimes gets us here.
-					   we don't want to die. */
-				} else {
-					/* error */
-					break;
-				}
-
-				check_conflicts(ip, port, *proto);
-				if (!retry)
-					break;
-
-				/* wait a while and try again. we're SIGHUPing
-				   so we most likely just closed it ourself.. */
-				sleep(1);
-			}
-
-			if (*fd == -1)
-				i_fatal("listen(%d) failed: %m", port);
-			net_set_nonblock(*fd, TRUE);
-			fd_close_on_exec(*fd, TRUE);
-		}
-	}
-
-	if (set->listen_fd == -1)
-		set->listen_fd = null_fd;
-	if (set->ssl_listen_fd == -1)
-		set->ssl_listen_fd = null_fd;
-}
-
-static void listen_fds_open(bool retry)
-{
-	struct server_settings *server;
-
-	for (server = settings_root; server != NULL; server = server->next) {
-		if (server->imap != NULL)
-			listen_protocols(server->imap, retry);
-		if (server->pop3 != NULL)
-			listen_protocols(server->pop3, retry);
-	}
-}
-
-static void listen_fds_close(struct server_settings *server)
-{
-	for (; server != NULL; server = server->next) {
-		if (server->imap != NULL) {
-			if (server->imap->listen_fd != null_fd &&
-			    close(server->imap->listen_fd) < 0)
-				i_error("close(imap.listen_fd) failed: %m");
-			if (server->imap->ssl_listen_fd != null_fd &&
-			    close(server->imap->ssl_listen_fd) < 0)
-				i_error("close(imap.ssl_listen_fd) failed: %m");
-		}
-		if (server->pop3 != NULL) {
-			if (server->pop3->listen_fd != null_fd &&
-			    close(server->pop3->listen_fd) < 0)
-				i_error("close(pop3.listen_fd) failed: %m");
-			if (server->pop3->ssl_listen_fd != null_fd &&
-			    close(server->pop3->ssl_listen_fd) < 0)
-				i_error("close(pop3.ssl_listen_fd) failed: %m");
-		}
-	}
-}
-
 static bool have_stderr_set(struct settings *set)
 {
 	if (*set->log_path != '\0' &&
@@ -391,7 +145,7 @@
 	}
 
 	if (!IS_INETD())
-		listen_fds_open(FALSE);
+		listeners_open_fds(FALSE);
 
 	/* close stdin and stdout. */
 	if (dup2(null_fd, 0) < 0)
@@ -471,6 +225,8 @@
 	dict_process_deinit();
 	ssl_deinit();
 
+	listeners_close_fds();
+
 	if (close(null_fd) < 0)
 		i_error("close(null_fd) failed: %m");
 
--- a/src/master/master-login-interface.h	Tue Jul 03 18:34:31 2007 +0300
+++ b/src/master/master-login-interface.h	Tue Jul 03 20:04:28 2007 +0300
@@ -3,8 +3,6 @@
 
 #include "network.h"
 
-#define LOGIN_LISTEN_FD 0
-#define LOGIN_SSL_LISTEN_FD 4
 #define LOGIN_MASTER_SOCKET_FD 3
 
 /* Increase the version number every time master_login_request
--- a/src/master/master-settings.c	Tue Jul 03 18:34:31 2007 +0300
+++ b/src/master/master-settings.c	Tue Jul 03 20:04:28 2007 +0300
@@ -276,8 +276,6 @@
 	MEMBER(pop3_logout_format) "top=%t/%p, retr=%r/%b, del=%d/%m, size=%s",
 
 	/* .. */
-	MEMBER(listen_fd) -1,
-	MEMBER(ssl_listen_fd) -1
 };
 
 struct auth_settings default_auth_settings = {
--- a/src/master/master-settings.h	Tue Jul 03 18:34:31 2007 +0300
+++ b/src/master/master-settings.h	Tue Jul 03 20:04:28 2007 +0300
@@ -10,6 +10,14 @@
 	MAIL_PROTOCOL_LDA
 };
 
+struct listener {
+	struct ip_addr ip;
+	unsigned int port;
+	int fd;
+	bool wanted;
+};
+ARRAY_DEFINE_TYPE(listener, struct listener);
+
 struct settings {
 	struct server_settings *server;
 	enum mail_protocol protocol;
@@ -124,14 +132,12 @@
 	const char *pop3_logout_format;
 
 	/* .. */
-	int listen_fd, ssl_listen_fd;
+	ARRAY_TYPE(listener) listens;
+	ARRAY_TYPE(listener) ssl_listens;
 
 	uid_t login_uid, mail_uid_t;
 	gid_t mail_gid_t;
 
-	struct ip_addr listen_ip, ssl_listen_ip;
-	unsigned int listen_port, ssl_listen_port;
-
 	const char *imap_generated_capability;
 
 	ARRAY_DEFINE(plugin_envs, const char *);