changeset 4538:9d9e72374164 HEAD

Fixes to login process handling, especially with login_process_per_connection=no. Removed login_max_logging_users setting since it was somewhat weird in how it worked. Added login_max_connections to replace it with login_process_per_connection=no, and with =yes its functionality is now within login_max_processes_count.
author Timo Sirainen <tss@iki.fi>
date Sun, 06 Aug 2006 23:05:32 +0300
parents 555c27e58cb1
children 93e62d5d12e4
files dovecot-example.conf src/imap-login/client.c src/login-common/common.h src/login-common/login-proxy.c src/login-common/login-proxy.h src/login-common/main.c src/login-common/master.c src/login-common/master.h src/login-common/ssl-proxy-openssl.c src/login-common/ssl-proxy.c src/login-common/ssl-proxy.h src/master/login-process.c src/master/login-process.h src/master/main.c src/master/master-login-interface.h src/master/master-settings.c src/master/master-settings.h src/pop3-login/client.c
diffstat 18 files changed, 601 insertions(+), 404 deletions(-) [+]
line wrap: on
line diff
--- a/dovecot-example.conf	Fri Aug 04 20:46:11 2006 +0300
+++ b/dovecot-example.conf	Sun Aug 06 23:05:32 2006 +0300
@@ -142,24 +142,35 @@
 # to create processes all the time.
 #login_process_per_connection = yes
 
-# Number of login processes to create. If login_process_per_connection is
-# yes, this is the number of extra processes waiting for users to log in.
+# Number of login processes to keep for listening new connections.
 #login_processes_count = 3
 
-# Maximum number of extra login processes to create. The extra process count
+# Maximum number of login processes to create. The listening process count
 # usually stays at login_processes_count, but when multiple users start logging
 # in at the same time more extra processes are created. To prevent fork-bombing
 # we check only once in a second if new processes should be created - if all
-# of them are used at the time, we double their amount until limit set by this
-# setting is reached. This setting is used only if
-# login_process_per_connection is yes.
+# of them are used at the time, we double their amount until the limit set by
+# this setting is reached.
 #login_max_processes_count = 128
 
-# Maximum number of connections allowed in login state. When this limit is
-# reached, the oldest connections are dropped. If login_process_per_connection
-# is no, this is a per-process value, so the absolute maximum number of users
-# logging in actually login_processes_count * max_logging_users.
-#login_max_logging_users = 256
+# Maximum number of connections allowed per each login process. This setting
+# is used only if login_process_per_connection=no. Once the limit is reached,
+# the process notifies master so that it can create a new login process.
+# When counting the number of connections and used file descriptors, you
+# can use this table:
+#   Type     Logged in  SSL/TLS    fds used   connections used
+#   Client   -          -          1          1
+#   Client   x          -          0          0
+#   Client   -          x          3          2
+#   Client   x          x          2          1
+#   Proxy    -          -          2          2
+#   Proxy    x          -          2          1
+#   Proxy    -          x          4          3
+#   Proxy    x          x          3          2
+# So in worst case scenario when you have n clients logging in, after STARTTLS
+# they can use 3*n fds and cause the connection count to go 2 times higher
+# than this limit. With proxying it can use 4*n fds and go 3 times higher.
+#login_max_connections = 256
 
 # Greeting message for clients.
 #login_greeting = Dovecot ready.
--- a/src/imap-login/client.c	Fri Aug 04 20:46:11 2006 +0300
+++ b/src/imap-login/client.c	Sun Aug 06 23:05:32 2006 +0300
@@ -121,7 +121,8 @@
 			       &client->common.proxy);
 	if (fd_ssl == -1) {
 		client_send_line(client, "* BYE TLS initialization failed.");
-		client_destroy(client, "TLS initialization failed.");
+		client_destroy(client,
+			       "Disconnected: TLS initialization failed.");
 		return;
 	}
 
@@ -358,16 +359,18 @@
 	struct hash_iterate_context *iter;
 	void *key, *value;
 	struct imap_client *destroy_buf[CLIENT_DESTROY_OLDEST_COUNT];
-	int i;
+	unsigned int i, destroy_count;
 
 	/* find the oldest clients and put them to destroy-buffer */
 	memset(destroy_buf, 0, sizeof(destroy_buf));
 
+	destroy_count = max_connections > CLIENT_DESTROY_OLDEST_COUNT*2 ?
+		CLIENT_DESTROY_OLDEST_COUNT : I_MIN(max_connections/2, 1);
 	iter = hash_iterate_init(clients);
 	while (hash_iterate(iter, &key, &value)) {
 		struct imap_client *client = key;
 
-		for (i = 0; i < CLIENT_DESTROY_OLDEST_COUNT; i++) {
+		for (i = 0; i < destroy_count; i++) {
 			if (destroy_buf[i] == NULL ||
 			    destroy_buf[i]->created > client->created) {
 				/* @UNSAFE */
@@ -382,7 +385,7 @@
 	hash_iterate_deinit(iter);
 
 	/* then kill them */
-	for (i = 0; i < CLIENT_DESTROY_OLDEST_COUNT; i++) {
+	for (i = 0; i < destroy_count; i++) {
 		if (destroy_buf[i] == NULL)
 			break;
 
@@ -411,12 +414,22 @@
 			     const struct ip_addr *ip)
 {
 	struct imap_client *client;
+	unsigned int current_count;
 
-	if (max_logging_users > CLIENT_DESTROY_OLDEST_COUNT &&
-	    hash_size(clients) >= max_logging_users) {
-		/* reached max. users count, kill few of the
-		   oldest connections */
-		client_destroy_oldest();
+	if (!process_per_connection) {
+		current_count = hash_size(clients) +
+			ssl_proxy_get_count() + login_proxy_get_count();
+		if (current_count >= max_connections) {
+			/* already reached max. users count, kill few of the
+			   oldest connections. this happens when we've maxed
+			   out the login process count also. */
+			client_destroy_oldest();
+		}
+		if (current_count + 1 >= max_connections) {
+			/* after this client we've reached max users count,
+			   so stop listening for more */
+			main_listen_stop();
+		}
 	}
 
 	/* always use nonblocking I/O */
@@ -444,7 +457,6 @@
                 client_send_greeting(client);
 	client_set_title(client);
 
-	client->created = TRUE;
 	return &client->common;
 }
 
@@ -500,6 +512,8 @@
 		client->common.proxy = NULL;
 	}
 	client_unref(client);
+
+	main_listen_start();
 }
 
 void client_destroy_internal_failure(struct imap_client *client)
@@ -617,7 +631,7 @@
 	while (hash_iterate(iter, &key, &value)) {
 		struct imap_client *client = key;
 
-		client_destroy(client, NULL);
+		client_destroy(client, "Disconnected: Shutting down");
 	}
 	hash_iterate_deinit(iter);
 }
--- a/src/login-common/common.h	Fri Aug 04 20:46:11 2006 +0300
+++ b/src/login-common/common.h	Sun Aug 06 23:05:32 2006 +0300
@@ -16,13 +16,15 @@
 extern const char *greeting, *log_format;
 extern const char *const *log_format_elements;
 extern const char *capability_string;
-extern unsigned int max_logging_users;
+extern unsigned int max_connections;
 extern unsigned int login_process_uid;
 extern struct auth_client *auth_client;
+extern bool closing_down;
 
 void main_ref(void);
 void main_unref(void);
 
-void main_close_listen(void);
+void main_listen_start(void);
+void main_listen_stop(void);
 
 #endif
--- a/src/login-common/login-proxy.c	Fri Aug 04 20:46:11 2006 +0300
+++ b/src/login-common/login-proxy.c	Sun Aug 06 23:05:32 2006 +0300
@@ -220,6 +220,13 @@
 	i_free(proxy->host);
 	i_free(proxy->user);
 	i_free(proxy);
+
+	main_listen_start();
+}
+
+unsigned int login_proxy_get_count(void)
+{
+	return login_proxies == NULL ? 0 : hash_size(login_proxies);
 }
 
 void login_proxy_detach(struct login_proxy *proxy, struct istream *client_input,
--- a/src/login-common/login-proxy.h	Fri Aug 04 20:46:11 2006 +0300
+++ b/src/login-common/login-proxy.h	Sun Aug 06 23:05:32 2006 +0300
@@ -20,6 +20,9 @@
 void login_proxy_detach(struct login_proxy *proxy, struct istream *client_input,
 			struct ostream *client_output);
 
+/* Return number of active detached login proxies */
+unsigned int login_proxy_get_count(void);
+
 void login_proxy_deinit(void);
 
 #endif
--- a/src/login-common/main.c	Fri Aug 04 20:46:11 2006 +0300
+++ b/src/login-common/main.c	Sun Aug 06 23:05:32 2006 +0300
@@ -22,15 +22,16 @@
 bool verbose_proctitle, verbose_ssl, verbose_auth;
 const char *greeting, *log_format;
 const char *const *log_format_elements;
-unsigned int max_logging_users;
+unsigned int max_connections;
 unsigned int login_process_uid;
 struct auth_client *auth_client;
+bool closing_down;
 
 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, closing_down;
+static bool is_inetd, listening;
 
 void main_ref(void)
 {
@@ -49,27 +50,6 @@
 	}
 }
 
-void main_close_listen(void)
-{
-	if (closing_down)
-		return;
-
-	if (io_listen != NULL) {
-		io_remove(&io_listen);
-		if (close(LOGIN_LISTEN_FD) < 0)
-			i_fatal("close(listen) failed: %m");
-	}
-
-	if (io_ssl_listen != NULL) {
-		io_remove(&io_ssl_listen);
-		if (close(LOGIN_SSL_LISTEN_FD) < 0)
-			i_fatal("close(ssl_listen) failed: %m");
-	}
-
-	closing_down = TRUE;
-	master_notify_finished();
-}
-
 static void sig_die(int signo, void *context __attr_unused__)
 {
 	/* warn about being killed because of some signal, except SIGINT (^C)
@@ -91,13 +71,15 @@
 		return;
 	}
 
-	if (process_per_connection)
-		main_close_listen();
-
 	if (net_getsockname(fd, &local_ip, NULL) < 0)
 		memset(&local_ip, 0, sizeof(local_ip));
 
 	(void)client_create(fd, FALSE, &local_ip, &ip);
+
+	if (process_per_connection) {
+		closing_down = TRUE;
+		main_listen_stop();
+	}
 }
 
 static void login_accept_ssl(void *context __attr_unused__)
@@ -114,8 +96,6 @@
 		return;
 	}
 
-	if (process_per_connection)
-		main_close_listen();
 	if (net_getsockname(fd, &local_ip, NULL) < 0)
 		memset(&local_ip, 0, sizeof(local_ip));
 
@@ -126,6 +106,82 @@
 		client = client_create(fd_ssl, TRUE, &local_ip, &ip);
 		client->proxy = proxy;
 	}
+
+	if (process_per_connection) {
+		closing_down = TRUE;
+		main_listen_stop();
+	}
+}
+
+void main_listen_start(void)
+{
+	unsigned int current_count;
+
+	if (listening)
+		return;
+	if (closing_down) {
+		/* typically happens only with
+		   login_process_per_connection=yes after client logs in */
+		master_notify_state_change(LOGIN_STATE_FULL_LOGINS);
+		return;
+	}
+
+	current_count = ssl_proxy_get_count() + login_proxy_get_count();
+	if (current_count >= max_connections) {
+		/* can't accept any more connections until existing proxies
+		   get destroyed */
+		return;
+	}
+
+	if (net_getsockname(LOGIN_LISTEN_FD, NULL, NULL) == 0) {
+		io_listen = io_add(LOGIN_LISTEN_FD, IO_READ,
+				   login_accept, NULL);
+	}
+
+	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);
+	}
+	listening = TRUE;
+
+	/* the initial notification tells master that we're ok. if we die
+	   before sending it, the master should shutdown itself. */
+	master_notify_state_change(LOGIN_STATE_LISTENING);
+}
+
+void main_listen_stop(void)
+{
+	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");
+		}
+	}
+
+	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");
+		}
+	}
+
+	listening = FALSE;
+	master_notify_state_change(clients_get_count() == 0 ?
+				   LOGIN_STATE_FULL_LOGINS :
+				   LOGIN_STATE_FULL_PRELOGINS);
 }
 
 static void auth_connect_notify(struct auth_client *client __attr_unused__,
@@ -183,8 +239,8 @@
         verbose_ssl = getenv("VERBOSE_SSL") != NULL;
         verbose_auth = getenv("VERBOSE_AUTH") != NULL;
 
-	value = getenv("MAX_LOGGING_USERS");
-	max_logging_users = value == NULL ? 0 : strtoul(value, NULL, 10);
+	value = getenv("MAX_CONNECTIONS");
+	max_connections = value == NULL ? 0 : strtoul(value, NULL, 10);
 
 	greeting = getenv("GREETING");
 	if (greeting == NULL)
@@ -222,27 +278,8 @@
 	io_listen = io_ssl_listen = NULL;
 
 	if (!is_inetd) {
-		if (net_getsockname(LOGIN_LISTEN_FD, NULL, NULL) == 0) {
-			io_listen = io_add(LOGIN_LISTEN_FD, IO_READ,
-					   login_accept, NULL);
-		}
-
-		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);
-		}
-
-		/* initialize master last - it sends the "we're ok"
-		   notification */
-		master_init(LOGIN_MASTER_SOCKET_FD, TRUE);
+		master_init(LOGIN_MASTER_SOCKET_FD);
+		main_listen_start();
 	}
 }
 
@@ -332,7 +369,7 @@
 				return 1;
 		}
 
-		master_init(master_fd, FALSE);
+		master_init(master_fd);
 		closing_down = TRUE;
 
 		if (fd != -1) {
--- a/src/login-common/master.c	Fri Aug 04 20:46:11 2006 +0300
+++ b/src/login-common/master.c	Sun Aug 06 23:05:32 2006 +0300
@@ -26,6 +26,13 @@
 	struct client *client;
 	master_callback_t *master_callback;
 
+	if (reply->tag == 0 && !process_per_connection) {
+		/* this means we have to start listening again.
+		   we've reached maximum number of login processes. */
+		main_listen_start();
+		return;
+	}
+
 	client = hash_lookup(master_requests, POINTER_CAST(reply->tag));
 	if (client == NULL)
 		i_fatal("Master sent reply with unknown tag %u", reply->tag);
@@ -73,7 +80,7 @@
 	client->master_callback = NULL;
 }
 
-void master_notify_finished(void)
+void master_notify_state_change(enum master_login_state state)
 {
 	struct master_login_request req;
 
@@ -82,6 +89,7 @@
 
 	memset(&req, 0, sizeof(req));
 	req.version = MASTER_LOGIN_PROTOCOL_VERSION;
+	req.tag = state;
 
 	/* sending -1 as fd does the notification */
 	if (fd_send(master_fd, -1, &req, sizeof(req)) != sizeof(req))
@@ -98,7 +106,8 @@
 		i_fatal("close(master) failed: %m");
 	master_fd = -1;
 
-        main_close_listen();
+	closing_down = TRUE;
+        main_listen_stop();
 	main_unref();
 
         /* may call this function again through main_unref() */
@@ -223,7 +232,7 @@
 	master_pos = 0;
 }
 
-void master_init(int fd, bool notify)
+void master_init(int fd)
 {
 	main_ref();
 
@@ -233,12 +242,6 @@
 
         master_pos = 0;
 	io_master = io_add(master_fd, IO_READ, master_input, NULL);
-
-	if (notify) {
-		/* just a note to master that we're ok. if we die before,
-		   master should shutdown itself. */
-		master_notify_finished();
-	}
 }
 
 void master_deinit(void)
--- a/src/login-common/master.h	Fri Aug 04 20:46:11 2006 +0300
+++ b/src/login-common/master.h	Sun Aug 06 23:05:32 2006 +0300
@@ -11,8 +11,8 @@
 			  unsigned int auth_pid, unsigned int auth_id);
 void master_request_abort(struct client *client);
 
-/* Notify master that we're not listening for new connections anymore. */
-void master_notify_finished(void);
+/* Notify master of a change in our state */
+void master_notify_state_change(enum master_login_state state);
 
 /* Close connection to master process */
 void master_close(void);
@@ -20,7 +20,7 @@
 /* inetd: Connect to existing master process, or create new one. */
 int master_connect(const char *group_name);
 
-void master_init(int fd, bool notify);
+void master_init(int fd);
 void master_deinit(void);
 
 #endif
--- a/src/login-common/ssl-proxy-openssl.c	Fri Aug 04 20:46:11 2006 +0300
+++ b/src/login-common/ssl-proxy-openssl.c	Sun Aug 06 23:05:32 2006 +0300
@@ -26,11 +26,11 @@
 /* Check every 30 minutes if parameters file has been updated */
 #define SSL_PARAMFILE_CHECK_INTERVAL (60*30)
 
-#define PLAIN_OUTPUT_OPTIMAL_SIZE 2048
-
-enum ssl_want {
-	WANT_INPUT,
-	WANT_OUTPUT
+enum ssl_io_action {
+	SSL_ADD_INPUT,
+	SSL_REMOVE_INPUT,
+	SSL_ADD_OUTPUT,
+	SSL_REMOVE_OUTPUT
 };
 
 struct ssl_proxy {
@@ -40,13 +40,10 @@
 	struct ip_addr ip;
 
 	int fd_ssl, fd_plain;
-	struct io *io_ssl, *io_plain_input;
+	struct io *io_ssl_read, *io_ssl_write, *io_plain_read, *io_plain_write;
 
-	enum ssl_want want;
-	void (*step)(struct ssl_proxy *);
-	unsigned int ssl_want_size;
-
-	struct ostream *plain_output;
+	unsigned char plainout_buf[1024];
+	unsigned int plainout_size;
 
 	unsigned char sslout_buf[1024];
 	unsigned int sslout_size;
@@ -70,8 +67,10 @@
 static struct hash_table *ssl_proxies;
 static struct ssl_parameters ssl_params;
 
-static void ssl_input(struct ssl_proxy *proxy);
-static void ssl_output(struct ssl_proxy *proxy);
+static void plain_read(void *context);
+static void plain_write(void *context);
+static void ssl_read(struct ssl_proxy *proxy);
+static void ssl_write(struct ssl_proxy *proxy);
 static void ssl_step(void *context);
 static void ssl_proxy_destroy(struct ssl_proxy *proxy);
 static void ssl_proxy_unref(struct ssl_proxy *proxy);
@@ -188,6 +187,120 @@
 	}
 }
 
+static void ssl_set_io(struct ssl_proxy *proxy, enum ssl_io_action action)
+{
+	switch (action) {
+	case SSL_ADD_INPUT:
+		if (proxy->io_ssl_read != NULL)
+			break;
+		proxy->io_ssl_read = io_add(proxy->fd_ssl, IO_READ,
+					    ssl_step, proxy);
+		break;
+	case SSL_REMOVE_INPUT:
+		if (proxy->io_ssl_read != NULL)
+			io_remove(&proxy->io_ssl_read);
+		break;
+	case SSL_ADD_OUTPUT:
+		if (proxy->io_ssl_write != NULL)
+			break;
+		proxy->io_ssl_write = io_add(proxy->fd_ssl, IO_WRITE,
+					     ssl_step, proxy);
+		break;
+	case SSL_REMOVE_OUTPUT:
+		if (proxy->io_ssl_write != NULL)
+			io_remove(&proxy->io_ssl_write);
+		break;
+	}
+}
+
+static void plain_block_input(struct ssl_proxy *proxy, bool block)
+{
+	if (block) {
+		if (proxy->io_plain_read != NULL)
+			io_remove(&proxy->io_plain_read);
+	} else {
+		if (proxy->io_plain_read == NULL) {
+			proxy->io_plain_read = io_add(proxy->fd_plain, IO_READ,
+						      plain_read, proxy);
+		}
+	}
+}
+
+static void plain_read(void *context)
+{
+	struct ssl_proxy *proxy = context;
+	ssize_t ret;
+	bool corked = FALSE;
+
+	if (proxy->sslout_size == sizeof(proxy->sslout_buf)) {
+		/* buffer full, block input until it's written */
+		plain_block_input(proxy, TRUE);
+		return;
+	}
+
+	proxy->refcount++;
+
+	while (proxy->sslout_size < sizeof(proxy->sslout_buf) &&
+	       !proxy->destroyed) {
+		ret = net_receive(proxy->fd_plain,
+				  proxy->sslout_buf + proxy->sslout_size,
+				  sizeof(proxy->sslout_buf) -
+				  proxy->sslout_size);
+		if (ret <= 0) {
+			if (ret < 0)
+				ssl_proxy_destroy(proxy);
+			break;
+		} else {
+			proxy->sslout_size += ret;
+			if (!corked) {
+				net_set_cork(proxy->fd_ssl, TRUE);
+				corked = TRUE;
+			}
+			ssl_write(proxy);
+		}
+	}
+
+	if (corked)
+		net_set_cork(proxy->fd_ssl, FALSE);
+
+	ssl_proxy_unref(proxy);
+}
+
+static void plain_write(void *context)
+{
+	struct ssl_proxy *proxy = context;
+	ssize_t ret;
+
+	proxy->refcount++;
+
+	ret = net_transmit(proxy->fd_plain, proxy->plainout_buf,
+			   proxy->plainout_size);
+	if (ret < 0)
+		ssl_proxy_destroy(proxy);
+	else {
+		proxy->plainout_size -= ret;
+		memmove(proxy->plainout_buf, proxy->plainout_buf + ret,
+			proxy->plainout_size);
+
+		if (proxy->plainout_size > 0) {
+			if (proxy->io_plain_write == NULL) {
+				proxy->io_plain_write =
+					io_add(proxy->fd_plain, IO_WRITE,
+					       plain_write, proxy);
+			}
+		} else {
+			if (proxy->io_plain_write != NULL)
+				io_remove(&proxy->io_plain_write);
+		}
+
+		ssl_set_io(proxy, SSL_ADD_INPUT);
+		if (SSL_pending(proxy->ssl) > 0)
+			ssl_read(proxy);
+	}
+
+	ssl_proxy_unref(proxy);
+}
+
 static const char *ssl_last_error(void)
 {
 	unsigned long err;
@@ -204,30 +317,8 @@
 	return buf;
 }
 
-static void ssl_set_io(struct ssl_proxy *proxy, enum ssl_want want)
-{
-	if (proxy->io_ssl != NULL) {
-		if (want == proxy->want)
-			return;
-		io_remove(&proxy->io_ssl);
-	}
-
-	proxy->want = want;
-	switch (want) {
-	case WANT_INPUT:
-		proxy->io_ssl =
-			io_add(proxy->fd_ssl, IO_READ, ssl_step, proxy);
-		break;
-	case WANT_OUTPUT:
-		proxy->io_ssl =
-			io_add(proxy->fd_ssl, IO_WRITE, ssl_step, proxy);
-		break;
-	}
-}
-
-static void
-ssl_handle_error(struct ssl_proxy *proxy, int ret, const char *func_name,
-		 void (*func)(struct ssl_proxy *), unsigned int want_size)
+static void ssl_handle_error(struct ssl_proxy *proxy, int ret,
+			     const char *func_name)
 {
 	const char *errstr;
 	int err;
@@ -236,14 +327,10 @@
 
 	switch (err) {
 	case SSL_ERROR_WANT_READ:
-		proxy->step = func;
-		proxy->ssl_want_size = want_size;
-		ssl_set_io(proxy, WANT_INPUT);
+		ssl_set_io(proxy, SSL_ADD_INPUT);
 		break;
 	case SSL_ERROR_WANT_WRITE:
-		proxy->step = func;
-		proxy->ssl_want_size = want_size;
-		ssl_set_io(proxy, WANT_OUTPUT);
+		ssl_set_io(proxy, SSL_ADD_OUTPUT);
 		break;
 	case SSL_ERROR_SYSCALL:
 		/* eat up the error queue */
@@ -282,150 +369,56 @@
 	}
 }
 
-static void plain_input(void *context)
+static void ssl_handshake(struct ssl_proxy *proxy)
 {
-	struct ssl_proxy *proxy = context;
-	ssize_t ret;
+	int ret;
 
-	if (proxy->sslout_size == sizeof(proxy->sslout_buf)) {
-		/* buffer full, block input until it's written */
-		io_remove(&proxy->io_plain_input);
-		return;
+	ret = SSL_accept(proxy->ssl);
+	if (ret != 1)
+		ssl_handle_error(proxy, ret, "SSL_accept()");
+	else {
+		proxy->handshaked = TRUE;
+
+		ssl_set_io(proxy, SSL_ADD_INPUT);
+		plain_block_input(proxy, FALSE);
 	}
+}
 
-	ret = net_receive(proxy->fd_plain,
-			  proxy->sslout_buf + proxy->sslout_size,
-			  sizeof(proxy->sslout_buf) - proxy->sslout_size);
-	if (ret <= 0) {
-		if (ret < 0)
-			ssl_proxy_destroy(proxy);
-	} else {
-		proxy->sslout_size += ret;
-		if (SSL_want(proxy->ssl) == SSL_NOTHING) {
-			i_assert(proxy->ssl_want_size == 0);
-			ssl_output(proxy);
+static void ssl_read(struct ssl_proxy *proxy)
+{
+	int ret;
+
+	while (proxy->plainout_size < sizeof(proxy->plainout_buf) &&
+	       !proxy->destroyed) {
+		ret = SSL_read(proxy->ssl,
+			       proxy->plainout_buf + proxy->plainout_size,
+			       sizeof(proxy->plainout_buf) -
+			       proxy->plainout_size);
+		if (ret <= 0) {
+			ssl_handle_error(proxy, ret, "SSL_read()");
+			break;
+		} else {
+			proxy->plainout_size += ret;
+			plain_write(proxy);
 		}
 	}
 }
 
-static int plain_output(void *context)
+static void ssl_write(struct ssl_proxy *proxy)
 {
-	struct ssl_proxy *proxy = context;
 	int ret;
 
-	if (proxy->ssl_want_size != 0)
-		return 0;
-
-	if ((ret = o_stream_flush(proxy->plain_output)) < 0) {
-		ssl_proxy_destroy(proxy);
-		return 1;
-	}
-
-	if (o_stream_get_buffer_used_size(proxy->plain_output) <
-	    PLAIN_OUTPUT_OPTIMAL_SIZE &&
-	    proxy->want == WANT_INPUT && proxy->io_ssl == NULL)
-		ssl_set_io(proxy, WANT_INPUT);
-
-	return ret;
-}
-
-static void ssl_handshake(struct ssl_proxy *proxy)
-{
-	int ret, old_errno;
-
-	net_set_cork(proxy->fd_ssl, TRUE);
-	ret = SSL_accept(proxy->ssl);
-
-	old_errno = errno;
-	net_set_cork(proxy->fd_ssl, FALSE);
-	errno = old_errno;
-
-	if (ret != 1)
-		ssl_handle_error(proxy, ret, "SSL_accept()", ssl_handshake, 0);
+	ret = SSL_write(proxy->ssl, proxy->sslout_buf, proxy->sslout_size);
+	if (ret <= 0)
+		ssl_handle_error(proxy, ret, "SSL_write()");
 	else {
-		proxy->handshaked = TRUE;
-		proxy->step = ssl_input;
-		ssl_set_io(proxy, WANT_INPUT);
-
-		proxy->io_plain_input = io_add(proxy->fd_plain, IO_READ,
-					       plain_input, proxy);
-	}
-}
-
-static void ssl_input(struct ssl_proxy *proxy)
-{
-	unsigned char buf[PLAIN_OUTPUT_OPTIMAL_SIZE];
-	size_t size, used;
-	ssize_t ret, ret2;
-
-	used = o_stream_get_buffer_used_size(proxy->plain_output);
-	if (used >= PLAIN_OUTPUT_OPTIMAL_SIZE) {
-		io_remove(&proxy->io_ssl);
-		return;
-	}
+		proxy->sslout_size -= ret;
+		memmove(proxy->sslout_buf, proxy->sslout_buf + ret,
+			proxy->sslout_size);
 
-	size = sizeof(buf) - used;
-	if (proxy->ssl_want_size != 0) {
-		i_assert(proxy->ssl_want_size <= size);
-		size = proxy->ssl_want_size;
-		proxy->ssl_want_size = 0;
-	}
-
-	do {
-		ret = SSL_read(proxy->ssl, buf, size);
-		if (ret <= 0) {
-			ssl_handle_error(proxy, ret, "SSL_read()",
-					 ssl_input, size);
-			return;
-		}
-		o_stream_cork(proxy->plain_output);
-		ret2 = o_stream_send(proxy->plain_output, buf, ret);
-		i_assert(ret2 < 0 || ret2 == ret);
-		o_stream_uncork(proxy->plain_output);
-
-		if (proxy->sslout_size > 0)
-			ssl_output(proxy);
-	} while (SSL_pending(proxy->ssl) > 0);
-}
-
-static void ssl_output(struct ssl_proxy *proxy)
-{
-	unsigned int size;
-	int ret, old_errno;
-
-	if (proxy->ssl_want_size == 0)
-		size = proxy->sslout_size;
-	else {
-		i_assert(proxy->ssl_want_size <= proxy->sslout_size);
-		size = proxy->ssl_want_size;
-		proxy->ssl_want_size = 0;
-	}
-
-	net_set_cork(proxy->fd_ssl, TRUE);
-	ret = SSL_write(proxy->ssl, proxy->sslout_buf, size);
-
-	old_errno = errno;
-	net_set_cork(proxy->fd_ssl, FALSE);
-	errno = old_errno;
-
-	if (ret <= 0) {
-		ssl_handle_error(proxy, ret, "SSL_write()", ssl_output, size);
-		return;
-	}
-
-	proxy->sslout_size -= ret;
-	memmove(proxy->sslout_buf, proxy->sslout_buf + ret, proxy->sslout_size);
-
-	if (proxy->sslout_size > 0) {
-		ssl_set_io(proxy, WANT_OUTPUT);
-		proxy->step = ssl_output;
-	} else {
-		ssl_set_io(proxy, WANT_INPUT);
-		proxy->step = ssl_input;
-	}
-	if (proxy->io_plain_input == NULL) {
-		proxy->io_plain_input = io_add(proxy->fd_plain, IO_READ,
-					       plain_input, proxy);
+		ssl_set_io(proxy, proxy->sslout_size > 0 ?
+			   SSL_ADD_OUTPUT : SSL_REMOVE_OUTPUT);
+		plain_block_input(proxy, FALSE);
 	}
 }
 
@@ -433,7 +426,27 @@
 {
 	struct ssl_proxy *proxy = context;
 
-	proxy->step(proxy);
+	proxy->refcount++;
+
+	if (!proxy->handshaked)
+		ssl_handshake(proxy);
+
+	if (proxy->handshaked) {
+		if (proxy->plainout_size == sizeof(proxy->plainout_buf))
+			ssl_set_io(proxy, SSL_REMOVE_INPUT);
+		else
+			ssl_read(proxy);
+
+		if (proxy->sslout_size == 0)
+			ssl_set_io(proxy, SSL_REMOVE_OUTPUT);
+		else {
+			net_set_cork(proxy->fd_ssl, TRUE);
+			ssl_write(proxy);
+			net_set_cork(proxy->fd_ssl, FALSE);
+		}
+	}
+
+	ssl_proxy_unref(proxy);
 }
 
 int ssl_proxy_new(int fd, struct ip_addr *ip, struct ssl_proxy **proxy_r)
@@ -479,16 +492,10 @@
 	proxy->fd_ssl = fd;
 	proxy->fd_plain = sfd[0];
 	proxy->ip = *ip;
-	proxy->plain_output =
-		o_stream_create_file(proxy->fd_plain, default_pool,
-				     (size_t)-1, FALSE);
-	o_stream_set_flush_callback(proxy->plain_output, plain_output, proxy);
-
         SSL_set_ex_data(ssl, extdata_index, proxy);
 
 	hash_insert(ssl_proxies, proxy, proxy);
 
-	proxy->step = ssl_handshake;
 	ssl_handshake(proxy);
 	main_ref();
 
@@ -549,16 +556,21 @@
 
 	hash_remove(ssl_proxies, proxy);
 
-	if (proxy->io_ssl != NULL)
-		io_remove(&proxy->io_ssl);
-	if (proxy->io_plain_input != NULL)
-		io_remove(&proxy->io_plain_input);
+	if (proxy->io_ssl_read != NULL)
+		io_remove(&proxy->io_ssl_read);
+	if (proxy->io_ssl_write != NULL)
+		io_remove(&proxy->io_ssl_write);
+	if (proxy->io_plain_read != NULL)
+		io_remove(&proxy->io_plain_read);
+	if (proxy->io_plain_write != NULL)
+		io_remove(&proxy->io_plain_write);
 
-	o_stream_unref(&proxy->plain_output);
 	(void)net_disconnect(proxy->fd_ssl);
 	(void)net_disconnect(proxy->fd_plain);
 
 	ssl_proxy_unref(proxy);
+
+	main_listen_start();
 }
 
 static RSA *ssl_gen_rsa_key(SSL *ssl __attr_unused__,
@@ -641,6 +653,11 @@
 	return strlen(buf);
 }
 
+unsigned int ssl_proxy_get_count(void)
+{
+	return hash_size(ssl_proxies);
+}
+
 void ssl_proxy_init(void)
 {
 	const char *cafile, *certfile, *keyfile, *cipher_list;
--- a/src/login-common/ssl-proxy.c	Fri Aug 04 20:46:11 2006 +0300
+++ b/src/login-common/ssl-proxy.c	Sun Aug 06 23:05:32 2006 +0300
@@ -28,6 +28,11 @@
 
 void ssl_proxy_free(struct ssl_proxy *proxy __attr_unused__) {}
 
+unsigned int ssl_proxy_get_count(void)
+{
+	return 0;
+}
+
 void ssl_proxy_init(void) {}
 void ssl_proxy_deinit(void) {}
 
--- a/src/login-common/ssl-proxy.h	Fri Aug 04 20:46:11 2006 +0300
+++ b/src/login-common/ssl-proxy.h	Sun Aug 06 23:05:32 2006 +0300
@@ -14,6 +14,9 @@
 const char *ssl_proxy_get_peer_name(struct ssl_proxy *proxy);
 void ssl_proxy_free(struct ssl_proxy *proxy);
 
+/* Return number of active SSL proxies */
+unsigned int ssl_proxy_get_count(void);
+
 void ssl_proxy_init(void);
 void ssl_proxy_deinit(void);
 
--- a/src/master/login-process.c	Fri Aug 04 20:46:11 2006 +0300
+++ b/src/master/login-process.c	Sun Aug 06 23:05:32 2006 +0300
@@ -21,16 +21,18 @@
 
 struct login_process {
 	struct login_group *group;
-	struct login_process *prev_nonlisten, *next_nonlisten;
+	struct login_process *prev_prelogin, *next_prelogin;
 	int refcount;
 
 	pid_t pid;
 	int fd;
 	struct io *io;
 	struct ostream *output;
+	enum master_login_state state;
+
 	unsigned int initialized:1;
-	unsigned int listening:1;
 	unsigned int destroyed:1;
+	unsigned int inetd_child:1;
 };
 
 struct login_auth_request {
@@ -114,28 +116,77 @@
 	i_free(request);
 }
 
-static void login_process_mark_nonlistening(struct login_process *p)
+static void process_remove_from_prelogin_lists(struct login_process *p)
+{
+	if (p->state != LOGIN_STATE_FULL_PRELOGINS)
+		return;
+
+	if (p->prev_prelogin == NULL)
+		p->group->oldest_prelogin_process = p->next_prelogin;
+	else
+		p->prev_prelogin->next_prelogin = p->next_prelogin;
+
+	if (p->next_prelogin == NULL)
+		p->group->newest_prelogin_process = p->prev_prelogin;
+	else
+		p->next_prelogin->prev_prelogin = p->prev_prelogin;
+
+	p->prev_prelogin = p->next_prelogin = NULL;
+}
+
+static void process_mark_nonlistening(struct login_process *p,
+				      enum master_login_state new_state)
 {
-	if (!p->listening) {
-		i_error("login: received another \"not listening\" "
-			"notification (if you can't login at all, "
-			"see src/lib/fdpass.c)");
+	if (p->group == NULL)
+		return;
+
+	if (p->state == LOGIN_STATE_LISTENING)
+		p->group->listening_processes--;
+
+	if (new_state == LOGIN_STATE_FULL_PRELOGINS) {
+		/* add to prelogin list */
+		i_assert(p->state != new_state);
+
+		p->prev_prelogin = p->group->newest_prelogin_process;
+		if (p->group->newest_prelogin_process == NULL)
+			p->group->oldest_prelogin_process = p;
+		else
+			p->group->newest_prelogin_process->next_prelogin = p;
+		p->group->newest_prelogin_process = p;
+	} else {
+		process_remove_from_prelogin_lists(p);
+	}
+}
+
+static void process_mark_listening(struct login_process *p)
+{
+	if (p->group == NULL)
+		return;
+
+	if (p->state != LOGIN_STATE_LISTENING)
+		p->group->listening_processes++;
+
+	process_remove_from_prelogin_lists(p);
+}
+
+static void
+login_process_set_state(struct login_process *p, enum master_login_state state)
+{
+	if (state == p->state || state > LOGIN_STATE_COUNT ||
+	    (state < p->state && p->group->set->login_process_per_connection)) {
+		i_error("login: tried to change state %d -> %d "
+			"(if you can't login at all, see src/lib/fdpass.c)",
+			p->state, state);
 		return;
 	}
 
-	p->listening = FALSE;
-
-	if (p->group != NULL) {
-		p->group->listening_processes--;
-		p->prev_nonlisten = p->group->newest_nonlisten_process;
+	if (state == LOGIN_STATE_LISTENING) {
+		process_mark_listening(p);
+	} else {
+		process_mark_nonlistening(p, state);
+	}
 
-		if (p->group->newest_nonlisten_process != NULL)
-			p->group->newest_nonlisten_process->next_nonlisten = p;
-		p->group->newest_nonlisten_process = p;
-
-		if (p->group->oldest_nonlisten_process == NULL)
-			p->group->oldest_nonlisten_process = p;
-	}
+	p->state = state;
 }
 
 static void login_process_groups_create(void)
@@ -271,12 +322,14 @@
 
 	if (client_fd == -1) {
 		/* just a notification that the login process */
+		enum master_login_state state = req.tag;
+
 		if (!p->initialized) {
 			/* initialization notify */
 			p->initialized = TRUE;;
 		} else {
-			/* not listening for new connections anymore */
-			login_process_mark_nonlistening(p);
+			/* change "listening for new connections" status */
+			login_process_set_state(p, state);
 		}
 		return;
 	}
@@ -316,7 +369,6 @@
 	p->refcount = 1;
 	p->pid = pid;
 	p->fd = fd;
-	p->listening = TRUE;
 	p->io = io_add(fd, IO_READ, login_process_input, p);
 	p->output = o_stream_create_file(fd, default_pool,
 					 sizeof(struct master_login_reply)*10,
@@ -325,6 +377,8 @@
 	PID_ADD_PROCESS_TYPE(pid, PROCESS_TYPE_LOGIN);
 	hash_insert(processes, POINTER_CAST(pid), p);
 
+	p->state = LOGIN_STATE_LISTENING;
+
 	if (p->group != NULL) {
 		p->group->processes++;
 		p->group->listening_processes++;
@@ -332,22 +386,13 @@
 	return p;
 }
 
-static void login_process_remove_from_lists(struct login_process *p)
+static void login_process_exited(struct login_process *p)
 {
-	if (p->group == NULL)
-		return;
+	if (p->group != NULL)
+		p->group->processes--;
 
-	if (p == p->group->oldest_nonlisten_process)
-		p->group->oldest_nonlisten_process = p->next_nonlisten;
-	else
-		p->prev_nonlisten->next_nonlisten = p->next_nonlisten;
-
-	if (p == p->group->newest_nonlisten_process)
-		p->group->newest_nonlisten_process = p->prev_nonlisten;
-	else
-		p->next_nonlisten->prev_nonlisten = p->prev_nonlisten;
-
-	p->next_nonlisten = p->prev_nonlisten = NULL;
+	hash_remove(processes, POINTER_CAST(p->pid));
+	login_process_unref(p);
 }
 
 static void login_process_destroy(struct login_process *p)
@@ -361,24 +406,15 @@
 		io_loop_stop(ioloop);
 	}
 
-	if (p->listening && p->group != NULL)
-		p->group->listening_processes--;
-
 	o_stream_close(p->output);
 	io_remove(&p->io);
 	if (close(p->fd) < 0)
 		i_error("close(login) failed: %m");
 
-	if (!p->listening)
-		login_process_remove_from_lists(p);
+	process_mark_nonlistening(p, LOGIN_STATE_FULL_LOGINS);
 
-	if (p->group != NULL)
-		p->group->processes--;
-
-	if (p->pid != 0)
-		hash_remove(processes, POINTER_CAST(p->pid));
-
-	login_process_unref(p);
+	if (p->inetd_child)
+		login_process_exited(p);
 }
 
 static void login_process_unref(struct login_process *p)
@@ -445,8 +481,8 @@
 		env_put("PROCESS_PER_CONNECTION=1");
 		env_put("MAX_LOGGING_USERS=1");
 	} else {
-		env_put(t_strdup_printf("MAX_LOGGING_USERS=%u",
-					set->login_max_logging_users));
+		env_put(t_strdup_printf("MAX_CONNECTIONS=%u",
+					set->login_max_connections));
 	}
 
 	env_put(t_strconcat("PROCESS_UID=", dec2str(pid), NULL));
@@ -473,13 +509,6 @@
 	pid_t pid;
 	int fd[2], log_fd;
 
-	if (group->set->login_process_per_connection &&
-	    group->processes - group->listening_processes >=
-	    group->set->login_max_logging_users) {
-		if (group->oldest_nonlisten_process != NULL)
-			login_process_destroy(group->oldest_nonlisten_process);
-	}
-
 	if (group->set->login_uid == 0)
 		i_fatal("Login process must not run as root");
 
@@ -569,25 +598,36 @@
 	return -1;
 }
 
-void login_process_abormal_exit(pid_t pid)
+void login_process_destroyed(pid_t pid, bool abnormal_exit)
 {
 	struct login_process *p;
 
-	/* don't start raising the process count if they're dying all
-	   the time */
 	p = hash_lookup(processes, POINTER_CAST(pid));
-	if (p != NULL && p->group != NULL)
-		p->group->wanted_processes_count = 0;
+	if (p == NULL)
+		i_panic("Lost login process PID %s", dec2str(pid));
+	i_assert(!p->inetd_child);
+
+	if (abnormal_exit) {
+		/* don't start raising the process count if they're dying all
+		   the time */
+		if (p->group != NULL)
+			p->group->wanted_processes_count = 0;
+	}
+
+	login_process_destroy(p);
+	login_process_exited(p);
 }
 
-void login_processes_destroy_all(void)
+void login_processes_destroy_all(bool unref)
 {
 	struct hash_iterate_context *iter;
 	void *key, *value;
 
 	iter = hash_iterate_init(processes);
-	while (hash_iterate(iter, &key, &value))
+	while (hash_iterate(iter, &key, &value)) {
 		login_process_destroy(value);
+		if (unref) login_process_unref(value);
+	}
 	hash_iterate_deinit(iter);
 
 	while (login_groups != NULL) {
@@ -598,17 +638,34 @@
 	}
 }
 
+static void login_processes_notify_group(struct login_group *group)
+{
+	struct hash_iterate_context *iter;
+	struct master_login_reply reply;
+	void *key, *value;
+
+	memset(&reply, 0, sizeof(reply));
+
+	iter = hash_iterate_init(processes);
+	while (hash_iterate(iter, &key, &value)) {
+		struct login_process *p = value;
+
+		if (p->group == group)
+			(void)o_stream_send(p->output, &reply, sizeof(reply));
+	}
+	hash_iterate_deinit(iter);
+}
+
 static int login_group_start_missings(struct login_group *group)
 {
-	if (!group->set->login_process_per_connection) {
-		/* create max. one process every second, that way if it keeps
-		   dying all the time we don't eat all cpu with fork()ing. */
-		if (group->listening_processes <
-		    group->set->login_processes_count) {
-			if (create_login_process(group) < 0)
-				return -1;
-		}
-		return 0;
+	if (group->set->login_process_per_connection &&
+	    group->processes >= group->set->login_max_processes_count &&
+	    group->listening_processes == 0) {
+		/* destroy the oldest listening process. non-listening
+		   processes are logged in users who we don't want to kick out
+		   because someone's started flooding */
+		if (group->oldest_prelogin_process != NULL)
+			login_process_destroy(group->oldest_prelogin_process);
 	}
 
 	/* we want to respond fast when multiple clients are connecting
@@ -627,15 +684,18 @@
 		 group->set->login_processes_count)
 		group->wanted_processes_count--;
 
-	if (group->wanted_processes_count >
-	    group->set->login_max_processes_count) {
-		group->wanted_processes_count =
-			group->set->login_max_processes_count;
+	while (group->listening_processes < group->wanted_processes_count &&
+	       group->processes < group->set->login_max_processes_count) {
+		if (create_login_process(group) < 0)
+			return -1;
 	}
 
-	while (group->listening_processes < group->wanted_processes_count) {
-		if (create_login_process(group) < 0)
-			return -1;
+	if (group->listening_processes == 0 &&
+	    !group->set->login_process_per_connection) {
+		/* we've reached our limit. notify the processes to start
+		   listening again which makes them kill some of their
+		   oldest clients when accepting the next connection */
+		login_processes_notify_group(group);
 	}
 	return 0;
 }
@@ -744,6 +804,7 @@
 
 		p = login_process_new(NULL, ++login_pid_counter, fd);
 		p->initialized = TRUE;
+		p->inetd_child = TRUE;
 	}
 }
 
@@ -771,6 +832,6 @@
 	if (io_listen != NULL)
 		io_remove(&io_listen);
 
-        login_processes_destroy_all();
+        login_processes_destroy_all(TRUE);
 	hash_destroy(processes);
 }
--- a/src/master/login-process.h	Fri Aug 04 20:46:11 2006 +0300
+++ b/src/master/login-process.h	Sun Aug 06 23:05:32 2006 +0300
@@ -11,12 +11,15 @@
 	unsigned int listening_processes;
 	unsigned int wanted_processes_count;
 
-	struct login_process *oldest_nonlisten_process;
-	struct login_process *newest_nonlisten_process;
+	/* if login_process_per_connection=yes this contains the list of
+	   processes that are in LOGIN_STATE_FULL_PRELOGINS state */
+	struct login_process *oldest_prelogin_process;
+	struct login_process *newest_prelogin_process;
 };
 
-void login_process_abormal_exit(pid_t pid);
-void login_processes_destroy_all(void);
+void login_process_destroyed(pid_t pid, bool abnormal_exit);
+
+void login_processes_destroy_all(bool unref);
 
 void login_processes_init(void);
 void login_processes_deinit(void);
--- a/src/master/main.c	Fri Aug 04 20:46:11 2006 +0300
+++ b/src/master/main.c	Sun Aug 06 23:05:32 2006 +0300
@@ -37,7 +37,6 @@
 };
 
 static const char *configfile = SYSCONFDIR "/" PACKAGE ".conf";
-static struct timeout *to;
 static const char *env_tz;
 
 struct ioloop *ioloop;
@@ -134,7 +133,7 @@
 	i_warning("SIGHUP received - reloading configuration");
 
 	/* restart auth and login processes */
-        login_processes_destroy_all();
+        login_processes_destroy_all(FALSE);
         auth_processes_destroy_all();
         dict_process_kill();
 
@@ -191,18 +190,45 @@
 	return NULL;
 }
 
-static void timeout_handler(void *context __attr_unused__)
+static void sigchld_handler(int signo __attr_unused__,
+			    void *context __attr_unused__)
 {
 	const char *process_type_name, *msg;
 	pid_t pid;
 	int status, process_type;
+	bool abnormal_exit;
 
 	while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
 		/* get the type and remove from hash */
 		process_type = PID_GET_PROCESS_TYPE(pid);
 		PID_REMOVE_PROCESS_TYPE(pid);
 
+		abnormal_exit = TRUE;
+
+		/* write errors to syslog */
+		process_type_name = process_names[process_type];
+		if (WIFEXITED(status)) {
+			status = WEXITSTATUS(status);
+			if (status == 0)
+				abnormal_exit = FALSE;
+			else {
+				msg = get_exit_status_message(status);
+				msg = msg == NULL ? "" :
+					t_strconcat(" (", msg, ")", NULL);
+				i_error("child %s (%s) returned error %d%s",
+					dec2str(pid), process_type_name,
+					status, msg);
+			}
+		} else if (WIFSIGNALED(status)) {
+			i_error("child %s (%s) killed with signal %d",
+				dec2str(pid), process_type_name,
+				WTERMSIG(status));
+		}
+
 		switch (process_type) {
+		case PROCESS_TYPE_LOGIN:
+			login_process_destroyed(pid, abnormal_exit);
+			break;
 		case PROCESS_TYPE_IMAP:
 		case PROCESS_TYPE_POP3:
 			mail_process_destroyed(pid);
@@ -214,29 +240,6 @@
 			dict_process_restart();
 			break;
 		}
-
-		/* write errors to syslog */
-		process_type_name = process_names[process_type];
-		if (WIFEXITED(status)) {
-			status = WEXITSTATUS(status);
-			if (status != 0) {
-				if (process_type == PROCESS_TYPE_LOGIN)
-					login_process_abormal_exit(pid);
-
-				msg = get_exit_status_message(status);
-				msg = msg == NULL ? "" :
-					t_strconcat(" (", msg, ")", NULL);
-				i_error("child %s (%s) returned error %d%s",
-					dec2str(pid), process_type_name,
-					status, msg);
-			}
-		} else if (WIFSIGNALED(status)) {
-			if (process_type == PROCESS_TYPE_LOGIN)
-				login_process_abormal_exit(pid);
-			i_error("child %s (%s) killed with signal %d",
-				dec2str(pid), process_type_name,
-				WTERMSIG(status));
-		}
 	}
 
 	if (pid == -1 && errno != EINTR && errno != ECHILD)
@@ -558,7 +561,7 @@
         lib_signals_set_handler(SIGUSR1, TRUE, sig_reopen_logs, NULL);
 
 	pids = hash_create(default_pool, default_pool, 128, NULL, NULL);
-	to = timeout_add(100, timeout_handler, NULL);
+	lib_signals_set_handler(SIGCHLD, TRUE, sigchld_handler, NULL);
 
 	ssl_init();
 	dict_process_init();
@@ -575,14 +578,14 @@
 				 "/master.pid", NULL));
 
 	/* make sure we log if child processes died unexpectedly */
-	timeout_handler(NULL);
+	sigchld_handler(SIGCHLD, NULL);
 
 	login_processes_deinit();
 	auth_processes_deinit();
 	dict_process_deinit();
 	ssl_deinit();
 
-	timeout_remove(&to);
+	lib_signals_unset_handler(SIGCHLD, sigchld_handler, NULL);
 
 	if (close(null_fd) < 0)
 		i_error("close(null_fd) failed: %m");
--- a/src/master/master-login-interface.h	Fri Aug 04 20:46:11 2006 +0300
+++ b/src/master/master-login-interface.h	Sun Aug 06 23:05:32 2006 +0300
@@ -9,10 +9,23 @@
 
 /* Increase the version number every time master_login_request
    (or something else) is changed. */
-#define MASTER_LOGIN_PROTOCOL_VERSION 1
+#define MASTER_LOGIN_PROTOCOL_VERSION 2
+
+enum master_login_state {
+	/* process is accepting new connections */
+	LOGIN_STATE_LISTENING = 0,
+	/* process isn't accepting new connections, but it'd be able to kill
+	   some connections which haven't logged in yet */
+	LOGIN_STATE_FULL_PRELOGINS,
+	/* process is handling only logged in users */
+	LOGIN_STATE_FULL_LOGINS,
+
+	LOGIN_STATE_COUNT
+};
 
 struct master_login_request {
 	uint32_t version;
+	/* if fd == -1, tag is used as master_login_state */
 	uint32_t tag;
 
 	uint32_t auth_pid;
--- a/src/master/master-settings.c	Fri Aug 04 20:46:11 2006 +0300
+++ b/src/master/master-settings.c	Sun Aug 06 23:05:32 2006 +0300
@@ -93,7 +93,7 @@
 	DEF(SET_INT, login_process_size),
 	DEF(SET_INT, login_processes_count),
 	DEF(SET_INT, login_max_processes_count),
-	DEF(SET_INT, login_max_logging_users),
+	DEF(SET_INT, login_max_connections),
 
 	/* mail */
 	DEF(SET_STR, valid_chroot_dirs),
@@ -299,7 +299,7 @@
 	MEMBER(login_process_size) 32,
 	MEMBER(login_processes_count) 3,
 	MEMBER(login_max_processes_count) 128,
-	MEMBER(login_max_logging_users) 256,
+	MEMBER(login_max_connections) 256,
 
 	/* mail */
 	MEMBER(valid_chroot_dirs) "",
@@ -846,8 +846,8 @@
 		i_error("login_processes_count must be at least 1");
 		return FALSE;
 	}
-	if (set->login_max_logging_users < 1) {
-		i_error("login_max_logging_users must be at least 1");
+	if (set->login_max_connections < 1) {
+		i_error("login_max_connections must be at least 1");
 		return FALSE;
 	}
 
--- a/src/master/master-settings.h	Fri Aug 04 20:46:11 2006 +0300
+++ b/src/master/master-settings.h	Sun Aug 06 23:05:32 2006 +0300
@@ -54,7 +54,7 @@
 	unsigned int login_process_size;
 	unsigned int login_processes_count;
 	unsigned int login_max_processes_count;
-	unsigned int login_max_logging_users;
+	unsigned int login_max_connections;
 
 	/* mail */
 	const char *valid_chroot_dirs;
--- a/src/pop3-login/client.c	Fri Aug 04 20:46:11 2006 +0300
+++ b/src/pop3-login/client.c	Sun Aug 06 23:05:32 2006 +0300
@@ -77,7 +77,8 @@
 			       &client->common.proxy);
 	if (fd_ssl == -1) {
 		client_send_line(client, "-ERR TLS initialization failed.");
-		client_destroy(client, "TLS initialization failed.");
+		client_destroy(client,
+			       "Disconnected: TLS initialization failed.");
 		return;
 	}
 
@@ -231,16 +232,18 @@
 	struct hash_iterate_context *iter;
 	void *key, *value;
 	struct pop3_client *destroy_buf[CLIENT_DESTROY_OLDEST_COUNT];
-	int i;
+	unsigned int i, destroy_count;
 
 	/* find the oldest clients and put them to destroy-buffer */
 	memset(destroy_buf, 0, sizeof(destroy_buf));
 
+	destroy_count = max_connections > CLIENT_DESTROY_OLDEST_COUNT*2 ?
+		CLIENT_DESTROY_OLDEST_COUNT : I_MIN(max_connections/2, 1);
 	iter = hash_iterate_init(clients);
 	while (hash_iterate(iter, &key, &value)) {
 		struct pop3_client *client = key;
 
-		for (i = 0; i < CLIENT_DESTROY_OLDEST_COUNT; i++) {
+		for (i = 0; i < destroy_count; i++) {
 			if (destroy_buf[i] == NULL ||
 			    destroy_buf[i]->created > client->created) {
 				/* @UNSAFE */
@@ -255,7 +258,7 @@
 	hash_iterate_deinit(iter);
 
 	/* then kill them */
-	for (i = 0; i < CLIENT_DESTROY_OLDEST_COUNT; i++) {
+	for (i = 0; i < destroy_count; i++) {
 		if (destroy_buf[i] == NULL)
 			break;
 
@@ -304,12 +307,22 @@
 			     const struct ip_addr *ip)
 {
 	struct pop3_client *client;
+	unsigned int current_count;
 
-	if (max_logging_users > CLIENT_DESTROY_OLDEST_COUNT &&
-	    hash_size(clients) >= max_logging_users) {
-		/* reached max. users count, kill few of the
-		   oldest connections */
-		client_destroy_oldest();
+	if (!process_per_connection) {
+		current_count = hash_size(clients) +
+			ssl_proxy_get_count() + login_proxy_get_count();
+		if (current_count >= max_connections) {
+			/* already reached max. users count, kill few of the
+			   oldest connections. this happens when we've maxed
+			   out the login process count also. */
+			client_destroy_oldest();
+		}
+		if (current_count + 1 >= max_connections) {
+			/* after this client we've reached max users count,
+			   so stop listening for more */
+			main_listen_stop();
+		}
 	}
 
 	/* always use nonblocking I/O */
@@ -388,6 +401,8 @@
 	if (client->common.proxy != NULL)
 		ssl_proxy_free(client->common.proxy);
 	client_unref(client);
+
+	main_listen_start();
 }
 
 void client_destroy_internal_failure(struct pop3_client *client)
@@ -496,7 +511,7 @@
 	while (hash_iterate(iter, &key, &value)) {
 		struct pop3_client *client = key;
 
-		client_destroy(client, NULL);
+		client_destroy(client, "Disconnected: Shutting down");
 	}
 	hash_iterate_deinit(iter);
 }