changeset 2236:43b82a35888d HEAD

Dovecot can now connect to externally running dovecot-auth.
author Timo Sirainen <tss@iki.fi>
date Wed, 23 Jun 2004 20:50:43 +0300
parents dcff4c088f1a
children 6b05e30c669a
files dovecot-example.conf src/auth/auth-master-connection.c src/auth/auth-master-connection.h src/auth/auth-master-interface.h src/auth/common.h src/auth/main.c src/master/auth-process.c src/master/master-settings.c src/master/master-settings.h
diffstat 9 files changed, 492 insertions(+), 115 deletions(-) [+]
line wrap: on
line diff
--- a/dovecot-example.conf	Wed Jun 23 20:48:35 2004 +0300
+++ b/dovecot-example.conf	Wed Jun 23 20:50:43 2004 +0300
@@ -484,3 +484,43 @@
 #  passdb = passwd-file /etc/passwd.imap
 #  user = dovecot-auth
 #}
+
+# It's possible to export the authentication interface to other programs,
+# for example SMTP server which supports talking to Dovecot. Client socket
+# handles the actual authentication - you give it a username and password
+# and it returns OK or failure. So it's pretty safe to allow anyone access to
+# it. Master socket is used to a) query if given client was successfully
+# authenticated, b) userdb lookups.
+
+# listener sockets will be created by Dovecot's master process using the
+# settings given inside the auth section
+#auth default_with_listener {
+#  mechanisms = plain
+#  passdb = passwd
+#  userdb = pam
+#  socket listen {
+#    master {
+#      path = /var/run/dovecot/auth-master
+#      #mode = 0600
+#      # Default user/group is the one who started dovecot-auth (root)
+#      #user = 
+#      #group = 
+#    }
+#    client {
+#      path = /var/run/dovecot-auth-client
+#      mode = 0660
+#    }
+#  }
+#}
+
+# connect sockets are assumed to be already running, Dovecot's master
+# process only tries to connect to them. They don't need any other settings
+# than path for the master socket, as the configuration is done elsewhere.
+# Note that the client sockets must exist in login_dir.
+#auth external {
+#  socket connect {
+#    master {
+#      path = /var/run/dovecot/auth-master
+#    }
+#  }
+#}
--- a/src/auth/auth-master-connection.c	Wed Jun 23 20:48:35 2004 +0300
+++ b/src/auth/auth-master-connection.c	Wed Jun 23 20:50:43 2004 +0300
@@ -18,11 +18,20 @@
 static struct auth_master_reply failure_reply =
 { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
 
+struct auth_listener {
+	struct auth_master_connection *master;
+	int client_listener;
+	int fd;
+	char *path;
+	struct io *io;
+};
+
 struct master_userdb_request {
 	struct auth_master_connection *conn;
 	unsigned int tag;
 };
 
+static void auth_master_connection_close(struct auth_master_connection *conn);
 static int auth_master_connection_unref(struct auth_master_connection *conn);
 
 static size_t reply_add(buffer_t *buf, const char *str)
@@ -89,7 +98,7 @@
 		ret = o_stream_send(conn->output, reply, reply_size);
 		if (ret < 0) {
 			/* master died, kill ourself too */
-			io_loop_stop(ioloop);
+			auth_master_connection_close(conn);
 			break;
 		}
 
@@ -100,7 +109,7 @@
 		i_warning("Master transmit buffer full, blocking..");
 		if (o_stream_flush(conn->output) < 0) {
 			/* transmit error, probably master died */
-			io_loop_stop(ioloop);
+			auth_master_connection_close(conn);
 			break;
 		}
 	}
@@ -169,7 +178,7 @@
 			  sizeof(conn->request_buf) - conn->request_pos);
 	if (ret < 0) {
 		/* master died, kill ourself too */
-		io_loop_stop(ioloop);
+                auth_master_connection_close(conn);
 		return;
 	}
 
@@ -224,8 +233,23 @@
 	master->handshake_reply = buffer_free_without_data(buf);
 }
 
+static void
+auth_master_connection_set_fd(struct auth_master_connection *conn, int fd)
+{
+	if (conn->output != NULL)
+		o_stream_unref(conn->output);
+	if (conn->io != NULL)
+		io_remove(conn->io);
+
+	conn->output = o_stream_create_file(fd, default_pool,
+					    MAX_OUTBUF_SIZE, FALSE);
+	conn->io = io_add(fd, IO_READ, master_input, conn);
+
+	conn->fd = fd;
+}
+
 struct auth_master_connection *
-auth_master_connection_new(int fd, unsigned int pid)
+auth_master_connection_create(int fd, unsigned int pid)
 {
 	struct auth_master_connection *conn;
 
@@ -235,42 +259,54 @@
 	conn->fd = fd;
 	conn->listeners_buf =
 		buffer_create_dynamic(default_pool, 64, (size_t)-1);
-	if (fd != -1) {
-		conn->output = o_stream_create_file(fd, default_pool,
-						    MAX_OUTBUF_SIZE, FALSE);
-		conn->io = io_add(fd, IO_READ, master_input, conn);
-	}
+	if (fd != -1)
+                auth_master_connection_set_fd(conn, fd);
 	master_get_handshake_reply(conn);
 	return conn;
 }
 
 void auth_master_connection_send_handshake(struct auth_master_connection *conn)
 {
+	struct auth_master_handshake_reply reply;
+
 	/* just a note to master that we're ok. if we die before,
 	   master should shutdown itself. */
-	if (conn->output != NULL)
-		o_stream_send(conn->output, "O", 1);
+	if (conn->output != NULL) {
+		memset(&reply, 0, sizeof(reply));
+		reply.server_pid = conn->pid;
+		o_stream_send(conn->output, &reply, sizeof(reply));
+	}
 }
 
-void auth_master_connection_free(struct auth_master_connection *conn)
+static void auth_master_connection_close(struct auth_master_connection *conn)
 {
-	struct auth_client_listener **l;
+	if (!standalone)
+		io_loop_stop(ioloop);
+
+	if (close(conn->fd) < 0)
+		i_error("close(): %m");
+	conn->fd = -1;
+
+	o_stream_close(conn->output);
+	conn->output = NULL;
+
+	io_remove(conn->io);
+	conn->io = NULL;
+}
+
+void auth_master_connection_destroy(struct auth_master_connection *conn)
+{
+	struct auth_listener **l;
 	size_t i, size;
 
 	if (conn->destroyed)
 		return;
 	conn->destroyed = TRUE;
 
-	if (conn->fd != -1) {
-		if (close(conn->fd) < 0)
-			i_error("close(): %m");
-		conn->fd = -1;
+	auth_client_connections_deinit(conn);
 
-		o_stream_close(conn->output);
-
-		io_remove(conn->io);
-		conn->io = NULL;
-	}
+	if (conn->fd != -1)
+		auth_master_connection_close(conn);
 
 	l = buffer_get_modifyable_data(conn->listeners_buf, &size);
 	size /= sizeof(*l);
@@ -303,7 +339,7 @@
 
 static void auth_accept(void *context)
 {
-	struct auth_client_listener *l = context;
+	struct auth_listener *l = context;
 	int fd;
 
 	fd = net_accept(l->fd, NULL, NULL);
@@ -312,17 +348,24 @@
 			i_fatal("accept() failed: %m");
 	} else {
 		net_set_nonblock(fd, TRUE);
-		(void)auth_client_connection_create(l->master, fd);
+		if (l->client_listener)
+			(void)auth_client_connection_create(l->master, fd);
+		else {
+			/* we'll just replace the previous master.. */
+			auth_master_connection_set_fd(l->master, fd);
+                        auth_master_connection_send_handshake(l->master);
+		}
 	}
 }
 
 void auth_master_connection_add_listener(struct auth_master_connection *conn,
-					 int fd, const char *path)
+					 int fd, const char *path, int client)
 {
-	struct auth_client_listener *l;
+	struct auth_listener *l;
 
-	l = i_new(struct auth_client_listener, 1);
+	l = i_new(struct auth_listener, 1);
 	l->master = conn;
+	l->client_listener = client;
 	l->fd = fd;
 	l->path = i_strdup(path);
 	l->io = io_add(fd, IO_READ, auth_accept, l);
--- a/src/auth/auth-master-connection.h	Wed Jun 23 20:48:35 2004 +0300
+++ b/src/auth/auth-master-connection.h	Wed Jun 23 20:50:43 2004 +0300
@@ -22,21 +22,14 @@
 	unsigned int destroyed:1;
 };
 
-struct auth_client_listener {
-	struct auth_master_connection *master;
-	int fd;
-	char *path;
-	struct io *io;
-};
-
 #define AUTH_MASTER_IS_DUMMY(master) (master->fd == -1)
 
 struct auth_master_connection *
-auth_master_connection_new(int fd, unsigned int pid);
+auth_master_connection_create(int fd, unsigned int pid);
 void auth_master_connection_send_handshake(struct auth_master_connection *conn);
-void auth_master_connection_free(struct auth_master_connection *conn);
+void auth_master_connection_destroy(struct auth_master_connection *conn);
 
 void auth_master_connection_add_listener(struct auth_master_connection *conn,
-					 int fd, const char *path);
+					 int fd, const char *path, int client);
 
 #endif
--- a/src/auth/auth-master-interface.h	Wed Jun 23 20:48:35 2004 +0300
+++ b/src/auth/auth-master-interface.h	Wed Jun 23 20:50:43 2004 +0300
@@ -3,6 +3,11 @@
 
 #define AUTH_MASTER_MAX_REPLY_DATA_SIZE 4096
 
+/* Server -> Master */
+struct auth_master_handshake_reply {
+	unsigned int server_pid;
+};
+
 struct auth_master_request {
 	unsigned int tag;
 
--- a/src/auth/common.h	Wed Jun 23 20:48:35 2004 +0300
+++ b/src/auth/common.h	Wed Jun 23 20:50:43 2004 +0300
@@ -8,5 +8,6 @@
 
 extern struct ioloop *ioloop;
 extern int verbose, verbose_debug;
+extern int standalone;
 
 #endif
--- a/src/auth/main.c	Wed Jun 23 20:48:35 2004 +0300
+++ b/src/auth/main.c	Wed Jun 23 20:50:43 2004 +0300
@@ -15,12 +15,17 @@
 #include "auth-master-connection.h"
 #include "auth-client-connection.h"
 
+#include <stdio.h>
 #include <stdlib.h>
 #include <unistd.h>
 #include <syslog.h>
+#include <pwd.h>
+#include <grp.h>
+#include <sys/stat.h>
 
 struct ioloop *ioloop;
 int verbose = FALSE, verbose_debug = FALSE;
+int standalone = FALSE;
 
 static buffer_t *masters_buf;
 
@@ -64,26 +69,120 @@
 	restrict_access_by_env(FALSE);
 }
 
-static void master_add_unix_listeners(struct auth_master_connection *master,
-				      const char *sockets_list)
+static uid_t get_uid(const char *user)
+{
+	struct passwd *pw;
+
+	if (user == NULL)
+		return (uid_t)-1;
+
+	if ((pw = getpwnam(user)) == NULL)
+		i_fatal("User doesn't exist: %s", user);
+	return pw->pw_uid;
+}
+
+static gid_t get_gid(const char *group)
 {
-	const char *const *sockets;
-	int fd;
+	struct group *gr;
+
+	if (group == NULL)
+		return (gid_t)-1;
+
+	if ((gr = getgrnam(group)) == NULL)
+		i_fatal("Group doesn't exist: %s", group);
+	return gr->gr_gid;
+}
+
+static int create_unix_listener(const char *env)
+{
+	const char *path, *mode, *user, *group;
+	mode_t old_umask;
+	unsigned int mask;
+	uid_t uid;
+	gid_t gid;
+	int fd, i;
+
+	path = getenv(env);
+	if (path == NULL)
+		return -1;
+
+	mode = getenv(t_strdup_printf("%s_MODE", env));
+	if (mode == NULL)
+		mask = 0177; /* default to 0600 */
+	else {
+		if (sscanf(mode, "%o", &mask) != 1)
+			i_fatal("%s: Invalid mode %s", env, mode);
+		mask = (mask ^ 0777) & 0777;
+	}
 
-	sockets = t_strsplit(sockets_list, ":");
-	while (*sockets != NULL) {
-		fd = net_listen_unix(*sockets);
-		if (fd == -1) {
-			i_fatal("net_listen_unix(%s) failed: %m",
-				*sockets);
+	old_umask = umask(mask);
+	for (i = 0; i < 5; i++) {
+		fd = net_listen_unix(path);
+		if (fd != -1)
+			break;
+
+		if (errno != EADDRINUSE)
+			i_fatal("net_listen_unix(%s) failed: %m", path);
+
+		/* see if it really exists */
+		if (net_connect_unix(path) != -1 || errno != ECONNREFUSED)
+			i_fatal("Socket already exists: %s", path);
+
+		/* delete and try again */
+		if (unlink(path) < 0)
+			i_fatal("unlink(%s) failed: %m", path);
+	}
+	umask(old_umask);
+
+	user = getenv(t_strdup_printf("%s_USER", env));
+	group = getenv(t_strdup_printf("%s_GROUP", env));
+
+	uid = get_uid(user); gid = get_gid(group);
+	if (chown(path, uid, gid) < 0) {
+		i_fatal("chown(%s, %s, %s) failed: %m",
+			path, dec2str(uid), dec2str(gid));
+	}
+
+	return fd;
+}
+
+static void add_extra_listeners(void)
+{
+	struct auth_master_connection *master;
+	const char *str, *client_path, *master_path;
+	int client_fd, master_fd;
+	unsigned int i;
+
+	for (i = 1;; i++) {
+		t_push();
+		client_path = getenv(t_strdup_printf("AUTH_%u", i));
+		master_path = getenv(t_strdup_printf("AUTH_%u_MASTER", i));
+		if (client_path == NULL && master_path == NULL) {
+			t_pop();
+			break;
 		}
 
-		auth_master_connection_add_listener(master, fd, *sockets);
-		sockets++;
+		str = t_strdup_printf("AUTH_%u", i);
+		client_fd = create_unix_listener(str);
+		str = t_strdup_printf("AUTH_%u_MASTER", i);
+		master_fd = create_unix_listener(str);
+
+		master = auth_master_connection_create(-1, getpid());
+		if (master_fd != -1) {
+			auth_master_connection_add_listener(master, master_fd,
+							    master_path, FALSE);
+		}
+		if (client_fd != -1) {
+			auth_master_connection_add_listener(master, client_fd,
+							    client_path, TRUE);
+		}
+		auth_client_connections_init(master);
+		buffer_append(masters_buf, &master, sizeof(master));
+		t_pop();
 	}
 }
 
-static void main_init(void)
+static void main_init(int nodaemon)
 {
 	struct auth_master_connection *master, **master_p;
 	size_t i, size;
@@ -103,48 +202,46 @@
 	masters_buf = buffer_create_dynamic(default_pool, 64, (size_t)-1);
 
 	env = getenv("AUTH_PROCESS");
-	if (env == NULL) {
+	standalone = env == NULL;
+	if (standalone) {
 		/* starting standalone */
-		env = getenv("AUTH_SOCKETS");
-		if (env == NULL)
-			i_fatal("AUTH_SOCKETS environment not set");
-
-		switch (fork()) {
-		case -1:
-			i_fatal("fork() failed: %m");
-		case 0:
-			break;
-		default:
-			exit(0);
+		if (getenv("AUTH_1") == NULL) {
+			i_fatal("dovecot-auth is usually started through "
+				"dovecot master process. If you wish to run "
+				"it standalone, you'll need to set AUTH_* "
+				"environment variables (AUTH_1 isn't set).");
 		}
 
-		if (setsid() < 0)
-			i_fatal("setsid() failed: %m");
+		if (!nodaemon) {
+			switch (fork()) {
+			case -1:
+				i_fatal("fork() failed: %m");
+			case 0:
+				break;
+			default:
+				exit(0);
+			}
 
-		if (chdir("/") < 0)
-			i_fatal("chdir(/) failed: %m");
+			if (setsid() < 0)
+				i_fatal("setsid() failed: %m");
+
+			if (chdir("/") < 0)
+				i_fatal("chdir(/) failed: %m");
+		}
        } else {
 		pid = atoi(env);
 		if (pid == 0)
 			i_fatal("AUTH_PROCESS can't be 0");
 
-		master = auth_master_connection_new(MASTER_SOCKET_FD, pid);
+		master = auth_master_connection_create(MASTER_SOCKET_FD, pid);
 		auth_master_connection_add_listener(master, LOGIN_LISTEN_FD,
-						    NULL);
-		auth_client_connections_init(master);
-		buffer_append(masters_buf, &master, sizeof(master));
-
-		/* accept also alternative listeners under dummy master */
-		env = getenv("AUTH_SOCKETS");
-	}
-
-	if (env != NULL && *env != '\0') {
-		master = auth_master_connection_new(-1, 0);
-		master_add_unix_listeners(master, env);
+						    NULL, TRUE);
 		auth_client_connections_init(master);
 		buffer_append(masters_buf, &master, sizeof(master));
 	}
 
+	add_extra_listeners();
+
 	/* everything initialized, notify masters that all is well */
 	master_p = buffer_get_modifyable_data(masters_buf, &size);
 	size /= sizeof(*master_p);
@@ -164,10 +261,8 @@
 
 	master = buffer_get_modifyable_data(masters_buf, &size);
 	size /= sizeof(*master);
-	for (i = 0; i < size; i++) {
-		auth_client_connections_deinit(master[i]);
-		auth_master_connection_free(master[i]);
-	}
+	for (i = 0; i < size; i++)
+		auth_master_connection_destroy(master[i]);
 
         password_schemes_deinit();
 	passdb_deinit();
@@ -179,10 +274,11 @@
 	closelog();
 }
 
-int main(int argc __attr_unused__, char *argv[] __attr_unused__)
+int main(int argc, char *argv[])
 {
 #ifdef DEBUG
-        fd_debug_verify_leaks(4, 1024);
+	if (getenv("GDB") == NULL)
+		fd_debug_verify_leaks(4, 1024);
 #endif
 	/* NOTE: we start rooted, so keep the code minimal until
 	   restrict_access_by_env() is called */
@@ -191,7 +287,7 @@
 
 	ioloop = io_loop_create(system_pool);
 
-	main_init();
+	main_init(argc > 1 && strcmp(argv[1], "-F") == 0);
         io_loop_run(ioloop);
 	main_deinit();
 
--- a/src/master/auth-process.c	Wed Jun 23 20:48:35 2004 +0300
+++ b/src/master/auth-process.c	Wed Jun 23 20:50:43 2004 +0300
@@ -45,6 +45,7 @@
 
 	struct hash_table *requests;
 
+	unsigned int external:1;
 	unsigned int initialized:1;
 	unsigned int in_auth_reply:1;
 };
@@ -152,16 +153,21 @@
 	}
 
 	if (!p->initialized) {
+		struct auth_master_handshake_reply rec;
+
 		data = i_stream_get_data(p->input, &size);
-		i_assert(size > 0);
+		if (size < sizeof(rec))
+			return;
 
-		if (data[0] != 'O') {
+		memcpy(&rec, data, sizeof(rec));
+		i_stream_skip(p->input, sizeof(rec));
+
+		if (rec.server_pid == 0) {
 			i_fatal("Auth process sent invalid initialization "
 				"notification");
 		}
 
-		i_stream_skip(p->input, 1);
-
+		p->pid = rec.server_pid;
 		p->initialized = TRUE;
 	}
 
@@ -197,7 +203,8 @@
 {
 	struct auth_process *p;
 
-	PID_ADD_PROCESS_TYPE(pid, PROCESS_TYPE_AUTH);
+	if (pid != 0)
+		PID_ADD_PROCESS_TYPE(pid, PROCESS_TYPE_AUTH);
 
 	p = i_new(struct auth_process, 1);
 	p->group = group;
@@ -223,7 +230,7 @@
 	void *key, *value;
 	struct auth_process **pos;
 
-	if (!p->initialized && io_loop_is_running(ioloop)) {
+	if (!p->initialized && io_loop_is_running(ioloop) && !p->external) {
 		i_error("Auth process died too early - shutting down");
 		io_loop_stop(ioloop);
 	}
@@ -250,14 +257,54 @@
 	i_free(p);
 }
 
-static pid_t create_auth_process(struct auth_process_group *group)
+static void
+socket_settings_env_put(const char *env_base, struct socket_settings *set)
+{
+	if (env_base == NULL)
+		return;
+
+	env_put(t_strdup_printf("%s_PATH=%s", env_base, set->path));
+	if (set->mode != 0)
+		env_put(t_strdup_printf("%s_MODE=%u", env_base, set->mode));
+	if (set->user != NULL)
+		env_put(t_strdup_printf("%s_USER=%s", env_base, set->user));
+	if (set->group != NULL)
+		env_put(t_strdup_printf("%s_GROUP=%s", env_base, set->group));
+}
+
+static int connect_auth_socket(struct auth_process_group *group,
+			       const char *path)
+{
+	struct auth_process *auth;
+	int fd;
+
+	fd = net_connect_unix(path);
+	if (fd == -1) {
+		i_error("net_connect_unix(%s) failed: %m", path);
+		return -1;
+	}
+
+	net_set_nonblock(fd, TRUE);
+	fd_close_on_exec(fd, TRUE);
+	auth = auth_process_new(0, fd, group);
+	auth->external = TRUE;
+	return 0;
+}
+
+static int create_auth_process(struct auth_process_group *group)
 {
 	static char *argv[] = { NULL, NULL };
-	const char *prefix;
+	struct auth_socket_settings *as;
+	const char *prefix, *str;
 	struct log_io *log;
 	pid_t pid;
 	int fd[2], log_fd, i;
 
+	/* see if this is a connect socket */
+	as = group->set->sockets;
+	if (as != NULL && strcmp(as->type, "connect") == 0)
+		return connect_auth_socket(group, as->master.path);
+
 	/* create communication to process with a socket pair */
 	if (socketpair(AF_UNIX, SOCK_STREAM, 0, fd) < 0) {
 		i_error("socketpair() failed: %m");
@@ -290,7 +337,7 @@
 		auth_process_new(pid, fd[0], group);
 		(void)close(fd[1]);
 		(void)close(log_fd);
-		return pid;
+		return 0;
 	}
 
 	prefix = t_strdup_printf("master-auth(%s): ", group->set->name);
@@ -337,7 +384,16 @@
 	env_put(t_strconcat("USERNAME_CHARS=", group->set->username_chars, NULL));
 	env_put(t_strconcat("ANONYMOUS_USERNAME=",
 			    group->set->anonymous_username, NULL));
-	env_put(t_strconcat("AUTH_SOCKETS=", group->set->extra_sockets));
+
+	for (as = group->set->sockets, i = 1; as != NULL; as = as->next, i++) {
+		if (strcmp(as->type, "listen") != 0)
+			continue;
+
+		str = t_strdup_printf("AUTH_%u", i);
+		socket_settings_env_put(str, &as->client);
+		socket_settings_env_put(t_strconcat(str, "_MASTER", NULL),
+					&as->master);
+	}
 
 	if (group->set->use_cyrus_sasl)
 		env_put("USE_CYRUS_SASL=1");
@@ -390,6 +446,13 @@
 	group = i_new(struct auth_process_group, 1);
 	group->set = auth_set;
 
+	group->next = process_groups;
+	process_groups = group;
+
+	if (auth_set->sockets != NULL &&
+	    strcmp(auth_set->sockets->type, "connect") == 0)
+		return;
+
 	/* create socket for listening auth requests from login */
 	path = t_strconcat(auth_set->parent->defaults->login_dir, "/",
 			   auth_set->name, NULL);
@@ -410,9 +473,6 @@
 			path, dec2str(master_uid),
 			dec2str(auth_set->parent->login_gid));
 	}
-
-	group->next = process_groups;
-	process_groups = group;
 }
 
 static void auth_process_group_destroy(struct auth_process_group *group)
--- a/src/master/master-settings.c	Wed Jun 23 20:48:35 2004 +0300
+++ b/src/master/master-settings.c	Wed Jun 23 20:50:43 2004 +0300
@@ -16,7 +16,9 @@
 	SETTINGS_TYPE_ROOT,
 	SETTINGS_TYPE_SERVER,
 	SETTINGS_TYPE_AUTH,
-        SETTINGS_TYPE_NAMESPACE
+	SETTINGS_TYPE_AUTH_SOCKET,
+        SETTINGS_TYPE_NAMESPACE,
+	SETTINGS_TYPE_SOCKET
 };
 
 struct settings_parse_ctx {
@@ -25,6 +27,8 @@
 
 	struct server_settings *root, *server;
 	struct auth_settings *auth;
+	struct socket_settings *socket;
+	struct auth_socket_settings *auth_socket;
         struct namespace_settings *namespace;
 
 	int level;
@@ -142,7 +146,29 @@
 
 	DEF(SET_INT, count),
 	DEF(SET_INT, process_size),
-	DEF(SET_STR, extra_sockets),
+
+	{ 0, NULL, 0 }
+};
+
+#undef DEF
+#define DEF(type, name) \
+	{ type, #name, offsetof(struct socket_settings, name) }
+
+static struct setting_def socket_setting_defs[] = {
+	DEF(SET_STR, path),
+	DEF(SET_INT, mode),
+	DEF(SET_STR, user),
+	DEF(SET_STR, group),
+
+	{ 0, NULL, 0 }
+};
+
+#undef DEF
+#define DEF(type, name) \
+	{ type, #name, offsetof(struct auth_socket_settings, name) }
+
+static struct setting_def auth_socket_setting_defs[] = {
+	DEF(SET_STR, type),
 
 	{ 0, NULL, 0 }
 };
@@ -281,11 +307,11 @@
 
 	MEMBER(count) 1,
 	MEMBER(process_size) 256,
-	MEMBER(extra_sockets) NULL,
 
 	/* .. */
 	MEMBER(uid) 0,
-	MEMBER(gid) 0
+	MEMBER(gid) 0,
+	MEMBER(sockets) NULL
 };
 
 static pool_t settings_pool, settings2_pool;
@@ -406,6 +432,22 @@
 	return TRUE;
 }
 
+static int settings_have_connect_sockets(struct settings *set)
+{
+	struct auth_settings *auth;
+	struct server_settings *server;
+
+	for (server = set->server; server != NULL; server = server->next) {
+		for (auth = server->auths; auth != NULL; auth = auth->next) {
+			if (auth->sockets != NULL &&
+			    strcmp(auth->sockets->type, "connect") == 0)
+				return TRUE;
+		}
+	}
+
+	return FALSE;
+}
+
 static int settings_verify(struct settings *set)
 {
 	const char *dir;
@@ -484,10 +526,15 @@
 			  PKG_RUNDIR);
 	}
 
-	/* wipe out contents of login directory, if it exists */
-	if (unlink_directory(set->login_dir, FALSE) < 0) {
-		i_error("unlink_directory() failed for %s: %m", set->login_dir);
-		return FALSE;
+	/* wipe out contents of login directory, if it exists.
+	   except if we're using external authentication - then we would
+	   otherwise wipe existing auth sockets */
+	if (!settings_have_connect_sockets(set)) {
+		if (unlink_directory(set->login_dir, FALSE) < 0) {
+			i_error("unlink_directory() failed for %s: %m",
+				set->login_dir);
+			return FALSE;
+		}
 	}
 
 	if (safe_mkdir(set->login_dir, 0750,
@@ -570,6 +617,44 @@
 	return auth_settings_new(server, name);
 }
 
+static struct auth_socket_settings *
+auth_socket_settings_new(struct auth_settings *auth, const char *type)
+{
+	struct auth_socket_settings *as, **as_p;
+
+	as = p_new(settings_pool, struct auth_socket_settings, 1);
+
+	as->parent = auth;
+	as->type = str_lcase(p_strdup(settings_pool, type));
+
+	as_p = &auth->sockets;
+	while (*as_p != NULL)
+		as_p = &(*as_p)->next;
+	*as_p = as;
+
+	return as;
+}
+
+static struct auth_socket_settings *
+parse_new_auth_socket(struct auth_settings *auth, const char *name,
+		      const char **errormsg)
+{
+	if (strcmp(name, "connect") != 0 && strcmp(name, "listen") != 0) {
+		*errormsg = "Unknown auth socket type";
+		return NULL;
+	}
+
+	if ((auth->sockets != NULL && strcmp(name, "connect") == 0) ||
+	    (auth->sockets != NULL &&
+	     strcmp(auth->sockets->type, "listen") == 0)) {
+		*errormsg = "With connect auth socket no other sockets "
+			"can be used in same auth section";
+		return NULL;
+	}
+
+	return auth_socket_settings_new(auth, name);
+}
+
 static struct namespace_settings *
 namespace_settings_new(struct server_settings *server, const char *type)
 {
@@ -657,10 +742,18 @@
 			key += 5;
 		return parse_setting_from_defs(settings_pool, auth_setting_defs,
 					       ctx->auth, key, value);
+	case SETTINGS_TYPE_AUTH_SOCKET:
+		return parse_setting_from_defs(settings_pool,
+					       auth_socket_setting_defs,
+					       ctx->auth_socket, key, value);
 	case SETTINGS_TYPE_NAMESPACE:
 		return parse_setting_from_defs(settings_pool,
 					       namespace_setting_defs,
 					       ctx->namespace, key, value);
+	case SETTINGS_TYPE_SOCKET:
+		return parse_setting_from_defs(settings_pool,
+					       socket_setting_defs,
+					       ctx->socket, key, value);
 	}
 
 	i_unreached();
@@ -705,12 +798,20 @@
 
 	if (type == NULL) {
 		/* section closing */
-		if (ctx->level > 0) {
-			ctx->level--;
+		if (--ctx->level > 0) {
+			ctx->type = ctx->parent_type;
 			ctx->protocol = MAIL_PROTOCOL_ANY;
+
+			switch (ctx->type) {
+			case SETTINGS_TYPE_AUTH_SOCKET:
+				ctx->parent_type = SETTINGS_TYPE_AUTH;
+				break;
+			default:
+				ctx->parent_type = SETTINGS_TYPE_ROOT;
+				break;
+			}
 		} else {
-			ctx->type = ctx->parent_type;
-			ctx->parent_type = SETTINGS_TYPE_ROOT;
+			ctx->type = SETTINGS_TYPE_ROOT;
 			ctx->server = ctx->root;
 			ctx->auth = &ctx->root->auth_defaults;
 			ctx->namespace = NULL;
@@ -718,15 +819,16 @@
 		return TRUE;
 	}
 
+	ctx->level++;
+	ctx->parent_type = ctx->type;
+
 	if (strcmp(type, "server") == 0) {
 		if (ctx->type != SETTINGS_TYPE_ROOT) {
 			*errormsg = "Server section not allowed here";
 			return FALSE;
 		}
 
-		ctx->parent_type = ctx->type;
 		ctx->type = SETTINGS_TYPE_SERVER;
-
 		ctx->server = create_new_server(name, ctx->server->imap,
 						ctx->server->pop3);
                 server = ctx->root;
@@ -739,7 +841,7 @@
 	if (strcmp(type, "protocol") == 0) {
 		if ((ctx->type != SETTINGS_TYPE_ROOT &&
 		     ctx->type != SETTINGS_TYPE_SERVER) ||
-		    ctx->level != 0) {
+		    ctx->level != 1) {
 			*errormsg = "Protocol section not allowed here";
 			return FALSE;
 		}
@@ -752,7 +854,6 @@
 			*errormsg = "Unknown protocol name";
 			return FALSE;
 		}
-		ctx->level++;
 		return TRUE;
 	}
 
@@ -768,6 +869,28 @@
 		return ctx->auth != NULL;
 	}
 
+	if (ctx->type == SETTINGS_TYPE_AUTH &&
+	    strcmp(type, "socket") == 0) {
+		ctx->type = SETTINGS_TYPE_AUTH_SOCKET;
+		ctx->auth_socket = parse_new_auth_socket(ctx->auth,
+							 name, errormsg);
+		return ctx->auth_socket != NULL;
+	}
+
+	if (ctx->type == SETTINGS_TYPE_AUTH_SOCKET) {
+		ctx->type = SETTINGS_TYPE_SOCKET;
+
+		if (strcmp(type, "master") == 0) {
+			ctx->socket = &ctx->auth_socket->master;
+			return TRUE;
+		}
+
+		if (strcmp(type, "client") == 0) {
+			ctx->socket = &ctx->auth_socket->client;
+			return TRUE;
+		}
+	}
+
 	if (strcmp(type, "namespace") == 0) {
 		if (ctx->type != SETTINGS_TYPE_ROOT &&
 		    ctx->type != SETTINGS_TYPE_SERVER) {
--- a/src/master/master-settings.h	Wed Jun 23 20:48:35 2004 +0300
+++ b/src/master/master-settings.h	Wed Jun 23 20:50:43 2004 +0300
@@ -98,6 +98,22 @@
 	int listen_fd, ssl_listen_fd;
 };
 
+struct socket_settings {
+	const char *path;
+	unsigned int mode;
+	const char *user;
+	const char *group;
+};
+
+struct auth_socket_settings {
+	struct auth_settings *parent;
+	struct auth_socket_settings *next;
+
+	const char *type;
+	struct socket_settings master;
+        struct socket_settings client;
+};
+
 struct auth_settings {
 	struct server_settings *parent;
 	struct auth_settings *next;
@@ -119,11 +135,11 @@
 
 	unsigned int count;
 	unsigned int process_size;
-	const char *extra_sockets;
 
 	/* .. */
 	uid_t uid;
 	gid_t gid;
+	struct auth_socket_settings *sockets;
 };
 
 struct namespace_settings {