changeset 11018:2e08ce368bc0 HEAD

Added support for userdb lookup to fail with a reason (many API changes).
author Timo Sirainen <tss@iki.fi>
date Wed, 31 Mar 2010 19:21:09 +0300
parents ce7ed594d99e
children b40ec803421e
files doc/auth-protocol.txt src/doveadm/doveadm-auth.c src/doveadm/doveadm-mail.c src/dsync/dsync.c src/imap/main.c src/lib-auth/auth-master.c src/lib-auth/auth-master.h src/lib-master/master-auth.c src/lib-master/master-auth.h src/lib-master/master-login-auth.c src/lib-master/master-login-auth.h src/lib-master/master-login.c src/lib-master/master-login.h src/lib-storage/mail-storage-service.c src/lib-storage/mail-storage-service.h src/lmtp/commands.c src/login-common/client-common-auth.c src/login-common/sasl-server.c src/login-common/sasl-server.h src/pop3/main.c
diffstat 20 files changed, 253 insertions(+), 124 deletions(-) [+]
line wrap: on
line diff
--- a/doc/auth-protocol.txt	Wed Mar 31 19:18:01 2010 +0300
+++ b/doc/auth-protocol.txt	Wed Mar 31 19:21:09 2010 +0300
@@ -164,7 +164,7 @@
  M: "USER" TAB <id> TAB <userid> TAB service=<service> [TAB <parameters>]
 
  S: "NOTFOUND" TAB <id>
- S: "FAIL" TAB <id> TAB <error message>
+ S: "FAIL" TAB <id> [TAB <parameters>]
  S: "USER" TAB <id> TAB <userid> [TAB <parameters>]
 
 Master commands can request information about existing authentication
@@ -180,7 +180,10 @@
 
 FAIL reply means an internal error occurred. Usually either a configuration
 mistake or temporary error caused by lost resource (eg. database down).
-Also unknown request IDs are reported as FAILs.
+Also unknown request IDs are reported as FAILs. Currently the only
+specified parameter is "reason", which is used when user is wanted to be
+put into "temporarily disabled" state and the reason string will be shown
+to user on login or to LMTP RCPT TO reply.
 
 USER reply is sent if request succeeded. It can return parameters:
 
--- a/src/doveadm/doveadm-auth.c	Wed Mar 31 19:18:01 2010 +0300
+++ b/src/doveadm/doveadm-auth.c	Wed Mar 31 19:21:09 2010 +0300
@@ -37,9 +37,14 @@
 	conn = auth_master_init(auth_socket_path, 0);
 	ret = auth_master_user_lookup(conn, input->username, &input->info,
 				      pool, &username, &fields);
-	if (ret < 0)
-		i_fatal("userdb lookup failed");
-	else if (ret == 0) {
+	if (ret < 0) {
+		if (fields[0] == NULL)
+			i_fatal("userdb lookup failed for %s", input->username);
+		else {
+			i_fatal("userdb lookup failed for %s: %s",
+				input->username, fields[0]);
+		}
+	} else if (ret == 0) {
 		printf("userdb lookup: user %s doesn't exist\n",
 		       input->username);
 	} else {
--- a/src/doveadm/doveadm-mail.c	Wed Mar 31 19:18:01 2010 +0300
+++ b/src/doveadm/doveadm-mail.c	Wed Mar 31 19:21:09 2010 +0300
@@ -99,11 +99,12 @@
 		return ret;
 	}
 
-	if (mail_storage_service_next(storage_service, service_user,
-				      &mail_user, &error) < 0) {
-		*error_r = t_strdup_printf("User init failed: %s", error);
+	ret = mail_storage_service_next(storage_service, service_user,
+					&mail_user);
+	if (ret < 0) {
+		*error_r = "User init failed";
 		mail_storage_service_user_free(&service_user);
-		return -2;
+		return ret;
 	}
 
 	cmd(mail_user, args);
--- a/src/dsync/dsync.c	Wed Mar 31 19:18:01 2010 +0300
+++ b/src/dsync/dsync.c	Wed Mar 31 19:21:09 2010 +0300
@@ -147,8 +147,8 @@
 					&service_user, &error) <= 0)
 		i_fatal("User lookup failed: %s", error);
 	if (mail_storage_service_next(storage_service, service_user,
-				      &mail_user, &error) < 0)
-		i_fatal("User init failed: %s", error);
+				      &mail_user) < 0)
+		i_fatal("User init failed");
 
 	if (mirror_cmd != NULL) {
 		/* user initialization may exec doveconf, so do our forking
@@ -170,8 +170,8 @@
 		if (settings_parse_line(set_parser, set_line) < 0)
 			i_unreached();
 		if (mail_storage_service_next(storage_service, service_user,
-					      &mail_user2, &error) < 0)
-			i_fatal("User init failed: %s", error);
+					      &mail_user2) < 0)
+			i_fatal("User init failed");
 
 		worker2 = dsync_worker_init_local(mail_user2, alt_char);
 
--- a/src/imap/main.c	Wed Mar 31 19:18:01 2010 +0300
+++ b/src/imap/main.c	Wed Mar 31 19:21:09 2010 +0300
@@ -15,6 +15,7 @@
 #include "master-login.h"
 #include "mail-user.h"
 #include "mail-storage-service.h"
+#include "imap-resp-code.h"
 #include "imap-commands.h"
 #include "imap-fetch.h"
 
@@ -104,52 +105,72 @@
 	}
 }
 
+struct client_input {
+	const char *tag;
+
+	const unsigned char *input;
+	unsigned int input_size;
+	bool send_untagged_capability;
+};
+
+static void
+client_parse_input(const unsigned char *data, unsigned int len,
+		   struct client_input *input_r)
+{
+	unsigned int taglen;
+
+	i_assert(len > 0);
+
+	memset(input_r, 0, sizeof(*input_r));
+
+	if (data[0] == '1')
+		input_r->send_untagged_capability = TRUE;
+	data++; len--;
+
+	input_r->tag = t_strndup(data, len);
+	taglen = strlen(input_r->tag) + 1;
+
+	if (len > taglen) {
+		input_r->input = data + taglen;
+		input_r->input_size = len - taglen;
+	}
+}
+
 static void client_add_input(struct client *client, const buffer_t *buf)
 {
 	struct ostream *output;
-	const char *tag;
-	unsigned int data_pos;
-	bool send_untagged_capability = FALSE;
+	struct client_input input;
 
 	if (buf != NULL && buf->used > 0) {
-		tag = t_strndup(buf->data, buf->used);
-		switch (*tag) {
-		case '0':
-			tag++;
-			break;
-		case '1':
-			send_untagged_capability = TRUE;
-			tag++;
-			break;
-		}
-		data_pos = strlen(tag) + 1;
-		if (data_pos > buf->used &&
-		    !i_stream_add_data(client->input,
-				       CONST_PTR_OFFSET(buf->data, data_pos),
-				       buf->used - data_pos))
+		client_parse_input(buf->data, buf->used, &input);
+		if (input.input_size > 0 &&
+		    !i_stream_add_data(client->input, input.input,
+				       input.input_size))
 			i_panic("Couldn't add client input to stream");
 	} else {
 		/* IMAPLOGINTAG environment is compatible with mailfront */
-		tag = getenv("IMAPLOGINTAG");
+		memset(&input, 0, sizeof(input));
+		input.tag = getenv("IMAPLOGINTAG");
 	}
 
 	output = client->output;
 	o_stream_ref(output);
 	o_stream_cork(output);
-	if (tag == NULL) {
+	if (input.tag == NULL) {
 		client_send_line(client, t_strconcat(
 			"* PREAUTH [CAPABILITY ",
 			str_c(client->capability_string), "] "
 			"Logged in as ", client->user->username, NULL));
-	} else if (send_untagged_capability) {
+	} else if (input.send_untagged_capability) {
 		/* client doesn't seem to understand tagged capabilities. send
 		   untagged instead and hope that it works. */
 		client_send_line(client, t_strconcat("* CAPABILITY ",
 			str_c(client->capability_string), NULL));
-		client_send_line(client, t_strconcat(tag, " OK Logged in", NULL));
+		client_send_line(client,
+				 t_strconcat(input.tag, " OK Logged in", NULL));
 	} else {
 		client_send_line(client, t_strconcat(
-			tag, " OK [CAPABILITY ",
+			input.tag, " OK [CAPABILITY ",
 			str_c(client->capability_string), "] Logged in", NULL));
 	}
 	(void)client_handle_input(client);
@@ -241,6 +262,18 @@
 	}
 }
 
+static void login_client_failed(const struct master_login_client *client,
+				const char *errormsg)
+{
+	struct client_input input;
+	const char *msg;
+
+	client_parse_input(client->data, client->auth_req.data_size, &input);
+	msg = t_strdup_printf("%s NO ["IMAP_RESP_CODE_UNAVAILABLE"] %s\r\n",
+			      input.tag, errormsg);
+	(void)write(client->fd, msg, strlen(msg));
+}
+
 static void client_connected(const struct master_service_connection *conn)
 {
 	if (master_login == NULL) {
@@ -315,7 +348,8 @@
 	} else {
 		master_login = master_login_init(master_service, "auth-master",
 						 postlogin_socket_path,
-						 login_client_connected);
+						 login_client_connected,
+						 login_client_failed);
 		io_loop_set_running(current_ioloop);
 	}
 
--- a/src/lib-auth/auth-master.c	Wed Mar 31 19:18:01 2010 +0300
+++ b/src/lib-auth/auth-master.c	Wed Mar 31 19:21:09 2010 +0300
@@ -50,6 +50,7 @@
 
 struct auth_master_lookup_ctx {
 	struct auth_master_connection *conn;
+	const char *user;
 	const char *expected_reply;
 	int return_value;
 
@@ -135,19 +136,21 @@
 	return 0;
 }
 
-static int parse_reply(struct auth_master_connection *conn,
-		       const char *cmd, const char *const *args,
-		       const char *expected_reply)
+static int parse_reply(const char *cmd, const char *const *args,
+		       const char *expected_reply, const char *user, bool debug)
 {
-	io_loop_stop(conn->ioloop);
-
 	if (strcmp(cmd, expected_reply) == 0)
 		return 1;
 	if (strcmp(cmd, "NOTFOUND") == 0)
 		return 0;
 	if (strcmp(cmd, "FAIL") == 0) {
-		i_error("Lookup failed: %s",
-			*args != NULL ? *args : "Internal failure");
+		if (*args == NULL) {
+			i_error("user %s: Auth %s lookup failed",
+				user, expected_reply);
+		} else if (debug) {
+			i_debug("user %s: Auth %s lookup returned temporary failure: %s",
+				user, expected_reply, *args);
+		}
 		return -1;
 	}
 	i_error("Unknown reply: %s", cmd);
@@ -159,17 +162,31 @@
 {
 	struct auth_master_lookup_ctx *ctx = context;
 	unsigned int i, len;
+	bool debug = (ctx->conn->flags & AUTH_MASTER_FLAG_DEBUG) != 0;
+
+	io_loop_stop(ctx->conn->ioloop);
 
 	ctx->return_value =
-		parse_reply(ctx->conn, cmd, args, ctx->expected_reply);
-	if (ctx->return_value > 0) {
-		len = str_array_length(args);
+		parse_reply(cmd, args, ctx->expected_reply, ctx->user, debug);
+
+	len = str_array_length(args);
+	if (ctx->return_value >= 0) {
 		ctx->fields = p_new(ctx->pool, const char *, len + 1);
 		for (i = 0; i < len; i++)
 			ctx->fields[i] = p_strdup(ctx->pool, args[i]);
-		if ((ctx->conn->flags & AUTH_MASTER_FLAG_DEBUG) != 0)
-			i_debug("auth input: %s", t_strarray_join(args, " "));
+	} else {
+		/* put the reason string into first field */
+		ctx->fields = p_new(ctx->pool, const char *, 2);
+		for (i = 0; i < len; i++) {
+			if (strncmp(args[i], "reason=", 7) == 0) {
+				ctx->fields[0] =
+					p_strdup(ctx->pool, args[i] + 7);
+				break;
+			}
+		}
 	}
+	if (debug)
+		i_debug("auth input: %s", t_strarray_join(args, " "));
 	return TRUE;
 }
 
@@ -398,6 +415,8 @@
 
 	if (!is_valid_string(user) || !is_valid_string(info->service)) {
 		/* non-allowed characters, the user can't exist */
+		*username_r = NULL;
+		*fields_r = NULL;
 		return 0;
 	}
 
@@ -406,6 +425,7 @@
 	ctx.return_value = -1;
 	ctx.pool = pool;
 	ctx.expected_reply = "USER";
+	ctx.user = user;
 
 	conn->reply_callback = auth_lookup_reply_callback;
 	conn->reply_context = &ctx;
@@ -422,7 +442,7 @@
 
 	if (ctx.return_value <= 0 || ctx.fields[0] == NULL) {
 		*username_r = NULL;
-		*fields_r = NULL;
+		*fields_r = ctx.fields;
 		if (ctx.return_value > 0) {
 			i_error("Userdb lookup didn't return username");
 			ctx.return_value = -1;
@@ -467,6 +487,7 @@
 
 	if (!is_valid_string(user) || !is_valid_string(info->service)) {
 		/* non-allowed characters, the user can't exist */
+		*fields_r = NULL;
 		return 0;
 	}
 
@@ -475,6 +496,7 @@
 	ctx.return_value = -1;
 	ctx.pool = pool;
 	ctx.expected_reply = "PASS";
+	ctx.user = user;
 
 	conn->reply_callback = auth_lookup_reply_callback;
 	conn->reply_context = &ctx;
--- a/src/lib-auth/auth-master.h	Wed Mar 31 19:18:01 2010 +0300
+++ b/src/lib-auth/auth-master.h	Wed Mar 31 19:21:09 2010 +0300
@@ -27,7 +27,9 @@
 auth_master_init(const char *auth_socket_path, enum auth_master_flags flags);
 void auth_master_deinit(struct auth_master_connection **conn);
 
-/* Do a USER lookup. Returns -1 = error, 0 = user not found, 1 = ok */
+/* Do a USER lookup. Returns -1 = error, 0 = user not found, 1 = ok.
+   When returning -1 and fields[0] isn't NULL, it contains an error message
+   that should be shown to user. */
 int auth_master_user_lookup(struct auth_master_connection *conn,
 			    const char *user, const struct auth_user_info *info,
 			    pool_t pool, const char **username_r,
--- a/src/lib-master/master-auth.c	Wed Mar 31 19:18:01 2010 +0300
+++ b/src/lib-master/master-auth.c	Wed Mar 31 19:21:09 2010 +0300
@@ -58,7 +58,6 @@
 master_auth_connection_deinit(struct master_auth_connection **_conn)
 {
 	struct master_auth_connection *conn = *_conn;
-	struct master_auth_reply reply;
 
 	*_conn = NULL;
 
@@ -67,11 +66,8 @@
 				  POINTER_CAST(conn->tag));
 	}
 
-	if (conn->callback != NULL) {
-		memset(&reply, 0, sizeof(reply));
-		reply.status = MASTER_AUTH_STATUS_INTERNAL_ERROR;
-		conn->callback(&reply, conn->context);
-	}
+	if (conn->callback != NULL)
+		conn->callback(NULL, conn->context);
 
 	if (conn->io != NULL)
 		io_remove(&conn->io);
--- a/src/lib-master/master-auth.h	Wed Mar 31 19:18:01 2010 +0300
+++ b/src/lib-master/master-auth.h	Wed Mar 31 19:21:09 2010 +0300
@@ -17,6 +17,9 @@
    to make sure there's space to transfer the command tag  */
 #define MASTER_AUTH_MAX_DATA_SIZE (1024*2)
 
+#define MASTER_AUTH_ERRMSG_INTERNAL_FAILURE \
+	"Internal error occurred. Refer to server log for more information."
+
 enum mail_auth_request_flags {
 	/* Connection has TLS compression enabled */
 	MAIL_AUTH_REQUEST_FLAG_TLS_COMPRESSION	= 0x01
@@ -60,6 +63,7 @@
 	pid_t mail_pid;
 };
 
+/* reply=NULL if the auth lookup was cancelled due to some error */
 typedef void master_auth_callback_t(const struct master_auth_reply *reply,
 				    void *context);
 
--- a/src/lib-master/master-login-auth.c	Wed Mar 31 19:18:01 2010 +0300
+++ b/src/lib-master/master-login-auth.c	Wed Mar 31 19:21:09 2010 +0300
@@ -59,7 +59,8 @@
 	iter = hash_table_iterate_init(auth->requests);
 	while (hash_table_iterate(iter, &key, &value)) {
 		struct master_login_auth_request *request = value;
-		request->callback(NULL, request->context);
+		request->callback(NULL, MASTER_AUTH_ERRMSG_INTERNAL_FAILURE,
+				  request->context);
 		i_free(request);
 	}
 	hash_table_iterate_deinit(&iter);
@@ -135,7 +136,7 @@
 
 	request = master_login_auth_lookup_request(auth, id);
 	if (request != NULL) {
-		request->callback(list + 1, request->context);
+		request->callback(list + 1, NULL, request->context);
 		i_free(request);
 	}
 	return TRUE;
@@ -152,28 +153,39 @@
 	request = master_login_auth_lookup_request(auth, id);
 	if (request != NULL) {
 		i_error("Authenticated user not found from userdb");
-		request->callback(NULL, request->context);
+		request->callback(NULL, MASTER_AUTH_ERRMSG_INTERNAL_FAILURE,
+				  request->context);
 		i_free(request);
 	}
 	return TRUE;
 }
 
 static bool
-master_login_auth_input_fail(struct master_login_auth *auth, const char *args)
+master_login_auth_input_fail(struct master_login_auth *auth,
+			     const char *args_line)
 {
 	struct master_login_auth_request *request;
- 	const char *error;
-	unsigned int id;
+ 	const char *const *args, *error = NULL;
+	unsigned int i, id;
 
-	error = strchr(args, '\t');
-	if (error != NULL)
-		error++;
+	args = t_strsplit(args_line, "\t");
+	if (args[0] == NULL) {
+		i_error("Auth server sent broken FAIL line");
+		return FALSE;
+	}
+	for (i = 1; args[i] != NULL; i++) {
+		if (strncmp(args[i], "reason=", 7) == 0)
+			error = args[i] + 7;
+	}
 
-	id = (unsigned int)strtoul(args, NULL, 10);
+	id = (unsigned int)strtoul(args[0], NULL, 10);
 	request = master_login_auth_lookup_request(auth, id);
 	if (request != NULL) {
-		i_error("Internal auth failure");
-		request->callback(NULL, request->context);
+		if (error != NULL)
+			i_error("Internal auth failure");
+		request->callback(NULL, error != NULL ? error :
+				  MASTER_AUTH_ERRMSG_INTERNAL_FAILURE,
+				  request->context);
 		i_free(request);
 	}
 	return TRUE;
@@ -266,7 +278,8 @@
 	str = t_str_new(128);
 	if (auth->fd == -1) {
 		if (master_login_auth_connect(auth) < 0) {
-			callback(NULL, context);
+			callback(NULL, MASTER_AUTH_ERRMSG_INTERNAL_FAILURE,
+				 context);
 			return;
 		}
 		str_printfa(str, "VERSION\t%u\t%u\n",
--- a/src/lib-master/master-login-auth.h	Wed Mar 31 19:18:01 2010 +0300
+++ b/src/lib-master/master-login-auth.h	Wed Mar 31 19:21:09 2010 +0300
@@ -5,7 +5,7 @@
 
 typedef void
 master_login_auth_request_callback_t(const char *const *auth_args,
-				     void *context);
+				     const char *errormsg, void *context);
 
 struct master_login_auth *master_login_auth_init(const char *auth_socket_path);
 void master_login_auth_deinit(struct master_login_auth **auth);
--- a/src/lib-master/master-login.c	Wed Mar 31 19:18:01 2010 +0300
+++ b/src/lib-master/master-login.c	Wed Mar 31 19:21:09 2010 +0300
@@ -39,6 +39,7 @@
 struct master_login {
 	struct master_service *service;
 	master_login_callback_t *callback;
+	master_login_failure_callback_t *failure_callback;
 	struct master_login_connection *conns;
 	struct master_login_auth *auth;
 	char *postlogin_socket_path;
@@ -52,13 +53,15 @@
 struct master_login *
 master_login_init(struct master_service *service, const char *auth_socket_path,
 		  const char *postlogin_socket_path,
-		  master_login_callback_t *callback)
+		  master_login_callback_t *callback,
+		  master_login_failure_callback_t *failure_callback)
 {
 	struct master_login *login;
 
 	login = i_new(struct master_login, 1);
 	login->service = service;
 	login->callback = callback;
+	login->failure_callback = failure_callback;
 	login->auth = master_login_auth_init(auth_socket_path);
 	login->postlogin_socket_path = i_strdup(postlogin_socket_path);
 
@@ -316,7 +319,8 @@
 }
 
 static void
-master_login_auth_callback(const char *const *auth_args, void *context)
+master_login_auth_callback(const char *const *auth_args, const char *errormsg,
+			   void *context)
 {
 	struct master_login_client *client = context;
 	struct master_auth_reply reply;
@@ -324,14 +328,17 @@
 
 	memset(&reply, 0, sizeof(reply));
 	reply.tag = client->auth_req.tag;
-	reply.status = auth_args != NULL ? MASTER_AUTH_STATUS_OK :
+	reply.status = errormsg == NULL ? MASTER_AUTH_STATUS_OK :
 		MASTER_AUTH_STATUS_INTERNAL_ERROR;
 	reply.mail_pid = getpid();
 	o_stream_send(client->conn->output, &reply, sizeof(reply));
 
-	if (auth_args == NULL || auth_args[0] == NULL) {
-		if (auth_args != NULL)
+	if (errormsg != NULL || auth_args[0] == NULL) {
+		if (auth_args != NULL) {
 			i_error("login client: Username missing from auth reply");
+			errormsg = MASTER_AUTH_ERRMSG_INTERNAL_FAILURE;
+		}
+		client->conn->login->failure_callback(client, errormsg);
 		master_login_client_free(&client);
 		return;
 	}
--- a/src/lib-master/master-login.h	Wed Mar 31 19:18:01 2010 +0300
+++ b/src/lib-master/master-login.h	Wed Mar 31 19:21:09 2010 +0300
@@ -14,11 +14,15 @@
 typedef void
 master_login_callback_t(const struct master_login_client *client,
 			const char *username, const char *const *extra_fields);
+typedef void
+master_login_failure_callback_t(const struct master_login_client *client,
+				const char *errormsg);
 
 struct master_login *
 master_login_init(struct master_service *service, const char *auth_socket_path,
 		  const char *postlogin_socket_path,
-		  master_login_callback_t *callback);
+		  master_login_callback_t *callback,
+		  master_login_failure_callback_t *failure_callback);
 void master_login_deinit(struct master_login **login);
 
 void master_login_add(struct master_login *login, int fd);
--- a/src/lib-storage/mail-storage-service.c	Wed Mar 31 19:18:01 2010 +0300
+++ b/src/lib-storage/mail-storage-service.c	Wed Mar 31 19:21:09 2010 +0300
@@ -36,6 +36,9 @@
 #define MAX_TIME_BACKWARDS_SLEEP 5
 #define MAX_NOWARN_FORWARD_SECS 10
 
+#define ERRSTR_INVALID_USER_SETTINGS \
+	"Invalid user settings. Refer to server log for more information."
+
 struct mail_storage_service_ctx {
 	pool_t pool;
 	struct master_service *service;
@@ -213,9 +216,13 @@
 		}
 		*user = new_username;
 	} else if (ret == 0)
-		*error_r = "unknown user";
-	else
-		*error_r = "userdb lookup failed";
+		*error_r = "Unknown user";
+	else if (**fields_r != NULL) {
+		*error_r = t_strdup(**fields_r);
+		ret = -2;
+	} else {
+		*error_r = MAIL_ERRSTR_CRITICAL_MSG;
+	}
 	return ret;
 }
 
@@ -670,8 +677,11 @@
 					  dyn_parsers);
 		if (master_service_settings_cache_read(ctx->set_cache,
 						       &set_input,
-						       parser_r, error_r) < 0)
+						       parser_r, error_r) < 0) {
+			*error_r = t_strdup_printf(
+				"Error reading configuration: %s", *error_r);
 			return -1;
+		}
 	} else {
 		dyn_parsers_update_parent(pool, &set_input.roots, dyn_parsers);
 		if (master_service_settings_read(ctx->service, &set_input,
@@ -746,7 +756,7 @@
 	const char *username = input->username;
 	const struct setting_parser_info *user_info;
 	const struct mail_user_settings *user_set;
-	const char *const *userdb_fields;
+	const char *const *userdb_fields, *error;
 	struct auth_user_reply reply;
 	const struct setting_parser_context *set_parser;
 	pool_t user_pool, temp_pool;
@@ -756,8 +766,10 @@
 
 	if (mail_storage_service_read_settings(ctx, input, user_pool,
 					       &user_info, &set_parser,
-					       error_r) < 0) {
+					       &error) < 0) {
+		i_error("user %s: %s", username, error);
 		pool_unref(&user_pool);
+		*error_r = MAIL_ERRSTR_CRITICAL_MSG;
 		return -1;
 	}
 	user_set = settings_parser_get_list(set_parser)[1];
@@ -790,7 +802,7 @@
 	user->user_info = user_info;
 
 	user->set_parser = settings_parser_dup(set_parser, user_pool);
-	if (!settings_parser_check(user->set_parser, user_pool, error_r))
+	if (!settings_parser_check(user->set_parser, user_pool, &error))
 		i_unreached();
 
 	user->user_set = settings_parser_get_list(user->set_parser)[1];
@@ -803,8 +815,12 @@
 
 	if (userdb_fields != NULL) {
 		auth_user_fields_parse(userdb_fields, temp_pool, &reply);
-		if (user_reply_handle(user, &reply, error_r) < 0)
+		if (user_reply_handle(user, &reply, &error) < 0) {
+			i_error("user %s: Invalid settings in userdb: %s",
+				username, error);
+			*error_r = ERRSTR_INVALID_USER_SETTINGS;
 			ret = -2;
+		}
 	}
 	pool_unref(&temp_pool);
 
@@ -820,8 +836,7 @@
 
 int mail_storage_service_next(struct mail_storage_service_ctx *ctx,
 			      struct mail_storage_service_user *user,
-			      struct mail_user **mail_user_r,
-			      const char **error_r)
+			      struct mail_user **mail_user_r)
 {
 	const struct mail_user_settings *user_set = user->user_set;
 	const char *home, *chroot, *error;
@@ -839,10 +854,10 @@
 				    user_set->mail_chroot);
 
 	if (*home != '/' && *home != '\0') {
-		*error_r = t_strdup_printf("user %s: "
+		i_error("user %s: "
 			"Relative home directory paths not supported: %s",
 			user->input.username, home);
-		return -1;
+		return -2;
 	}
 
 	if ((ctx->flags & MAIL_STORAGE_SERVICE_FLAG_NO_LOG_INIT) == 0)
@@ -852,8 +867,8 @@
 		if (service_drop_privileges(user_set, user->system_groups_user,
 					    home, chroot, disallow_root,
 					    temp_priv_drop, FALSE, &error) < 0) {
-			*error_r = t_strdup_printf(
-				"Couldn't drop privileges: %s", error);
+			i_error("user %s: Couldn't drop privileges: %s",
+				user->input.username, error);
 			return -1;
 		}
 		if (!temp_priv_drop ||
@@ -884,8 +899,11 @@
 			t_strconcat(chroot, "/", home, NULL));
 	}
 	if (mail_storage_service_init_post(ctx, user, home,
-					   mail_user_r, error_r) < 0)
-		return -1;
+					   mail_user_r, &error) < 0) {
+		i_error("user %s: Initialization failed: %s",
+			user->input.username, error);
+		return -2;
+	}
 	return 0;
 }
 
@@ -913,18 +931,18 @@
 				     const char **error_r)
 {
 	struct mail_storage_service_user *user;
-	const char *error;
 	int ret;
 
-	ret = mail_storage_service_lookup(ctx, input, &user, &error);
-	if (ret <= 0) {
-		*error_r = t_strdup_printf("User lookup failed: %s", error);
+	ret = mail_storage_service_lookup(ctx, input, &user, error_r);
+	if (ret <= 0)
 		return ret;
-	}
-	if (mail_storage_service_next(ctx, user, mail_user_r, &error) < 0) {
+
+	ret = mail_storage_service_next(ctx, user, mail_user_r);
+	if (ret < 0) {
 		mail_storage_service_user_free(&user);
-		*error_r = t_strdup_printf("User init failed: %s", error);
-		return -2;
+		*error_r = ret == -2 ? ERRSTR_INVALID_USER_SETTINGS :
+			MAIL_ERRSTR_CRITICAL_MSG;
+		return ret;
 	}
 	*user_r = user;
 	return 1;
--- a/src/lib-storage/mail-storage-service.h	Wed Mar 31 19:18:01 2010 +0300
+++ b/src/lib-storage/mail-storage-service.h	Wed Mar 31 19:21:09 2010 +0300
@@ -61,20 +61,19 @@
 void mail_storage_service_init_settings(struct mail_storage_service_ctx *ctx,
 					const struct mail_storage_service_input *input);
 /* Returns 1 if ok, 0 if user wasn't found, -1 if fatal error,
-   -2 if user had invalid settings. */
+   -2 if error is user-specific (e.g. invalid settings).
+   Error can be safely shown to untrusted users. */
 int mail_storage_service_lookup(struct mail_storage_service_ctx *ctx,
 				const struct mail_storage_service_input *input,
 				struct mail_storage_service_user **user_r,
 				const char **error_r);
-/* Returns 0 if ok, -1 if user had invalid settings. */
+/* Returns 0 if ok, -1 if fatal error, -2 if error is user-specific. */
 int mail_storage_service_next(struct mail_storage_service_ctx *ctx,
 			      struct mail_storage_service_user *user,
-			      struct mail_user **mail_user_r,
-			      const char **error_r);
+			      struct mail_user **mail_user_r);
 void mail_storage_service_restrict_setenv(struct mail_storage_service_ctx *ctx,
 					  struct mail_storage_service_user *user);
-/* Combine lookup() and next() into one call. If either one fails with
-   "invalid settings", this function returns -2. */
+/* Combine lookup() and next() into one call. */
 int mail_storage_service_lookup_next(struct mail_storage_service_ctx *ctx,
 				     const struct mail_storage_service_input *input,
 				     struct mail_storage_service_user **user_r,
--- a/src/lmtp/commands.c	Wed Mar 31 19:18:01 2010 +0300
+++ b/src/lmtp/commands.c	Wed Mar 31 19:21:09 2010 +0300
@@ -28,7 +28,9 @@
 #include <stdlib.h>
 
 #define ERRSTR_TEMP_MAILBOX_FAIL "451 4.3.0 <%s> Temporary internal error"
-#define ERRSTR_TEMP_USERDB_FAIL "451 4.3.0 <%s> Temporary user lookup failure"
+#define ERRSTR_TEMP_USERDB_FAIL_PREFIX "451 4.3.0 <%s> "
+#define ERRSTR_TEMP_USERDB_FAIL \
+	ERRSTR_TEMP_USERDB_FAIL_PREFIX "Temporary user lookup failure"
 
 #define LMTP_PROXY_DEFAULT_TIMEOUT_MSECS (1000*30)
 
@@ -199,7 +201,7 @@
 	struct lmtp_proxy_settings set;
 	struct auth_user_info info;
 	struct mail_storage_service_input input;
-	const char *args, *const *fields, *orig_username = username;
+	const char *args, *const *fields, *errstr, *orig_username = username;
 	pool_t pool;
 	int ret;
 
@@ -219,10 +221,11 @@
 	ret = auth_master_pass_lookup(auth_conn, username, &info,
 				      pool, &fields);
 	if (ret <= 0) {
+		errstr = ret < 0 && fields[0] != NULL ? t_strdup(fields[0]) :
+			t_strdup_printf(ERRSTR_TEMP_USERDB_FAIL, address);
 		pool_unref(&pool);
 		if (ret < 0) {
-			client_send_line(client, ERRSTR_TEMP_USERDB_FAIL,
-					 address);
+			client_send_line(client, "%s", errstr);
 			return TRUE;
 		} else {
 			/* user not found from passdb. try userdb also. */
@@ -332,7 +335,7 @@
 {
 	struct mail_recipient rcpt;
 	struct mail_storage_service_input input;
-	const char *address, *username, *detail;
+	const char *address, *username, *detail, *prefix;
 	const char *error = NULL, *arg, *const *argv;
 	unsigned int len;
 	int ret = 0;
@@ -384,8 +387,9 @@
 					  &rcpt.service_user, &error);
 
 	if (ret < 0) {
-		i_error("User lookup failed: %s", error);
-		client_send_line(client, ERRSTR_TEMP_USERDB_FAIL, username);
+		prefix = t_strdup_printf(ERRSTR_TEMP_USERDB_FAIL_PREFIX,
+					 username);
+		client_send_line(client, "%s%s", prefix, error);
 		return 0;
 	}
 	if (ret == 0) {
@@ -449,9 +453,7 @@
 	i_set_failure_prefix(t_strdup_printf("lmtp(%s, %s): ",
 					     my_pid, username));
 	if (mail_storage_service_next(storage_service, rcpt->service_user,
-				      &client->state.dest_user,
-				      &error) < 0) {
-		i_error("%s", error);
+				      &client->state.dest_user) < 0) {
 		client_send_line(client, ERRSTR_TEMP_MAILBOX_FAIL,
 				 rcpt->address);
 		return -1;
--- a/src/login-common/client-common-auth.c	Wed Mar 31 19:18:01 2010 +0300
+++ b/src/login-common/client-common-auth.c	Wed Mar 31 19:21:09 2010 +0300
@@ -378,6 +378,7 @@
 
 	i_assert(!client->destroyed ||
 		 sasl_reply == SASL_SERVER_REPLY_AUTH_ABORTED ||
+		 sasl_reply == SASL_SERVER_REPLY_MASTER_ABORTED ||
 		 sasl_reply == SASL_SERVER_REPLY_MASTER_FAILED);
 
 	switch (sasl_reply) {
@@ -428,6 +429,10 @@
 			client_destroy_success(client, data);
 		}
 		break;
+	case SASL_SERVER_REPLY_MASTER_ABORTED:
+		/* mail process already sent the error message to client */
+		client_destroy_success(client, data);
+		break;
 	case SASL_SERVER_REPLY_CONTINUE:
 		client->v.auth_send_challenge(client, data);
 
--- a/src/login-common/sasl-server.c	Wed Mar 31 19:18:01 2010 +0300
+++ b/src/login-common/sasl-server.c	Wed Mar 31 19:21:09 2010 +0300
@@ -94,12 +94,15 @@
 
 	client->master_tag = 0;
 	client->authenticating = FALSE;
-	switch (reply->status) {
-	case MASTER_AUTH_STATUS_OK:
-		sasl_reply = SASL_SERVER_REPLY_SUCCESS;
-		break;
-	case MASTER_AUTH_STATUS_INTERNAL_ERROR:
-		break;
+	if (reply != NULL) {
+		switch (reply->status) {
+		case MASTER_AUTH_STATUS_OK:
+			sasl_reply = SASL_SERVER_REPLY_SUCCESS;
+			break;
+		case MASTER_AUTH_STATUS_INTERNAL_ERROR:
+			sasl_reply = SASL_SERVER_REPLY_MASTER_ABORTED;
+			break;
+		}
 	}
 	client->mail_pid = reply->mail_pid;
 	call_client_callback(client, sasl_reply, data, NULL);
--- a/src/login-common/sasl-server.h	Wed Mar 31 19:18:01 2010 +0300
+++ b/src/login-common/sasl-server.h	Wed Mar 31 19:21:09 2010 +0300
@@ -8,6 +8,7 @@
 	SASL_SERVER_REPLY_AUTH_FAILED,
 	SASL_SERVER_REPLY_AUTH_ABORTED,
 	SASL_SERVER_REPLY_MASTER_FAILED,
+	SASL_SERVER_REPLY_MASTER_ABORTED,
 	SASL_SERVER_REPLY_CONTINUE
 };
 
--- a/src/pop3/main.c	Wed Mar 31 19:18:01 2010 +0300
+++ b/src/pop3/main.c	Wed Mar 31 19:21:09 2010 +0300
@@ -161,6 +161,15 @@
 	}
 }
 
+static void login_client_failed(const struct master_login_client *client,
+				const char *errormsg)
+{
+	const char *msg;
+
+	msg = t_strdup_printf("-ERR [IN-USE] %s\r\n", errormsg);
+	(void)write(client->fd, msg, strlen(msg));
+}
+
 static void client_connected(const struct master_service_connection *conn)
 {
 	if (master_login == NULL) {
@@ -231,7 +240,8 @@
 	} else {
 		master_login = master_login_init(master_service, "auth-master",
 						 postlogin_socket_path,
-						 login_client_connected);
+						 login_client_connected,
+						 login_client_failed);
 		io_loop_set_running(current_ioloop);
 	}