changeset 9235:2e2b957f1cca HEAD

Implemented anvil service, which is used to implement mail_max_userip_connections.
author Timo Sirainen <tss@iki.fi>
date Tue, 05 May 2009 21:28:34 -0400
parents 783a81c3deb7
children 955e68007ada
files .hgignore TODO configure.in dovecot-master-example.conf src/anvil/Makefile.am src/anvil/anvil-connection.c src/anvil/anvil-connection.h src/anvil/common.h src/anvil/connect-limit.c src/anvil/connect-limit.h src/anvil/main.c src/imap-login/client.c src/imap/imap-client.c src/imap/imap-client.h src/lib-master/master-interface.h src/lib-master/master-service-settings.c src/lib-master/master-service.c src/lib-master/master-service.h src/lib-storage/mail-user.c src/lib-storage/mail-user.h src/login-common/common.h src/login-common/login-settings.c src/login-common/login-settings.h src/login-common/main.c src/login-common/sasl-server.c src/master/Makefile.am src/master/master-settings.c src/master/service-anvil.c src/master/service-anvil.h src/master/service-auth-server.c src/master/service-log.c src/master/service-log.h src/master/service-monitor.c src/master/service-process-notify.c src/master/service-process-notify.h src/master/service-process.c src/master/service-process.h src/master/service.c src/master/service.h src/pop3-login/client.c src/pop3/pop3-client.c src/pop3/pop3-client.h
diffstat 42 files changed, 926 insertions(+), 135 deletions(-) [+]
line wrap: on
line diff
--- a/.hgignore	Tue May 05 20:26:40 2009 -0400
+++ b/.hgignore	Tue May 05 21:28:34 2009 -0400
@@ -51,9 +51,11 @@
 
 doc/wiki/*.txt
 doc/wiki/Makefile.am
+src/anvil/anvil
 src/auth/checkpassword-reply
 src/auth/dovecot-auth
 src/config/all-settings.c
+src/config/config
 src/config/doveconf
 src/lda/dovecot-lda
 src/dict/dict
--- a/TODO	Tue May 05 20:26:40 2009 -0400
+++ b/TODO	Tue May 05 21:28:34 2009 -0400
@@ -1,6 +1,6 @@
- - mail_max_userip_connections
+ - move ssl proxying code to lib-master
+ - rawlog is broken because it can't get $HOME etc.
  - dovecot stop, dovecot reload
- - make sure status/log messages which are important get through to the server
  - log prefixes work in a weird way now. failures.c prefixes are used only
    when not doing internal logging. that won't work in future..
  - library dependency tracking still broken. .la changes get noticed,
--- a/configure.in	Tue May 05 20:26:40 2009 -0400
+++ b/configure.in	Tue May 05 21:28:34 2009 -0400
@@ -2383,6 +2383,7 @@
 src/lib-storage/index/raw/Makefile
 src/lib-storage/index/shared/Makefile
 src/lib-storage/register/Makefile
+src/anvil/Makefile
 src/auth/Makefile
 src/config/Makefile
 src/lda/Makefile
--- a/dovecot-master-example.conf	Tue May 05 20:26:40 2009 -0400
+++ b/dovecot-master-example.conf	Tue May 05 21:28:34 2009 -0400
@@ -2,15 +2,29 @@
   type = config
   executable = config
   user = dovecot
-  drop_priv_before_exec = yes
+
   unix_listener {
     path = config
+    mode = 0666
   }
 }
 
 service log {
   type = log
   executable = log
+  process_limit = 1
+}
+
+service anvil {
+  type = anvil
+  executable = anvil
+  process_limit = 1
+  user = dovecot
+  chroot = empty
+
+  unix_listener {
+    path = anvil
+  }
 }
 
 service auth {
@@ -56,7 +70,6 @@
 }
 
 service imap {
-  type = auth-destination
   executable = imap
 }
 
@@ -80,7 +93,6 @@
 }
 
 service pop3 {
-  type = auth-destination
   executable = pop3
 }
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/anvil/Makefile.am	Tue May 05 21:28:34 2009 -0400
@@ -0,0 +1,24 @@
+pkglibexecdir = $(libexecdir)/dovecot
+
+pkglibexec_PROGRAMS = anvil
+
+AM_CPPFLAGS = \
+	-I$(top_srcdir)/src/lib \
+	-I$(top_srcdir)/src/lib-settings \
+	-I$(top_srcdir)/src/lib-master
+
+anvil_LDADD = \
+	$(LIBDOVECOT) \
+	$(MODULE_LIBS) \
+	$(RAND_LIBS)
+anvil_DEPENDENCIES = $(LIBDOVECOT)
+
+anvil_SOURCES = \
+	main.c \
+	anvil-connection.c \
+	connect-limit.c
+
+noinst_HEADERS = \
+	anvil-connection.h \
+	common.h \
+	connect-limit.h
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/anvil/anvil-connection.c	Tue May 05 21:28:34 2009 -0400
@@ -0,0 +1,165 @@
+/* Copyright (C) 2009 Dovecot authors, see the included COPYING file */
+
+#include "common.h"
+#include "llist.h"
+#include "istream.h"
+#include "ostream.h"
+#include "master-interface.h"
+#include "connect-limit.h"
+#include "anvil-connection.h"
+
+#include <stdlib.h>
+#include <unistd.h>
+
+#define MAX_INBUF_SIZE 1024
+
+#define ANVIL_CLIENT_PROTOCOL_MAJOR_VERSION 1
+#define ANVIL_CLIENT_PROTOCOL_MINOR_VERSION 0
+
+struct anvil_connection {
+	struct anvil_connection *prev, *next;
+
+	int fd;
+	struct istream *input;
+	struct ostream *output;
+	struct io *io;
+
+	unsigned int version_received:1;
+	unsigned int handshaked:1;
+};
+
+struct anvil_connection *anvil_connections = NULL;
+
+static const char *const *
+anvil_connection_next_line(struct anvil_connection *conn)
+{
+	const char *line;
+
+	line = i_stream_next_line(conn->input);
+	if (line == NULL)
+		return NULL;
+
+	return t_strsplit(line, "\t");
+}
+
+static int
+anvil_connection_request(struct anvil_connection *conn,
+			 const char *const *args, const char **error_r)
+{
+	const char *cmd = args[0];
+	unsigned int count;
+	pid_t pid;
+
+	args++;
+	if (strcmp(cmd, "CONNECT") == 0) {
+		if (args[0] == NULL || args[1] == NULL) {
+			*error_r = "CONNECT: Not enough parameters";
+			return -1;
+		}
+		pid = strtol(args[0], NULL, 10);
+		connect_limit_connect(connect_limit, pid, args[1]);
+		return 0;
+	} else if (strcmp(cmd, "DISCONNECT") == 0) {
+		if (args[0] == NULL || args[1] == NULL) {
+			*error_r = "DISCONNECT: Not enough parameters";
+			return -1;
+		}
+		pid = strtol(args[0], NULL, 10);
+		connect_limit_disconnect(connect_limit, pid, args[1]);
+		return 0;
+	} else if (strcmp(cmd, "KILL") == 0) {
+		if (args[0] == NULL) {
+			*error_r = "KILL: Not enough parameters";
+			return -1;
+		}
+		if (conn->fd != MASTER_LISTEN_FD_FIRST) {
+			*error_r = "KILL sent by a non-master connection";
+			return -1;
+		}
+		pid = strtol(args[0], NULL, 10);
+		connect_limit_disconnect_pid(connect_limit, pid);
+		return 0;
+	} else if (strcmp(cmd, "LOOKUP") == 0) {
+		if (args[0] == NULL) {
+			*error_r = "LOOKUP: Not enough parameters";
+			return -1;
+		}
+		count = connect_limit_lookup(connect_limit, args[0]);
+		(void)o_stream_send_str(conn->output,
+					t_strdup_printf("%u\n", count));
+		return 0;
+	} else {
+		*error_r = t_strconcat("Unknown command: ", cmd, NULL);
+		return -1;
+	}
+}
+
+static void anvil_connection_input(void *context)
+{
+	struct anvil_connection *conn = context;
+	const char *const *args, *line, *error;
+
+	switch (i_stream_read(conn->input)) {
+	case -2:
+		i_error("BUG: Anvil client connection sent too much data");
+                anvil_connection_destroy(conn);
+		return;
+	case -1:
+                anvil_connection_destroy(conn);
+		return;
+	}
+
+	if (!conn->version_received) {
+		line = i_stream_next_line(conn->input);
+		if (line == NULL)
+			return;
+
+		if (strncmp(line, "VERSION\t", 8) != 0 ||
+		    atoi(t_strcut(line + 8, '\t')) !=
+		    ANVIL_CLIENT_PROTOCOL_MAJOR_VERSION) {
+			i_error("Anvil client not compatible with this server "
+				"(mixed old and new binaries?)");
+			anvil_connection_destroy(conn);
+			return;
+		}
+		conn->version_received = TRUE;
+	}
+
+	while ((args = anvil_connection_next_line(conn)) != NULL) {
+		if (args[0] != NULL) {
+			if (anvil_connection_request(conn, args, &error) < 0)
+				i_error("Anvil client input error: %s", error);
+		}
+	}
+}
+
+struct anvil_connection *anvil_connection_create(int fd)
+{
+	struct anvil_connection *conn;
+
+	conn = i_new(struct anvil_connection, 1);
+	conn->fd = fd;
+	conn->input = i_stream_create_fd(fd, MAX_INBUF_SIZE, FALSE);
+	conn->output = o_stream_create_fd(fd, (size_t)-1, FALSE);
+	conn->io = io_add(fd, IO_READ, anvil_connection_input, conn);
+	DLLIST_PREPEND(&anvil_connections, conn);
+	return conn;
+}
+
+void anvil_connection_destroy(struct anvil_connection *conn)
+{
+	DLLIST_REMOVE(&anvil_connections, conn);
+
+	io_remove(&conn->io);
+	i_stream_destroy(&conn->input);
+	o_stream_destroy(&conn->output);
+	if (close(conn->fd) < 0)
+		i_error("close(anvil conn) failed: %m");
+	i_free(conn);
+}
+
+void anvil_connections_destroy_all(void)
+{
+	while (anvil_connections != NULL)
+		anvil_connection_destroy(anvil_connections);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/anvil/anvil-connection.h	Tue May 05 21:28:34 2009 -0400
@@ -0,0 +1,9 @@
+#ifndef ANVIL_CONNECTION_H
+#define ANVIL_CONNECTION_H
+
+struct anvil_connection *anvil_connection_create(int fd);
+void anvil_connection_destroy(struct anvil_connection *conn);
+
+void anvil_connections_destroy_all(void);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/anvil/common.h	Tue May 05 21:28:34 2009 -0400
@@ -0,0 +1,8 @@
+#ifndef COMMON_H
+#define COMMON_H
+
+#include "lib.h"
+
+extern struct connect_limit *connect_limit;
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/anvil/connect-limit.c	Tue May 05 21:28:34 2009 -0400
@@ -0,0 +1,166 @@
+/* Copyright (C) 2009 Dovecot authors, see the included COPYING file */
+
+#include "common.h"
+#include "hash.h"
+#include "connect-limit.h"
+
+struct ident_pid {
+	/* ident string points to ident_hash keys */
+	const char *ident;
+	pid_t pid;
+	unsigned int refcount;
+};
+
+struct connect_limit {
+	/* ident => refcount */
+	struct hash_table *ident_hash;
+	/* struct ident_pid => struct ident_pid */
+	struct hash_table *ident_pid_hash;
+};
+
+static unsigned int ident_pid_hash(const void *p)
+{
+	const struct ident_pid *i = p;
+
+	return str_hash(i->ident) ^ i->pid;
+}
+
+static int ident_pid_cmp(const void *p1, const void *p2)
+{
+	const struct ident_pid *i1 = p1, *i2 = p2;
+
+	if (i1->pid < i2->pid)
+		return -1;
+	else if (i1->pid > i2->pid)
+		return 1;
+	else
+		return strcmp(i1->ident, i2->ident);
+}
+
+struct connect_limit *connect_limit_init(void)
+{
+	struct connect_limit *limit;
+
+	limit = i_new(struct connect_limit, 1);
+	limit->ident_hash =
+		hash_table_create(default_pool, default_pool, 0,
+				  str_hash, (hash_cmp_callback_t *)strcmp);
+	limit->ident_pid_hash =
+		hash_table_create(default_pool, default_pool, 0,
+				  ident_pid_hash, ident_pid_cmp);
+	return limit;
+}
+
+void connect_limit_deinit(struct connect_limit **_limit)
+{
+	struct connect_limit *limit = *_limit;
+
+	*_limit = NULL;
+	hash_table_destroy(&limit->ident_hash);
+	hash_table_destroy(&limit->ident_pid_hash);
+	i_free(limit);
+}
+
+unsigned int connect_limit_lookup(struct connect_limit *limit,
+				  const char *ident)
+{
+	void *value;
+
+	value = hash_table_lookup(limit->ident_hash, ident);
+	if (value == NULL)
+		return 0;
+
+	return POINTER_CAST_TO(value, unsigned int);
+}
+
+void connect_limit_connect(struct connect_limit *limit, pid_t pid,
+			   const char *ident)
+{
+	struct ident_pid *i, lookup_i;
+	void *key, *value;
+
+	if (!hash_table_lookup_full(limit->ident_hash, ident, &key, &value)) {
+		key = i_strdup(ident);
+		value = POINTER_CAST(1);
+		hash_table_insert(limit->ident_hash, key, value);
+	} else {
+		value = POINTER_CAST(POINTER_CAST_TO(value, unsigned int) + 1);
+		hash_table_update(limit->ident_hash, key, value);
+	}
+
+	lookup_i.ident = ident;
+	lookup_i.pid = pid;
+	i = hash_table_lookup(limit->ident_pid_hash, &lookup_i);
+	if (i == NULL) {
+		i = i_new(struct ident_pid, 1);
+		i->ident = key;
+		i->pid = pid;
+		i->refcount = 1;
+		hash_table_insert(limit->ident_pid_hash, i, i);
+	} else {
+		i->refcount++;
+	}
+}
+
+static void
+connect_limit_ident_hash_unref(struct connect_limit *limit, const char *ident)
+{
+	void *key, *value;
+	unsigned int new_refcount;
+
+	if (!hash_table_lookup_full(limit->ident_hash, ident, &key, &value))
+		i_panic("connect limit hash tables are inconsistent");
+
+	new_refcount = POINTER_CAST_TO(value, unsigned int) - 1;
+	if (new_refcount > 0) {
+		value = POINTER_CAST(new_refcount);
+		hash_table_update(limit->ident_hash, key, value);
+	} else {
+		hash_table_remove(limit->ident_hash, key);
+		i_free(key);
+	}
+}
+
+void connect_limit_disconnect(struct connect_limit *limit, pid_t pid,
+			      const char *ident)
+{
+	struct ident_pid *i, lookup_i;
+
+	lookup_i.ident = ident;
+	lookup_i.pid = pid;
+
+	i = hash_table_lookup(limit->ident_pid_hash, &lookup_i);
+	if (i == NULL) {
+		i_error("connect limit: disconnection for unknown "
+			"pid %s + ident %s", dec2str(pid), ident);
+		return;
+	}
+
+	if (--i->refcount == 0) {
+		hash_table_remove(limit->ident_pid_hash, i);
+		i_free(i);
+	}
+
+	connect_limit_ident_hash_unref(limit, ident);
+}
+
+void connect_limit_disconnect_pid(struct connect_limit *limit, pid_t pid)
+{
+	struct hash_iterate_context *iter;
+	struct ident_pid *i;
+	void *key, *value;
+
+	/* this should happen rarely (or never), so this slow implementation
+	   should be fine. */
+	iter = hash_table_iterate_init(limit->ident_pid_hash);
+	while (hash_table_iterate(iter, &key, &value)) {
+		i = key;
+		if (i->pid == pid) {
+			hash_table_remove(limit->ident_pid_hash, i);
+			for (; i->refcount > 0; i->refcount--)
+				connect_limit_ident_hash_unref(limit, i->ident);
+			i_free(i);
+		}
+	}
+	hash_table_iterate_deinit(&iter);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/anvil/connect-limit.h	Tue May 05 21:28:34 2009 -0400
@@ -0,0 +1,15 @@
+#ifndef CONNECT_LIMIT_H
+#define CONNECT_LIMIT_H
+
+struct connect_limit *connect_limit_init(void);
+void connect_limit_deinit(struct connect_limit **limit);
+
+unsigned int connect_limit_lookup(struct connect_limit *limit,
+				  const char *ident);
+void connect_limit_connect(struct connect_limit *limit, pid_t pid,
+			   const char *ident);
+void connect_limit_disconnect(struct connect_limit *limit, pid_t pid,
+			      const char *ident);
+void connect_limit_disconnect_pid(struct connect_limit *limit, pid_t pid);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/anvil/main.c	Tue May 05 21:28:34 2009 -0400
@@ -0,0 +1,42 @@
+/* Copyright (C) 2009 Dovecot authors, see the included COPYING file */
+
+#include "common.h"
+#include "array.h"
+#include "env-util.h"
+#include "master-service.h"
+#include "connect-limit.h"
+#include "anvil-connection.h"
+
+#include <stdlib.h>
+#include <unistd.h>
+
+struct connect_limit *connect_limit;
+
+static struct master_service *service;
+
+static void client_connected(const struct master_service_connection *conn)
+{
+	anvil_connection_create(conn->fd);
+}
+
+int main(int argc, char *argv[])
+{
+	int c;
+
+	service = master_service_init("anvil", 0, argc, argv);
+	while ((c = getopt(argc, argv, master_service_getopt_string())) > 0) {
+		if (!master_service_parse_option(service, c, optarg))
+			exit(FATAL_DEFAULT);
+	}
+
+	master_service_init_log(service, "anvil: ", 0);
+	master_service_init_finish(service);
+	connect_limit = connect_limit_init();
+
+	master_service_run(service, client_connected);
+
+	connect_limit_deinit(&connect_limit);
+	anvil_connections_destroy_all();
+	master_service_deinit(&service);
+        return 0;
+}
--- a/src/imap-login/client.c	Tue May 05 20:26:40 2009 -0400
+++ b/src/imap-login/client.c	Tue May 05 21:28:34 2009 -0400
@@ -49,7 +49,7 @@
 #define AUTH_MASTER_WAITING_MSG \
 	"* OK Waiting for authentication master process to respond.."
 
-const char *login_protocol = "IMAP";
+const char *login_protocol = "imap";
 const char *login_process_name = "imap-login";
 
 static void client_set_title(struct imap_client *client)
--- a/src/imap/imap-client.c	Tue May 05 20:26:40 2009 -0400
+++ b/src/imap/imap-client.c	Tue May 05 21:28:34 2009 -0400
@@ -4,6 +4,7 @@
 #include "ioloop.h"
 #include "llist.h"
 #include "str.h"
+#include "hostpid.h"
 #include "network.h"
 #include "istream.h"
 #include "ostream.h"
@@ -34,6 +35,7 @@
 {
 	struct client *client;
 	struct mail_namespace *ns;
+	const char *ident;
 
 	/* always use nonblocking I/O */
 	net_set_nonblock(fd_in, TRUE);
@@ -68,6 +70,13 @@
 	str_append(client->capability_string, *set->imap_capability != '\0' ?
 		   set->imap_capability : CAPABILITY_STRING);
 
+	ident = mail_user_get_anvil_userip_ident(client->user);
+	if (ident != NULL) {
+		master_service_anvil_send(service, t_strconcat("CONNECT\t",
+			my_pid, "\t", ident, "/imap\n", NULL));
+		client->anvil_sent = TRUE;
+	}
+
 	i_assert(my_client == NULL);
 	my_client = client;
 
@@ -174,6 +183,12 @@
 		client_search_updates_free(client);
 		mailbox_close(&client->mailbox);
 	}
+	if (client->anvil_sent) {
+		master_service_anvil_send(service, t_strconcat("DISCONNECT\t",
+			my_pid, "\t",
+			mail_user_get_anvil_userip_ident(client->user), "/imap"
+			"\n", NULL));
+	}
 	mail_user_unref(&client->user);
 
 	if (client->free_parser != NULL)
--- a/src/imap/imap-client.h	Tue May 05 20:26:40 2009 -0400
+++ b/src/imap/imap-client.h	Tue May 05 21:28:34 2009 -0400
@@ -139,6 +139,7 @@
 	unsigned int syncing:1;
 	unsigned int id_logged:1;
 	unsigned int mailbox_examined:1;
+	unsigned int anvil_sent:1;
 	unsigned int input_skip_line:1; /* skip all the data until we've
 					   found a new line */
 	unsigned int modseqs_sent_since_sync:1;
--- a/src/lib-master/master-interface.h	Tue May 05 20:26:40 2009 -0400
+++ b/src/lib-master/master-interface.h	Tue May 05 21:28:34 2009 -0400
@@ -60,9 +60,7 @@
 
 enum master_auth_status {
 	MASTER_AUTH_STATUS_OK,
-	MASTER_AUTH_STATUS_INTERNAL_ERROR,
-	/* user reached max. simultaneous connections */
-	MASTER_AUTH_STATUS_MAX_CONNECTIONS
+	MASTER_AUTH_STATUS_INTERNAL_ERROR
 };
 
 struct master_auth_reply {
@@ -85,8 +83,9 @@
 /* getenv(MASTER_DOVECOT_VERSION_ENV) provides master's version number */
 #define MASTER_DOVECOT_VERSION_ENV "DOVECOT_VERSION"
 
-/* points to /dev/null for now */
-#define MASTER_RESERVED_FD 3
+/* Write pipe to anvil. Currently available only for auth destination
+   services, for others it's /dev/null. */
+#define MASTER_ANVIL_FD 3
 
 /* Socket for sending master_auth_requests. Also used by auth server process
    as a master socket. */
--- a/src/lib-master/master-service-settings.c	Tue May 05 20:26:40 2009 -0400
+++ b/src/lib-master/master-service-settings.c	Tue May 05 21:28:34 2009 -0400
@@ -59,7 +59,7 @@
 
 	binary_path = service->argv[0];
 	path = getenv("PATH");
-	if (*service->argv[0] != '/') {
+	if (*service->argv[0] != '/' && path != NULL) {
 		/* we have to find our executable from path */
 		paths = t_strsplit(path, ":");
 		for (; *paths != NULL; paths++) {
--- a/src/lib-master/master-service.c	Tue May 05 20:26:40 2009 -0400
+++ b/src/lib-master/master-service.c	Tue May 05 21:28:34 2009 -0400
@@ -356,6 +356,23 @@
         io_loop_stop(service->ioloop);
 }
 
+void master_service_anvil_send(struct master_service *service, const char *cmd)
+{
+	ssize_t ret;
+
+	if ((service->flags & MASTER_SERVICE_FLAG_STANDALONE) != 0)
+		return;
+
+	ret = write(MASTER_ANVIL_FD, cmd, strlen(cmd));
+	if (ret < 0)
+		i_error("write(anvil) failed: %m");
+	else if (ret == 0)
+		i_error("write(anvil) failed: EOF");
+	else {
+		i_assert((size_t)ret == strlen(cmd));
+	}
+}
+
 void master_service_client_connection_destroyed(struct master_service *service)
 {
 	if (service->listeners == NULL) {
--- a/src/lib-master/master-service.h	Tue May 05 20:26:40 2009 -0400
+++ b/src/lib-master/master-service.h	Tue May 05 21:28:34 2009 -0400
@@ -75,6 +75,8 @@
 /* Stop a running service. */
 void master_service_stop(struct master_service *service);
 
+/* Send command to anvil process, if we have fd to it. */
+void master_service_anvil_send(struct master_service *service, const char *cmd);
 /* Call whenever a client connection is destroyed. */
 void master_service_client_connection_destroyed(struct master_service *service);
 
--- a/src/lib-storage/mail-user.c	Tue May 05 20:26:40 2009 -0400
+++ b/src/lib-storage/mail-user.c	Tue May 05 21:28:34 2009 -0400
@@ -5,6 +5,7 @@
 #include "hostpid.h"
 #include "network.h"
 #include "str.h"
+#include "strescape.h"
 #include "var-expand.h"
 #include "settings-parser.h"
 #include "auth-master.h"
@@ -317,6 +318,14 @@
 	return mail_storage_get_temp_prefix(ns->storage);
 }
 
+const char *mail_user_get_anvil_userip_ident(struct mail_user *user)
+{
+	if (user->remote_ip == NULL)
+		return NULL;
+	return t_strconcat(net_ip2addr(user->remote_ip), "/",
+			   str_tabescape(user->username), NULL);
+}
+
 void mail_users_init(const char *auth_socket_path, bool debug)
 {
 	auth_master_conn = auth_master_init(auth_socket_path, debug);
--- a/src/lib-storage/mail-user.h	Tue May 05 20:26:40 2009 -0400
+++ b/src/lib-storage/mail-user.h	Tue May 05 21:28:34 2009 -0400
@@ -105,5 +105,7 @@
 const char *mail_user_home_expand(struct mail_user *user, const char *path);
 /* Returns 0 if ok, -1 if home directory isn't set. */
 int mail_user_try_home_expand(struct mail_user *user, const char **path);
+/* Returns unique user+ip identifier for anvil. */
+const char *mail_user_get_anvil_userip_ident(struct mail_user *user);
 
 #endif
--- a/src/login-common/common.h	Tue May 05 20:26:40 2009 -0400
+++ b/src/login-common/common.h	Tue May 05 21:28:34 2009 -0400
@@ -16,6 +16,7 @@
 
 extern struct auth_client *auth_client;
 extern bool closing_down;
+extern int anvil_fd;
 
 extern struct master_service *service;
 extern struct login_settings *login_settings;
--- a/src/login-common/login-settings.c	Tue May 05 20:26:40 2009 -0400
+++ b/src/login-common/login-settings.c	Tue May 05 21:28:34 2009 -0400
@@ -43,6 +43,7 @@
 	DEF(SET_BOOL, verbose_proctitle),
 
 	DEF(SET_UINT, login_max_connections),
+	DEF(SET_UINT, mail_max_userip_connections),
 
 	SETTING_DEFINE_LIST_END
 };
@@ -75,7 +76,8 @@
 	MEMBER(auth_debug) FALSE,
 	MEMBER(verbose_proctitle) FALSE,
 
-	MEMBER(login_max_connections) 256
+	MEMBER(login_max_connections) 256,
+	MEMBER(mail_max_userip_connections) 10
 };
 
 struct setting_parser_info login_setting_parser_info = {
--- a/src/login-common/login-settings.h	Tue May 05 20:26:40 2009 -0400
+++ b/src/login-common/login-settings.h	Tue May 05 21:28:34 2009 -0400
@@ -31,6 +31,7 @@
 	bool verbose_proctitle;
 
 	unsigned int login_max_connections;
+	unsigned int mail_max_userip_connections;
 
 	/* generated: */
 	const char *const *log_format_elements_split;
--- a/src/login-common/main.c	Tue May 05 20:26:40 2009 -0400
+++ b/src/login-common/main.c	Tue May 05 21:28:34 2009 -0400
@@ -20,6 +20,7 @@
 
 struct auth_client *auth_client;
 bool closing_down;
+int anvil_fd = -1;
 
 struct master_service *service;
 struct login_settings *login_settings;
@@ -65,6 +66,21 @@
                 clients_notify_auth_connected();
 }
 
+static int anvil_connect(void)
+{
+#define ANVIL_HANDSHAKE "VERSION\t1\t0\n"
+	int fd;
+
+	fd = net_connect_unix("anvil");
+	if (fd < 0)
+		i_fatal("net_connect_unix(anvil) failed: %m");
+	net_set_nonblock(fd, FALSE);
+
+	if (write(fd, ANVIL_HANDSHAKE, strlen(ANVIL_HANDSHAKE)) < 0)
+		i_fatal("write(anvil) failed: %m");
+	return fd;
+}
+
 static void main_preinit(void)
 {
 	unsigned int max_fds;
@@ -85,6 +101,9 @@
 
 	i_assert(strcmp(login_settings->ssl, "no") == 0 || ssl_initialized);
 
+	if (login_settings->mail_max_userip_connections > 0)
+		anvil_fd = anvil_connect();
+
 	restrict_access_by_env(NULL, TRUE);
 }
 
@@ -113,6 +132,11 @@
 	if (auth_client != NULL)
 		auth_client_free(&auth_client);
 	clients_deinit();
+
+	if (anvil_fd != -1) {
+		if (close(anvil_fd) < 0)
+			i_error("close(anvil) failed: %m");
+	}
 	master_auth_deinit(service);
 }
 
--- a/src/login-common/sasl-server.c	Tue May 05 20:26:40 2009 -0400
+++ b/src/login-common/sasl-server.c	Tue May 05 21:28:34 2009 -0400
@@ -4,6 +4,8 @@
 #include "base64.h"
 #include "buffer.h"
 #include "istream.h"
+#include "write-full.h"
+#include "strescape.h"
 #include "str-sanitize.h"
 #include "auth-client.h"
 #include "ssl-proxy.h"
@@ -11,6 +13,13 @@
 #include "master-auth.h"
 #include "client-common.h"
 
+#include <stdlib.h>
+#include <unistd.h>
+
+#define ERR_TOO_MANY_USERIP_CONNECTIONS \
+	"Maximum number of connections from user+IP exceeded " \
+	"(mail_max_userip_connections)"
+
 static enum auth_request_flags
 client_get_auth_flags(struct client *client)
 {
@@ -54,10 +63,6 @@
 		break;
 	case MASTER_AUTH_STATUS_INTERNAL_ERROR:
 		break;
-	case MASTER_AUTH_STATUS_MAX_CONNECTIONS:
-		data = "Maximum number of connections from user+IP exceeded "
-			"(mail_max_userip_connections)";
-		break;
 	}
 	client->mail_pid = reply->mail_pid;
 	call_client_callback(client, sasl_reply, data, NULL);
@@ -92,6 +97,35 @@
 				    master_auth_callback, client);
 }
 
+static bool anvil_has_too_many_connections(struct client *client)
+{
+	const char *ident;
+	char buf[64];
+	ssize_t ret;
+
+	if (client->virtual_user == NULL)
+		return FALSE;
+	if (login_settings->mail_max_userip_connections == 0)
+		return FALSE;
+
+	ident = t_strconcat("LOOKUP\t", net_ip2addr(&client->ip), "/",
+			    str_tabescape(client->virtual_user), "/",
+			    login_protocol, "\n", NULL);
+	if (write_full(anvil_fd, ident, strlen(ident)) < 0)
+		i_fatal("write(anvil) failed: %m");
+	ret = read(anvil_fd, buf, sizeof(buf)-1);
+	if (ret < 0)
+		i_fatal("read(anvil) failed: %m");
+	else if (ret == 0)
+		i_fatal("read(anvil) failed: EOF");
+	if (buf[ret-1] != '\n')
+		i_fatal("anvil lookup failed: Invalid input in reply");
+	buf[ret-1] = '\0';
+
+	return strtoul(buf, NULL, 10) >=
+		login_settings->mail_max_userip_connections;
+}
+
 static void authenticate_callback(struct auth_request *request, int status,
 				  const char *data_base64,
 				  const char *const *args, void *context)
@@ -133,6 +167,11 @@
 			client->authenticating = FALSE;
 			call_client_callback(client, SASL_SERVER_REPLY_SUCCESS,
 					     NULL, args);
+		} else if (anvil_has_too_many_connections(client)) {
+			client->authenticating = FALSE;
+			call_client_callback(client,
+					SASL_SERVER_REPLY_MASTER_FAILED,
+					ERR_TOO_MANY_USERIP_CONNECTIONS, NULL);
 		} else {
 			master_send_request(client, request);
 		}
--- a/src/master/Makefile.am	Tue May 05 20:26:40 2009 -0400
+++ b/src/master/Makefile.am	Tue May 05 21:28:34 2009 -0400
@@ -25,12 +25,14 @@
 	dup2-array.c \
 	main.c \
 	master-settings.c \
+	service-anvil.c \
 	service-auth-server.c \
 	service-auth-source.c \
 	service-listen.c \
 	service-log.c \
 	service-monitor.c \
 	service-process.c \
+	service-process-notify.c \
 	service.c
 
 noinst_HEADERS = \
@@ -38,10 +40,12 @@
 	common.h \
 	dup2-array.h \
 	master-settings.h \
+	service-anvil.h \
 	service-auth-server.h \
 	service-auth-source.h \
 	service-listen.h \
 	service-log.h \
 	service-monitor.h \
 	service-process.h \
+	service-process-notify.h \
 	service.h
--- a/src/master/master-settings.c	Tue May 05 20:26:40 2009 -0400
+++ b/src/master/master-settings.c	Tue May 05 21:28:34 2009 -0400
@@ -283,6 +283,7 @@
 		if (*services[i]->type != '\0' &&
 		    strcmp(services[i]->type, "log") != 0 &&
 		    strcmp(services[i]->type, "config") != 0 &&
+		    strcmp(services[i]->type, "anvil") != 0 &&
 		    strcmp(services[i]->type, "auth") != 0 &&
 		    strcmp(services[i]->type, "auth-source") != 0) {
 			*error_r = t_strconcat("Unknown service type: ",
@@ -428,7 +429,7 @@
 
 bool master_settings_do_fixes(const struct master_settings *set)
 {
-	const char *login_dir;
+	const char *login_dir, *empty_dir;
 	struct stat st;
 	gid_t gid;
 
@@ -479,5 +480,11 @@
 			return FALSE;
 		}
 	}
+
+	empty_dir = t_strconcat(set->base_dir, "/empty", NULL);
+	if (safe_mkdir(empty_dir, 0755, master_uid, getegid()) == 0) {
+		i_warning("Corrected permissions for empty directory "
+			  "%s", empty_dir);
+	}
 	return TRUE;
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/master/service-anvil.c	Tue May 05 21:28:34 2009 -0400
@@ -0,0 +1,92 @@
+/* Copyright (c) 2009 Dovecot authors, see the included COPYING file */
+
+#include "common.h"
+#include "fd-close-on-exec.h"
+#include "fd-set-nonblock.h"
+#include "service.h"
+#include "service-process.h"
+#include "service-process-notify.h"
+#include "service-anvil.h"
+
+#include <unistd.h>
+
+#define ANVIL_HANDSHAKE "VERSION\t1\t0\n"
+
+static int anvil_send_handshake(int fd, const char **error_r)
+{
+	ssize_t ret;
+
+	ret = write(fd, ANVIL_HANDSHAKE, strlen(ANVIL_HANDSHAKE));
+	if (ret < 0) {
+		*error_r = t_strdup_printf("write(anvil) failed: %m");
+		return -1;
+	}
+	if (ret == 0) {
+		*error_r = t_strdup_printf("write(anvil) returned EOF");
+		return -1;
+	}
+	/* this is a pipe, it either wrote all of it or nothing */
+	i_assert(ret == strlen(ANVIL_HANDSHAKE));
+	return 0;
+}
+
+static int
+service_process_write_anvil_kill(int fd, struct service_process *process)
+{
+	const char *data;
+
+	data = t_strdup_printf("KILL\t%s\n", dec2str(process->pid));
+	if (write(fd, data, strlen(data)) < 0) {
+		if (errno != EAGAIN)
+			i_error("write(anvil process) failed: %m");
+		return -1;
+	}
+	return 0;
+}
+
+int service_list_init_anvil(struct service_list *service_list,
+			    const char **error_r)
+{
+	if (pipe(service_list->blocking_anvil_fd) < 0) {
+		*error_r = t_strdup_printf("pipe() failed: %m");
+		return -1;
+	}
+	if (pipe(service_list->nonblocking_anvil_fd) < 0) {
+		(void)close(service_list->blocking_anvil_fd[0]);
+		(void)close(service_list->blocking_anvil_fd[1]);
+		*error_r = t_strdup_printf("pipe() failed: %m");
+		return -1;
+	}
+	fd_set_nonblock(service_list->nonblocking_anvil_fd[1], TRUE);
+
+	fd_close_on_exec(service_list->blocking_anvil_fd[0], TRUE);
+	fd_close_on_exec(service_list->blocking_anvil_fd[1], TRUE);
+	fd_close_on_exec(service_list->nonblocking_anvil_fd[0], TRUE);
+	fd_close_on_exec(service_list->nonblocking_anvil_fd[1], TRUE);
+
+	if (anvil_send_handshake(service_list->blocking_anvil_fd[1],
+				 error_r) < 0)
+		return -1;
+	if (anvil_send_handshake(service_list->nonblocking_anvil_fd[1],
+				 error_r) < 0)
+		return -1;
+
+	i_assert(service_list->anvil_kills == NULL);
+	service_list->anvil_kills =
+		service_process_notify_init(service_list->nonblocking_anvil_fd[1],
+					    service_process_write_anvil_kill);
+	return 0;
+}
+
+void service_list_deinit_anvil(struct service_list *service_list)
+{
+	service_process_notify_deinit(&service_list->anvil_kills);
+	if (close(service_list->blocking_anvil_fd[0]) < 0)
+		i_error("close(anvil) failed: %m");
+	if (close(service_list->blocking_anvil_fd[1]) < 0)
+		i_error("close(anvil) failed: %m");
+	if (close(service_list->nonblocking_anvil_fd[0]) < 0)
+		i_error("close(anvil) failed: %m");
+	if (close(service_list->nonblocking_anvil_fd[1]) < 0)
+		i_error("close(anvil) failed: %m");
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/master/service-anvil.h	Tue May 05 21:28:34 2009 -0400
@@ -0,0 +1,8 @@
+#ifndef SERVICE_ANVIL_H
+#define SERVICE_ANVIL_H
+
+int service_list_init_anvil(struct service_list *service_list,
+			    const char **error_r);
+void service_list_deinit_anvil(struct service_list *service_list);
+
+#endif
--- a/src/master/service-auth-server.c	Tue May 05 20:26:40 2009 -0400
+++ b/src/master/service-auth-server.c	Tue May 05 21:28:34 2009 -0400
@@ -107,11 +107,8 @@
 			request->process->process.service->auth_dest_service;
 		struct service_process *dest_process;
 
-		/* FIXME: handle MASTER_AUTH_STATUS_MAX_CONNECTIONS */
 		dest_process = service_process_create(dest_service, list + 1,
-						      request->fd,
-						      request->data,
-						      request->data_size);
+						      request);
 		status = dest_process != NULL ?
 			MASTER_AUTH_STATUS_OK :
 			MASTER_AUTH_STATUS_INTERNAL_ERROR;
--- a/src/master/service-log.c	Tue May 05 20:26:40 2009 -0400
+++ b/src/master/service-log.c	Tue May 05 21:28:34 2009 -0400
@@ -9,6 +9,7 @@
 #include "fd-set-nonblock.h"
 #include "service.h"
 #include "service-process.h"
+#include "service-process-notify.h"
 #include "service-log.h"
 
 #include <unistd.h>
@@ -48,6 +49,22 @@
 	return 0;
 }
 
+static int
+service_process_write_log_bye(int fd, struct service_process *process)
+{
+	const char *data;
+
+	data = t_strdup_printf("%d %s BYE\n",
+			       process->service->log_process_internal_fd,
+			       dec2str(process->pid));
+	if (write(fd, data, strlen(data)) < 0) {
+		if (errno != EAGAIN)
+			i_error("write(log process) failed: %m");
+		return -1;
+	}
+	return 0;
+}
+
 int services_log_init(struct service_list *service_list)
 {
 	struct service *const *services;
@@ -66,6 +83,11 @@
 	else
 		fd_set_nonblock(service_list->master_log_fd[1], TRUE);
 
+	i_assert(service_list->log_byes == NULL);
+	service_list->log_byes =
+		service_process_notify_init(service_list->master_log_fd[1],
+					    service_process_write_log_bye);
+
 	n = 1;
 	for (i = 0; i < count; i++) {
 		if (services[i]->type == SERVICE_TYPE_LOG)
@@ -90,26 +112,6 @@
 	return 0;
 }
 
-void services_log_clear_byes(struct service_list *service_list)
-{
-	struct service_process *const *processes, *process;
-	unsigned int i, count;
-
-	if (service_list->io_log_write == NULL)
-		return;
-
-	processes = array_idx_modifiable(&service_list->bye_arr, 0);
-	count = aqueue_count(service_list->bye_queue);
-	for (i = 0; i < count; i++) {
-		process = processes[aqueue_idx(service_list->bye_queue, i)];
-		service_process_unref(process);
-	}
-	aqueue_clear(service_list->bye_queue);
-	array_clear(&service_list->bye_arr);
-
-	io_remove(&service_list->io_log_write);
-}
-
 void services_log_deinit(struct service_list *service_list)
 {
 	struct service *const *services;
@@ -131,7 +133,7 @@
 			services[i]->log_process_internal_fd = -1;
 		}
 	}
-	services_log_clear_byes(service_list);
+	service_process_notify_deinit(&service_list->log_byes);
 	if (service_list->master_log_fd[0] != -1) {
 		if (close(service_list->master_log_fd[0]) < 0)
 			i_error("close(master log fd) failed: %m");
--- a/src/master/service-log.h	Tue May 05 20:26:40 2009 -0400
+++ b/src/master/service-log.h	Tue May 05 21:28:34 2009 -0400
@@ -6,7 +6,6 @@
 int services_log_init(struct service_list *service_list);
 void services_log_deinit(struct service_list *service_list);
 
-void services_log_clear_byes(struct service_list *service_list);
 void services_log_dup2(ARRAY_TYPE(dup2) *dups,
 		       struct service_list *service_list,
 		       unsigned int first_fd, unsigned int *fd_count);
--- a/src/master/service-monitor.c	Tue May 05 20:26:40 2009 -0400
+++ b/src/master/service-monitor.c	Tue May 05 21:28:34 2009 -0400
@@ -7,6 +7,7 @@
 #include "hash.h"
 #include "service.h"
 #include "service-process.h"
+#include "service-process-notify.h"
 #include "service-log.h"
 #include "service-monitor.h"
 
@@ -129,7 +130,7 @@
 	}
 
 	/* create a child process and let it accept() this connection */
-	if (service_process_create(service, NULL, -1, NULL, 0) == NULL)
+	if (service_process_create(service, NULL, NULL) == NULL)
 		service_monitor_throttle(service);
 	else
 		service_monitor_listen_stop(service);
@@ -195,9 +196,9 @@
 			service_monitor_listen_start(services[i]);
 	}
 
-	if (service_process_create(service_list->log, NULL, -1, NULL, 0) != NULL)
+	if (service_process_create(service_list->log, NULL, NULL) != NULL)
 		service_monitor_listen_stop(service_list->log);
-	if (service_process_create(service_list->config, NULL, -1, NULL, 0) != NULL)
+	if (service_process_create(service_list->config, NULL, NULL) != NULL)
 		service_monitor_listen_stop(service_list->config);
 }
 
@@ -235,6 +236,18 @@
 	services_log_deinit(service_list);
 }
 
+static void service_process_failure(struct service_process *process, int status)
+{
+	struct service *service = process->service;
+
+	service_process_log_status_error(process, status);
+	if (process->total_count == 0)
+		service_monitor_throttle(service);
+
+	if (service->list->anvil_kills != NULL)
+		service_process_notify_add(service->list->anvil_kills, process);
+}
+
 void services_monitor_reap_children(struct service_list *service_list)
 {
 	struct service_process *process;
@@ -256,12 +269,8 @@
 			if (service->listen_pending)
 				service_monitor_listen_start(service);
 		} else {
-			/* failure */
-			service_process_log_status_error(process, status);
-			if (process->total_count == 0)
-				service_monitor_throttle(service);
+			service_process_failure(process, status);
 		}
-
 		service_process_destroy(process);
 
                 if (service->process_avail == 0 && service->to_throttle == NULL)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/master/service-process-notify.c	Tue May 05 21:28:34 2009 -0400
@@ -0,0 +1,102 @@
+/* Copyright (c) 2009 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "aqueue.h"
+#include "ioloop.h"
+#include "service.h"
+#include "service-process.h"
+#include "service-process-notify.h"
+
+struct service_process_notify {
+	service_process_notify_callback_t *write_callback;
+
+	int fd;
+	struct io *io_write;
+	struct aqueue *process_queue;
+	ARRAY_DEFINE(processes, struct service_process *);
+};
+
+struct service_process_notify *
+service_process_notify_init(int fd,
+			    service_process_notify_callback_t *write_callback)
+{
+	struct service_process_notify *notify;
+
+	notify = i_new(struct service_process_notify, 1);
+	notify->fd = fd;
+	notify->write_callback = write_callback;
+
+	i_array_init(&notify->processes, 64);
+	notify->process_queue = aqueue_init(&notify->processes.arr);
+	return notify;
+}
+
+static void service_process_notify_reset(struct service_process_notify *notify)
+{
+	struct service_process *const *processes, *process;
+	unsigned int i, count;
+
+	if (notify->io_write == NULL)
+		return;
+
+	processes = array_idx_modifiable(&notify->processes, 0);
+	count = aqueue_count(notify->process_queue);
+	for (i = 0; i < count; i++) {
+		process = processes[aqueue_idx(notify->process_queue, i)];
+		service_process_unref(process);
+	}
+	aqueue_clear(notify->process_queue);
+	array_clear(&notify->processes);
+
+	io_remove(&notify->io_write);
+}
+
+static void notify_flush(struct service_process_notify *notify)
+{
+	struct service_process *const *processes, *process;
+
+	while (aqueue_count(notify->process_queue) > 0) {
+		processes = array_idx_modifiable(&notify->processes, 0);
+		process = processes[aqueue_idx(notify->process_queue, 0)];
+
+		if (notify->write_callback(notify->fd, process) < 0) {
+			if (errno != EAGAIN)
+				service_process_notify_reset(notify);
+			return;
+		}
+		service_process_unref(process);
+		aqueue_delete_tail(notify->process_queue);
+	}
+	io_remove(&notify->io_write);
+}
+
+void service_process_notify_deinit(struct service_process_notify **_notify)
+{
+	struct service_process_notify *notify = *_notify;
+
+	*_notify = NULL;
+
+	service_process_notify_reset(notify);
+	if (notify->io_write != NULL)
+		io_remove(&notify->io_write);
+	aqueue_deinit(&notify->process_queue);
+	array_free(&notify->processes);
+	i_free(notify);
+}
+
+void service_process_notify_add(struct service_process_notify *notify,
+				struct service_process *process)
+{
+	if (notify->write_callback(notify->fd, process) < 0) {
+		if (errno != EAGAIN)
+			return;
+
+		if (notify->io_write == NULL) {
+			notify->io_write = io_add(notify->fd, IO_WRITE,
+						  notify_flush, notify);
+		}
+		aqueue_append(notify->process_queue, &process);
+		service_process_ref(process);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/master/service-process-notify.h	Tue May 05 21:28:34 2009 -0400
@@ -0,0 +1,15 @@
+#ifndef SERVICE_PROCESS_NOTIFY_H
+#define SERVICE_PROCESS_NOTIFY_H
+
+typedef int
+service_process_notify_callback_t(int fd, struct service_process *process);
+
+struct service_process_notify *
+service_process_notify_init(int fd,
+			    service_process_notify_callback_t *write_callback);
+void service_process_notify_deinit(struct service_process_notify **notify);
+
+void service_process_notify_add(struct service_process_notify *notify,
+				struct service_process *process);
+
+#endif
--- a/src/master/service-process.c	Tue May 05 20:26:40 2009 -0400
+++ b/src/master/service-process.c	Tue May 05 21:28:34 2009 -0400
@@ -20,6 +20,7 @@
 #include "service-log.h"
 #include "service-auth-server.h"
 #include "service-auth-source.h"
+#include "service-process-notify.h"
 #include "service-process.h"
 
 #include <stdlib.h>
@@ -30,7 +31,8 @@
 #include <sys/wait.h>
 
 static void
-service_dup_fds(struct service *service, int auth_fd, int std_fd)
+service_dup_fds(struct service *service, int auth_fd, int std_fd,
+		bool give_anvil_fd)
 {
 	struct service_listener *const *listeners;
 	ARRAY_TYPE(dup2) dups;
@@ -47,11 +49,24 @@
 	listeners = array_get(&service->listeners, &count);
 	t_array_init(&dups, count + 10);
 
-	if (service->type == SERVICE_TYPE_LOG) {
+	switch (service->type) {
+	case SERVICE_TYPE_LOG:
 		i_assert(n == 0);
 		services_log_dup2(&dups, service->list, MASTER_LISTEN_FD_FIRST,
 				  &socket_listener_count);
 		n += socket_listener_count;
+		break;
+	case SERVICE_TYPE_ANVIL:
+		/* nonblocking anvil fd must be the first one. anvil treats it
+		   as the master's fd */
+		dup2_append(&dups, service->list->nonblocking_anvil_fd[0],
+			    MASTER_LISTEN_FD_FIRST + n++);
+		dup2_append(&dups, service->list->blocking_anvil_fd[0],
+			    MASTER_LISTEN_FD_FIRST + n++);
+		socket_listener_count += 2;
+		break;
+	default:
+		break;
 	}
 
 	/* first add non-ssl listeners */
@@ -77,7 +92,12 @@
 		}
 	}
 
-	dup2_append(&dups, null_fd, MASTER_RESERVED_FD);
+	if (!give_anvil_fd)
+		dup2_append(&dups, null_fd, MASTER_ANVIL_FD);
+	else {
+		dup2_append(&dups, service->list->blocking_anvil_fd[1],
+			    MASTER_ANVIL_FD);
+	}
 	dup2_append(&dups, service->status_fd[1], MASTER_STATUS_FD);
 
 	switch (service->type) {
@@ -326,14 +346,32 @@
 	timeout_remove(&process->to_status);
 }
 
+static void
+handle_request(const struct service_process_auth_request *request)
+{
+	string_t *str;
+
+	if (request == NULL)
+		return;
+
+	if (request->data_size > 0) {
+		str = t_str_new(request->data_size*3);
+		str_append(str, "CLIENT_INPUT=");
+		base64_encode(request->data, request->data_size, str);
+		env_put(str_c(str));
+	}
+
+	env_put(t_strconcat("LOCAL_IP=", net_ip2addr(&request->local_ip), NULL));
+	env_put(t_strconcat("IP=", net_ip2addr(&request->remote_ip), NULL));
+}
+
 struct service_process *
 service_process_create(struct service *service, const char *const *auth_args,
-		       int std_fd, const unsigned char *data, size_t data_size)
+		       const struct service_process_auth_request *request)
 {
 	static unsigned int uid_counter = 0;
 	struct service_process *process;
 	unsigned int uid = ++uid_counter;
-	string_t *str;
 	int fd[2];
 	pid_t pid;
 
@@ -366,13 +404,9 @@
 		if (fd[0] != -1)
 			(void)close(fd[0]);
 		service_process_setup_environment(service, uid);
-		if (data_size > 0) {
-			str = t_str_new(data_size*3);
-			str_append(str, "CLIENT_INPUT=");
-			base64_encode(data, data_size, str);
-			env_put(str_c(str));
-		}
-		service_dup_fds(service, fd[1], std_fd);
+		handle_request(request);
+		service_dup_fds(service, fd[1], request == NULL ? -1 :
+				request->fd, auth_args != NULL);
 		drop_privileges(service, auth_args);
 		process_exec(service->executable, NULL);
 	}
@@ -418,65 +452,6 @@
 	return process;
 }
 
-static int service_process_write_bye(struct service_process *process)
-{
-	const char *data;
-
-	data = t_strdup_printf("%d %s BYE\n",
-			       process->service->log_process_internal_fd,
-			       dec2str(process->pid));
-	if (write(process->service->list->master_log_fd[1],
-		  data, strlen(data)) < 0) {
-		if (errno != EAGAIN)
-			i_error("write(log process) failed: %m");
-		return -1;
-	}
-	return 0;
-}
-
-static void service_list_log_flush_byes(struct service_list *service_list)
-{
-	struct service_process *const *processes, *process;
-
-	while (aqueue_count(service_list->bye_queue) > 0) {
-		processes = array_idx_modifiable(&service_list->bye_arr, 0);
-		process = processes[aqueue_idx(service_list->bye_queue, 0)];
-
-		if (service_process_write_bye(process) < 0) {
-			if (errno != EAGAIN)
-				services_log_clear_byes(service_list);
-			return;
-		}
-		service_process_unref(process);
-		aqueue_delete_tail(service_list->bye_queue);
-	}
-	io_remove(&service_list->io_log_write);
-}
-
-static void service_process_log_bye(struct service_process *process)
-{
-	struct service_list *service_list = process->service->list;
-
-	if (process->service->log_fd[1] == -1) {
-		/* stopping all services */
-		return;
-	}
-
-	if (service_process_write_bye(process) < 0) {
-		if (errno != EAGAIN)
-			return;
-
-		if (service_list->io_log_write == NULL) {
-			service_list->io_log_write =
-				io_add(service_list->master_log_fd[1], IO_WRITE,
-				       service_list_log_flush_byes,
-				       service_list);
-		}
-		aqueue_append(service_list->bye_queue, &process);
-		service_process_ref(process);
-	}
-}
-
 void service_process_destroy(struct service_process *process)
 {
 	struct service *service = process->service;
@@ -502,7 +477,8 @@
 		break;
 	}
 
-	service_process_log_bye(process);
+	if (service->list->log_byes != NULL)
+		service_process_notify_add(service->list->log_byes, process);
 
 	process->destroyed = TRUE;
 	service_process_unref(process);
--- a/src/master/service-process.h	Tue May 05 20:26:40 2009 -0400
+++ b/src/master/service-process.h	Tue May 05 21:28:34 2009 -0400
@@ -64,7 +64,7 @@
 
 struct service_process *
 service_process_create(struct service *service, const char *const *auth_args,
-		       int std_fd, const unsigned char *data, size_t data_size);
+		       const struct service_process_auth_request *request);
 void service_process_destroy(struct service_process *process);
 
 void service_process_ref(struct service_process *process);
--- a/src/master/service.c	Tue May 05 20:26:40 2009 -0400
+++ b/src/master/service.c	Tue May 05 21:28:34 2009 -0400
@@ -6,6 +6,7 @@
 #include "hash.h"
 #include "str.h"
 #include "service.h"
+#include "service-anvil.h"
 #include "service-process.h"
 #include "service-monitor.h"
 
@@ -136,6 +137,8 @@
 			service->type = SERVICE_TYPE_LOG;
 		else if (strcmp(set->type, "config") == 0)
 			service->type = SERVICE_TYPE_CONFIG;
+		else if (strcmp(set->type, "anvil") == 0)
+			service->type = SERVICE_TYPE_ANVIL;
 		else if (strcmp(set->type, "auth") == 0)
 			service->type = SERVICE_TYPE_AUTH_SERVER;
 		else if (strcmp(set->type, "auth-source") == 0)
@@ -370,10 +373,11 @@
 		return NULL;
 	}
 
+	if (service_list_init_anvil(service_list, error_r) < 0)
+		return NULL;
+
 	service_list->pids = hash_table_create(default_pool, pool, 0,
 					       pid_hash, pid_hash_cmp);
-	p_array_init(&service_list->bye_arr, pool, 64);
-	service_list->bye_queue = aqueue_init(&service_list->bye_arr.arr);
 	return service_list;
 }
 
@@ -413,7 +417,7 @@
 		service_process_destroy(value);
 	hash_table_iterate_deinit(&iter);
 
+	service_list_deinit_anvil(service_list);
 	hash_table_destroy(&service_list->pids);
-	aqueue_deinit(&service_list->bye_queue);
 	pool_unref(&service_list->pool);
 }
--- a/src/master/service.h	Tue May 05 20:26:40 2009 -0400
+++ b/src/master/service.h	Tue May 05 21:28:34 2009 -0400
@@ -3,6 +3,8 @@
 
 #include "network.h"
 
+struct master_settings;
+
 /* If a service process doesn't send its first status notification in
    this many seconds, kill the process */
 #define SERVICE_FIRST_STATUS_TIMEOUT_SECS 30
@@ -10,6 +12,7 @@
 enum service_type {
 	SERVICE_TYPE_UNKNOWN,
 	SERVICE_TYPE_LOG,
+	SERVICE_TYPE_ANVIL,
 	SERVICE_TYPE_CONFIG,
 	SERVICE_TYPE_AUTH_SERVER,
 	SERVICE_TYPE_AUTH_SOURCE
@@ -97,11 +100,13 @@
 
 	/* nonblocking log fds usd by master */
 	int master_log_fd[2];
-	/* we're waiting to be able to send "bye" to log process */
-	struct io *io_log_write;
-	/* List of processes who are waiting for the "bye" */
-	struct aqueue *bye_queue;
-	ARRAY_DEFINE(bye_arr, struct service_process *);
+	struct service_process_notify *log_byes;
+
+	/* passed to auth destination processes */
+	int blocking_anvil_fd[2];
+	/* used by master process to notify about dying processes */
+	int nonblocking_anvil_fd[2];
+	struct service_process_notify *anvil_kills;
 
 	ARRAY_DEFINE(services, struct service *);
 };
--- a/src/pop3-login/client.c	Tue May 05 20:26:40 2009 -0400
+++ b/src/pop3-login/client.c	Tue May 05 21:28:34 2009 -0400
@@ -35,7 +35,7 @@
 #  error client idle timeout must be smaller than authentication timeout
 #endif
 
-const char *login_protocol = "POP3";
+const char *login_protocol = "pop3";
 const char *login_process_name = "pop3-login";
 
 static void client_set_title(struct pop3_client *client)
--- a/src/pop3/pop3-client.c	Tue May 05 20:26:40 2009 -0400
+++ b/src/pop3/pop3-client.c	Tue May 05 21:28:34 2009 -0400
@@ -7,6 +7,7 @@
 #include "istream.h"
 #include "ostream.h"
 #include "str.h"
+#include "hostpid.h"
 #include "var-expand.h"
 #include "master-service.h"
 #include "mail-storage.h"
@@ -215,7 +216,7 @@
 			     const struct pop3_settings *set)
 {
 	struct mail_storage *storage;
-	const char *inbox;
+	const char *inbox, *ident;
 	struct client *client;
         enum mailbox_open_flags flags;
 	const char *errmsg;
@@ -286,6 +287,13 @@
 	if (!set->pop3_no_flag_updates && client->messages_count > 0)
 		client->seen_bitmask = i_malloc(MSGS_BITMASK_SIZE(client));
 
+	ident = mail_user_get_anvil_userip_ident(client->user);
+	if (ident != NULL) {
+		master_service_anvil_send(service, t_strconcat("CONNECT\t",
+			my_pid, "\t", ident, "/pop3\n", NULL));
+		client->anvil_sent = TRUE;
+	}
+
 	i_assert(my_client == NULL);
 	my_client = client;
 
@@ -364,6 +372,12 @@
 	}
 	if (client->mailbox != NULL)
 		mailbox_close(&client->mailbox);
+	if (client->anvil_sent) {
+		master_service_anvil_send(service, t_strconcat("DISCONNECT\t",
+			my_pid, "\t",
+			mail_user_get_anvil_userip_ident(client->user), "/pop3"
+			"\n", NULL));
+	}
 	mail_user_unref(&client->user);
 
 	i_free(client->message_sizes);
--- a/src/pop3/pop3-client.h	Tue May 05 20:26:40 2009 -0400
+++ b/src/pop3/pop3-client.h	Tue May 05 21:28:34 2009 -0400
@@ -56,6 +56,7 @@
 	unsigned int disconnected:1;
 	unsigned int deleted:1;
 	unsigned int waiting_input:1;
+	unsigned int anvil_sent:1;
 };
 
 /* Create new client with specified input/output handles. socket specifies