changeset 2077:d5b20d679b8a HEAD

Removed hardcoded mechanism lists. It's now possible to add them dynamically. Added support for SASL initial response.
author Timo Sirainen <tss@iki.fi>
date Sun, 30 May 2004 00:40:30 +0300
parents 6a72075e3543
children b3daf55df932
files src/auth/Makefile.am src/auth/auth-client-connection.c src/auth/auth-client-interface.h src/auth/auth-master-connection.c src/auth/auth-master-connection.h src/auth/auth-master-interface.h src/auth/auth-mech-desc.h src/auth/main.c src/auth/mech-anonymous.c src/auth/mech-cram-md5.c src/auth/mech-digest-md5.c src/auth/mech-plain.c src/auth/mech.c src/auth/mech.h src/auth/passdb.c src/imap-login/client-authenticate.c src/lib-auth/auth-client.c src/lib-auth/auth-client.h src/lib-auth/auth-server-connection.c src/lib-auth/auth-server-connection.h src/lib-auth/auth-server-request.c src/lib/file-dotlock.c src/pop3-login/client-authenticate.c
diffstat 23 files changed, 638 insertions(+), 336 deletions(-) [+]
line wrap: on
line diff
--- a/src/auth/Makefile.am	Sat May 29 20:06:49 2004 +0300
+++ b/src/auth/Makefile.am	Sun May 30 00:40:30 2004 +0300
@@ -58,7 +58,6 @@
 	auth-client-interface.h \
 	auth-master-connection.h \
 	auth-master-interface.h \
-	auth-mech-desc.h \
 	auth-module.h \
 	db-ldap.h \
 	db-mysql.h \
--- a/src/auth/auth-client-connection.c	Sat May 29 20:06:49 2004 +0300
+++ b/src/auth/auth-client-connection.c	Sun May 30 00:40:30 2004 +0300
@@ -109,19 +109,26 @@
 	   structures, as it may not be aligned properly. */
 	memcpy(&type, data, sizeof(type));
 
-	if (type == AUTH_CLIENT_REQUEST_NEW) {
+	conn->refcount++;
+	switch (type) {
+	case AUTH_CLIENT_REQUEST_NEW: {
 		struct auth_client_request_new request;
 
 		if (size < sizeof(request))
 			return;
 
 		memcpy(&request, data, sizeof(request));
-		i_stream_skip(conn->input, sizeof(request));
+		if (size < sizeof(request) + request.data_size)
+			return;
 
 		/* we have a full init request */
 		conn->refcount++;
-		mech_request_new(conn, &request, request_callback);
-	} else if (type == AUTH_CLIENT_REQUEST_CONTINUE) {
+		mech_request_new(conn, &request, data + sizeof(request),
+				 request_callback);
+		i_stream_skip(conn->input, sizeof(request) + request.data_size);
+		break;
+	}
+	case AUTH_CLIENT_REQUEST_CONTINUE: {
                 struct auth_client_request_continue request;
 
 		if (size < sizeof(request))
@@ -131,8 +138,6 @@
 		if (size < sizeof(request) + request.data_size)
 			return;
 
-		i_stream_skip(conn->input, sizeof(request) + request.data_size);
-
 		/* we have a full continued request */
 		conn->refcount++;
 		mech_request_continue(conn, &request, data + sizeof(request),
@@ -140,12 +145,16 @@
 
 		/* clear any sensitive data from memory */
 		safe_memset(data + sizeof(request), 0, request.data_size);
-	} else {
+		i_stream_skip(conn->input, sizeof(request) + request.data_size);
+		break;
+	}
+	default:
 		/* unknown request */
-		i_error("BUG: Auth client %u sent us unknown request %u",
+		i_error("BUG: Auth client %u sent us unknown request type %u",
 			conn->pid, type);
 		auth_client_connection_destroy(conn);
 	}
+	auth_client_connection_unref(conn);
 }
 
 static void auth_client_input(void *context)
@@ -198,8 +207,9 @@
 	conn->next = master->clients;
 	master->clients = conn;
 
-	if (o_stream_send(conn->output, &master->handshake_reply,
-			  sizeof(master->handshake_reply)) < 0) {
+	if (o_stream_send(conn->output, master->handshake_reply,
+			  sizeof(*master->handshake_reply) +
+			  master->handshake_reply->data_size) < 0) {
 		auth_client_connection_destroy(conn);
 		conn = NULL;
 	}
@@ -298,9 +308,6 @@
 
 void auth_client_connections_init(struct auth_master_connection *master)
 {
-	master->handshake_reply.server_pid = master->pid;
-	master->handshake_reply.auth_mechanisms = auth_mechanisms;
-
 	master->to_clients = timeout_add(5000, request_timeout, master);
 }
 
--- a/src/auth/auth-client-interface.h	Sat May 29 20:06:49 2004 +0300
+++ b/src/auth/auth-client-interface.h	Sun May 30 00:40:30 2004 +0300
@@ -4,22 +4,10 @@
 /* max. size for auth_client_request_continue.data[] */
 #define AUTH_CLIENT_MAX_REQUEST_DATA_SIZE 4096
 
-/* sizeof(struct auth_client_request_new->protocol) */
-#define AUTH_CLIENT_PROTOCOL_BUF_SIZE 12
-
 /* Client process must finish with single authentication requests in this time,
    or the whole connection will be killed. */
 #define AUTH_REQUEST_TIMEOUT 120
 
-enum auth_mech {
-	AUTH_MECH_PLAIN		= 0x01,
-	AUTH_MECH_DIGEST_MD5	= 0x02,
-	AUTH_MECH_ANONYMOUS	= 0x04,
-	AUTH_MECH_CRAM_MD5	= 0x08,
-
-	AUTH_MECH_COUNT
-};
-
 enum auth_client_request_new_flags {
 	AUTH_CLIENT_FLAG_SSL_VALID_CLIENT_CERT = 0x01
 };
@@ -40,10 +28,19 @@
 	unsigned int client_pid; /* unique identifier for client process */
 };
 
+struct auth_client_handshake_mech_desc {
+	uint32_t name_idx;
+	unsigned int plaintext:1;
+	unsigned int advertise:1;
+};
+
 /* Server -> Client */
 struct auth_client_handshake_reply {
 	unsigned int server_pid; /* unique auth process identifier */
-	enum auth_mech auth_mechanisms; /* valid authentication mechanisms */
+
+	uint32_t mech_count;
+	uint32_t data_size;
+	/* struct auth_client_handshake_mech_desc mech_desc[auth_mech_count]; */
 };
 
 /* New authentication request */
@@ -51,17 +48,24 @@
 	enum auth_client_request_type type; /* AUTH_CLIENT_REQUEST_NEW */
 	unsigned int id; /* unique ID for the request */
 
-	enum auth_mech mech;
 	enum auth_client_request_new_flags flags;
-	char protocol[AUTH_CLIENT_PROTOCOL_BUF_SIZE];
+
+	uint32_t protocol_idx;
+	uint32_t mech_idx;
+	uint32_t initial_resp_idx;
+
+	uint32_t data_size;
+	/* unsigned char data[]; */
 };
+#define AUTH_CLIENT_REQUEST_HAVE_INITIAL_RESPONSE(request) \
+        ((request)->initial_resp_idx != (request)->data_size)
 
 /* Continue authentication request */
 struct auth_client_request_continue {
 	enum auth_client_request_type type; /* AUTH_CLIENT_REQUEST_CONTINUE */
 	unsigned int id;
 
-	size_t data_size;
+	uint32_t data_size;
 	/* unsigned char data[]; */
 };
 
@@ -73,10 +77,10 @@
 
 	/* variable width data, indexes into data[].
 	   Ignore if it points outside data_size. */
-	size_t username_idx; /* NUL-terminated */
-	size_t reply_idx; /* last, non-NUL terminated */
+	uint32_t username_idx; /* NUL-terminated */
+	uint32_t reply_idx; /* last, non-NUL terminated */
 
-	size_t data_size;
+	uint32_t data_size;
 	/* unsigned char data[]; */
 };
 
--- a/src/auth/auth-master-connection.c	Sat May 29 20:06:49 2004 +0300
+++ b/src/auth/auth-master-connection.c	Sun May 30 00:40:30 2004 +0300
@@ -182,6 +182,48 @@
 	}
 }
 
+static void master_get_handshake_reply(struct auth_master_connection *master)
+{
+	struct mech_module_list *list;
+	buffer_t *buf;
+	struct auth_client_handshake_reply reply;
+	struct auth_client_handshake_mech_desc mech_desc;
+	uint32_t mech_desc_offset;
+
+	memset(&reply, 0, sizeof(reply));
+	memset(&mech_desc, 0, sizeof(mech_desc));
+
+	reply.server_pid = master->pid;
+
+	buf = buffer_create_dynamic(default_pool, 128, (size_t)-1);
+
+	for (list = mech_modules; list != NULL; list = list->next)
+		reply.mech_count++;
+	buffer_set_used_size(buf, sizeof(reply) +
+			     sizeof(mech_desc) * reply.mech_count);
+
+	mech_desc_offset = sizeof(reply);
+	for (list = mech_modules; list != NULL; list = list->next) {
+		mech_desc.name_idx = buffer_get_used_size(buf) - sizeof(reply);
+		mech_desc.plaintext = list->module.plaintext;
+		mech_desc.advertise = list->module.advertise;
+
+		memcpy(buffer_get_space_unsafe(buf, mech_desc_offset,
+					       sizeof(mech_desc)),
+		       &mech_desc, sizeof(mech_desc));
+		buffer_append(buf, list->module.mech_name,
+			      strlen(list->module.mech_name) + 1);
+
+		mech_desc_offset += sizeof(mech_desc);
+	}
+
+	reply.data_size = buffer_get_used_size(buf);
+	memcpy(buffer_get_space_unsafe(buf, 0, sizeof(reply)),
+	       &reply, sizeof(reply));
+
+	master->handshake_reply = buffer_free_without_data(buf);
+}
+
 struct auth_master_connection *
 auth_master_connection_new(int fd, unsigned int pid)
 {
@@ -198,6 +240,7 @@
 						    MAX_OUTBUF_SIZE, FALSE);
 		conn->io = io_add(fd, IO_READ, master_input, conn);
 	}
+	master_get_handshake_reply(conn);
 	return conn;
 }
 
@@ -241,6 +284,7 @@
 		i_free(l[i]);
 	}
 	buffer_free(conn->listeners_buf);
+	conn->listeners_buf = NULL;
 
 	auth_master_connection_unref(conn);
 }
@@ -252,6 +296,7 @@
 
 	if (conn->output != NULL)
 		o_stream_unref(conn->output);
+	i_free(conn->handshake_reply);
 	i_free(conn);
 	return FALSE;
 }
@@ -280,7 +325,7 @@
 	l->master = conn;
 	l->fd = fd;
 	l->path = i_strdup(path);
-	l->io = io_add(fd, IO_READ, auth_accept, &l);
+	l->io = io_add(fd, IO_READ, auth_accept, l);
 
 	buffer_append(conn->listeners_buf, &l, sizeof(l));
 }
--- a/src/auth/auth-master-connection.h	Sat May 29 20:06:49 2004 +0300
+++ b/src/auth/auth-master-connection.h	Sun May 30 00:40:30 2004 +0300
@@ -15,7 +15,7 @@
 	unsigned int request_pos;
 	unsigned char request_buf[sizeof(struct auth_master_request)];
 
-	struct auth_client_handshake_reply handshake_reply;
+	struct auth_client_handshake_reply *handshake_reply;
 	struct auth_client_connection *clients;
 	struct timeout *to_clients;
 
--- a/src/auth/auth-master-interface.h	Sat May 29 20:06:49 2004 +0300
+++ b/src/auth/auth-master-interface.h	Sun May 30 00:40:30 2004 +0300
@@ -21,11 +21,11 @@
 	/* variable width fields are packed into data[]. These variables
 	   contain indexes to the data, they're all NUL-terminated.
 	   Ignore if it points outside data_size. */
-	size_t system_user_idx;
-	size_t virtual_user_idx;
-	size_t home_idx, mail_idx, chroot_idx;
+	uint32_t system_user_idx;
+	uint32_t virtual_user_idx;
+	uint32_t home_idx, mail_idx, chroot_idx;
 
-	size_t data_size;
+	uint32_t data_size;
 	/* unsigned char data[]; */
 };
 
--- a/src/auth/auth-mech-desc.h	Sat May 29 20:06:49 2004 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,18 +0,0 @@
-#ifndef __AUTH_MECH_DESC_H
-#define __AUTH_MECH_DESC_H
-
-struct auth_mech_desc {
-	enum auth_mech mech;
-	const char *name;
-	int plaintext;
-	int advertise;
-};
-
-static struct auth_mech_desc auth_mech_desc[AUTH_MECH_COUNT] = {
-	{ AUTH_MECH_PLAIN,		"PLAIN",	TRUE, FALSE },
-	{ AUTH_MECH_CRAM_MD5,		"CRAM-MD5",	FALSE, TRUE },
-	{ AUTH_MECH_DIGEST_MD5,		"DIGEST-MD5",	FALSE, TRUE },
-	{ AUTH_MECH_ANONYMOUS,		"ANONYMOUS",	FALSE, TRUE }
-};
-
-#endif
--- a/src/auth/main.c	Sat May 29 20:06:49 2004 +0300
+++ b/src/auth/main.c	Sun May 30 00:40:30 2004 +0300
@@ -132,7 +132,7 @@
 		env = getenv("AUTH_SOCKETS");
 	}
 
-	if (env != NULL) {
+	if (env != NULL && *env != '\0') {
 		master = auth_master_connection_new(-1, 0);
 		master_add_unix_listeners(master, env);
 		auth_client_connections_init(master);
--- a/src/auth/mech-anonymous.c	Sat May 29 20:06:49 2004 +0300
+++ b/src/auth/mech-anonymous.c	Sun May 30 00:40:30 2004 +0300
@@ -5,15 +5,14 @@
 
 static int
 mech_anonymous_auth_continue(struct auth_request *auth_request,
-			     struct auth_client_request_continue *request,
-			     const unsigned char *data,
+			     const unsigned char *data, size_t data_size,
 			     mech_callback_t *callback)
 {
 	i_assert(anonymous_username != NULL);
 
 	if (verbose) {
 		i_info("mech-anonymous: login by %s",
-		       t_strndup(data, request->data_size));
+		       t_strndup(data, data_size));
 	}
 
 	auth_request->callback = callback;
@@ -22,37 +21,62 @@
 	return TRUE;
 }
 
+static int
+mech_anonymous_auth_initial(struct auth_request *auth_request,
+			    struct auth_client_request_new *request,
+			    const unsigned char *data,
+			    mech_callback_t *callback)
+{
+	struct auth_client_request_reply reply;
+	size_t data_size;
+
+	if (AUTH_CLIENT_REQUEST_HAVE_INITIAL_RESPONSE(request)) {
+		data += request->initial_resp_idx;
+		data_size = request->data_size - request->initial_resp_idx;
+
+		return auth_request->auth_continue(auth_request, data,
+						   data_size, callback);
+	}
+
+	/* initialize reply */
+	memset(&reply, 0, sizeof(reply));
+	reply.id = auth_request->id;
+	reply.result = AUTH_CLIENT_RESULT_CONTINUE;
+
+	callback(&reply, NULL, auth_request->conn);
+	return TRUE;
+}
+
 static void
 mech_anonymous_auth_free(struct auth_request *auth_request)
 {
 	pool_unref(auth_request->pool);
 }
 
-static struct auth_request *
-mech_anonymous_auth_new(struct auth_client_connection *conn, unsigned int id,
-			mech_callback_t *callback)
+static struct auth_request *mech_anonymous_auth_new(void)
 {
         struct auth_request *auth_request;
-	struct auth_client_request_reply reply;
 	pool_t pool;
 
 	pool = pool_alloconly_create("anonymous_auth_request", 256);
 	auth_request = p_new(pool, struct auth_request, 1);
 	auth_request->refcount = 1;
 	auth_request->pool = pool;
+	auth_request->auth_initial = mech_anonymous_auth_initial;
 	auth_request->auth_continue = mech_anonymous_auth_continue;
         auth_request->auth_free = mech_anonymous_auth_free;
 
-	/* initialize reply */
-	memset(&reply, 0, sizeof(reply));
-	reply.id = id;
-	reply.result = AUTH_CLIENT_RESULT_CONTINUE;
-
-	callback(&reply, NULL, conn);
 	return auth_request;
 }
 
 struct mech_module mech_anonymous = {
-	AUTH_MECH_ANONYMOUS,
+	"ANONYMOUS",
+
+	MEMBER(plaintext) FALSE,
+	MEMBER(advertise) TRUE,
+
+	MEMBER(passdb_need_plain) FALSE,
+	MEMBER(passdb_need_credentials) FALSE,
+
 	mech_anonymous_auth_new
 };
--- a/src/auth/mech-cram-md5.c	Sat May 29 20:06:49 2004 +0300
+++ b/src/auth/mech-cram-md5.c	Sun May 30 00:40:30 2004 +0300
@@ -149,15 +149,14 @@
 
 static int
 mech_cram_md5_auth_continue(struct auth_request *auth_request,
-	struct auth_client_request_continue *request __attr_unused__,
-	const unsigned char *data,
-	mech_callback_t *callback)
+			    const unsigned char *data, size_t data_size,
+			    mech_callback_t *callback)
 {
 	struct cram_auth_request *auth =
 		(struct cram_auth_request *)auth_request;
 	const char *error;
 
-	if (parse_cram_response(auth, data, request->data_size, &error)) {
+	if (parse_cram_response(auth, data, data_size, &error)) {
 		auth_request->callback = callback;
 
 		auth_request->user =
@@ -186,16 +185,43 @@
 	return FALSE;
 }
 
+static int
+mech_cram_md5_auth_initial(struct auth_request *auth_request,
+			   struct auth_client_request_new *request,
+			   const unsigned char *data __attr_unused__,
+			   mech_callback_t *callback)
+{
+	struct cram_auth_request *auth =
+		(struct cram_auth_request *)auth_request;
+
+	struct auth_client_request_reply reply;
+
+	if (AUTH_CLIENT_REQUEST_HAVE_INITIAL_RESPONSE(request)) {
+		/* No initial response in CRAM-MD5 */
+		return FALSE;
+	}
+
+	auth->challenge = p_strdup(auth->pool, get_cram_challenge());
+
+	/* initialize reply */
+	mech_init_auth_client_reply(&reply);
+	reply.id = request->id;
+	reply.result = AUTH_CLIENT_RESULT_CONTINUE;
+
+	/* send the initial challenge */
+	reply.reply_idx = 0;
+	reply.data_size = strlen(auth->challenge);
+	callback(&reply, auth->challenge, auth_request->conn);
+	return TRUE;
+}
+
 static void mech_cram_md5_auth_free(struct auth_request *auth_request)
 {
 	pool_unref(auth_request->pool);
 }
 
-static struct auth_request *
-mech_cram_md5_auth_new(struct auth_client_connection *conn,
-		       unsigned int id, mech_callback_t *callback)
+static struct auth_request *mech_cram_md5_auth_new(void)
 {
-	struct auth_client_request_reply reply;
 	struct cram_auth_request *auth;
 	pool_t pool;
 
@@ -205,25 +231,21 @@
 
 	auth->auth_request.refcount = 1;
 	auth->auth_request.pool = pool;
+	auth->auth_request.auth_initial = mech_cram_md5_auth_initial;
 	auth->auth_request.auth_continue = mech_cram_md5_auth_continue;
 	auth->auth_request.auth_free = mech_cram_md5_auth_free;
 
-	auth->challenge = p_strdup(auth->pool, get_cram_challenge());
-
-	/* initialize reply */
-	mech_init_auth_client_reply(&reply);
-	reply.id = id;
-	reply.result = AUTH_CLIENT_RESULT_CONTINUE;
-
-	/* send the initial challenge */
-	reply.reply_idx = 0;
-	reply.data_size = strlen(auth->challenge);
-	callback(&reply, auth->challenge, conn);
-
 	return &auth->auth_request;
 }
 
 struct mech_module mech_cram_md5 = {
-	AUTH_MECH_CRAM_MD5,
+	"CRAM-MD5",
+
+	MEMBER(plaintext) FALSE,
+	MEMBER(advertise) TRUE,
+
+	MEMBER(passdb_need_plain) FALSE,
+	MEMBER(passdb_need_credentials) TRUE,
+
 	mech_cram_md5_auth_new
 };
--- a/src/auth/mech-digest-md5.c	Sat May 29 20:06:49 2004 +0300
+++ b/src/auth/mech-digest-md5.c	Sun May 30 00:40:30 2004 +0300
@@ -525,8 +525,7 @@
 
 static int
 mech_digest_md5_auth_continue(struct auth_request *auth_request,
-			      struct auth_client_request_continue *request,
-			      const unsigned char *data,
+			      const unsigned char *data, size_t data_size,
 			      mech_callback_t *callback)
 {
 	struct digest_auth_request *auth =
@@ -536,7 +535,7 @@
 
 	/* initialize reply */
 	mech_init_auth_client_reply(&reply);
-	reply.id = request->id;
+	reply.id = auth_request->id;
 
 	if (auth->authenticated) {
 		/* authentication is done, we were just waiting the last
@@ -545,7 +544,7 @@
 		return TRUE;
 	}
 
-	if (parse_digest_response(auth, data, request->data_size, &error)) {
+	if (parse_digest_response(auth, data, data_size, &error)) {
 		auth_request->callback = callback;
 
 		realm = auth->realm != NULL ? auth->realm : default_realm;
@@ -582,19 +581,50 @@
 	return FALSE;
 }
 
+static int
+mech_digest_md5_auth_initial(struct auth_request *auth_request,
+			     struct auth_client_request_new *request,
+			     const unsigned char *data __attr_unused__,
+			     mech_callback_t *callback)
+{
+	struct digest_auth_request *auth =
+		(struct digest_auth_request *)auth_request;
+	struct auth_client_request_reply reply;
+	string_t *challenge;
+	size_t data_size;
+
+	if (AUTH_CLIENT_REQUEST_HAVE_INITIAL_RESPONSE(request)) {
+		/* FIXME: support subsequent authentication? */
+		data += request->initial_resp_idx;
+		data_size = request->data_size - request->initial_resp_idx;
+
+		return auth_request->auth_continue(auth_request, data,
+						   data_size, callback);
+	}
+
+	/* initialize reply */
+	mech_init_auth_client_reply(&reply);
+	reply.id = request->id;
+	reply.result = AUTH_CLIENT_RESULT_CONTINUE;
+
+	/* send the initial challenge */
+	reply.reply_idx = 0;
+	challenge = get_digest_challenge(auth);
+	reply.data_size = str_len(challenge);
+	callback(&reply, str_data(challenge), auth_request->conn);
+	return TRUE;
+}
+
 static void mech_digest_md5_auth_free(struct auth_request *auth_request)
 {
 	pool_unref(auth_request->pool);
 }
 
 static struct auth_request *
-mech_digest_md5_auth_new(struct auth_client_connection *conn,
-			 unsigned int id, mech_callback_t *callback)
+mech_digest_md5_auth_new(void)
 {
-	struct auth_client_request_reply reply;
 	struct digest_auth_request *auth;
 	pool_t pool;
-	string_t *challenge;
 
 	pool = pool_alloconly_create("digest_md5_auth_request", 2048);
 	auth = p_new(pool, struct digest_auth_request, 1);
@@ -602,25 +632,21 @@
 
 	auth->auth_request.refcount = 1;
 	auth->auth_request.pool = pool;
+	auth->auth_request.auth_initial = mech_digest_md5_auth_initial;
 	auth->auth_request.auth_continue = mech_digest_md5_auth_continue;
 	auth->auth_request.auth_free = mech_digest_md5_auth_free;
 	auth->qop = QOP_AUTH;
-
-	/* initialize reply */
-	mech_init_auth_client_reply(&reply);
-	reply.id = id;
-	reply.result = AUTH_CLIENT_RESULT_CONTINUE;
-
-	/* send the initial challenge */
-	reply.reply_idx = 0;
-	challenge = get_digest_challenge(auth);
-	reply.data_size = str_len(challenge);
-	callback(&reply, str_data(challenge), conn);
-
 	return &auth->auth_request;
 }
 
 struct mech_module mech_digest_md5 = {
-	AUTH_MECH_DIGEST_MD5,
+	"DIGEST-MD5",
+
+	MEMBER(plaintext) FALSE,
+	MEMBER(advertise) TRUE,
+
+	MEMBER(passdb_need_plain) FALSE,
+	MEMBER(passdb_need_credentials) TRUE,
+
 	mech_digest_md5_auth_new
 };
--- a/src/auth/mech-plain.c	Sat May 29 20:06:49 2004 +0300
+++ b/src/auth/mech-plain.c	Sun May 30 00:40:30 2004 +0300
@@ -13,8 +13,8 @@
 
 static int
 mech_plain_auth_continue(struct auth_request *auth_request,
-			 struct auth_client_request_continue *request,
-			 const unsigned char *data, mech_callback_t *callback)
+			 const unsigned char *data, size_t data_size,
+			 mech_callback_t *callback)
 {
 	const char *authid, *authenid;
 	char *pass;
@@ -28,13 +28,13 @@
 	authenid = NULL; pass = "";
 
 	count = 0;
-	for (i = 0; i < request->data_size; i++) {
+	for (i = 0; i < data_size; i++) {
 		if (data[i] == '\0') {
 			if (++count == 1)
 				authenid = (const char *) data + i+1;
 			else {
 				i++;
-				len = request->data_size - i;
+				len = data_size - i;
 				pass = p_strndup(unsafe_data_stack_pool,
 						 data+i, len);
 				break;
@@ -76,37 +76,61 @@
 	return TRUE;
 }
 
+static int
+mech_plain_auth_initial(struct auth_request *auth_request,
+			struct auth_client_request_new *request,
+			const unsigned char *data,
+			mech_callback_t *callback)
+{
+	struct auth_client_request_reply reply;
+	size_t data_size;
+
+	if (AUTH_CLIENT_REQUEST_HAVE_INITIAL_RESPONSE(request)) {
+		data += request->initial_resp_idx;
+		data_size = request->data_size - request->initial_resp_idx;
+
+		return auth_request->auth_continue(auth_request, data,
+						   data_size, callback);
+	}
+
+	/* initialize reply */
+	memset(&reply, 0, sizeof(reply));
+	reply.id = request->id;
+	reply.result = AUTH_CLIENT_RESULT_CONTINUE;
+
+	callback(&reply, NULL, auth_request->conn);
+	return TRUE;
+}
+
 static void
 mech_plain_auth_free(struct auth_request *auth_request)
 {
 	pool_unref(auth_request->pool);
 }
 
-static struct auth_request *
-mech_plain_auth_new(struct auth_client_connection *conn, unsigned int id,
-		    mech_callback_t *callback)
+static struct auth_request *mech_plain_auth_new(void)
 {
         struct auth_request *auth_request;
-	struct auth_client_request_reply reply;
 	pool_t pool;
 
 	pool = pool_alloconly_create("plain_auth_request", 256);
 	auth_request = p_new(pool, struct auth_request, 1);
 	auth_request->refcount = 1;
 	auth_request->pool = pool;
+	auth_request->auth_initial = mech_plain_auth_initial;
 	auth_request->auth_continue = mech_plain_auth_continue;
         auth_request->auth_free = mech_plain_auth_free;
-
-	/* initialize reply */
-	memset(&reply, 0, sizeof(reply));
-	reply.id = id;
-	reply.result = AUTH_CLIENT_RESULT_CONTINUE;
-
-	callback(&reply, NULL, conn);
 	return auth_request;
 }
 
 struct mech_module mech_plain = {
-	AUTH_MECH_PLAIN,
+	"PLAIN",
+
+	MEMBER(plaintext) TRUE,
+	MEMBER(advertise) FALSE,
+
+	MEMBER(passdb_need_plain) TRUE,
+	MEMBER(passdb_need_credentials) FALSE,
+
 	mech_plain_auth_new
 };
--- a/src/auth/mech.c	Sat May 29 20:06:49 2004 +0300
+++ b/src/auth/mech.c	Sun May 30 00:40:30 2004 +0300
@@ -5,19 +5,14 @@
 #include "buffer.h"
 #include "hash.h"
 #include "mech.h"
+#include "str.h"
 #include "var-expand.h"
 #include "auth-client-connection.h"
 #include "auth-master-connection.h"
 
 #include <stdlib.h>
 
-struct mech_module_list {
-	struct mech_module_list *next;
-
-	struct mech_module module;
-};
-
-enum auth_mech auth_mechanisms;
+struct mech_module_list *mech_modules;
 const char *const *auth_realms;
 const char *default_realm;
 const char *anonymous_username;
@@ -25,17 +20,12 @@
 
 static int set_use_cyrus_sasl;
 static int ssl_require_client_cert;
-static struct mech_module_list *mech_modules;
 static struct auth_client_request_reply failure_reply;
 
 void mech_register_module(struct mech_module *module)
 {
 	struct mech_module_list *list;
 
-	i_assert((auth_mechanisms & module->mech) == 0);
-
-	auth_mechanisms |= module->mech;
-
 	list = i_new(struct mech_module_list, 1);
 	list->module = *module;
 
@@ -47,13 +37,8 @@
 {
 	struct mech_module_list **pos, *list;
 
-	if ((auth_mechanisms & module->mech) == 0)
-		return; /* not registered */
-
-        auth_mechanisms &= ~module->mech;
-
 	for (pos = &mech_modules; *pos != NULL; pos = &(*pos)->next) {
-		if ((*pos)->module.mech == module->mech) {
+		if (strcmp((*pos)->module.mech_name, module->mech_name) == 0) {
 			list = *pos;
 			*pos = (*pos)->next;
 			i_free(list);
@@ -62,17 +47,56 @@
 	}
 }
 
-void mech_request_new(struct auth_client_connection *conn,
-		      struct auth_client_request_new *request,
-		      mech_callback_t *callback)
+const string_t *auth_mechanisms_get_list(void)
+{
+	struct mech_module_list *list;
+	string_t *str;
+
+	str = t_str_new(128);
+	for (list = mech_modules; list != NULL; list = list->next)
+		str_append(str, list->module.mech_name);
+
+	return str;
+}
+
+static struct mech_module *mech_module_find(const char *name)
 {
 	struct mech_module_list *list;
+
+	for (list = mech_modules; list != NULL; list = list->next) {
+		if (strcmp(list->module.mech_name, name) == 0)
+			return &list->module;
+	}
+	return NULL;
+}
+
+void mech_request_new(struct auth_client_connection *conn,
+		      struct auth_client_request_new *request,
+		      const unsigned char *data,
+		      mech_callback_t *callback)
+{
+        struct mech_module *mech;
 	struct auth_request *auth_request;
 
-	if ((auth_mechanisms & request->mech) == 0) {
+	/* make sure data is NUL-terminated */
+	if (request->data_size == 0 || request->initial_resp_idx == 0 ||
+	    request->mech_idx >= request->data_size ||
+	    request->protocol_idx >= request->data_size ||
+	    request->initial_resp_idx > request->data_size ||
+	    data[request->initial_resp_idx-1] != '\0') {
+		i_error("BUG: Auth client %u sent corrupted request",
+			conn->pid);
+		failure_reply.id = request->id;
+		callback(&failure_reply, NULL, conn);
+		return;
+	}
+
+	mech = mech_module_find((const char *)data + request->mech_idx);
+	if (mech == NULL) {
 		/* unsupported mechanism */
 		i_error("BUG: Auth client %u requested unsupported "
-			"auth mechanism %d", conn->pid, request->mech);
+			"auth mechanism %s", conn->pid,
+			(const char *)data + request->mech_idx);
 		failure_reply.id = request->id;
 		callback(&failure_reply, NULL, conn);
 		return;
@@ -89,32 +113,26 @@
 	}
 
 #ifdef USE_CYRUS_SASL2
-	if (set_use_cyrus_sasl) {
+	if (set_use_cyrus_sasl)
 		auth_request = mech_cyrus_sasl_new(conn, request, callback);
-	} else
+	else
 #endif
-	{
-		auth_request = NULL;
-
-		for (list = mech_modules; list != NULL; list = list->next) {
-			if (list->module.mech == request->mech) {
-				auth_request =
-					list->module.auth_new(conn, request->id,
-							      callback);
-				break;
-			}
-		}
-	}
+		auth_request = mech->auth_new();
 
 	if (auth_request != NULL) {
 		auth_request->created = ioloop_time;
 		auth_request->conn = conn;
 		auth_request->id = request->id;
-		strocpy(auth_request->protocol, request->protocol,
-			sizeof(auth_request->protocol));
+		auth_request->protocol =
+			p_strdup(auth_request->pool,
+				 (const char *)data + request->protocol_idx);
 
 		hash_insert(conn->auth_requests, POINTER_CAST(request->id),
 			    auth_request);
+
+		if (!auth_request->auth_initial(auth_request, request, data,
+						callback))
+			mech_request_free(auth_request, request->id);
 	}
 }
 
@@ -133,7 +151,8 @@
 		callback(&failure_reply, NULL, conn);
 	} else {
 		if (!auth_request->auth_continue(auth_request,
-						 request, data, callback))
+						 data, request->data_size,
+						 callback))
 			mech_request_free(auth_request, request->id);
 	}
 }
@@ -276,7 +295,6 @@
 	const char *env;
 
         mech_modules = NULL;
-	auth_mechanisms = 0;
 
 	memset(&failure_reply, 0, sizeof(failure_reply));
 	failure_reply.result = AUTH_CLIENT_RESULT_FAILURE;
@@ -312,7 +330,7 @@
 		mechanisms++;
 	}
 
-	if (auth_mechanisms == 0)
+	if (mech_modules == NULL)
 		i_fatal("No authentication mechanisms configured");
 
 	/* get our realm - note that we allocate from data stack so
--- a/src/auth/mech.h	Sat May 29 20:06:49 2004 +0300
+++ b/src/auth/mech.h	Sun May 30 00:40:30 2004 +0300
@@ -19,26 +19,38 @@
 	unsigned int id;
 	time_t created;
 
-	char protocol[AUTH_CLIENT_PROTOCOL_BUF_SIZE];
+	char *protocol;
 	mech_callback_t *callback;
 
+	int (*auth_initial)(struct auth_request *auth_request,
+                            struct auth_client_request_new *request,
+			    const unsigned char *data,
+			    mech_callback_t *callback);
 	int (*auth_continue)(struct auth_request *auth_request,
-			     struct auth_client_request_continue *request,
-			     const unsigned char *data,
+			     const unsigned char *data, size_t data_size,
 			     mech_callback_t *callback);
 	void (*auth_free)(struct auth_request *auth_request);
 	/* ... mechanism specific data ... */
 };
 
 struct mech_module {
-	enum auth_mech mech;
+	const char *mech_name;
 
-	struct auth_request *(*auth_new)(struct auth_client_connection *conn,
-					 unsigned int id,
-					 mech_callback_t *callback);
+	unsigned int plaintext:1;
+	unsigned int advertise:1;
+	unsigned int passdb_need_plain:1;
+	unsigned int passdb_need_credentials:1;
+
+	struct auth_request *(*auth_new)(void);
 };
 
-extern enum auth_mech auth_mechanisms;
+struct mech_module_list {
+	struct mech_module_list *next;
+
+	struct mech_module module;
+};
+
+extern struct mech_module_list *mech_modules;
 extern const char *const *auth_realms;
 extern const char *default_realm;
 extern const char *anonymous_username;
@@ -48,8 +60,11 @@
 void mech_register_module(struct mech_module *module);
 void mech_unregister_module(struct mech_module *module);
 
+const string_t *auth_mechanisms_get_list(void);
+
 void mech_request_new(struct auth_client_connection *conn,
 		      struct auth_client_request_new *request,
+		      const unsigned char *data,
 		      mech_callback_t *callback);
 void mech_request_continue(struct auth_client_connection *conn,
 			   struct auth_client_request_continue *request,
@@ -70,6 +85,7 @@
 struct auth_request *
 mech_cyrus_sasl_new(struct auth_client_connection *conn,
 		    struct auth_client_request_new *request,
+		    const unsigned char *data,
 		    mech_callback_t *callback);
 
 void auth_request_ref(struct auth_request *request);
--- a/src/auth/passdb.c	Sat May 29 20:06:49 2004 +0300
+++ b/src/auth/passdb.c	Sun May 30 00:40:30 2004 +0300
@@ -71,6 +71,26 @@
 	callback(password, auth_request);
 }
 
+static void
+mech_list_verify_passdb(struct passdb_module *passdb, const char *name)
+{
+	struct mech_module_list *list;
+
+	for (list = mech_modules; list != NULL; list = list->next) {
+		if (list->module.passdb_need_plain &&
+		    passdb->verify_plain == NULL)
+			break;
+		if (list->module.passdb_need_credentials &&
+		    passdb->lookup_credentials == NULL)
+			break;
+	}
+
+	if (list != NULL) {
+		i_fatal("Passdb %s doesn't support %s method",
+			name, list->module.mech_name);
+	}
+}
+
 void passdb_init(void)
 {
 	const char *name, *args;
@@ -135,17 +155,7 @@
 	if (passdb->init != NULL)
 		passdb->init(args != NULL ? args+1 : "");
 
-	if ((auth_mechanisms & AUTH_MECH_PLAIN) &&
-	    passdb->verify_plain == NULL)
-		i_fatal("Passdb %s doesn't support PLAIN method", name);
-
-	if ((auth_mechanisms & AUTH_MECH_CRAM_MD5) &&
-	    passdb->lookup_credentials == NULL)
-		i_fatal("Passdb %s doesn't support CRAM-MD5 method", name);
-
-	if ((auth_mechanisms & AUTH_MECH_DIGEST_MD5) &&
-	    passdb->lookup_credentials == NULL)
-		i_fatal("Passdb %s doesn't support DIGEST-MD5 method", name);
+	mech_list_verify_passdb(passdb, name);
 }
 
 void passdb_deinit(void)
--- a/src/imap-login/client-authenticate.c	Sat May 29 20:06:49 2004 +0300
+++ b/src/imap-login/client-authenticate.c	Sun May 30 00:40:30 2004 +0300
@@ -10,7 +10,6 @@
 #include "str.h"
 #include "imap-parser.h"
 #include "auth-client.h"
-#include "../auth/auth-mech-desc.h"
 #include "ssl-proxy.h"
 #include "client.h"
 #include "client-authenticate.h"
@@ -19,54 +18,27 @@
 
 const char *client_authenticate_get_capabilities(int secured)
 {
-	static enum auth_mech cached_auth_mechs = 0;
-	static char *cached_capability = NULL;
-        enum auth_mech auth_mechs;
+	const struct auth_mech_desc *mech;
+	unsigned int i, count;
 	string_t *str;
-	int i;
-
-	auth_mechs = auth_client_get_available_mechs(auth_client);
-	if (auth_mechs == cached_auth_mechs)
-		return cached_capability;
-
-	cached_auth_mechs = auth_mechs;
-	i_free(cached_capability);
 
 	str = t_str_new(128);
-
-	for (i = 0; i < AUTH_MECH_COUNT; i++) {
-		if ((auth_mechs & auth_mech_desc[i].mech) == 0)
-			continue; /* not available */
-
+	mech = auth_client_get_available_mechs(auth_client, &count);
+	for (i = 0; i < count; i++) {
 		/* a) transport is secured
 		   b) auth mechanism isn't plaintext
 		   c) we allow insecure authentication
 		        - but don't advertise AUTH=PLAIN, as RFC 2595 requires
 		*/
-		if (secured || !auth_mech_desc[i].plaintext ||
-		    (!disable_plaintext_auth &&
-		     auth_mech_desc[i].mech != AUTH_MECH_PLAIN)) {
+		if (mech[i].advertise &&
+		    (secured || !mech[i].plaintext)) {
 			str_append_c(str, ' ');
 			str_append(str, "AUTH=");
-			str_append(str, auth_mech_desc[i].name);
+			str_append(str, mech[i].name);
 		}
 	}
 
-	cached_capability = i_strdup_empty(str_c(str));
-	return cached_capability;
-}
-
-static struct auth_mech_desc *auth_mech_find(const char *name)
-{
-	int i;
-
-	for (i = 0; i < AUTH_MECH_COUNT; i++) {
-		if (auth_mech_desc[i].name != NULL &&
-		    strcasecmp(auth_mech_desc[i].name, name) == 0)
-			return &auth_mech_desc[i];
-	}
-
-	return NULL;
+	return str_c(str);
 }
 
 static void client_auth_abort(struct imap_client *client, const char *msg)
@@ -207,9 +179,10 @@
 	client_ref(client);
 
 	client->common.auth_request =
-		auth_client_request_new(auth_client, AUTH_MECH_PLAIN, "IMAP",
+		auth_client_request_new(auth_client, "PLAIN", "IMAP",
 					client_get_auth_flags(client),
-					login_callback, client, &error);
+					NULL, 0, login_callback,
+					client, &error);
 	if (client->common.auth_request == NULL) {
 		client_send_tagline(client, t_strconcat(
 			"NO Login failed: ", error, NULL));
@@ -307,7 +280,7 @@
 
 int cmd_authenticate(struct imap_client *client, struct imap_arg *args)
 {
-	struct auth_mech_desc *mech;
+	const struct auth_mech_desc *mech;
 	const char *mech_name, *error;
 
 	/* we want only one argument: authentication mechanism name */
@@ -320,7 +293,7 @@
 	if (*mech_name == '\0')
 		return FALSE;
 
-	mech = auth_mech_find(mech_name);
+	mech = auth_client_find_mech(auth_client, mech_name);
 	if (mech == NULL) {
 		client_send_tagline(client,
 				    "NO Unsupported authentication mechanism.");
@@ -335,9 +308,9 @@
 
 	client_ref(client);
 	client->common.auth_request =
-		auth_client_request_new(auth_client, mech->mech, "IMAP",
+		auth_client_request_new(auth_client, mech->name, "IMAP",
 					client_get_auth_flags(client),
-					authenticate_callback,
+					NULL, 0, authenticate_callback,
 					client, &error);
 	if (client->common.auth_request != NULL) {
 		/* following input data will go to authentication */
--- a/src/lib-auth/auth-client.c	Sat May 29 20:06:49 2004 +0300
+++ b/src/lib-auth/auth-client.c	Sun May 30 00:40:30 2004 +0300
@@ -1,6 +1,7 @@
 /* Copyright (C) 2003 Timo Sirainen */
 
 #include "lib.h"
+#include "buffer.h"
 #include "ioloop.h"
 #include "hash.h"
 #include "auth-client.h"
@@ -15,6 +16,8 @@
 
 	client = i_new(struct auth_client, 1);
 	client->pid = client_pid;
+	client->available_auth_mechs =
+		buffer_create_dynamic(default_pool, 128, (size_t)-1);
 
 	auth_client_connect_missing_servers(client);
 	return client;
@@ -23,6 +26,14 @@
 void auth_client_free(struct auth_client *client)
 {
 	struct auth_server_connection *next;
+	struct auth_mech_desc *mech;
+	size_t i, size;
+
+	mech = buffer_get_modifyable_data(client->available_auth_mechs, &size);
+	size /= sizeof(*mech);
+	for (i = 0; i < size; i++)
+		i_free(mech[i].name);
+	buffer_free(client->available_auth_mechs);
 
 	while (client->connections != NULL) {
 		next = client->connections->next;
@@ -35,9 +46,32 @@
 	i_free(client);
 }
 
-enum auth_mech auth_client_get_available_mechs(struct auth_client *client)
+const struct auth_mech_desc *
+auth_client_get_available_mechs(struct auth_client *client,
+				unsigned int *mech_count)
 {
-	return client->available_auth_mechs;
+	const struct auth_mech_desc *mechs;
+	size_t size;
+
+	mechs = buffer_get_data(client->available_auth_mechs, &size);
+	*mech_count = size / sizeof(*mechs);
+	return mechs;
+}
+
+const struct auth_mech_desc *
+auth_client_find_mech(struct auth_client *client, const char *name)
+{
+	const struct auth_mech_desc *mech;
+	size_t i, size;
+
+	mech = buffer_get_data(client->available_auth_mechs, &size);
+	size /= sizeof(*mech);
+	for (i = 0; i < size; i++) {
+		if (strcasecmp(mech[i].name, name) == 0)
+			return &mech[i];
+	}
+
+	return NULL;
 }
 
 int auth_client_is_connected(struct auth_client *client)
--- a/src/lib-auth/auth-client.h	Sat May 29 20:06:49 2004 +0300
+++ b/src/lib-auth/auth-client.h	Sun May 30 00:40:30 2004 +0300
@@ -6,6 +6,12 @@
 struct auth_client;
 struct auth_request;
 
+struct auth_mech_desc {
+	char *name;
+	unsigned int plaintext:1;
+	unsigned int advertise:1;
+};
+
 /* reply is NULL if auth connection died */
 typedef void auth_request_callback_t(struct auth_request *request,
 				     struct auth_client_request_reply *reply,
@@ -22,7 +28,11 @@
 void auth_client_set_connect_notify(struct auth_client *client,
 				    auth_connect_notify_callback_t *callback,
 				    void *context);
-enum auth_mech auth_client_get_available_mechs(struct auth_client *client);
+const struct auth_mech_desc *
+auth_client_get_available_mechs(struct auth_client *client,
+				unsigned int *mech_count);
+const struct auth_mech_desc *
+auth_client_find_mech(struct auth_client *client, const char *name);
 
 void auth_client_connect_missing_servers(struct auth_client *client);
 
@@ -30,8 +40,10 @@
    happens for the request. */
 struct auth_request *
 auth_client_request_new(struct auth_client *client,
-			enum auth_mech mech, const char *protocol,
+			const char *mech, const char *protocol,
 			enum auth_client_request_new_flags flags,
+			const unsigned char *initial_resp_data,
+			size_t initial_resp_size,
 			auth_request_callback_t *callback, void *context,
 			const char **error_r);
 
--- a/src/lib-auth/auth-server-connection.c	Sat May 29 20:06:49 2004 +0300
+++ b/src/lib-auth/auth-server-connection.c	Sun May 30 00:40:30 2004 +0300
@@ -1,6 +1,7 @@
 /* Copyright (C) 2003 Timo Sirainen */
 
 #include "lib.h"
+#include "buffer.h"
 #include "hash.h"
 #include "ioloop.h"
 #include "istream.h"
@@ -21,30 +22,78 @@
 
 static void auth_server_connection_unref(struct auth_server_connection *conn);
 
-static void update_available_auth_mechs(struct auth_client *client)
+static void update_available_auth_mechs(struct auth_server_connection *conn)
 {
-	struct auth_server_connection *conn;
+	struct auth_client *client = conn->client;
+	const struct auth_mech_desc *mech;
+	struct auth_mech_desc *new_mech;
+	unsigned int i;
 
-        client->available_auth_mechs = 0;
-	for (conn = client->connections; conn != NULL; conn = conn->next)
-                client->available_auth_mechs |= conn->available_auth_mechs;
+	mech = conn->available_auth_mechs;
+	for (i = 0; i < conn->available_auth_mechs_count; i++) {
+		if (auth_client_find_mech(client, mech[i].name) == NULL) {
+			new_mech = buffer_append_space_unsafe(
+				client->available_auth_mechs, sizeof(*mech));
+			*new_mech = mech[i];
+			new_mech->name = i_strdup(mech[i].name);
+		}
+	}
 }
 
 static void auth_handle_handshake(struct auth_server_connection *conn,
-				  struct auth_client_handshake_reply *handshake)
+				  struct auth_client_handshake_reply *handshake,
+				  const unsigned char *data)
 {
+	struct auth_client_handshake_mech_desc handshake_mech_desc;
+	struct auth_mech_desc mech_desc;
+	buffer_t *buf;
+	unsigned int i;
+
 	if (handshake->server_pid == 0) {
 		i_error("BUG: Auth server said it's PID 0");
 		auth_server_connection_destroy(conn, FALSE);
 		return;
 	}
 
+	if (handshake->data_size == 0 || data[handshake->data_size-1] != '\0' ||
+	    handshake->mech_count * sizeof(handshake_mech_desc) >=
+	    handshake->data_size)  {
+		i_error("BUG: Auth server sent corrupted handshake");
+		auth_server_connection_destroy(conn, FALSE);
+		return;
+	}
+
+	buf = buffer_create_dynamic(conn->pool, sizeof(mech_desc) *
+				    handshake->mech_count, (size_t)-1);
+	for (i = 0; i < handshake->mech_count; i++) {
+		memcpy(&handshake_mech_desc,
+		       data + sizeof(handshake_mech_desc) * i,
+		       sizeof(handshake_mech_desc));
+
+		if (handshake_mech_desc.name_idx >= handshake->data_size) {
+			i_error("BUG: Auth server sent corrupted handshake");
+			auth_server_connection_destroy(conn, FALSE);
+			return;
+		}
+
+		mech_desc.name = p_strdup(conn->pool, (const char *)data +
+					  handshake_mech_desc.name_idx);
+		mech_desc.plaintext = handshake_mech_desc.plaintext;
+		mech_desc.advertise = handshake_mech_desc.advertise;
+		buffer_append(buf, &mech_desc, sizeof(mech_desc));
+
+		if (strcmp(mech_desc.name, "PLAIN") == 0)
+			conn->has_plain_mech = TRUE;
+	}
+
 	conn->pid = handshake->server_pid;
-	conn->available_auth_mechs = handshake->auth_mechanisms;
+	conn->available_auth_mechs_count =
+		buffer_get_used_size(buf) / sizeof(mech_desc);
+	conn->available_auth_mechs = buffer_free_without_data(buf);
 	conn->handshake_received = TRUE;
 
         conn->client->conn_waiting_handshake_count--;
-	update_available_auth_mechs(conn->client);
+	update_available_auth_mechs(conn);
 
 	if (conn->client->connect_notify_callback != NULL &&
 	    auth_client_is_connected(conn->client)) {
@@ -77,17 +126,19 @@
 
 	if (!conn->handshake_received) {
 		data = i_stream_get_data(conn->input, &size);
-		if (size == sizeof(handshake)) {
-			memcpy(&handshake, data, sizeof(handshake));
-			i_stream_skip(conn->input, sizeof(handshake));
+		if (size < sizeof(handshake))
+			return;
+
+		memcpy(&handshake, data, sizeof(handshake));
+		if (size < sizeof(handshake) + handshake.data_size)
+			return;
 
-			auth_handle_handshake(conn, &handshake);
-		} else if (size > sizeof(handshake)) {
-			i_error("BUG: Auth server sent us too large handshake "
-				"(%"PRIuSIZE_T " vs %"PRIuSIZE_T")", size,
-				sizeof(handshake));
-			auth_server_connection_destroy(conn, FALSE);
-		}
+		conn->refcount++;
+		auth_handle_handshake(conn, &handshake,
+				      data + sizeof(handshake));
+		i_stream_skip(conn->input, sizeof(handshake) +
+			      handshake.data_size);
+		auth_server_connection_unref(conn);
 		return;
 	}
 
@@ -210,6 +261,7 @@
 {
 	if (--conn->refcount > 0)
 		return;
+	i_assert(conn->refcount == 0);
 
 	hash_destroy(conn->requests);
 
@@ -233,16 +285,21 @@
 
 struct auth_server_connection *
 auth_server_connection_find_mech(struct auth_client *client,
-				 enum auth_mech mech, const char **error_r)
+				 const char *name, const char **error_r)
 {
 	struct auth_server_connection *conn;
+	const struct auth_mech_desc *mech;
+	unsigned int i;
 
 	for (conn = client->connections; conn != NULL; conn = conn->next) {
-		if ((conn->available_auth_mechs & mech))
-			return conn;
+		mech = conn->available_auth_mechs;
+		for (i = 0; i < conn->available_auth_mechs_count; i++) {
+			if (strcmp(mech[i].name, name) == 0)
+				return conn;
+		}
 	}
 
-	if ((client->available_auth_mechs & mech) == 0)
+	if (auth_client_find_mech(client, name) == NULL)
 		*error_r = "Unsupported authentication mechanism";
 	else {
 		*error_r = "Authentication server isn't connected, "
--- a/src/lib-auth/auth-server-connection.h	Sat May 29 20:06:49 2004 +0300
+++ b/src/lib-auth/auth-server-connection.h	Sun May 30 00:40:30 2004 +0300
@@ -9,7 +9,7 @@
 
 	unsigned int conn_waiting_handshake_count;
 
-	enum auth_mech available_auth_mechs;
+	buffer_t *available_auth_mechs;
 	unsigned int request_id_counter;
 
 	auth_connect_notify_callback_t *connect_notify_callback;
@@ -31,13 +31,15 @@
 	struct ostream *output;
 
 	unsigned int pid;
-	enum auth_mech available_auth_mechs;
+	const struct auth_mech_desc *available_auth_mechs;
+	unsigned int available_auth_mechs_count;
         struct auth_client_request_reply reply;
 
         struct hash_table *requests;
 
 	unsigned int handshake_received:1;
 	unsigned int reply_received:1;
+	unsigned int has_plain_mech:1;
 };
 
 struct auth_server_connection *
@@ -50,6 +52,6 @@
 
 struct auth_server_connection *
 auth_server_connection_find_mech(struct auth_client *client,
-				 enum auth_mech mech, const char **error_r);
+				 const char *name, const char **error_r);
 
 #endif
--- a/src/lib-auth/auth-server-request.c	Sat May 29 20:06:49 2004 +0300
+++ b/src/lib-auth/auth-server-request.c	Sun May 30 00:40:30 2004 +0300
@@ -1,6 +1,7 @@
 /* Copyright (C) 2003 Timo Sirainen */
 
 #include "lib.h"
+#include "buffer.h"
 #include "hash.h"
 #include "ostream.h"
 #include "auth-client.h"
@@ -10,12 +11,14 @@
 struct auth_request {
         struct auth_server_connection *conn;
 
-	enum auth_mech mech;
-	char protocol[AUTH_CLIENT_PROTOCOL_BUF_SIZE];
+	char *mech, *protocol;
 	enum auth_client_request_new_flags flags;
 
 	unsigned int id;
 
+	unsigned char *initial_resp_data;
+	size_t initial_resp_size;
+
 	auth_request_callback_t *callback;
 	void *context;
 
@@ -31,16 +34,42 @@
 					struct auth_request *request)
 {
 	struct auth_client_request_new auth_request;
+	buffer_t *buf;
+	int ret;
 
+	memset(&auth_request, 0, sizeof(auth_request));
 	auth_request.type = AUTH_CLIENT_REQUEST_NEW;
 	auth_request.id = request->id;
-	strocpy(auth_request.protocol, request->protocol,
-		sizeof(auth_request.protocol));
-	auth_request.mech = request->mech;
 	auth_request.flags = request->flags;
 
-	if (o_stream_send(conn->output, &auth_request,
-			  sizeof(auth_request)) < 0) {
+	t_push();
+	buf = buffer_create_dynamic(pool_datastack_create(), 256, (size_t)-1);
+	buffer_set_used_size(buf, sizeof(auth_request));
+
+	auth_request.mech_idx =
+		buffer_get_used_size(buf) - sizeof(auth_request);
+	buffer_append(buf, request->mech, strlen(request->mech)+1);
+
+	auth_request.protocol_idx =
+		buffer_get_used_size(buf) - sizeof(auth_request);
+	buffer_append(buf, request->protocol, strlen(request->protocol)+1);
+
+	auth_request.initial_resp_idx =
+		buffer_get_used_size(buf) - sizeof(auth_request);
+	buffer_append(buf, request->initial_resp_data,
+		      request->initial_resp_size);
+
+	auth_request.data_size =
+		buffer_get_used_size(buf) - sizeof(auth_request);
+
+	memcpy(buffer_get_space_unsafe(buf, 0, sizeof(auth_request)),
+	       &auth_request, sizeof(auth_request));
+
+	ret = o_stream_send(conn->output, buffer_get_data(buf, NULL),
+			    buffer_get_used_size(buf));
+	t_pop();
+
+	if (ret < 0) {
 		errno = conn->output->stream_errno;
 		i_warning("Error sending request to auth server: %m");
 		auth_server_connection_destroy(conn, TRUE);
@@ -75,7 +104,7 @@
 {
 	conn = conn->next;
 	while (conn != NULL) {
-		if ((conn->available_auth_mechs & AUTH_MECH_PLAIN) != 0)
+		if (conn->has_plain_mech)
 			return conn;
 		conn = conn->next;
 	}
@@ -179,8 +208,10 @@
 
 struct auth_request *
 auth_client_request_new(struct auth_client *client,
-			enum auth_mech mech, const char *protocol,
+			const char *mech, const char *protocol,
 			enum auth_client_request_new_flags flags,
+			const unsigned char *initial_resp_data,
+			size_t initial_resp_size,
 			auth_request_callback_t *callback, void *context,
 			const char **error_r)
 {
@@ -193,10 +224,18 @@
 
 	request = i_new(struct auth_request, 1);
 	request->conn = conn;
-	request->mech = mech;
-	strocpy(request->protocol, protocol, sizeof(request->protocol));
+	request->mech = i_strdup(mech);
+	request->protocol = i_strdup(protocol);
 	request->flags = flags;
 	request->id = ++client->request_id_counter;
+
+	if (initial_resp_size != 0) {
+		request->initial_resp_size = initial_resp_size;
+		request->initial_resp_data = i_malloc(initial_resp_size);
+		memcpy(request->initial_resp_data, initial_resp_data,
+		       initial_resp_size);
+	}
+	
 	if (request->id == 0) {
 		/* wrapped - ID 0 not allowed */
 		request->id = ++client->request_id_counter;
@@ -216,8 +255,8 @@
 {
 	auth_server_send_continue(request->conn, request, data, data_size);
 
-	if (request->mech == AUTH_MECH_PLAIN &&
-	    request->plaintext_data == NULL) {
+	if (strcmp(request->mech, "PLAIN") == 0 &&
+	    request->plaintext_data == NULL && request->conn != NULL) {
 		request->next_conn = get_next_plain_server(request->conn);
 		if (request->next_conn != NULL) {
 			/* plaintext authentication - save the data so we can
@@ -245,7 +284,10 @@
 
 	request->callback(request, NULL, NULL, request->context);
 
+	i_free(request->initial_resp_data);
 	i_free(request->plaintext_data);
+	i_free(request->mech);
+	i_free(request->protocol);
 	i_free(request);
 }
 
--- a/src/lib/file-dotlock.c	Sat May 29 20:06:49 2004 +0300
+++ b/src/lib/file-dotlock.c	Sun May 30 00:40:30 2004 +0300
@@ -371,13 +371,27 @@
 		return -1;
 	}
 
+	dotlock_r->dev = st.st_dev;
+	dotlock_r->ino = st.st_ino;
+
 	if (close(fd) < 0) {
 		i_error("fstat(%s) failed: %m", lock_path);
 		return -1;
 	}
 
-	dotlock_r->dev = st.st_dev;
-	dotlock_r->ino = st.st_ino;
+	/* some NFS implementations may have used cached mtime in previous
+	   fstat() call. Check again to avoid "dotlock was modified" errors. */
+	if (stat(lock_path, &st) < 0) {
+		i_error("stat(%s) failed: %m", lock_path);
+		return -1;
+	}
+	/* extra sanity check won't hurt.. */
+	if (st.st_dev != dotlock_r->dev ||
+	    st.st_ino != dotlock_r->ino) {
+		i_error("dotlock %s was immediately recreated under us",
+			lock_path);
+		return -1;
+	}
 	dotlock_r->mtime = st.st_mtime;
 	return 1;
 }
--- a/src/pop3-login/client-authenticate.c	Sat May 29 20:06:49 2004 +0300
+++ b/src/pop3-login/client-authenticate.c	Sun May 30 00:40:30 2004 +0300
@@ -9,7 +9,6 @@
 #include "safe-memset.h"
 #include "str.h"
 #include "auth-client.h"
-#include "../auth/auth-mech-desc.h"
 #include "../pop3/capability.h"
 #include "ssl-proxy.h"
 #include "master.h"
@@ -20,62 +19,36 @@
 
 int cmd_capa(struct pop3_client *client, const char *args __attr_unused__)
 {
-	static enum auth_mech cached_auth_mechs = 0;
-	static char *cached_capability = NULL;
-        enum auth_mech auth_mechs;
+	const struct auth_mech_desc *mech;
+	unsigned int i, count;
 	string_t *str;
-	int i;
 
-	auth_mechs = auth_client_get_available_mechs(auth_client);
-	if (cached_auth_mechs != auth_mechs) {
-		cached_auth_mechs = auth_mechs;
-		i_free(cached_capability);
-
-		str = t_str_new(128);
+	str = t_str_new(128);
+	str_append(str, "SASL");
 
-		str_append(str, "SASL");
-		for (i = 0; i < AUTH_MECH_COUNT; i++) {
-			if ((auth_mechs & auth_mech_desc[i].mech) == 0)
-				continue; /* not available */
-
-			/* a) transport is secured
-			   b) auth mechanism isn't plaintext
-			   c) we allow insecure authentication
-			       - but don't advertise AUTH=PLAIN,
-			         as RFC 2595 requires
-			*/
-			if (client->secured || !auth_mech_desc[i].plaintext ||
-			    (!disable_plaintext_auth &&
-			     auth_mech_desc[i].mech != AUTH_MECH_PLAIN)) {
-				str_append_c(str, ' ');
-				str_append(str, auth_mech_desc[i].name);
-			}
+	mech = auth_client_get_available_mechs(auth_client, &count);
+	for (i = 0; i < count; i++) {
+		/* a) transport is secured
+		   b) auth mechanism isn't plaintext
+		   c) we allow insecure authentication
+		        - but don't advertise AUTH=PLAIN, as RFC 2595 requires
+		*/
+		if (mech[i].advertise &&
+		    (client->secured || !mech[i].plaintext)) {
+			str_append_c(str, ' ');
+			str_append(str, "AUTH=");
+			str_append(str, mech[i].name);
 		}
-
-		cached_capability = i_strdup(str_c(str));
 	}
 
 	client_send_line(client, t_strconcat("+OK\r\n" POP3_CAPABILITY_REPLY,
 					     (ssl_initialized && !client->tls) ?
 					     "STLS\r\n" : "",
-					     cached_capability,
+					     str_c(str),
 					     "\r\n.", NULL));
 	return TRUE;
 }
 
-static struct auth_mech_desc *auth_mech_find(const char *name)
-{
-	int i;
-
-	for (i = 0; i < AUTH_MECH_COUNT; i++) {
-		if (auth_mech_desc[i].name != NULL &&
-		    strcasecmp(auth_mech_desc[i].name, name) == 0)
-			return &auth_mech_desc[i];
-	}
-
-	return NULL;
-}
-
 static void client_auth_abort(struct pop3_client *client, const char *msg)
 {
 	if (client->common.auth_request != NULL) {
@@ -150,23 +123,15 @@
 {
 	struct pop3_client *client = context;
 	const char *error;
-	const void *ptr;
-	size_t size;
 
 	switch (auth_callback(request, reply, data, &client->common,
 			      master_callback, &error)) {
 	case -1:
+	case 0:
 		/* login failed */
 		client_auth_abort(client, error);
 		break;
 
-	case 0:
-		ptr = buffer_get_data(client->plain_login, &size);
-		auth_client_request_continue(request, ptr, size);
-
-		buffer_set_used_size(client->plain_login, 0);
-		break;
-
 	default:
 		/* success, we should be able to log in. if we fail, just
 		   disconnect the client. */
@@ -206,9 +171,13 @@
 
 	client_ref(client);
 	client->common.auth_request =
-		auth_client_request_new(auth_client, AUTH_MECH_PLAIN, "POP3",
+		auth_client_request_new(auth_client, "PLAIN", "POP3",
                                         client_get_auth_flags(client),
+					str_data(client->plain_login),
+					str_len(client->plain_login),
 					login_callback, client, &error);
+	buffer_set_used_size(client->plain_login, 0);
+
 	if (client->common.auth_request != NULL) {
 		/* don't read any input from client until login is finished */
 		if (client->common.io != NULL) {
@@ -296,11 +265,22 @@
 
 int cmd_auth(struct pop3_client *client, const char *args)
 {
-	struct auth_mech_desc *mech;
-	const char *error;
+	const struct auth_mech_desc *mech;
+	const char *mech_name, *error, *p;
+	string_t *buf;
+	size_t argslen;
 
-	/* we have only one argument: authentication mechanism name */
-	mech = auth_mech_find(args);
+	/* <mechanism name> <initial response> */
+	p = strchr(args, ' ');
+	if (p == NULL) {
+		mech_name = args;
+		args = "";
+	} else {
+		mech_name = t_strdup_until(args, p);
+		args = p+1;
+	}
+
+	mech = auth_client_find_mech(auth_client, mech_name);
 	if (mech == NULL) {
 		client_send_line(client,
 				 "-ERR Unsupported authentication mechanism.");
@@ -313,10 +293,21 @@
 		return TRUE;
 	}
 
+	argslen = strlen(args);
+	buf = buffer_create_static_hard(pool_datastack_create(), argslen);
+
+	if (base64_decode((const unsigned char *)args, argslen,
+			  NULL, buf) <= 0) {
+		/* failed */
+		client_send_line(client, "-ERR Invalid base64 data.");
+		return TRUE;
+	}
+
 	client_ref(client);
 	client->common.auth_request =
-		auth_client_request_new(auth_client, mech->mech, "POP3",
+		auth_client_request_new(auth_client, mech->name, "POP3",
                                         client_get_auth_flags(client),
+					str_data(buf), str_len(buf),
 					authenticate_callback, client, &error);
 	if (client->common.auth_request != NULL) {
 		/* following input data will go to authentication */