changeset 14576:fbb1ecb9b888

Merged changes from v2.1 tree.
author Timo Sirainen <tss@iki.fi>
date Sun, 20 May 2012 03:25:04 +0300
parents 80688ab1ea3d (diff) dbe6d05fd595 (current diff)
children a47c95872745
files Makefile.am configure.in dovecot-config.in.in src/auth/auth-request.c src/auth/auth-request.h src/imap-login/client.c src/imap-login/imap-proxy.c src/imap/cmd-fetch.c src/imap/imap-client.c src/imap/imap-client.h src/lib-index/mail-index-map.c src/lib-index/mail-index-modseq.c src/lib-mail/message-parser.c src/lib-storage/index/pop3c/pop3c-client.c src/lib-storage/mail-storage-private.h src/lib-storage/mail-storage-service.c src/lib-storage/mail-storage.c src/lib-storage/mail-storage.h src/lib/network.c src/lmtp/commands.c src/login-common/Makefile.am src/login-common/client-common-auth.c src/login-common/client-common.c src/login-common/client-common.h src/login-common/login-proxy.c src/login-common/login-proxy.h src/pop3-login/client.c src/pop3-login/pop3-proxy.c src/pop3/main.c src/pop3/pop3-client.c src/pop3/pop3-client.h
diffstat 79 files changed, 1627 insertions(+), 1184 deletions(-) [+]
line wrap: on
line diff
--- a/configure.in	Sun May 20 03:08:01 2012 +0300
+++ b/configure.in	Sun May 20 03:25:04 2012 +0300
@@ -1,5 +1,5 @@
 AC_PREREQ([2.59])
-AC_INIT([Dovecot],[2.1.6],[dovecot@dovecot.org])
+AC_INIT([Dovecot],[2.2.UNSTABLE],[dovecot@dovecot.org])
 AC_CONFIG_SRCDIR([src])
 
 AM_INIT_AUTOMAKE([foreign])
@@ -292,7 +292,7 @@
   sys/quota.h sys/fs/ufs_quota.h ufs/ufs/quota.h jfs/quota.h sys/fs/quota_common.h \
   mntent.h sys/mnttab.h sys/event.h sys/time.h sys/mkdev.h linux/dqblk_xfs.h \
   xfs/xqm.h execinfo.h ucontext.h malloc_np.h sys/utsname.h sys/vmount.h \
-  sys/utsname.h glob.h linux/falloc.h ucred.h)
+  sys/utsname.h glob.h linux/falloc.h ucred.h sys/ucred.h)
 
 dnl * clang check
 have_clang=no
@@ -408,6 +408,8 @@
 	       walkcontext dirfd clearenv malloc_usable_size glob fallocate \
 	       posix_fadvise getpeereid getpeerucred)
 
+AC_CHECK_TYPES([struct sockpeercred])
+
 AC_CHECK_LIB(rt, clock_gettime, [
   AC_DEFINE(HAVE_CLOCK_GETTIME,, Define if you have the clock_gettime function)
   LIBS="$LIBS -lrt"
@@ -2857,3 +2859,6 @@
 if test "$not_fts" != ""; then
   echo "                 :$not_fts"
 fi
+
+echo
+echo "NOTE: This is the UNSTABLE development branch of Dovecot v2.2."
--- a/doc/man/Makefile.am	Sun May 20 03:08:01 2012 +0300
+++ b/doc/man/Makefile.am	Sun May 20 03:25:04 2012 +0300
@@ -1,5 +1,7 @@
 pkgsysconfdir = $(sysconfdir)/dovecot
 
+SUFFIXES = .1.in .1
+
 dist_man1_MANS = \
 	deliver.1 \
 	doveadm-config.1 \
@@ -81,114 +83,15 @@
 
 CLEANFILES = $(nodist_man1_MANS)
 
-doveadm.1: $(srcdir)/doveadm.1.in $(man_includefiles) Makefile
-	$(SHELL) $(srcdir)/sed.sh $(srcdir) $(rundir) $(pkgsysconfdir) \
-		< $(srcdir)/doveadm.1.in > doveadm.1
-
-doveadm-altmove.1: $(srcdir)/doveadm-altmove.1.in $(man_includefiles) Makefile
-	$(SHELL) $(srcdir)/sed.sh $(srcdir) $(rundir) $(pkgsysconfdir) \
-		< $(srcdir)/doveadm-altmove.1.in > doveadm-altmove.1
-
-doveadm-auth.1: $(srcdir)/doveadm-auth.1.in $(man_includefiles) Makefile
-	$(SHELL) $(srcdir)/sed.sh $(srcdir) $(rundir) $(pkgsysconfdir) \
-		< $(srcdir)/doveadm-auth.1.in > doveadm-auth.1
-
-doveadm-director.1: $(srcdir)/doveadm-director.1.in $(man_includefiles) Makefile
+.1.in.1: $(man_includefiles) Makefile
 	$(SHELL) $(srcdir)/sed.sh $(srcdir) $(rundir) $(pkgsysconfdir) \
-		< $(srcdir)/doveadm-director.1.in > doveadm-director.1
-
-doveadm-dump.1: $(srcdir)/doveadm-dump.1.in $(man_includefiles) Makefile
-	$(SHELL) $(srcdir)/sed.sh $(srcdir) $(rundir) $(pkgsysconfdir) \
-		< $(srcdir)/doveadm-dump.1.in > doveadm-dump.1
-
-doveadm-expunge.1: $(srcdir)/doveadm-expunge.1.in $(man_includefiles) Makefile
-	$(SHELL) $(srcdir)/sed.sh $(srcdir) $(rundir) $(pkgsysconfdir) \
-		< $(srcdir)/doveadm-expunge.1.in > doveadm-expunge.1
-
-doveadm-fetch.1: $(srcdir)/doveadm-fetch.1.in $(man_includefiles) Makefile
-	$(SHELL) $(srcdir)/sed.sh $(srcdir) $(rundir) $(pkgsysconfdir) \
-		< $(srcdir)/doveadm-fetch.1.in > doveadm-fetch.1
-
-doveadm-import.1: $(srcdir)/doveadm-import.1.in $(man_includefiles) Makefile
-	$(SHELL) $(srcdir)/sed.sh $(srcdir) $(rundir) $(pkgsysconfdir) \
-		< $(srcdir)/doveadm-import.1.in > doveadm-import.1
+		< $(srcdir)/$< > $@
 
 doveadm-instance.1: $(srcdir)/doveadm-instance.1.in $(man_includefiles) Makefile
 	$(SHELL) $(srcdir)/sed.sh $(srcdir) $(rundir) $(pkgsysconfdir) \
 		< $(srcdir)/doveadm-instance.1.in > doveadm-instance.1
 
-doveadm-index.1: $(srcdir)/doveadm-index.1.in $(man_includefiles) Makefile
-	$(SHELL) $(srcdir)/sed.sh $(srcdir) $(rundir) $(pkgsysconfdir) \
-		< $(srcdir)/doveadm-index.1.in > doveadm-index.1
-
-doveadm-force-resync.1: $(srcdir)/doveadm-force-resync.1.in $(man_includefiles) Makefile
-	$(SHELL) $(srcdir)/sed.sh $(srcdir) $(rundir) $(pkgsysconfdir) \
-		< $(srcdir)/doveadm-force-resync.1.in > doveadm-force-resync.1
-
-doveadm-help.1: $(srcdir)/doveadm-help.1.in $(man_includefiles) Makefile
-	$(SHELL) $(srcdir)/sed.sh $(srcdir) $(rundir) $(pkgsysconfdir) \
-		< $(srcdir)/doveadm-help.1.in > doveadm-help.1
-
-doveadm-kick.1: $(srcdir)/doveadm-kick.1.in $(man_includefiles) Makefile
-	$(SHELL) $(srcdir)/sed.sh $(srcdir) $(rundir) $(pkgsysconfdir) \
-		< $(srcdir)/doveadm-kick.1.in > doveadm-kick.1
-
-doveadm-log.1: $(srcdir)/doveadm-log.1.in $(man_includefiles) Makefile
-	$(SHELL) $(srcdir)/sed.sh $(srcdir) $(rundir) $(pkgsysconfdir) \
-		< $(srcdir)/doveadm-log.1.in > doveadm-log.1
-
-doveadm-mailbox.1: $(srcdir)/doveadm-mailbox.1.in $(man_includefiles) Makefile
-	$(SHELL) $(srcdir)/sed.sh $(srcdir) $(rundir) $(pkgsysconfdir) \
-		< $(srcdir)/doveadm-mailbox.1.in > doveadm-mailbox.1
 
 doveadm-mount.1: $(srcdir)/doveadm-mount.1.in $(man_includefiles) Makefile
 	$(SHELL) $(srcdir)/sed.sh $(srcdir) $(rundir) $(pkgsysconfdir) \
-		< $(srcdir)/doveadm-mount.1.in > doveadm-mount.1
-
-doveadm-move.1: $(srcdir)/doveadm-move.1.in $(man_includefiles) Makefile
-	$(SHELL) $(srcdir)/sed.sh $(srcdir) $(rundir) $(pkgsysconfdir) \
-		< $(srcdir)/doveadm-move.1.in > doveadm-move.1
-
-doveadm-penalty.1: $(srcdir)/doveadm-penalty.1.in $(man_includefiles) Makefile
-	$(SHELL) $(srcdir)/sed.sh $(srcdir) $(rundir) $(pkgsysconfdir) \
-		< $(srcdir)/doveadm-penalty.1.in > doveadm-penalty.1
-
-doveadm-purge.1: $(srcdir)/doveadm-purge.1.in $(man_includefiles) Makefile
-	$(SHELL) $(srcdir)/sed.sh $(srcdir) $(rundir) $(pkgsysconfdir) \
-		< $(srcdir)/doveadm-purge.1.in > doveadm-purge.1
-
-doveadm-pw.1: $(srcdir)/doveadm-pw.1.in $(man_includefiles) Makefile
-	$(SHELL) $(srcdir)/sed.sh $(srcdir) $(rundir) $(pkgsysconfdir) \
-		< $(srcdir)/doveadm-pw.1.in > doveadm-pw.1
-
-doveadm-quota.1: $(srcdir)/doveadm-quota.1.in $(man_includefiles) Makefile
-	$(SHELL) $(srcdir)/sed.sh $(srcdir) $(rundir) $(pkgsysconfdir) \
-		< $(srcdir)/doveadm-quota.1.in > doveadm-quota.1
-
-doveadm-search.1: $(srcdir)/doveadm-search.1.in $(man_includefiles) Makefile
-	$(SHELL) $(srcdir)/sed.sh $(srcdir) $(rundir) $(pkgsysconfdir) \
-		< $(srcdir)/doveadm-search.1.in > doveadm-search.1
-
-doveadm-user.1: $(srcdir)/doveadm-user.1.in $(man_includefiles) Makefile
-	$(SHELL) $(srcdir)/sed.sh $(srcdir) $(rundir) $(pkgsysconfdir) \
-		< $(srcdir)/doveadm-user.1.in > doveadm-user.1
-
-doveadm-who.1: $(srcdir)/doveadm-who.1.in $(man_includefiles) Makefile
-	$(SHELL) $(srcdir)/sed.sh $(srcdir) $(rundir) $(pkgsysconfdir) \
-		< $(srcdir)/doveadm-who.1.in > doveadm-who.1
-
-doveconf.1: $(srcdir)/doveconf.1.in $(man_includefiles) Makefile
-	$(SHELL) $(srcdir)/sed.sh $(srcdir) $(rundir) $(pkgsysconfdir) \
-		< $(srcdir)/doveconf.1.in > doveconf.1
-
-dovecot.1: $(srcdir)/dovecot.1.in $(man_includefiles) Makefile
-	$(SHELL) $(srcdir)/sed.sh $(srcdir) $(rundir) $(pkgsysconfdir) \
-		< $(srcdir)/dovecot.1.in > dovecot.1
-
-dovecot-lda.1: $(srcdir)/dovecot-lda.1.in $(man_includefiles) Makefile
-	$(SHELL) $(srcdir)/sed.sh $(srcdir) $(rundir) $(pkgsysconfdir) \
-		< $(srcdir)/dovecot-lda.1.in > dovecot-lda.1
-
-dsync.1: $(srcdir)/dsync.1.in $(man_includefiles) Makefile
-	$(SHELL) $(srcdir)/sed.sh $(srcdir) $(rundir) $(pkgsysconfdir) \
-		< $(srcdir)/dsync.1.in > dsync.1
+		< $(srcdir)/doveadm-mount.1.in > doveadm-mount.1
\ No newline at end of file
--- a/dovecot-config.in.in	Sun May 20 03:08:01 2012 +0300
+++ b/dovecot-config.in.in	Sun May 20 03:25:04 2012 +0300
@@ -15,11 +15,10 @@
 LIBDOVECOT_LDA_DEPS="@LIBDOVECOT_LDA@"
 LIBDOVECOT_STORAGE_DEPS="@LIBDOVECOT_STORAGE_DEPS@"
 
-LIBDOVECOT_INCLUDE="-I$(incdir) -I$(incdir)/src/lib -I$(incdir)/src/lib-dict -I$(incdir)/src/lib-dns -I$(incdir)/src/lib-mail -I$(incdir)/src/lib-imap -I$(incdir)/src/lib-fs -I$(incdir)/src/lib-charset"
+LIBDOVECOT_INCLUDE="-I$(incdir) -I$(incdir)/src/lib -I$(incdir)/src/lib-dict -I$(incdir)/src/lib-dns -I$(incdir)/src/lib-mail -I$(incdir)/src/lib-imap -I$(incdir)/src/lib-fs -I$(incdir)/src/lib-charset -I$(incdir)/src/lib-auth -I$(incdir)/src/lib-master -I$(incdir)/src/lib-settings"
 LIBDOVECOT_LDA_INCLUDE="-I$(incdir)/src/lib-lda -I$(incdir)/src/lda"
-LIBDOVECOT_SERVICE_INCLUDE="-I$(incdir)/src/lib-master -I$(incdir)/src/lib-settings"
 LIBDOVECOT_STORAGE_INCLUDE="-I$(incdir)/src/lib-index -I$(incdir)/src/lib-storage -I$(incdir)/src/lib-storage/index -I$(incdir)/src/lib-storage/index/raw"
-LIBDOVECOT_LOGIN_INCLUDE="-I$(incdir)/src/lib-auth -I$(incdir)/src/login-common"
+LIBDOVECOT_LOGIN_INCLUDE="-I$(incdir)/src/login-common"
 LIBDOVECOT_IMAP_INCLUDE="-I$(incdir)/src/imap"
 LIBDOVECOT_CONFIG_INCLUDE="-I$(incdir)/src/config"
 
--- a/src/auth/auth-request.c	Sun May 20 03:08:01 2012 +0300
+++ b/src/auth/auth-request.c	Sun May 20 03:25:04 2012 +0300
@@ -33,6 +33,12 @@
 #define AUTH_DNS_WARN_MSECS 500
 #define CACHED_PASSWORD_SCHEME "SHA1"
 
+struct auth_request_proxy_dns_lookup_ctx {
+	struct auth_request *request;
+	auth_request_proxy_cb_t *callback;
+	struct dns_lookup *dns_lookup;
+};
+
 unsigned int auth_request_state_count[AUTH_REQUEST_STATE_MAX];
 
 static void get_log_prefix(string_t *str, struct auth_request *auth_request,
@@ -167,6 +173,8 @@
 			    strlen(request->mech_password));
 	}
 
+	if (request->dns_lookup_ctx != NULL)
+		dns_lookup_abort(&request->dns_lookup_ctx->dns_lookup);
 	if (request->to_abort != NULL)
 		timeout_remove(&request->to_abort);
 	if (request->to_penalty != NULL)
@@ -1450,23 +1458,28 @@
 
 static bool auth_request_proxy_is_self(struct auth_request *request)
 {
-	const char *const *tmp, *port = NULL, *destuser = NULL;
+	const char *const *tmp, *port = NULL;
 
 	if (!request->proxy_host_is_self)
 		return FALSE;
 
+	/* check if the port is the same */
 	tmp = auth_stream_split(request->extra_fields);
 	for (; *tmp != NULL; tmp++) {
 		if (strncmp(*tmp, "port=", 5) == 0)
 			port = *tmp + 5;
-		else if (strncmp(*tmp, "destuser=", 9) == 0)
-			destuser = *tmp + 9;
 	}
 
 	if (port != NULL && !str_uint_equals(port, request->local_port))
 		return FALSE;
-	return destuser == NULL ||
-		strcmp(destuser, request->original_username) == 0;
+	/* don't check destuser. in some systems destuser is intentionally
+	   changed to proxied connections, but that shouldn't affect the
+	   proxying decision.
+
+	   it's unlikely any systems would actually want to proxy a connection
+	   to itself only to change the username, since it can already be done
+	   without proxying by changing the "user" field. */
+	return TRUE;
 }
 
 static bool
@@ -1510,20 +1523,17 @@
 	}
 }
 
-struct auth_request_proxy_dns_lookup_ctx {
-	struct auth_request *request;
-	auth_request_proxy_cb_t *callback;
-};
-
 static void
 auth_request_proxy_dns_callback(const struct dns_lookup_result *result,
-				void *context)
+				struct auth_request_proxy_dns_lookup_ctx *ctx)
 {
-	struct auth_request_proxy_dns_lookup_ctx *ctx = context;
 	struct auth_request *request = ctx->request;
 	const char *host;
 	unsigned int i;
 
+	request->dns_lookup_ctx = NULL;
+	ctx->dns_lookup = NULL;
+
 	host = auth_stream_reply_find(request->extra_fields, "host");
 	i_assert(host != NULL);
 
@@ -1552,7 +1562,6 @@
 	}
 	if (ctx->callback != NULL)
 		ctx->callback(result->ret == 0, request);
-	i_free(ctx);
 }
 
 static int auth_request_proxy_host_lookup(struct auth_request *request,
@@ -1587,10 +1596,11 @@
 		}
 	}
 
-	ctx = i_new(struct auth_request_proxy_dns_lookup_ctx, 1);
+	ctx = p_new(request->pool, struct auth_request_proxy_dns_lookup_ctx, 1);
 	ctx->request = request;
 
-	if (dns_lookup(host, &dns_set, auth_request_proxy_dns_callback, ctx) < 0) {
+	if (dns_lookup(host, &dns_set, auth_request_proxy_dns_callback, ctx,
+		       &ctx->dns_lookup) < 0) {
 		/* failed early */
 		request->internal_failure = TRUE;
 		auth_request_proxy_finish_failure(request);
--- a/src/auth/auth-request.h	Sun May 20 03:08:01 2012 +0300
+++ b/src/auth/auth-request.h	Sun May 20 03:25:04 2012 +0300
@@ -56,6 +56,7 @@
 	struct auth_stream_reply *extra_cache_fields;
 	/* the whole userdb result reply */
 	struct auth_stream_reply *userdb_reply;
+	struct auth_request_proxy_dns_lookup_ctx *dns_lookup_ctx;
 	/* Result of passdb lookup */
 	enum passdb_result passdb_result;
 
--- a/src/dns/dns-client-settings.c	Sun May 20 03:08:01 2012 +0300
+++ b/src/dns/dns-client-settings.c	Sun May 20 03:25:04 2012 +0300
@@ -9,12 +9,10 @@
 
 /* <settings checks> */
 static struct file_listener_settings dns_client_unix_listeners_array[] = {
-	{ "dns-client", 0666, "", "" },
-	{ "login/dns-client", 0666, "", "" }
+	{ "dns-client", 0666, "", "" }
 };
 static struct file_listener_settings *dns_client_unix_listeners[] = {
-	&dns_client_unix_listeners_array[0],
-	&dns_client_unix_listeners_array[1]
+	&dns_client_unix_listeners_array[0]
 };
 static buffer_t dns_client_unix_listeners_buf = {
 	dns_client_unix_listeners, sizeof(dns_client_unix_listeners), { 0, }
--- a/src/imap-login/client-authenticate.c	Sun May 20 03:08:01 2012 +0300
+++ b/src/imap-login/client-authenticate.c	Sun May 20 03:25:04 2012 +0300
@@ -3,14 +3,12 @@
 #include "login-common.h"
 #include "base64.h"
 #include "buffer.h"
-#include "hostpid.h"
 #include "ioloop.h"
 #include "istream.h"
 #include "ostream.h"
 #include "safe-memset.h"
 #include "str.h"
 #include "str-sanitize.h"
-#include "time-util.h"
 #include "imap-resp-code.h"
 #include "imap-parser.h"
 #include "auth-client.h"
@@ -33,14 +31,19 @@
 	}
 }
 
-bool imap_client_auth_handle_reply(struct client *client,
-				   const struct client_auth_reply *reply)
+void imap_client_auth_result(struct client *client,
+			     enum client_auth_result result,
+			     const struct client_auth_reply *reply,
+			     const char *text)
 {
-	struct imap_client *imap_client = (struct imap_client *)client;
-	string_t *str;
-	const char *timestamp, *msg;
+	string_t *referral;
 
-	if (reply->host != NULL) {
+	switch (result) {
+	case CLIENT_AUTH_RESULT_SUCCESS:
+		/* nothing to be done for IMAP */
+		break;
+	case CLIENT_AUTH_RESULT_REFERRAL_SUCCESS:
+	case CLIENT_AUTH_RESULT_REFERRAL_NOLOGIN:
 		/* IMAP referral
 
 		   [nologin] referral host=.. [port=..] [destuser=..]
@@ -50,55 +53,46 @@
 		   OK [...] Logged in, but you should use this server instead.
 		   .. [REFERRAL ..] (Reason from auth server)
 		*/
-		str = t_str_new(128);
-		str_append(str, imap_client->cmd_tag);
-		str_append_c(str, ' ');
-		str_append(str, reply->nologin ? "NO " : "OK ");
-		str_printfa(str, "[REFERRAL imap://%s;AUTH=%s@%s",
+		referral = t_str_new(128);
+		str_printfa(referral, "REFERRAL imap://%s;AUTH=%s@%s",
 			    reply->destuser, client->auth_mech_name,
 			    reply->host);
 		if (reply->port != 143)
-			str_printfa(str, ":%u", reply->port);
-		str_append(str, "/] ");
-		if (reply->reason != NULL)
-			str_append(str, reply->reason);
-		else if (reply->nologin)
-			str_append(str, "Try this server instead.");
-		else {
-			str_append(str, "Logged in, but you should use "
-				   "this server instead.");
-		}
-		str_append(str, "\r\n");
-		client_send_raw(client, str_c(str));
-		if (!reply->nologin) {
-			client_destroy_success(client, "Login with referral");
-			return TRUE;
+			str_printfa(referral, ":%u", reply->port);
+		str_append(referral, "/");
+
+		if (result == CLIENT_AUTH_RESULT_REFERRAL_SUCCESS) {
+			client_send_reply_code(client, IMAP_CMD_REPLY_OK,
+					       str_c(referral), text);
+		} else {
+			client_send_reply_code(client, IMAP_CMD_REPLY_NO,
+					       str_c(referral), text);
 		}
-	} else if (!reply->nologin) {
-		/* normal login/failure */
-		return FALSE;
-	} else if (reply->reason != NULL) {
-		client_send_line(client, CLIENT_CMD_REPLY_AUTH_FAIL_REASON,
-				 reply->reason);
-	} else if (reply->temp) {
-		timestamp = t_strflocaltime("%Y-%m-%d %H:%M:%S", ioloop_time);
-		msg = t_strdup_printf(AUTH_TEMP_FAILED_MSG" [%s:%s]",
-				      my_hostname, timestamp);
-		client_send_line(client,
-				 CLIENT_CMD_REPLY_AUTH_FAIL_TEMP, msg);
-	} else if (reply->authz_failure) {
-		client_send_line(client, CLIENT_CMD_REPLY_AUTHZ_FAILED,
-				 "Authorization failed");
-	} else {
-		client_send_line(client, CLIENT_CMD_REPLY_AUTH_FAILED,
-				 AUTH_FAILED_MSG);
+		break;
+	case CLIENT_AUTH_RESULT_ABORTED:
+		client_send_reply(client, IMAP_CMD_REPLY_BAD, text);
+		break;
+	case CLIENT_AUTH_RESULT_AUTHFAILED_REASON:
+		client_send_reply_code(client, IMAP_CMD_REPLY_NO,
+				       "ALERT", text);
+		break;
+	case CLIENT_AUTH_RESULT_AUTHZFAILED:
+		client_send_reply_code(client, IMAP_CMD_REPLY_NO,
+				       IMAP_RESP_CODE_AUTHZFAILED, text);
+		break;
+	case CLIENT_AUTH_RESULT_TEMPFAIL:
+		client_send_reply_code(client, IMAP_CMD_REPLY_NO,
+				       IMAP_RESP_CODE_UNAVAILABLE, text);
+		break;
+	case CLIENT_AUTH_RESULT_SSL_REQUIRED:
+		client_send_reply_code(client, IMAP_CMD_REPLY_NO,
+				       IMAP_RESP_CODE_PRIVACYREQUIRED, text);
+		break;
+	case CLIENT_AUTH_RESULT_AUTHFAILED:
+		client_send_reply_code(client, IMAP_CMD_REPLY_NO,
+				       IMAP_RESP_CODE_AUTHFAILED, text);
+		break;
 	}
-
-	i_assert(reply->nologin);
-
-	if (!client->destroyed)
-		client_auth_failed(client);
-	return TRUE;
 }
 
 static int
--- a/src/imap-login/client-authenticate.h	Sun May 20 03:08:01 2012 +0300
+++ b/src/imap-login/client-authenticate.h	Sun May 20 03:25:04 2012 +0300
@@ -5,8 +5,10 @@
 
 void client_authenticate_get_capabilities(struct client *client, string_t *str);
 
-bool imap_client_auth_handle_reply(struct client *client,
-				   const struct client_auth_reply *reply);
+void imap_client_auth_result(struct client *client,
+			     enum client_auth_result result,
+			     const struct client_auth_reply *reply,
+			     const char *text);
 
 int cmd_login(struct imap_client *client, const struct imap_arg *args);
 int cmd_authenticate(struct imap_client *imap_client, bool *parsed_r);
--- a/src/imap-login/client.c	Sun May 20 03:08:01 2012 +0300
+++ b/src/imap-login/client.c	Sun May 20 03:25:04 2012 +0300
@@ -82,8 +82,8 @@
 		imap_client->client_ignores_capability_resp_code = TRUE;
 	client_send_raw(client, t_strconcat(
 		"* CAPABILITY ", get_capability(client), "\r\n", NULL));
-	client_send_line(client, CLIENT_CMD_REPLY_OK,
-			 "Pre-login capabilities listed, post-login capabilities have more.");
+	client_send_reply(client, IMAP_CMD_REPLY_OK,
+		"Pre-login capabilities listed, post-login capabilities have more.");
 	return 1;
 }
 
@@ -94,6 +94,16 @@
 }
 
 static void
+imap_client_notify_starttls(struct client *client,
+			    bool success, const char *text)
+{
+	if (success)
+		client_send_reply(client, IMAP_CMD_REPLY_OK, text);
+	else
+		client_send_reply(client, IMAP_CMD_REPLY_BAD, text);
+}
+
+static void
 client_update_info(struct imap_client *client, const struct imap_arg *args)
 {
 	const char *key, *value;
@@ -111,6 +121,8 @@
 			(void)net_addr2ip(value, &client->common.local_ip);
 		else if (strcasecmp(key, "x-connected-port") == 0)
 			client->common.local_port = atoi(value);
+		else if (strcasecmp(key, "x-proxy-ttl") == 0)
+			client->common.proxy_ttl = atoi(value);
 		else if (strcasecmp(key, "x-session-id") == 0) {
 			client->common.session_id =
 				p_strdup(client->common.pool, value);
@@ -138,22 +150,22 @@
 	client_send_raw(&client->common,
 		t_strdup_printf("* ID %s\r\n",
 			imap_id_reply_generate(client->set->imap_id_send)));
-	client_send_line(&client->common, CLIENT_CMD_REPLY_OK, "ID completed.");
+	client_send_reply(&client->common, IMAP_CMD_REPLY_OK, "ID completed.");
 	return 1;
 }
 
 static int cmd_noop(struct imap_client *client)
 {
-	client_send_line(&client->common, CLIENT_CMD_REPLY_OK,
-			 "NOOP completed.");
+	client_send_reply(&client->common, IMAP_CMD_REPLY_OK,
+			  "NOOP completed.");
 	return 1;
 }
 
 static int cmd_logout(struct imap_client *client)
 {
-	client_send_line(&client->common, CLIENT_CMD_REPLY_BYE, "Logging out");
-	client_send_line(&client->common, CLIENT_CMD_REPLY_OK,
-			 "Logout completed.");
+	client_send_reply(&client->common, IMAP_CMD_REPLY_BYE, "Logging out");
+	client_send_reply(&client->common, IMAP_CMD_REPLY_OK,
+			  "Logout completed.");
 	client_destroy(&client->common, "Aborted login");
 	return 1;
 }
@@ -161,8 +173,8 @@
 static int cmd_enable(struct imap_client *client)
 {
 	client_send_raw(&client->common, "* ENABLED\r\n");
-	client_send_line(&client->common, CLIENT_CMD_REPLY_OK,
-			 "ENABLE ignored in non-authenticated state.");
+	client_send_reply(&client->common, IMAP_CMD_REPLY_OK,
+			  "ENABLE ignored in non-authenticated state.");
 	return 1;
 }
 
@@ -226,14 +238,14 @@
 		/* error */
 		msg = imap_parser_get_error(client->parser, &fatal);
 		if (fatal) {
-			client_send_line(&client->common,
-					 CLIENT_CMD_REPLY_BYE, msg);
+			client_send_reply(&client->common,
+					  IMAP_CMD_REPLY_BYE, msg);
 			client_destroy(&client->common,
 				t_strconcat("Disconnected: ", msg, NULL));
 			return FALSE;
 		}
 
-		client_send_line(&client->common, CLIENT_CMD_REPLY_BAD, msg);
+		client_send_reply(&client->common, IMAP_CMD_REPLY_BAD, msg);
 		client->cmd_finished = TRUE;
 		client->skip_line = TRUE;
 		return -1;
@@ -312,7 +324,7 @@
 
 	client->cmd_finished = TRUE;
 	if (ret == -2 && strcasecmp(client->cmd_tag, "LOGIN") == 0) {
-		client_send_line(&client->common, CLIENT_CMD_REPLY_BAD,
+		client_send_reply(&client->common, IMAP_CMD_REPLY_BAD,
 			"First parameter in line is IMAP's command tag, "
 			"not the command name. Add that before the command, "
 			"like: a login user pass");
@@ -320,13 +332,13 @@
 		if (*client->cmd_tag == '\0')
 			client->cmd_tag = "*";
 		if (++client->common.bad_counter >= CLIENT_MAX_BAD_COMMANDS) {
-			client_send_line(&client->common, CLIENT_CMD_REPLY_BYE,
+			client_send_reply(&client->common, IMAP_CMD_REPLY_BYE,
 				"Too many invalid IMAP commands.");
 			client_destroy(&client->common,
 				"Disconnected: Too many invalid commands");
 			return FALSE;
 		}
-		client_send_line(&client->common, CLIENT_CMD_REPLY_BAD,
+		client_send_reply(&client->common, IMAP_CMD_REPLY_BAD,
 			"Error in IMAP command received by server.");
 	}
 
@@ -346,8 +358,8 @@
 		if (!auth_client_is_connected(auth_client)) {
 			/* we're not currently connected to auth process -
 			   don't allow any commands */
-			client_send_line(client, CLIENT_CMD_REPLY_STATUS,
-					 AUTH_SERVER_WAITING_MSG);
+			client_notify_status(client, FALSE,
+					     AUTH_SERVER_WAITING_MSG);
 			if (client->to_auth_waiting != NULL)
 				timeout_remove(&client->to_auth_waiting);
 
@@ -389,7 +401,7 @@
 	imap_parser_unref(&imap_client->parser);
 }
 
-static void imap_client_send_greeting(struct client *client)
+static void imap_client_notify_auth_ready(struct client *client)
 {
 	string_t *greet;
 
@@ -400,7 +412,6 @@
 	str_append(greet, "\r\n");
 
 	client_send_raw(client, str_c(greet));
-	client->greeting_sent = TRUE;
 }
 
 static void imap_client_starttls(struct client *client)
@@ -417,50 +428,11 @@
 }
 
 static void
-imap_client_send_line(struct client *client, enum client_cmd_reply reply,
-		      const char *text)
+client_send_reply_raw(struct client *client,
+		      const char *prefix, const char *resp_code,
+		      const char *text, bool tagged)
 {
 	struct imap_client *imap_client = (struct imap_client *)client;
-	const char *resp_code = NULL;
-	const char *prefix = "NO";
-	bool tagged = TRUE;
-
-	switch (reply) {
-	case CLIENT_CMD_REPLY_OK:
-		prefix = "OK";
-		break;
-	case CLIENT_CMD_REPLY_AUTH_FAILED:
-		resp_code = IMAP_RESP_CODE_AUTHFAILED;
-		break;
-	case CLIENT_CMD_REPLY_AUTHZ_FAILED:
-		resp_code = IMAP_RESP_CODE_AUTHZFAILED;
-		break;
-	case CLIENT_CMD_REPLY_AUTH_FAIL_TEMP:
-		resp_code = IMAP_RESP_CODE_UNAVAILABLE;
-		break;
-	case CLIENT_CMD_REPLY_AUTH_FAIL_REASON:
-		resp_code = "ALERT";
-		break;
-	case CLIENT_CMD_REPLY_AUTH_FAIL_NOSSL:
-		resp_code = IMAP_RESP_CODE_PRIVACYREQUIRED;
-		break;
-	case CLIENT_CMD_REPLY_BAD:
-		prefix = "BAD";
-		break;
-	case CLIENT_CMD_REPLY_BYE:
-		prefix = "BYE";
-		tagged = FALSE;
-		break;
-	case CLIENT_CMD_REPLY_STATUS:
-		prefix = "OK";
-		tagged = FALSE;
-		break;
-	case CLIENT_CMD_REPLY_STATUS_BAD:
-		prefix = "BAD";
-		tagged = FALSE;
-		resp_code = "ALERT";
-		break;
-	}
 
 	T_BEGIN {
 		string_t *line = t_str_new(256);
@@ -477,11 +449,61 @@
 		str_append(line, text);
 		str_append(line, "\r\n");
 
-		client_send_raw_data(client, str_data(line),
-				     str_len(line));
+		client_send_raw_data(client, str_data(line), str_len(line));
 	} T_END;
 }
 
+void client_send_reply_code(struct client *client, enum imap_cmd_reply reply,
+			    const char *resp_code, const char *text)
+{
+	const char *prefix = "NO";
+	bool tagged = TRUE;
+
+	switch (reply) {
+	case IMAP_CMD_REPLY_OK:
+		prefix = "OK";
+		break;
+	case IMAP_CMD_REPLY_NO:
+		break;
+	case IMAP_CMD_REPLY_BAD:
+		prefix = "BAD";
+		break;
+	case IMAP_CMD_REPLY_BYE:
+		prefix = "BYE";
+		tagged = FALSE;
+		break;
+	}
+	client_send_reply_raw(client, prefix, resp_code, text, tagged);
+}
+
+void client_send_reply(struct client *client, enum imap_cmd_reply reply,
+		       const char *text)
+{
+	client_send_reply_code(client, reply, NULL, text);
+}
+
+static void
+imap_client_notify_status(struct client *client, bool bad, const char *text)
+{
+	if (bad)
+		client_send_reply_raw(client, "BAD", "ALERT", text, FALSE);
+	else
+		client_send_reply_raw(client, "OK", NULL, text, FALSE);
+}
+
+static void 
+imap_client_notify_disconnect(struct client *client,
+			      enum client_disconnect_reason reason,
+			      const char *text)
+{
+	if (reason == CLIENT_DISCONNECT_INTERNAL_ERROR) {
+		client_send_reply_code(client, IMAP_CMD_REPLY_BYE,
+				       IMAP_RESP_CODE_UNAVAILABLE, text);
+	} else {
+		client_send_reply_code(client, IMAP_CMD_REPLY_BYE, NULL, text);
+	}
+}
+
 static void imap_login_preinit(void)
 {
 	login_set_roots = imap_login_setting_roots;
@@ -500,15 +522,18 @@
 	imap_client_alloc,
 	imap_client_create,
 	imap_client_destroy,
-	imap_client_send_greeting,
+	imap_client_notify_auth_ready,
+	imap_client_notify_disconnect,
+	imap_client_notify_status,
+	imap_client_notify_starttls,
 	imap_client_starttls,
 	imap_client_input,
-	imap_client_send_line,
-	imap_client_auth_handle_reply,
 	NULL,
 	NULL,
+	imap_client_auth_result,
 	imap_proxy_reset,
-	imap_proxy_parse_line
+	imap_proxy_parse_line,
+	imap_proxy_error
 };
 
 static const struct login_binary imap_login_binary = {
--- a/src/imap-login/client.h	Sun May 20 03:08:01 2012 +0300
+++ b/src/imap-login/client.h	Sun May 20 03:25:04 2012 +0300
@@ -28,4 +28,18 @@
 
 bool client_skip_line(struct imap_client *client);
 
+enum imap_cmd_reply {
+	IMAP_CMD_REPLY_OK,
+	IMAP_CMD_REPLY_NO,
+	IMAP_CMD_REPLY_BAD,
+	IMAP_CMD_REPLY_BYE
+};
+
+void client_send_reply(struct client *client,
+		       enum imap_cmd_reply reply, const char *text);
+
+void client_send_reply_code(struct client *client,
+			    enum imap_cmd_reply reply, const char *resp_code,
+			    const char *text);
+
 #endif
--- a/src/imap-login/imap-proxy.c	Sun May 20 03:08:01 2012 +0300
+++ b/src/imap-login/imap-proxy.c	Sun May 20 03:25:04 2012 +0300
@@ -29,17 +29,21 @@
 
 static void proxy_write_id(struct imap_client *client, string_t *str)
 {
+	i_assert(client->common.proxy_ttl > 0);
+
 	str_printfa(str, "I ID ("
 		    "\"x-session-id\" \"%s\" "
 		    "\"x-originating-ip\" \"%s\" "
 		    "\"x-originating-port\" \"%u\" "
 		    "\"x-connected-ip\" \"%s\" "
-		    "\"x-connected-port\" \"%u\")\r\n",
+		    "\"x-connected-port\" \"%u\" "
+		    "\"x-proxy-ttl\" \"%u\")\r\n",
 		    client_get_session_id(&client->common),
 		    net_ip2addr(&client->common.ip),
 		    client->common.remote_port,
 		    net_ip2addr(&client->common.local_ip),
-		    client->common.local_port);
+		    client->common.local_port,
+		    client->common.proxy_ttl - 1);
 }
 
 static void proxy_free_password(struct client *client)
@@ -248,8 +252,9 @@
 			   the remote is sending a different error message
 			   an attacker can't find out what users exist in
 			   the system. */
-			client_send_line(client, CLIENT_CMD_REPLY_AUTH_FAILED,
-					 AUTH_FAILED_MSG);
+			client_send_reply_code(client, IMAP_CMD_REPLY_NO,
+					       IMAP_RESP_CODE_AUTHFAILED,
+					       AUTH_FAILED_MSG);
 		} else if (strncmp(line, "NO [", 4) == 0) {
 			/* remote sent some other resp-code. forward it. */
 			client_send_raw(client, t_strconcat(
@@ -262,8 +267,9 @@
 			   failures. since other errors are pretty rare,
 			   it's safer to just hide them. they're still
 			   available in logs though. */
-			client_send_line(client, CLIENT_CMD_REPLY_AUTH_FAILED,
-					 AUTH_FAILED_MSG);
+			client_send_reply_code(client, IMAP_CMD_REPLY_NO,
+					       IMAP_RESP_CODE_AUTHFAILED,
+					       AUTH_FAILED_MSG);
 		}
 
 		client->proxy_auth_failed = TRUE;
@@ -304,3 +310,9 @@
 	imap_client->proxy_wait_auth_continue = FALSE;
 	client->proxy_state = IMAP_PROXY_STATE_NONE;
 }
+
+void imap_proxy_error(struct client *client, const char *text)
+{
+	client_send_reply_code(client, IMAP_CMD_REPLY_NO,
+			       IMAP_RESP_CODE_UNAVAILABLE, text);
+}
--- a/src/imap-login/imap-proxy.h	Sun May 20 03:08:01 2012 +0300
+++ b/src/imap-login/imap-proxy.h	Sun May 20 03:25:04 2012 +0300
@@ -4,4 +4,6 @@
 void imap_proxy_reset(struct client *client);
 int imap_proxy_parse_line(struct client *client, const char *line);
 
+void imap_proxy_error(struct client *client, const char *text);
+
 #endif
--- a/src/imap/cmd-fetch.c	Sun May 20 03:08:01 2012 +0300
+++ b/src/imap/cmd-fetch.c	Sun May 20 03:25:04 2012 +0300
@@ -21,13 +21,14 @@
 };
 
 static bool
-fetch_parse_args(struct imap_fetch_context *ctx, const struct imap_arg *arg,
-		 const struct imap_arg **next_arg_r)
+fetch_parse_args(struct imap_fetch_context *ctx,
+		 struct client_command_context *cmd,
+		 const struct imap_arg *arg, const struct imap_arg **next_arg_r)
 {
 	const char *str, *const *macro;
 
-	if (ctx->cmd->uid) {
-		if (!imap_fetch_init_handler(ctx, "UID", &arg))
+	if (cmd->uid) {
+		if (!imap_fetch_cmd_init_handler(ctx, cmd, "UID", &arg))
 			return FALSE;
 	}
 	if (imap_arg_get_atom(arg, &str)) {
@@ -43,12 +44,12 @@
 			macro = full_macro;
 		else {
 			macro = NULL;
-			if (!imap_fetch_init_handler(ctx, str, &arg))
+			if (!imap_fetch_cmd_init_handler(ctx, cmd, str, &arg))
 				return FALSE;
 		}
 		if (macro != NULL) {
 			while (*macro != NULL) {
-				if (!imap_fetch_init_handler(ctx, *macro, &arg))
+				if (!imap_fetch_cmd_init_handler(ctx, cmd, *macro, &arg))
 					return FALSE;
 				macro++;
 			}
@@ -58,18 +59,18 @@
 		*next_arg_r = arg + 1;
 		arg = imap_arg_as_list(arg);
 		if (IMAP_ARG_IS_EOL(arg)) {
-			client_send_command_error(ctx->cmd,
+			client_send_command_error(cmd,
 						  "FETCH list is empty.");
 			return FALSE;
 		}
 		while (imap_arg_get_atom(arg, &str)) {
 			str = t_str_ucase(str);
 			arg++;
-			if (!imap_fetch_init_handler(ctx, str, &arg))
+			if (!imap_fetch_cmd_init_handler(ctx, cmd, str, &arg))
 				return FALSE;
 		}
 		if (!IMAP_ARG_IS_EOL(arg)) {
-			client_send_command_error(ctx->cmd,
+			client_send_command_error(cmd,
 				"FETCH list contains non-atoms.");
 			return FALSE;
 		}
@@ -79,6 +80,7 @@
 
 static bool
 fetch_parse_modifier(struct imap_fetch_context *ctx,
+		     struct client_command_context *cmd,
 		     const char *name, const struct imap_arg **args)
 {
 	const char *str;
@@ -87,58 +89,59 @@
 	if (strcmp(name, "CHANGEDSINCE") == 0) {
 		if (!imap_arg_get_atom(*args, &str) ||
 		    str_to_uint64(str, &modseq) < 0) {
-			client_send_command_error(ctx->cmd,
+			client_send_command_error(cmd,
 				"Invalid CHANGEDSINCE modseq.");
 			return FALSE;
 		}
 		*args += 1;
-		return imap_fetch_add_changed_since(ctx, modseq);
+		imap_fetch_add_changed_since(ctx, modseq);
+		return TRUE;
 	}
-	if (strcmp(name, "VANISHED") == 0 && ctx->cmd->uid) {
+	if (strcmp(name, "VANISHED") == 0 && cmd->uid) {
 		if ((ctx->client->enabled_features &
 		     MAILBOX_FEATURE_QRESYNC) == 0) {
-			client_send_command_error(ctx->cmd,
-						  "QRESYNC not enabled");
+			client_send_command_error(cmd, "QRESYNC not enabled");
 			return FALSE;
 		}
 		ctx->send_vanished = TRUE;
 		return TRUE;
 	}
 
-	client_send_command_error(ctx->cmd, "Unknown FETCH modifier");
+	client_send_command_error(cmd, "Unknown FETCH modifier");
 	return FALSE;
 }
 
 static bool
 fetch_parse_modifiers(struct imap_fetch_context *ctx,
+		      struct client_command_context *cmd,
 		      const struct imap_arg *args)
 {
 	const char *name;
 
 	while (!IMAP_ARG_IS_EOL(args)) {
 		if (!imap_arg_get_atom(args, &name)) {
-			client_send_command_error(ctx->cmd,
+			client_send_command_error(cmd,
 				"FETCH modifiers contain non-atoms.");
 			return FALSE;
 		}
 		args++;
-		if (!fetch_parse_modifier(ctx, t_str_ucase(name), &args))
+		if (!fetch_parse_modifier(ctx, cmd, t_str_ucase(name), &args))
 			return FALSE;
 	}
 	if (ctx->send_vanished &&
 	    (ctx->search_args->args->next == NULL ||
 	     ctx->search_args->args->next->type != SEARCH_MODSEQ)) {
-		client_send_command_error(ctx->cmd,
+		client_send_command_error(cmd,
 			"VANISHED used without CHANGEDSINCE");
 		return FALSE;
 	}
 	return TRUE;
 }
 
-static bool cmd_fetch_finish(struct imap_fetch_context *ctx)
+static bool cmd_fetch_finish(struct imap_fetch_context *ctx,
+			     struct client_command_context *cmd)
 {
 	static const char *ok_message = "OK Fetch completed.";
-	struct client_command_context *cmd = ctx->cmd;
 	const char *tagged_reply = ok_message;
 
 	if (ctx->partial_fetch) {
@@ -175,11 +178,11 @@
 {
         struct imap_fetch_context *ctx = cmd->context;
 
-	if (imap_fetch_more(ctx) == 0) {
+	if (imap_fetch_more(ctx, cmd) == 0) {
 		/* unfinished */
 		return FALSE;
 	}
-	return cmd_fetch_finish(ctx);
+	return cmd_fetch_finish(ctx, cmd);
 }
 
 bool cmd_fetch(struct client_command_context *cmd)
@@ -218,15 +221,15 @@
 	}
 	ctx->search_args = search_args;
 
-	if (!fetch_parse_args(ctx, &args[1], &next_arg) ||
+	if (!fetch_parse_args(ctx, cmd, &args[1], &next_arg) ||
 	    (imap_arg_get_list(next_arg, &list_arg) &&
-	     !fetch_parse_modifiers(ctx, list_arg))) {
+	     !fetch_parse_modifiers(ctx, cmd, list_arg))) {
 		imap_fetch_deinit(ctx);
 		return TRUE;
 	}
 
 	if (imap_fetch_begin(ctx) == 0) {
-		if (imap_fetch_more(ctx) == 0) {
+		if (imap_fetch_more(ctx, cmd) == 0) {
 			/* unfinished */
 			cmd->state = CLIENT_COMMAND_STATE_WAIT_OUTPUT;
 
@@ -235,5 +238,5 @@
 			return FALSE;
 		}
 	}
-	return cmd_fetch_finish(ctx);
+	return cmd_fetch_finish(ctx, cmd);
 }
--- a/src/imap/cmd-idle.c	Sun May 20 03:08:01 2012 +0300
+++ b/src/imap/cmd-idle.c	Sun May 20 03:25:04 2012 +0300
@@ -253,14 +253,8 @@
 	ctx->client = client;
 	idle_add_keepalive_timeout(ctx);
 
-	if (client->mailbox != NULL) {
-		const struct mail_storage_settings *set;
-
-		set = mailbox_get_settings(client->mailbox);
-		mailbox_notify_changes(client->mailbox,
-				       set->mailbox_idle_check_interval,
-				       idle_callback, ctx);
-	}
+	if (client->mailbox != NULL)
+		mailbox_notify_changes(client->mailbox, idle_callback, ctx);
 	client_send_line(client, "+ idling");
 
 	io_remove(&client->io);
--- a/src/imap/cmd-select.c	Sun May 20 03:08:01 2012 +0300
+++ b/src/imap/cmd-select.c	Sun May 20 03:25:04 2012 +0300
@@ -214,7 +214,7 @@
         struct imap_select_context *ctx = cmd->context;
 	int ret;
 
-	if (imap_fetch_more(ctx->fetch_ctx) == 0) {
+	if (imap_fetch_more(ctx->fetch_ctx, cmd) == 0) {
 		/* unfinished */
 		return FALSE;
 	}
@@ -247,16 +247,13 @@
 	fetch_ctx->qresync_sample_seqset = &ctx->qresync_sample_seqset;
 	fetch_ctx->qresync_sample_uidset = &ctx->qresync_sample_uidset;
 
-	if (!imap_fetch_add_changed_since(fetch_ctx, ctx->qresync_modseq) ||
-	    !imap_fetch_init_handler(fetch_ctx, "UID", NULL) ||
-	    !imap_fetch_init_handler(fetch_ctx, "FLAGS", NULL) ||
-	    !imap_fetch_init_handler(fetch_ctx, "MODSEQ", NULL)) {
-		(void)imap_fetch_deinit(fetch_ctx);
-		return -1;
-	}
+	imap_fetch_add_changed_since(fetch_ctx, ctx->qresync_modseq);
+	imap_fetch_init_nofail_handler(fetch_ctx, imap_fetch_uid_init);
+	imap_fetch_init_nofail_handler(fetch_ctx, imap_fetch_flags_init);
+	imap_fetch_init_nofail_handler(fetch_ctx, imap_fetch_modseq_init);
 
 	if (imap_fetch_begin(fetch_ctx) == 0) {
-		if (imap_fetch_more(fetch_ctx) == 0) {
+		if (imap_fetch_more(fetch_ctx, ctx->cmd) == 0) {
 			/* unfinished */
 			ctx->fetch_ctx = fetch_ctx;
 			ctx->cmd->state = CLIENT_COMMAND_STATE_WAIT_OUTPUT;
--- a/src/imap/imap-client.c	Sun May 20 03:08:01 2012 +0300
+++ b/src/imap/imap-client.c	Sun May 20 03:25:04 2012 +0300
@@ -20,6 +20,8 @@
 #include <unistd.h>
 
 extern struct mail_storage_callbacks mail_storage_callbacks;
+extern struct imap_client_vfuncs imap_client_vfuncs;
+
 struct imap_module_register imap_module_register = { 0 };
 
 struct client *imap_clients = NULL;
@@ -48,6 +50,7 @@
 	pool = pool_alloconly_create("imap client", 2048);
 	client = p_new(pool, struct client, 1);
 	client->pool = pool;
+	client->v = imap_client_vfuncs;
 	client->set = set;
 	client->service_user = service_user;
 	client->session_id = p_strdup(pool, session_id);
@@ -174,6 +177,11 @@
 
 void client_destroy(struct client *client, const char *reason)
 {
+	client->v.destroy(client, reason);
+}
+
+static void client_default_destroy(struct client *client, const char *reason)
+{
 	struct client_command_context *cmd;
 
 	i_assert(!client->destroyed);
@@ -1006,3 +1014,7 @@
 		client_destroy(imap_clients, "Server shutting down.");
 	}
 }
+
+struct imap_client_vfuncs imap_client_vfuncs = {
+	client_default_destroy
+};
--- a/src/imap/imap-client.h	Sun May 20 03:08:01 2012 +0300
+++ b/src/imap/imap-client.h	Sun May 20 03:25:04 2012 +0300
@@ -44,15 +44,6 @@
 	CLIENT_COMMAND_STATE_DONE
 };
 
-struct imap_module_register {
-	unsigned int id;
-};
-
-union imap_module_context {
-	struct imap_module_register *reg;
-};
-extern struct imap_module_register imap_module_register;
-
 struct client_command_context {
 	struct client_command_context *prev, *next;
 	struct client *client;
@@ -96,10 +87,16 @@
 	struct message_size pos;
 };
 
+struct imap_client_vfuncs {
+	void (*destroy)(struct client *client, const char *reason);
+};
+
 struct client {
 	struct client *prev, *next;
 
+	struct imap_client_vfuncs v;
 	const char *session_id;
+
 	int fd_in, fd_out;
 	struct io *io;
 	struct istream *input;
@@ -166,6 +163,16 @@
 	unsigned int modseqs_sent_since_sync:1;
 };
 
+struct imap_module_register {
+	unsigned int id;
+};
+
+union imap_module_context {
+	struct imap_client_vfuncs super;
+	struct imap_module_register *reg;
+};
+extern struct imap_module_register imap_module_register;
+
 extern struct client *imap_clients;
 extern unsigned int imap_client_count;
 
--- a/src/imap/imap-fetch-body.c	Sun May 20 03:08:01 2012 +0300
+++ b/src/imap/imap-fetch-body.c	Sun May 20 03:25:04 2012 +0300
@@ -305,7 +305,7 @@
 {
 	string_t *str;
 
-	ctx->cur_name = p_strconcat(ctx->cmd->pool,
+	ctx->cur_name = p_strconcat(ctx->pool,
 				    "[", body->section, "]", NULL);
 	ctx->cur_size = get_send_size(body, size->virtual_size);
 
@@ -607,63 +607,60 @@
 	return TRUE;
 }
 
-static bool fetch_body_header_fields_init(struct imap_fetch_context *ctx,
+static bool fetch_body_header_fields_init(struct imap_fetch_init_context *ctx,
 					  struct imap_fetch_body_data *body,
 					  const char *section)
 {
-	const char *const *arr, *name;
+	const char *const *arr;
 
 	if (!fetch_body_header_fields_check(section))
 		return FALSE;
 
-	name = get_body_name(body);
-	if ((ctx->fetch_data & (MAIL_FETCH_STREAM_HEADER |
-				MAIL_FETCH_STREAM_BODY)) != 0) {
+	if ((ctx->fetch_ctx->fetch_data & (MAIL_FETCH_STREAM_HEADER |
+					   MAIL_FETCH_STREAM_BODY)) != 0) {
 		/* we'll need to open the file anyway, don't try to get the
 		   headers from cache. */
-		imap_fetch_add_handler(ctx, FALSE, FALSE, name, "NIL",
+		imap_fetch_add_handler(ctx, 0, "NIL",
 				       fetch_body_header_partial, body);
 		return TRUE;
 	}
 
 	for (arr = body->fields; *arr != NULL; arr++) {
-		const char *hdr = p_strdup(ctx->cmd->pool, *arr);
-		array_append(&ctx->all_headers, &hdr, 1);
+		const char *hdr = p_strdup(ctx->fetch_ctx->pool, *arr);
+		array_append(&ctx->fetch_ctx->all_headers, &hdr, 1);
 	}
 
-	body->header_ctx = mailbox_header_lookup_init(ctx->box, body->fields);
-	imap_fetch_add_handler(ctx, FALSE, TRUE, name, "NIL",
+	body->header_ctx = mailbox_header_lookup_init(ctx->fetch_ctx->box,
+						      body->fields);
+	imap_fetch_add_handler(ctx, IMAP_FETCH_HANDLER_FLAG_WANT_DEINIT, "NIL",
 			       fetch_body_header_fields, body);
 	return TRUE;
 }
 
-static bool fetch_body_section_name_init(struct imap_fetch_context *ctx,
-					 struct imap_fetch_body_data *body)
+static bool
+fetch_body_section_name_init(struct imap_fetch_init_context *ctx,
+			     struct imap_fetch_body_data *body)
 {
-	const char *name, *section = body->section;
+	const char *section = body->section;
 
-	name = get_body_name(body);
 	if (*section == '\0') {
-		ctx->fetch_data |= MAIL_FETCH_STREAM_HEADER |
+		ctx->fetch_ctx->fetch_data |= MAIL_FETCH_STREAM_HEADER |
 			MAIL_FETCH_STREAM_BODY;
-		imap_fetch_add_handler(ctx, FALSE, FALSE, name, "NIL",
-				       fetch_body, body);
+		imap_fetch_add_handler(ctx, 0, "NIL", fetch_body, body);
 		return TRUE;
 	}
 
 	if (strcmp(section, "TEXT") == 0) {
-		ctx->fetch_data |= MAIL_FETCH_STREAM_BODY;
-		imap_fetch_add_handler(ctx, FALSE, FALSE, name, "NIL",
-				       fetch_body, body);
+		ctx->fetch_ctx->fetch_data |= MAIL_FETCH_STREAM_BODY;
+		imap_fetch_add_handler(ctx, 0, "NIL", fetch_body, body);
 		return TRUE;
 	}
 
 	if (strncmp(section, "HEADER", 6) == 0) {
 		/* exact header matches could be cached */
 		if (section[6] == '\0') {
-			ctx->fetch_data |= MAIL_FETCH_STREAM_HEADER;
-			imap_fetch_add_handler(ctx, FALSE, FALSE, name, "NIL",
-					       fetch_body, body);
+			ctx->fetch_ctx->fetch_data |= MAIL_FETCH_STREAM_HEADER;
+			imap_fetch_add_handler(ctx, 0, "NIL", fetch_body, body);
 			return TRUE;
 		}
 
@@ -673,12 +670,12 @@
 
 		if (strncmp(section, "HEADER.FIELDS.NOT ", 18) == 0 &&
 		    fetch_body_header_fields_check(section+18)) {
-			imap_fetch_add_handler(ctx, FALSE, FALSE, name, "NIL",
+			imap_fetch_add_handler(ctx, 0, "NIL",
 					       fetch_body_header_partial, body);
 			return TRUE;
 		}
 	} else if (*section >= '0' && *section <= '9') {
-		ctx->fetch_data |= MAIL_FETCH_STREAM_BODY |
+		ctx->fetch_ctx->fetch_data |= MAIL_FETCH_STREAM_BODY |
 			MAIL_FETCH_MESSAGE_PARTS;
 
 		while ((*section >= '0' && *section <= '9') ||
@@ -692,14 +689,13 @@
 		     fetch_body_header_fields_check(section+14)) ||
 		    (strncmp(section, "HEADER.FIELDS.NOT ", 18) == 0 &&
 		     fetch_body_header_fields_check(section+18))) {
-			imap_fetch_add_handler(ctx, FALSE, FALSE, name, "NIL",
+			imap_fetch_add_handler(ctx, 0, "NIL",
 					       fetch_body_mime, body);
 			return TRUE;
 		}
 	}
 
-	client_send_command_error(ctx->cmd,
-		"Invalid BODY[..] parameter: Unknown or broken section");
+	ctx->error = "Invalid BODY[..] parameter: Unknown or broken section";
 	return FALSE;
 }
 
@@ -723,34 +719,32 @@
 	return TRUE;
 }
 
-static bool body_section_build(struct imap_fetch_context *ctx,
-			       struct imap_fetch_body_data *body,
-			       const char *prefix,
-			       const struct imap_arg *args,
-			       unsigned int args_count)
+static bool
+body_section_build(struct imap_fetch_init_context *ctx,
+		   struct imap_fetch_body_data *body, const char *prefix,
+		   const struct imap_arg *args, unsigned int args_count)
 {
 	string_t *str;
 	const char **arr, *value;
 	size_t i;
 
-	str = str_new(ctx->cmd->pool, 128);
+	str = str_new(ctx->fetch_ctx->pool, 128);
 	str_append(str, prefix);
 	str_append(str, " (");
 
 	/* @UNSAFE: NULL-terminated list of headers */
-	arr = p_new(ctx->cmd->pool, const char *, args_count + 1);
+	arr = p_new(ctx->fetch_ctx->pool, const char *, args_count + 1);
 
 	for (i = 0; i < args_count; i++) {
 		if (!imap_arg_get_astring(&args[i], &value)) {
-			client_send_command_error(ctx->cmd,
-				"Invalid BODY[..] parameter: "
-				"Header list contains non-strings");
+			ctx->error = "Invalid BODY[..] parameter: "
+				"Header list contains non-strings";
 			return FALSE;
 		}
 
 		if (i != 0)
 			str_append_c(str, ' ');
-		arr[i] = p_strdup(ctx->cmd->pool, t_str_ucase(value));
+		arr[i] = p_strdup(ctx->fetch_ctx->pool, t_str_ucase(value));
 
 		if (args[i].type == IMAP_ARG_ATOM)
 			str_append(str, arr[i]);
@@ -769,53 +763,51 @@
 	return TRUE;
 }
   
-bool fetch_body_section_init(struct imap_fetch_context *ctx, const char *name,
-			     const struct imap_arg **args)
+bool imap_fetch_body_section_init(struct imap_fetch_init_context *ctx)
 {
 	struct imap_fetch_body_data *body;
 	const struct imap_arg *list_args;
 	unsigned int list_count;
-	const char *partial, *str;
-	const char *p = name + 4;
+	const char *partial, *str, *p;
 
-	body = p_new(ctx->cmd->pool, struct imap_fetch_body_data, 1);
+	i_assert(strncmp(ctx->name, "BODY", 4) == 0);
+	p = ctx->name + 4;
+
+	body = p_new(ctx->fetch_ctx->pool, struct imap_fetch_body_data, 1);
 	body->max_size = (uoff_t)-1;
 
 	if (strncmp(p, ".PEEK", 5) == 0) {
 		body->peek = TRUE;
 		p += 5;
 	} else {
-		ctx->flags_update_seen = TRUE;
+		ctx->fetch_ctx->flags_update_seen = TRUE;
 	}
 
 	if (*p != '[') {
-		client_send_command_error(ctx->cmd,
-			"Invalid BODY[..] parameter: Missing '['");
+		ctx->error = "Invalid BODY[..] parameter: Missing '['";
 		return FALSE;
 	}
 
-	if (imap_arg_get_list_full(&(*args)[0], &list_args, &list_count)) {
+	if (imap_arg_get_list_full(&ctx->args[0], &list_args, &list_count)) {
 		/* BODY[HEADER.FIELDS.. (headers list)] */
-		if (!imap_arg_get_atom(&(*args)[1], &str) ||
+		if (!imap_arg_get_atom(&ctx->args[1], &str) ||
 		    str[0] != ']') {
-			client_send_command_error(ctx->cmd,
-				"Invalid BODY[..] parameter: Missing ']'");
+			ctx->error = "Invalid BODY[..] parameter: Missing ']'";
 			return FALSE;
 		}
 		if (!body_section_build(ctx, body, p+1, list_args, list_count))
 			return FALSE;
 		p = str;
-		*args += 2;
+		ctx->args += 2;
 	} else {
 		/* no headers list */
 		body->section = p+1;
 		p = strchr(body->section, ']');
 		if (p == NULL) {
-			client_send_command_error(ctx->cmd,
-				"Invalid BODY[..] parameter: Missing ']'");
+			ctx->error = "Invalid BODY[..] parameter: Missing ']'";
 			return FALSE;
 		}
-		body->section = p_strdup_until(ctx->cmd->pool,
+		body->section = p_strdup_until(ctx->fetch_ctx->pool,
 					       body->section, p);
 	}
 
@@ -827,9 +819,8 @@
 
 		if (!read_uoff_t(&p, &body->skip) || body->skip > OFF_T_MAX) {
 			/* wrapped */
-			client_send_command_error(ctx->cmd,
-				"Invalid BODY[..] parameter: "
-				"Too big partial start");
+			ctx->error = "Invalid BODY[..] parameter: "
+				"Too big partial start";
 			return FALSE;
 		}
 
@@ -838,22 +829,22 @@
 			if (!read_uoff_t(&p, &body->max_size) ||
 			    body->max_size > OFF_T_MAX) {
 				/* wrapped */
-				client_send_command_error(ctx->cmd,
-					"Invalid BODY[..] parameter: "
-					"Too big partial end");
+				ctx->error = "Invalid BODY[..] parameter: "
+					"Too big partial end";
 				return FALSE;
 			}
 		}
 
 		if (*p != '>') {
-			client_send_command_error(ctx->cmd,
-				t_strdup_printf("Invalid BODY[..] parameter: "
-						"Missing '>' in '%s'",
-						partial));
+			ctx->error = t_strdup_printf(
+				"Invalid BODY[..] parameter: "
+				"Missing '>' in '%s'", partial);
 			return FALSE;
 		}
 	}
 
+	/* sanitize the name */
+	ctx->name = get_body_name(body);
 	return fetch_body_section_name_init(ctx, body);
 }
 
@@ -954,39 +945,37 @@
 	return fetch_stream(ctx, &body_size);
 }
 
-bool fetch_rfc822_init(struct imap_fetch_context *ctx, const char *name,
-		       const struct imap_arg **args ATTR_UNUSED)
+bool imap_fetch_rfc822_init(struct imap_fetch_init_context *ctx)
 {
+	const char *name = ctx->name;
+
 	if (name[6] == '\0') {
-		ctx->fetch_data |= MAIL_FETCH_STREAM_HEADER |
+		ctx->fetch_ctx->fetch_data |= MAIL_FETCH_STREAM_HEADER |
 			MAIL_FETCH_STREAM_BODY;
-		ctx->flags_update_seen = TRUE;
-		imap_fetch_add_handler(ctx, FALSE, FALSE, name, "NIL",
-				       fetch_rfc822, NULL);
+		ctx->fetch_ctx->flags_update_seen = TRUE;
+		imap_fetch_add_handler(ctx, 0, "NIL", fetch_rfc822, NULL);
 		return TRUE;
 	}
 
 	if (strcmp(name+6, ".SIZE") == 0) {
-		ctx->fetch_data |= MAIL_FETCH_VIRTUAL_SIZE;
-		imap_fetch_add_handler(ctx, TRUE, FALSE, name, "0",
-				       fetch_rfc822_size, NULL);
+		ctx->fetch_ctx->fetch_data |= MAIL_FETCH_VIRTUAL_SIZE;
+		imap_fetch_add_handler(ctx, IMAP_FETCH_HANDLER_FLAG_BUFFERED,
+				       "0", fetch_rfc822_size, NULL);
 		return TRUE;
 	}
 	if (strcmp(name+6, ".HEADER") == 0) {
-		ctx->fetch_data |= MAIL_FETCH_STREAM_HEADER;
-		imap_fetch_add_handler(ctx, FALSE, FALSE, name, "NIL",
+		ctx->fetch_ctx->fetch_data |= MAIL_FETCH_STREAM_HEADER;
+		imap_fetch_add_handler(ctx, 0, "NIL",
 				       fetch_rfc822_header, NULL);
 		return TRUE;
 	}
 	if (strcmp(name+6, ".TEXT") == 0) {
-		ctx->fetch_data |= MAIL_FETCH_STREAM_BODY;
-		ctx->flags_update_seen = TRUE;
-		imap_fetch_add_handler(ctx, FALSE, FALSE, name, "NIL",
-				       fetch_rfc822_text, NULL);
+		ctx->fetch_ctx->fetch_data |= MAIL_FETCH_STREAM_BODY;
+		ctx->fetch_ctx->flags_update_seen = TRUE;
+		imap_fetch_add_handler(ctx, 0, "NIL", fetch_rfc822_text, NULL);
 		return TRUE;
 	}
 
-	client_send_command_error(ctx->cmd, t_strconcat(
-		"Unknown parameter ", name, NULL));
+	ctx->error = t_strconcat("Unknown parameter ", name, NULL);
 	return FALSE;
 }
--- a/src/imap/imap-fetch.c	Sun May 20 03:08:01 2012 +0300
+++ b/src/imap/imap-fetch.c	Sun May 20 03:25:04 2012 +0300
@@ -45,24 +45,53 @@
 	return strcmp(name, h->name);
 }
 
-bool imap_fetch_init_handler(struct imap_fetch_context *ctx, const char *name,
-			     const struct imap_arg **args)
+bool imap_fetch_init_handler(struct imap_fetch_init_context *init_ctx)
 {
 	const struct imap_fetch_handler *handler;
 	const char *lookup_name, *p;
 
-	for (p = name; i_isalnum(*p) || *p == '-'; p++) ;
-	lookup_name = t_strdup_until(name, p);
+	for (p = init_ctx->name; i_isalnum(*p) || *p == '-'; p++) ;
+	lookup_name = t_strdup_until(init_ctx->name, p);
 
 	handler = array_bsearch(&fetch_handlers, lookup_name,
 				imap_fetch_handler_bsearch);
 	if (handler == NULL) {
-		client_send_command_error(ctx->cmd,
-			t_strconcat("Unknown parameter ", name, NULL));
+		init_ctx->error = t_strdup_printf("Unknown parameter: %s",
+						  init_ctx->name);
 		return FALSE;
 	}
+	return handler->init(init_ctx);
+}
 
-	return handler->init(ctx, name, args);
+void imap_fetch_init_nofail_handler(struct imap_fetch_context *ctx,
+				    bool (*init)(struct imap_fetch_init_context *))
+{
+	struct imap_fetch_init_context init_ctx;
+
+	memset(&init_ctx, 0, sizeof(init_ctx));
+	init_ctx.fetch_ctx = ctx;
+	if (!init(&init_ctx))
+		i_unreached();
+}
+
+bool imap_fetch_cmd_init_handler(struct imap_fetch_context *ctx,
+				 struct client_command_context *cmd,
+				 const char *name, const struct imap_arg **args)
+{
+	struct imap_fetch_init_context init_ctx;
+
+	memset(&init_ctx, 0, sizeof(init_ctx));
+	init_ctx.fetch_ctx = ctx;
+	init_ctx.name = name;
+	init_ctx.args = *args;
+
+	if (!imap_fetch_init_handler(&init_ctx)) {
+		i_assert(init_ctx.error != NULL);
+		client_send_command_error(cmd, init_ctx.error);
+		return FALSE;
+	}
+	*args = init_ctx.args;
+	return TRUE;
 }
 
 struct imap_fetch_context *
@@ -73,7 +102,7 @@
 
 	ctx = p_new(cmd->pool, struct imap_fetch_context, 1);
 	ctx->client = client;
-	ctx->cmd = cmd;
+	ctx->pool = cmd->pool;
 	ctx->box = box;
 
 	ctx->cur_str = str_new(default_pool, 8192);
@@ -85,7 +114,7 @@
 	return ctx;
 }
 
-bool imap_fetch_add_changed_since(struct imap_fetch_context *ctx,
+void imap_fetch_add_changed_since(struct imap_fetch_context *ctx,
 				  uint64_t modseq)
 {
 	struct mail_search_arg *search_arg;
@@ -93,26 +122,26 @@
 	search_arg = p_new(ctx->search_args->pool, struct mail_search_arg, 1);
 	search_arg->type = SEARCH_MODSEQ;
 	search_arg->value.modseq =
-		p_new(ctx->cmd->pool, struct mail_search_modseq, 1);
+		p_new(ctx->pool, struct mail_search_modseq, 1);
 	search_arg->value.modseq->modseq = modseq + 1;
 
 	search_arg->next = ctx->search_args->args->next;
 	ctx->search_args->args->next = search_arg;
 
-	return imap_fetch_init_handler(ctx, "MODSEQ", NULL);
+	imap_fetch_init_nofail_handler(ctx, imap_fetch_modseq_init);
 }
 
 #undef imap_fetch_add_handler
-void imap_fetch_add_handler(struct imap_fetch_context *ctx,
-			    bool buffered, bool want_deinit,
-			    const char *name, const char *nil_reply,
+void imap_fetch_add_handler(struct imap_fetch_init_context *ctx,
+			    enum imap_fetch_handler_flags flags,
+			    const char *nil_reply,
 			    imap_fetch_handler_t *handler, void *context)
 {
 	/* partially because of broken clients, but also partially because
 	   it potentially can make client implementations faster, we have a
 	   buffered parameter which basically means that the handler promises
-	   to write the output in ctx->cur_str. The cur_str is then sent to
-	   client before calling any non-buffered handlers.
+	   to write the output in fetch_ctx->cur_str. The cur_str is then sent
+	   to client before calling any non-buffered handlers.
 
 	   We try to keep the handler registration order the same as the
 	   client requested them. This is especially useful to get UID
@@ -123,7 +152,7 @@
 
 	if (context == NULL) {
 		/* don't allow duplicate handlers */
-		array_foreach(&ctx->handlers, ctx_handler) {
+		array_foreach(&ctx->fetch_ctx->handlers, ctx_handler) {
 			if (ctx_handler->handler == handler &&
 			    ctx_handler->context == NULL)
 				return;
@@ -133,17 +162,17 @@
 	memset(&h, 0, sizeof(h));
 	h.handler = handler;
 	h.context = context;
-	h.buffered = buffered;
-	h.want_deinit = want_deinit;
-	h.name = p_strdup(ctx->cmd->pool, name);
-	h.nil_reply = p_strdup(ctx->cmd->pool, nil_reply);
+	h.buffered = (flags & IMAP_FETCH_HANDLER_FLAG_BUFFERED) != 0;
+	h.want_deinit = (flags & IMAP_FETCH_HANDLER_FLAG_WANT_DEINIT) != 0;
+	h.name = p_strdup(ctx->fetch_ctx->pool, ctx->name);
+	h.nil_reply = p_strdup(ctx->fetch_ctx->pool, nil_reply);
 
-	if (!buffered)
-		array_append(&ctx->handlers, &h, 1);
+	if (!h.buffered)
+		array_append(&ctx->fetch_ctx->handlers, &h, 1);
 	else {
-		array_insert(&ctx->handlers, ctx->buffered_handlers_count,
-			     &h, 1);
-                ctx->buffered_handlers_count++;
+		array_insert(&ctx->fetch_ctx->handlers,
+			     ctx->fetch_ctx->buffered_handlers_count, &h, 1);
+                ctx->fetch_ctx->buffered_handlers_count++;
 	}
 }
 
@@ -312,7 +341,7 @@
 			ctx->flags_update_seen = FALSE;
 		else if (!ctx->flags_have_handler) {
 			ctx->flags_show_only_seen_changes = TRUE;
-			(void)imap_fetch_init_handler(ctx, "FLAGS", NULL);
+			imap_fetch_init_nofail_handler(ctx, imap_fetch_flags_init);
 		}
 	}
 
@@ -337,7 +366,7 @@
 
 	/* Delayed uidset -> seqset conversion. VANISHED needs the uidset. */
 	mail_search_args_init(ctx->search_args, ctx->box, TRUE,
-			      &ctx->cmd->client->search_saved_uidset);
+			      &ctx->client->search_saved_uidset);
 	ctx->search_ctx =
 		mailbox_search_init(ctx->trans, ctx->search_args, NULL,
 				    ctx->fetch_data, ctx->all_headers_ctx);
@@ -387,7 +416,8 @@
 	return 0;
 }
 
-static int imap_fetch_more_int(struct imap_fetch_context *ctx)
+static int imap_fetch_more_int(struct imap_fetch_context *ctx,
+			       struct client_command_context *cmd)
 {
 	struct client *client = ctx->client;
 	const struct imap_fetch_context_handler *handlers;
@@ -430,7 +460,7 @@
 		}
 
 		if (ctx->cur_mail == NULL) {
-			if (ctx->cmd->cancel)
+			if (cmd->cancel)
 				return 1;
 
 			if (!mailbox_search_next(ctx->search_ctx,
@@ -503,19 +533,20 @@
 	return 1;
 }
 
-int imap_fetch_more(struct imap_fetch_context *ctx)
+int imap_fetch_more(struct imap_fetch_context *ctx,
+		    struct client_command_context *cmd)
 {
 	int ret;
 
 	i_assert(ctx->client->output_lock == NULL ||
-		 ctx->client->output_lock == ctx->cmd);
+		 ctx->client->output_lock == cmd);
 
-	ret = imap_fetch_more_int(ctx);
+	ret = imap_fetch_more_int(ctx, cmd);
 	if (ret < 0)
 		ctx->failed = TRUE;
 	if (ctx->line_partial) {
 		/* nothing can be sent until FETCH is finished */
-		ctx->client->output_lock = ctx->cmd;
+		ctx->client->output_lock = cmd;
 	}
 	return ret;
 }
@@ -580,16 +611,15 @@
 	return 1;
 }
 
-static bool fetch_body_init(struct imap_fetch_context *ctx, const char *name,
-			    const struct imap_arg **args)
+static bool fetch_body_init(struct imap_fetch_init_context *ctx)
 {
-	if (name[4] == '\0') {
-		ctx->fetch_data |= MAIL_FETCH_IMAP_BODY;
-		imap_fetch_add_handler(ctx, FALSE, FALSE, name,
-				       "("BODY_NIL_REPLY")", fetch_body, NULL);
+	if (ctx->name[4] == '\0') {
+		ctx->fetch_ctx->fetch_data |= MAIL_FETCH_IMAP_BODY;
+		imap_fetch_add_handler(ctx, 0, "("BODY_NIL_REPLY")",
+				       fetch_body, NULL);
 		return TRUE;
 	}
-	return fetch_body_section_init(ctx, name, args);
+	return imap_fetch_body_section_init(ctx);
 }
 
 static int fetch_bodystructure(struct imap_fetch_context *ctx,
@@ -616,13 +646,10 @@
 	return 1;
 }
 
-static bool
-fetch_bodystructure_init(struct imap_fetch_context *ctx, const char *name,
-			 const struct imap_arg **args ATTR_UNUSED)
+static bool fetch_bodystructure_init(struct imap_fetch_init_context *ctx)
 {
-	ctx->fetch_data |= MAIL_FETCH_IMAP_BODYSTRUCTURE;
-	imap_fetch_add_handler(ctx, FALSE, FALSE, name,
-			       "("BODY_NIL_REPLY" NIL NIL NIL NIL)",
+	ctx->fetch_ctx->fetch_data |= MAIL_FETCH_IMAP_BODYSTRUCTURE;
+	imap_fetch_add_handler(ctx, 0, "("BODY_NIL_REPLY" NIL NIL NIL NIL)",
 			       fetch_bodystructure, NULL);
 	return TRUE;
 }
@@ -649,12 +676,10 @@
 	return 1;
 }
 
-static bool
-fetch_envelope_init(struct imap_fetch_context *ctx, const char *name,
-		    const struct imap_arg **args ATTR_UNUSED)
+static bool fetch_envelope_init(struct imap_fetch_init_context *ctx)
 {
-	ctx->fetch_data |= MAIL_FETCH_IMAP_ENVELOPE;
-	imap_fetch_add_handler(ctx, FALSE, FALSE, name, ENVELOPE_NIL_REPLY,
+	ctx->fetch_ctx->fetch_data |= MAIL_FETCH_IMAP_ENVELOPE;
+	imap_fetch_add_handler(ctx, 0, ENVELOPE_NIL_REPLY,
 			       fetch_envelope, NULL);
 	return TRUE;
 }
@@ -684,13 +709,12 @@
 	return 1;
 }
 
-static bool
-fetch_flags_init(struct imap_fetch_context *ctx, const char *name,
-		 const struct imap_arg **args ATTR_UNUSED)
+bool imap_fetch_flags_init(struct imap_fetch_init_context *ctx)
 {
-	ctx->flags_have_handler = TRUE;
-	ctx->fetch_data |= MAIL_FETCH_FLAGS;
-	imap_fetch_add_handler(ctx, TRUE, FALSE, name, "()", fetch_flags, NULL);
+	ctx->fetch_ctx->flags_have_handler = TRUE;
+	ctx->fetch_ctx->fetch_data |= MAIL_FETCH_FLAGS;
+	imap_fetch_add_handler(ctx, IMAP_FETCH_HANDLER_FLAG_BUFFERED,
+			       "()", fetch_flags, NULL);
 	return TRUE;
 }
 
@@ -707,12 +731,10 @@
 	return 1;
 }
 
-static bool
-fetch_internaldate_init(struct imap_fetch_context *ctx, const char *name,
-			const struct imap_arg **args ATTR_UNUSED)
+static bool fetch_internaldate_init(struct imap_fetch_init_context *ctx)
 {
-	ctx->fetch_data |= MAIL_FETCH_RECEIVED_DATE;
-	imap_fetch_add_handler(ctx, TRUE, FALSE, name,
+	ctx->fetch_ctx->fetch_data |= MAIL_FETCH_RECEIVED_DATE;
+	imap_fetch_add_handler(ctx, IMAP_FETCH_HANDLER_FLAG_BUFFERED,
 			       "\"01-Jan-1970 00:00:00 +0000\"",
 			       fetch_internaldate, NULL);
 	return TRUE;
@@ -731,13 +753,11 @@
 	return 1;
 }
 
-static bool
-fetch_modseq_init(struct imap_fetch_context *ctx, const char *name,
-		  const struct imap_arg **args ATTR_UNUSED)
+bool imap_fetch_modseq_init(struct imap_fetch_init_context *ctx)
 {
-	(void)client_enable(ctx->client, MAILBOX_FEATURE_CONDSTORE);
-	imap_fetch_add_handler(ctx, TRUE, FALSE, name, NULL,
-			       fetch_modseq, NULL);
+	(void)client_enable(ctx->fetch_ctx->client, MAILBOX_FEATURE_CONDSTORE);
+	imap_fetch_add_handler(ctx, IMAP_FETCH_HANDLER_FLAG_BUFFERED,
+			       NULL, fetch_modseq, NULL);
 	return TRUE;
 }
 
@@ -748,11 +768,10 @@
 	return 1;
 }
 
-static bool
-fetch_uid_init(struct imap_fetch_context *ctx ATTR_UNUSED, const char *name,
-	       const struct imap_arg **args ATTR_UNUSED)
+bool imap_fetch_uid_init(struct imap_fetch_init_context *ctx)
 {
-	imap_fetch_add_handler(ctx, TRUE, FALSE, name, NULL, fetch_uid, NULL);
+	imap_fetch_add_handler(ctx, IMAP_FETCH_HANDLER_FLAG_BUFFERED,
+			       NULL, fetch_uid, NULL);
 	return TRUE;
 }
 
@@ -770,12 +789,11 @@
 	return 1;
 }
 
-static bool
-fetch_guid_init(struct imap_fetch_context *ctx ATTR_UNUSED, const char *name,
-		const struct imap_arg **args ATTR_UNUSED)
+static bool fetch_guid_init(struct imap_fetch_init_context *ctx)
 {
-	ctx->fetch_data |= MAIL_FETCH_GUID;
-	imap_fetch_add_handler(ctx, TRUE, FALSE, name, "", fetch_guid, NULL);
+	ctx->fetch_ctx->fetch_data |= MAIL_FETCH_GUID;
+	imap_fetch_add_handler(ctx, IMAP_FETCH_HANDLER_FLAG_BUFFERED,
+			       "", fetch_guid, NULL);
 	return TRUE;
 }
 
@@ -798,13 +816,10 @@
 	return 1;
 }
 
-static bool
-fetch_x_mailbox_init(struct imap_fetch_context *ctx ATTR_UNUSED,
-		     const char *name,
-		     const struct imap_arg **args ATTR_UNUSED)
+static bool fetch_x_mailbox_init(struct imap_fetch_init_context *ctx)
 {
-	imap_fetch_add_handler(ctx, TRUE, FALSE, name, NULL,
-			       fetch_x_mailbox, NULL);
+	imap_fetch_add_handler(ctx, IMAP_FETCH_HANDLER_FLAG_BUFFERED,
+			       NULL, fetch_x_mailbox, NULL);
 	return TRUE;
 }
 
@@ -816,13 +831,10 @@
 	return 1;
 }
 
-static bool
-fetch_x_real_uid_init(struct imap_fetch_context *ctx ATTR_UNUSED,
-		      const char *name,
-		      const struct imap_arg **args ATTR_UNUSED)
+static bool fetch_x_real_uid_init(struct imap_fetch_init_context *ctx)
 {
-	imap_fetch_add_handler(ctx, TRUE, FALSE, name, NULL,
-			       fetch_x_real_uid, NULL);
+	imap_fetch_add_handler(ctx, IMAP_FETCH_HANDLER_FLAG_BUFFERED,
+			       NULL, fetch_x_real_uid, NULL);
 	return TRUE;
 }
 
@@ -839,12 +851,10 @@
 	return 1;
 }
 
-static bool
-fetch_x_savedate_init(struct imap_fetch_context *ctx, const char *name,
-		      const struct imap_arg **args ATTR_UNUSED)
+static bool fetch_x_savedate_init(struct imap_fetch_init_context *ctx)
 {
-	ctx->fetch_data |= MAIL_FETCH_SAVE_DATE;
-	imap_fetch_add_handler(ctx, TRUE, FALSE, name,
+	ctx->fetch_ctx->fetch_data |= MAIL_FETCH_SAVE_DATE;
+	imap_fetch_add_handler(ctx, IMAP_FETCH_HANDLER_FLAG_BUFFERED,
 			       "\"01-Jan-1970 00:00:00 +0000\"",
 			       fetch_x_savedate, NULL);
 	return TRUE;
@@ -855,11 +865,11 @@
 	{ "BODY", fetch_body_init },
 	{ "BODYSTRUCTURE", fetch_bodystructure_init },
 	{ "ENVELOPE", fetch_envelope_init },
-	{ "FLAGS", fetch_flags_init },
+	{ "FLAGS", imap_fetch_flags_init },
 	{ "INTERNALDATE", fetch_internaldate_init },
-	{ "MODSEQ", fetch_modseq_init },
-	{ "RFC822", fetch_rfc822_init },
-	{ "UID", fetch_uid_init },
+	{ "MODSEQ", imap_fetch_modseq_init },
+	{ "RFC822", imap_fetch_rfc822_init },
+	{ "UID", imap_fetch_uid_init },
 	{ "X-GUID", fetch_guid_init },
 	{ "X-MAILBOX", fetch_x_mailbox_init },
 	{ "X-REAL-UID", fetch_x_real_uid_init },
--- a/src/imap/imap-fetch.h	Sun May 20 03:08:01 2012 +0300
+++ b/src/imap/imap-fetch.h	Sun May 20 03:25:04 2012 +0300
@@ -3,17 +3,29 @@
 
 struct imap_fetch_context;
 
+enum imap_fetch_handler_flags {
+	IMAP_FETCH_HANDLER_FLAG_BUFFERED	= 0x01,
+	IMAP_FETCH_HANDLER_FLAG_WANT_DEINIT	= 0x02
+};
+
 /* Returns 1 = ok, 0 = client output buffer full, call again, -1 = error.
    mail = NULL for deinit. */
 typedef int imap_fetch_handler_t(struct imap_fetch_context *ctx,
 				 struct mail *mail, void *context);
 
+struct imap_fetch_init_context {
+	struct imap_fetch_context *fetch_ctx;
+	const char *name;
+	const struct imap_arg *args;
+
+	const char *error;
+};
+
 struct imap_fetch_handler {
 	const char *name;
 
-	/* Returns FALSE if arg is invalid. */
-	bool (*init)(struct imap_fetch_context *ctx, const char *name,
-		     const struct imap_arg **args);
+	/* Returns FALSE and sets ctx->error if arg is invalid */
+	bool (*init)(struct imap_fetch_init_context *ctx);
 };
 
 struct imap_fetch_context_handler {
@@ -29,8 +41,8 @@
 
 struct imap_fetch_context {
 	struct client *client;
-	struct client_command_context *cmd;
 	struct mailbox *box;
+	pool_t pool;
 
 	struct mailbox_transaction_context *trans;
 	struct mail_search_args *search_args;
@@ -76,40 +88,45 @@
 void imap_fetch_handlers_register(const struct imap_fetch_handler *handlers,
 				  size_t count);
 
-void imap_fetch_add_handler(struct imap_fetch_context *ctx,
-			    bool buffered, bool want_deinit,
-			    const char *name, const char *nil_reply,
+void imap_fetch_add_handler(struct imap_fetch_init_context *ctx,
+			    enum imap_fetch_handler_flags flags,
+			    const char *nil_reply,
 			    imap_fetch_handler_t *handler, void *context);
 #ifdef CONTEXT_TYPE_SAFETY
-#  define imap_fetch_add_handler(ctx, buffered, want_deinit, name, nil_reply, \
-				 handler, context) \
+#  define imap_fetch_add_handler(ctx, flags, nil_reply, handler, context) \
 	({(void)(1 ? 0 : handler((struct imap_fetch_context *)NULL, \
 				 (struct mail *)NULL, context)); \
-	  imap_fetch_add_handler(ctx, buffered, want_deinit, name, nil_reply, \
+	  imap_fetch_add_handler(ctx, flags, nil_reply, \
 		(imap_fetch_handler_t *)handler, context); })
 #else
-#  define imap_fetch_add_handler(ctx, buffered, want_deinit, name, nil_reply, \
-				 handler, context) \
-	  imap_fetch_add_handler(ctx, buffered, want_deinit, name, nil_reply, \
+#  define imap_fetch_add_handler(ctx, flags, nil_reply, handler, context) \
+	  imap_fetch_add_handler(ctx, flags, nil_reply, \
 		(imap_fetch_handler_t *)handler, context)
 #endif
 
 struct imap_fetch_context *
 imap_fetch_init(struct client_command_context *cmd, struct mailbox *box);
 int imap_fetch_deinit(struct imap_fetch_context *ctx);
-bool imap_fetch_init_handler(struct imap_fetch_context *ctx, const char *name,
-			     const struct imap_arg **args);
+bool imap_fetch_init_handler(struct imap_fetch_init_context *init_ctx);
+void imap_fetch_init_nofail_handler(struct imap_fetch_context *ctx,
+				    bool (*init)(struct imap_fetch_init_context *));
+bool imap_fetch_cmd_init_handler(struct imap_fetch_context *ctx,
+				 struct client_command_context *cmd,
+				 const char *name, const struct imap_arg **args);
 
-bool imap_fetch_add_changed_since(struct imap_fetch_context *ctx,
+void imap_fetch_add_changed_since(struct imap_fetch_context *ctx,
 				  uint64_t modseq);
 
 int imap_fetch_begin(struct imap_fetch_context *ctx);
-int imap_fetch_more(struct imap_fetch_context *ctx);
+int imap_fetch_more(struct imap_fetch_context *ctx,
+		    struct client_command_context *cmd);
 
-bool fetch_body_section_init(struct imap_fetch_context *ctx, const char *name,
-			     const struct imap_arg **args);
-bool fetch_rfc822_init(struct imap_fetch_context *ctx, const char *name,
-		       const struct imap_arg **args);
+bool imap_fetch_flags_init(struct imap_fetch_init_context *ctx);
+bool imap_fetch_modseq_init(struct imap_fetch_init_context *ctx);
+bool imap_fetch_uid_init(struct imap_fetch_init_context *ctx);
+
+bool imap_fetch_body_section_init(struct imap_fetch_init_context *ctx);
+bool imap_fetch_rfc822_init(struct imap_fetch_init_context *ctx);
 
 void imap_fetch_handlers_init(void);
 void imap_fetch_handlers_deinit(void);
--- a/src/lib-dns/dns-lookup.c	Sun May 20 03:08:01 2012 +0300
+++ b/src/lib-dns/dns-lookup.c	Sun May 20 03:25:04 2012 +0300
@@ -121,6 +121,7 @@
 
 #undef dns_lookup
 int dns_lookup(const char *host, const struct dns_lookup_settings *set,
+	       struct dns_lookup **lookup_r,
 	       dns_lookup_callback_t *callback, void *context)
 {
 	struct dns_lookup *lookup;
@@ -162,6 +163,8 @@
 	lookup->context = context;
 	if (gettimeofday(&lookup->start_time, NULL) < 0)
 		i_fatal("gettimeofday() failed: %m");
+
+	*lookup_r = lookup;
 	return 0;
 }
 
@@ -182,3 +185,8 @@
 	i_free(lookup->path);
 	i_free(lookup);
 }
+
+void dns_lookup_abort(struct dns_lookup **lookup)
+{
+	dns_lookup_free(lookup);
+}
--- a/src/lib-dns/dns-lookup.h	Sun May 20 03:08:01 2012 +0300
+++ b/src/lib-dns/dns-lookup.h	Sun May 20 03:25:04 2012 +0300
@@ -1,6 +1,8 @@
 #ifndef DNS_LOOKUP_H
 #define DNS_LOOKUP_H
 
+struct dns_lookup;
+
 struct dns_lookup_settings {
 	const char *dns_client_socket_path;
 	unsigned int timeout_msecs;
@@ -22,11 +24,18 @@
 typedef void dns_lookup_callback_t(const struct dns_lookup_result *result,
 				   void *context);
 
+/* Do asynchronous DNS lookup via dns-client UNIX socket. Returns 0 if lookup
+   started, -1 if there was an error communicating with the UNIX socket.
+   When failing with -1, the callback is called before returning from the
+   function. */
 int dns_lookup(const char *host, const struct dns_lookup_settings *set,
+	       struct dns_lookup **lookup_r,
 	       dns_lookup_callback_t *callback, void *context);
-#define dns_lookup(host, set, callback, context) \
+#define dns_lookup(host, set, callback, context, lookup_r) \
 	CONTEXT_CALLBACK2(dns_lookup, dns_lookup_callback_t, \
 			  callback, const struct dns_lookup_result *, \
-			  context, host, set)
+			  context, host, set, lookup_r)
+/* Abort the DNS lookup without calling the callback. */
+void dns_lookup_abort(struct dns_lookup **lookup);
 
 #endif
--- a/src/lib-imap-client/imapc-connection.c	Sun May 20 03:08:01 2012 +0300
+++ b/src/lib-imap-client/imapc-connection.c	Sun May 20 03:25:04 2012 +0300
@@ -86,6 +86,7 @@
 	struct imap_parser *parser;
 	struct timeout *to;
 	struct timeout *to_output;
+	struct dns_lookup *dns_lookup;
 
 	struct ssl_iostream *ssl_iostream;
 
@@ -352,6 +353,8 @@
 	if (conn->client->set.debug)
 		i_debug("imapc(%s): Disconnected", conn->name);
 
+	if (conn->dns_lookup != NULL)
+		dns_lookup_abort(&conn->dns_lookup);
 	imapc_connection_lfiles_free(conn);
 	imapc_connection_literal_reset(&conn->literal);
 	if (conn->to != NULL)
@@ -1315,9 +1318,9 @@
 
 static void
 imapc_connection_dns_callback(const struct dns_lookup_result *result,
-			      void *context)
+			      struct imapc_connection *conn)
 {
-	struct imapc_connection *conn = context;
+	conn->dns_lookup = NULL;
 
 	if (result->ret != 0) {
 		i_error("imapc(%s): dns_lookup(%s) failed: %s",
@@ -1385,7 +1388,8 @@
 
 	if (conn->ips_count == 0) {
 		(void)dns_lookup(conn->client->set.host, &dns_set,
-				 imapc_connection_dns_callback, conn);
+				 imapc_connection_dns_callback, conn,
+				 &conn->dns_lookup);
 	} else {
 		imapc_connection_connect_next_ip(conn);
 	}
--- a/src/lib-index/mail-index-fsck.c	Sun May 20 03:08:01 2012 +0300
+++ b/src/lib-index/mail-index-fsck.c	Sun May 20 03:25:04 2012 +0300
@@ -454,8 +454,7 @@
 		mail_index_fsck_map(index, map);
 	} T_END;
 
-	map->write_base_header = TRUE;
-	map->write_atomic = TRUE;
+	map->header_changed = TRUE;
 	mail_index_write(index, FALSE);
 
 	if (!orig_locked)
--- a/src/lib-index/mail-index-lock.c	Sun May 20 03:08:01 2012 +0300
+++ b/src/lib-index/mail-index-lock.c	Sun May 20 03:25:04 2012 +0300
@@ -36,27 +36,15 @@
 			      timeout_secs, lock_r);
 }
 
-static int mail_index_lock(struct mail_index *index, int lock_type,
+static int mail_index_lock(struct mail_index *index,
 			   unsigned int timeout_secs, unsigned int *lock_id_r)
 {
 	int ret;
 
-	i_assert(lock_type == F_RDLCK || lock_type == F_WRLCK);
-
-	if (lock_type == F_RDLCK && index->lock_type != F_UNLCK) {
+	if (index->lock_type != F_UNLCK) {
+		/* file is already locked */
 		index->shared_lock_count++;
 		*lock_id_r = index->lock_id_counter;
-		ret = 1;
-	} else if (lock_type == F_WRLCK && index->lock_type == F_WRLCK) {
-		index->excl_lock_count++;
-		*lock_id_r = index->lock_id_counter + 1;
-		ret = 1;
-	} else {
-		ret = 0;
-	}
-
-	if (ret > 0) {
-		/* file is already locked */
 		return 1;
 	}
 
@@ -65,39 +53,25 @@
 		/* FIXME: exclusive locking will rewrite the index file every
 		   time. shouldn't really be needed.. reading doesn't require
 		   locks then, though */
-		if (lock_type == F_WRLCK)
-			return 0;
-
 		index->shared_lock_count++;
 		index->lock_type = F_RDLCK;
 		*lock_id_r = index->lock_id_counter;
 		return 1;
 	}
 
-	if (index->file_lock == NULL) {
-		i_assert(index->lock_type == F_UNLCK);
-		ret = mail_index_lock_fd(index, index->filepath, index->fd,
-					 lock_type, timeout_secs,
-					 &index->file_lock);
-	} else {
-		i_assert(index->lock_type == F_RDLCK && lock_type == F_WRLCK);
-		ret = file_lock_try_update(index->file_lock, lock_type);
-	}
+	i_assert(index->lock_type == F_UNLCK);
+	ret = mail_index_lock_fd(index, index->filepath, index->fd,
+				 F_RDLCK, timeout_secs,
+				 &index->file_lock);
 	if (ret <= 0)
 		return ret;
 
 	if (index->lock_type == F_UNLCK)
 		index->lock_id_counter += 2;
-	index->lock_type = lock_type;
+	index->lock_type = F_RDLCK;
 
-	if (lock_type == F_RDLCK) {
-		index->shared_lock_count++;
-		*lock_id_r = index->lock_id_counter;
-	} else {
-		index->excl_lock_count++;
-		*lock_id_r = index->lock_id_counter + 1;
-	}
-
+	index->shared_lock_count++;
+	*lock_id_r = index->lock_id_counter;
 	return 1;
 }
 
@@ -125,7 +99,7 @@
 
 	timeout_secs = I_MIN(MAIL_INDEX_SHARED_LOCK_TIMEOUT,
 			     index->max_lock_timeout_secs);
-	ret = mail_index_lock(index, F_RDLCK, timeout_secs, lock_id_r);
+	ret = mail_index_lock(index, timeout_secs, lock_id_r);
 	if (ret > 0) {
 		mail_index_flush_read_cache(index, index->filepath,
 					    index->fd, TRUE);
@@ -141,48 +115,23 @@
 	return -1;
 }
 
-int mail_index_try_lock_exclusive(struct mail_index *index,
-				  unsigned int *lock_id_r)
-{
-	int ret;
-
-	if ((ret = mail_index_lock(index, F_WRLCK, 0, lock_id_r)) > 0) {
-		mail_index_flush_read_cache(index, index->filepath,
-					    index->fd, TRUE);
-	}
-	return ret;
-}
-
 void mail_index_unlock(struct mail_index *index, unsigned int *_lock_id)
 {
 	unsigned int lock_id = *_lock_id;
 
 	*_lock_id = 0;
 
-	if ((lock_id & 1) == 0) {
-		/* shared lock */
-		if (!mail_index_is_locked(index, lock_id)) {
-			/* unlocking some older generation of the index file.
-			   we've already closed the file so just ignore this. */
-			return;
-		}
-
-		i_assert(index->shared_lock_count > 0);
-		index->shared_lock_count--;
-	} else {
-		/* exclusive lock */
-		i_assert(lock_id == index->lock_id_counter + 1);
-		i_assert(index->excl_lock_count > 0);
-		i_assert(index->lock_type == F_WRLCK);
-		if (--index->excl_lock_count == 0 &&
-		    index->shared_lock_count > 0) {
-			/* drop back to a shared lock. */
-			index->lock_type = F_RDLCK;
-			(void)file_lock_try_update(index->file_lock, F_RDLCK);
-		}
+	/* shared lock */
+	if (!mail_index_is_locked(index, lock_id)) {
+		/* unlocking some older generation of the index file.
+		   we've already closed the file so just ignore this. */
+		return;
 	}
 
-	if (index->shared_lock_count == 0 && index->excl_lock_count == 0) {
+	i_assert(index->shared_lock_count > 0);
+	index->shared_lock_count--;
+
+	if (index->shared_lock_count == 0) {
 		index->lock_id_counter += 2;
 		index->lock_type = F_UNLCK;
 		if (index->lock_method != FILE_LOCK_METHOD_DOTLOCK) {
--- a/src/lib-index/mail-index-map-read.c	Sun May 20 03:08:01 2012 +0300
+++ b/src/lib-index/mail-index-map-read.c	Sun May 20 03:25:04 2012 +0300
@@ -400,7 +400,6 @@
 {
 	int ret;
 
-	i_assert(index->lock_type != F_WRLCK);
 	i_assert(!index->mapping);
 
 	index->mapping = TRUE;
--- a/src/lib-index/mail-index-map.c	Sun May 20 03:08:01 2012 +0300
+++ b/src/lib-index/mail-index-map.c	Sun May 20 03:25:04 2012 +0300
@@ -339,10 +339,7 @@
 	dest->records = buffer_get_modifiable_data(dest->buffer, NULL);
 	dest->records_count = src->records_count;
 
-	/* if the map is ever written back to disk, we need to keep track of
-	   what has changed. */
-	dest->write_seq_first = src->write_seq_first;
-	dest->write_seq_last = src->write_seq_last;
+	dest->records_changed = src->records_changed;
 }
 
 static void mail_index_map_copy_header(struct mail_index_map *dest,
@@ -405,9 +402,7 @@
 
 	mail_index_map_copy_header(mem_map, map);
 
-	mem_map->write_atomic = map->write_atomic;
-	mem_map->write_base_header = map->write_base_header;
-	mem_map->write_ext_header = map->write_ext_header;
+	mem_map->header_changed = map->header_changed;
 
 	/* copy extensions */
 	if (array_is_created(&map->ext_id_map)) {
--- a/src/lib-index/mail-index-modseq.c	Sun May 20 03:08:01 2012 +0300
+++ b/src/lib-index/mail-index-modseq.c	Sun May 20 03:25:04 2012 +0300
@@ -463,8 +463,7 @@
 			} T_END;
 		}
 	}
-	mail_index_sync_write_seq_update(ctx->sync_map_ctx, 1,
-					 map->hdr.messages_count);
+	map->rec_map->records_changed = TRUE;
 	mail_transaction_log_view_close(&ctx->log_view);
 }
 
@@ -515,7 +514,7 @@
 		buffer_write(map->hdr_copy_buf, ext->hdr_offset,
 			     &new_modseq_hdr, sizeof(new_modseq_hdr));
 		map->hdr_base = map->hdr_copy_buf->data;
-		map->write_ext_header = TRUE;
+		map->header_changed = TRUE;
 	}
 }
 
--- a/src/lib-index/mail-index-private.h	Sun May 20 03:08:01 2012 +0300
+++ b/src/lib-index/mail-index-private.h	Sun May 20 03:25:04 2012 +0300
@@ -129,10 +129,8 @@
 	struct mail_index_map_modseq *modseq;
 	uint32_t last_appended_uid;
 
-	/* If this mapping is written to disk and write_atomic=FALSE,
-	   write_seq_* specify the message sequence range that needs to be
-	   written. */
-	uint32_t write_seq_first, write_seq_last;
+	/* The records have changed since it was read */
+	bool records_changed;
 };
 
 struct mail_index_map {
@@ -151,9 +149,7 @@
 
 	struct mail_index_record_map *rec_map;
 
-	unsigned int write_base_header:1;
-	unsigned int write_ext_header:1;
-	unsigned int write_atomic:1; /* write to a new file and rename() */
+	unsigned int header_changed:1;
 };
 
 struct mail_index_module_register {
@@ -209,7 +205,7 @@
 	/* syncing will update this if non-NULL */
 	struct mail_index_transaction_commit_result *sync_commit_result;
 
-	int lock_type, shared_lock_count, excl_lock_count;
+	int lock_type, shared_lock_count;
 	unsigned int lock_id_counter;
 	enum file_lock_method lock_method;
 	unsigned int max_lock_timeout_secs;
@@ -279,9 +275,6 @@
 
 /* Returns 0 = ok, -1 = error. */
 int mail_index_lock_shared(struct mail_index *index, unsigned int *lock_id_r);
-/* Returns 1 = ok, 0 = already locked, -1 = error. */
-int mail_index_try_lock_exclusive(struct mail_index *index,
-				  unsigned int *lock_id_r);
 void mail_index_unlock(struct mail_index *index, unsigned int *lock_id);
 /* Returns TRUE if given lock_id is valid. */
 bool mail_index_is_locked(struct mail_index *index, unsigned int lock_id);
--- a/src/lib-index/mail-index-sync-ext.c	Sun May 20 03:08:01 2012 +0300
+++ b/src/lib-index/mail-index-sync-ext.c	Sun May 20 03:25:04 2012 +0300
@@ -310,7 +310,7 @@
 		i_assert((map->hdr_copy_buf->used % sizeof(uint64_t)) == 0);
 		map->hdr_base = map->hdr_copy_buf->data;
 		map->hdr.header_size = map->hdr_copy_buf->used;
-		map->write_base_header = map->write_ext_header = TRUE;
+		map->header_changed = TRUE;
 
 		ext_hdr = get_ext_header(map, ext);
 		ext_hdr->reset_id = ext->reset_id;
@@ -570,8 +570,7 @@
 		memset(PTR_OFFSET(rec, ext->record_offset), 0,
 		       ext->record_size);
 	}
-	map->rec_map->write_seq_first = 1;
-	map->rec_map->write_seq_last = view->map->rec_map->records_count;
+	map->rec_map->records_changed = TRUE;
 }
 
 int mail_index_sync_ext_reset(struct mail_index_sync_map_ctx *ctx,
@@ -635,7 +634,7 @@
 	if (ext->index_idx == ctx->view->index->modseq_ext_id)
 		mail_index_modseq_hdr_update(ctx->modseq_ctx);
 
-	map->write_ext_header = TRUE;
+	map->header_changed = TRUE;
 	return 1;
 }
 
@@ -684,7 +683,7 @@
 			return ret;
 	}
 
-	mail_index_sync_write_seq_update(ctx, seq, seq);
+	view->map->rec_map->records_changed = TRUE;
 
 	/* @UNSAFE */
 	memcpy(old_data, u + 1, ext->record_size);
@@ -786,6 +785,6 @@
 		return -1;
 	}
 
-	mail_index_sync_write_seq_update(ctx, seq, seq);
+	view->map->rec_map->records_changed = TRUE;
 	return 1;
 }
--- a/src/lib-index/mail-index-sync-keywords.c	Sun May 20 03:08:01 2012 +0300
+++ b/src/lib-index/mail-index-sync-keywords.c	Sun May 20 03:25:04 2012 +0300
@@ -215,7 +215,7 @@
 	if (!mail_index_lookup_seq_range(view, uid1, uid2, &seq1, &seq2))
 		return 1;
 
-	mail_index_sync_write_seq_update(ctx, seq1, seq2);
+	view->map->rec_map->records_changed = TRUE;
 	mail_index_modseq_update_keyword(ctx->modseq_ctx, keyword_idx,
 					  seq1, seq2);
 
@@ -337,7 +337,7 @@
 						 &seq1, &seq2))
 			continue;
 
-		mail_index_sync_write_seq_update(ctx, seq1, seq2);
+		map->rec_map->records_changed = TRUE;
 		mail_index_modseq_reset_keywords(ctx->modseq_ctx, seq1, seq2);
 		for (seq1--; seq1 < seq2; seq1++) {
 			rec = MAIL_INDEX_MAP_IDX(map, seq1);
--- a/src/lib-index/mail-index-sync-private.h	Sun May 20 03:08:01 2012 +0300
+++ b/src/lib-index/mail-index-sync-private.h	Sun May 20 03:25:04 2012 +0300
@@ -60,8 +60,6 @@
 
 struct mail_index_map *
 mail_index_sync_get_atomic_map(struct mail_index_sync_map_ctx *ctx);
-void mail_index_sync_write_seq_update(struct mail_index_sync_map_ctx *ctx,
-				      uint32_t seq1, uint32_t seq2);
 
 void mail_index_sync_init_expunge_handlers(struct mail_index_sync_map_ctx *ctx);
 void
--- a/src/lib-index/mail-index-sync-update.c	Sun May 20 03:08:01 2012 +0300
+++ b/src/lib-index/mail-index-sync-update.c	Sun May 20 03:25:04 2012 +0300
@@ -95,7 +95,6 @@
 	mail_index_sync_move_to_private_memory(ctx);
 	mail_index_record_map_move_to_private(ctx->view->map);
 	mail_index_modseq_sync_map_replaced(ctx->modseq_ctx);
-	ctx->view->map->write_atomic = TRUE;
 	return ctx->view->map;
 }
 
@@ -344,18 +343,6 @@
 	return 1;
 }
 
-void mail_index_sync_write_seq_update(struct mail_index_sync_map_ctx *ctx,
-				      uint32_t seq1, uint32_t seq2)
-{
-	struct mail_index_map *map = ctx->view->map;
-
-	if (map->rec_map->write_seq_first == 0 ||
-	    map->rec_map->write_seq_first > seq1)
-		map->rec_map->write_seq_first = seq1;
-	if (map->rec_map->write_seq_last < seq2)
-		map->rec_map->write_seq_last = seq2;
-}
-
 static int sync_append(const struct mail_index_record *rec,
 		       struct mail_index_sync_map_ctx *ctx)
 {
@@ -396,9 +383,7 @@
 		map->rec_map->last_appended_uid = rec->uid;
 		new_flags = rec->flags;
 
-		mail_index_sync_write_seq_update(ctx,
-						 map->rec_map->records_count,
-						 map->rec_map->records_count);
+		map->rec_map->records_changed = TRUE;
 		mail_index_modseq_append(ctx->modseq_ctx,
 					 map->rec_map->records_count);
 	}
@@ -426,7 +411,7 @@
 	if (!mail_index_lookup_seq_range(view, u->uid1, u->uid2, &seq1, &seq2))
 		return 1;
 
-	mail_index_sync_write_seq_update(ctx, seq1, seq2);
+	view->map->rec_map->records_changed = TRUE;
 	if (!MAIL_TRANSACTION_FLAG_UPDATE_IS_INTERNAL(u)) {
 		mail_index_modseq_update_flags(ctx->modseq_ctx,
 					       u->add_flags | u->remove_flags,
@@ -483,7 +468,7 @@
 
 	buffer_write(map->hdr_copy_buf, u->offset, u + 1, u->size);
 	map->hdr_base = map->hdr_copy_buf->data;
-	map->write_base_header = TRUE;
+	map->header_changed = TRUE;
 
 	/* @UNSAFE */
 	if ((uint32_t)(u->offset + u->size) <= sizeof(map->hdr)) {
@@ -979,7 +964,7 @@
 	had_dirty = (map->hdr.flags & MAIL_INDEX_HDR_FLAG_HAVE_DIRTY) != 0;
 	if (had_dirty) {
 		map->hdr.flags &= ~MAIL_INDEX_HDR_FLAG_HAVE_DIRTY;
-		map->write_base_header = TRUE;
+		map->header_changed = TRUE;
 	}
 
 	if (map->hdr_base != map->hdr_copy_buf->data) {
--- a/src/lib-index/mail-index-write.c	Sun May 20 03:08:01 2012 +0300
+++ b/src/lib-index/mail-index-write.c	Sun May 20 03:25:04 2012 +0300
@@ -119,152 +119,21 @@
 	return ret;
 }
 
-static int mail_index_write_map_over(struct mail_index *index)
-{
-	struct mail_index_map *map = index->map;
-	struct mail_index_record_map *rec_map = map->rec_map;
-	unsigned int base_size;
-
-	if (MAIL_INDEX_IS_IN_MEMORY(index))
-		return 0;
-
-	/* write extended headers */
-	if (map->write_ext_header) {
-		base_size = map->hdr.base_header_size;
-		if (pwrite_full(index->fd,
-				CONST_PTR_OFFSET(map->hdr_base, base_size),
-				map->hdr.header_size - base_size,
-				base_size) < 0)
-			return -1;
-	}
-
-	/* write records. */
-	if (rec_map->write_seq_first != 0) {
-		size_t rec_offset =
-			(rec_map->write_seq_first-1) * map->hdr.record_size;
-		size_t recs_size = map->hdr.record_size *
-			(rec_map->write_seq_last -
-			 rec_map->write_seq_first + 1);
-
-		if (pwrite_full(index->fd,
-				CONST_PTR_OFFSET(rec_map->records, rec_offset),
-				recs_size,
-				map->hdr.header_size + rec_offset) < 0)
-			return -1;
-	}
-
-	/* Write base header last. If we happen to crash in above pwrites, it
-	   doesn't matter because we haven't yet written log file offsets, so
-	   all the changes will be re-applied and the header/data state will
-	   stay valid.
-
-	   The base header changes practically always, so
-	   map->write_base_header might not be TRUE here in all situations.
-	   It's used only to figure out if we want to write the map at all. */
-	base_size = I_MIN(map->hdr.base_header_size, sizeof(map->hdr));
-	if (pwrite_full(index->fd, &map->hdr, base_size, 0) < 0)
-		return -1;
-	return 0;
-}
-
-static bool mail_index_has_last_changed(struct mail_index *index)
-{
-	struct mail_index_header hdr;
-	int ret;
-
-	if ((ret = pread_full(index->fd, &hdr, sizeof(hdr), 0)) <= 0) {
-		if (ret < 0 && errno != ESTALE)
-			mail_index_set_syscall_error(index, "pread_full()");
-		return TRUE;
-	}
-
-	return hdr.log_file_head_offset !=
-		index->last_read_log_file_head_offset ||
-		hdr.log_file_seq != index->last_read_log_file_seq;
-}
-
 #define mail_index_map_has_changed(map) \
-	((map)->write_base_header || (map)->write_ext_header || \
-	 (map)->rec_map->write_seq_first != 0)
+	((map)->header_changed || (map)->rec_map->records_changed)
 
 void mail_index_write(struct mail_index *index, bool want_rotate)
 {
 	struct mail_index_map *map = index->map;
 	const struct mail_index_header *hdr = &map->hdr;
-	struct stat st;
-	unsigned int lock_id;
-	int ret;
 
 	i_assert(index->log_sync_locked);
 
 	if (!mail_index_map_has_changed(map) || index->readonly)
 		return;
 
-	if (hdr->base_header_size < sizeof(*hdr)) {
-		/* header size growed. we can't update this file anymore. */
-		map->write_atomic = TRUE;
-	}
-	if (index->fd == -1 || index->last_read_log_file_seq == 0) {
-		/* index file doesn't exist, it's corrupted or we haven't
-		   opened it for some reason */
-		map->write_atomic = TRUE;
-	}
-
-	if (index->last_read_stat.st_size < MAIL_INDEX_MIN_UPDATE_SIZE ||
-	    (map->rec_map->write_seq_last - map->rec_map->write_seq_first + 1) +
-	    MAIL_INDEX_MAX_OVERWRITE_NEG_SEQ_COUNT >=
-	    map->rec_map->records_count) {
-		/* the file is so small that we don't even bother trying to
-		   update it / changes are so large we might as well recreate */
-		map->write_atomic = TRUE;
-	}
-
-	if (!map->write_atomic) {
-		/* we can't update the file unless it's the same as it was
-		   when we last read it. this is the first quick check before
-		   locking. */
-		if (stat(index->filepath, &st) < 0) {
-			if (errno != ENOENT)
-				mail_index_set_syscall_error(index, "stat()");
-			map->write_atomic = TRUE;
-		} else if (st.st_ino != index->last_read_stat.st_ino ||
-			   !CMP_ST_CTIME(&st, &index->last_read_stat))
-			map->write_atomic = TRUE;
-	}
-
-	if (!map->write_atomic) {
-		if (mail_index_try_lock_exclusive(index, &lock_id) <= 0) {
-			/* locking failed, recreate */
-			map->write_atomic = TRUE;
-		} else if (mail_index_has_last_changed(index)) {
-			/* changed, we can't trust updating it anymore */
-			map->write_atomic = TRUE;
-			mail_index_unlock(index, &lock_id);
-		}
-	}
-
-	if (map->write_atomic) {
-		if (!MAIL_INDEX_IS_IN_MEMORY(index)) {
-			if (mail_index_recreate(index) < 0) {
-				mail_index_move_to_memory(index);
-				return;
-			}
-		}
-	} else {
-		ret = mail_index_write_map_over(index);
-		if (ret < 0)
-			mail_index_set_syscall_error(index, "pwrite_full()");
-		else if (index->fsync_mode == FSYNC_MODE_ALWAYS) {
-			ret = fdatasync(index->fd);
-			if (ret < 0) {
-				mail_index_set_syscall_error(index,
-							     "fdatasync()");
-			}
-		}
-		mail_index_unlock(index, &lock_id);
-
-		if (ret < 0) {
-			/* hopefully didn't break badly */
+	if (!MAIL_INDEX_IS_IN_MEMORY(index)) {
+		if (mail_index_recreate(index) < 0) {
 			mail_index_move_to_memory(index);
 			return;
 		}
@@ -274,10 +143,8 @@
 	index->last_read_log_file_head_offset = hdr->log_file_head_offset;
 	index->last_read_log_file_tail_offset = hdr->log_file_tail_offset;
 
-	map->rec_map->write_seq_first = map->rec_map->write_seq_last = 0;
-	map->write_atomic = FALSE;
-	map->write_base_header = FALSE;
-	map->write_ext_header = FALSE;
+	map->rec_map->records_changed = FALSE;
+	map->header_changed = FALSE;
 
 	if (want_rotate &&
 	    hdr->log_file_seq == index->log->head->hdr.file_seq &&
--- a/src/lib-index/mail-index.c	Sun May 20 03:08:01 2012 +0300
+++ b/src/lib-index/mail-index.c	Sun May 20 03:25:04 2012 +0300
@@ -570,7 +570,6 @@
 		i_strconcat(index->dir, "/", index->prefix, NULL);
 
 	index->shared_lock_count = 0;
-	index->excl_lock_count = 0;
 	index->lock_type = F_UNLCK;
 	index->lock_id_counter = 2;
 
@@ -629,7 +628,6 @@
 	index->lock_id_counter += 2;
 	index->lock_type = F_UNLCK;
 	index->shared_lock_count = 0;
-	index->excl_lock_count = 0;
 }
 
 void mail_index_close(struct mail_index *index)
@@ -695,7 +693,6 @@
 
 	i_assert(index->shared_lock_count == 0 ||
 		 (index->flags & MAIL_INDEX_OPEN_FLAG_NFS_FLUSH) == 0);
-	i_assert(index->excl_lock_count == 0);
 
 	if (MAIL_INDEX_IS_IN_MEMORY(index))
 		return 0;
--- a/src/lib-index/test-mail-index-sync-ext.c	Sun May 20 03:08:01 2012 +0300
+++ b/src/lib-index/test-mail-index-sync-ext.c	Sun May 20 03:25:04 2012 +0300
@@ -32,9 +32,6 @@
 	*seq_r = uid;
 	return TRUE;
 }
-void mail_index_sync_write_seq_update(struct mail_index_sync_map_ctx *ctx ATTR_UNUSED,
-				      uint32_t seq1 ATTR_UNUSED,
-				      uint32_t seq2 ATTR_UNUSED) {}
 
 static void test_mail_index_sync_ext_atomic_inc(void)
 {
--- a/src/lib-lda/lmtp-client.c	Sun May 20 03:08:01 2012 +0300
+++ b/src/lib-lda/lmtp-client.c	Sun May 20 03:25:04 2012 +0300
@@ -6,6 +6,7 @@
 #include "network.h"
 #include "istream.h"
 #include "ostream.h"
+#include "str.h"
 #include "dns-lookup.h"
 #include "lmtp-client.h"
 
@@ -20,7 +21,8 @@
 	LMTP_INPUT_STATE_MAIL_FROM,
 	LMTP_INPUT_STATE_RCPT_TO,
 	LMTP_INPUT_STATE_DATA_CONTINUE,
-	LMTP_INPUT_STATE_DATA
+	LMTP_INPUT_STATE_DATA,
+	LMTP_INPUT_STATE_XCLIENT
 };
 
 struct lmtp_rcpt {
@@ -44,7 +46,10 @@
 	enum lmtp_client_protocol protocol;
 	enum lmtp_input_state input_state;
 	const char *global_fail_string;
+	string_t *input_multiline;
+	const char **xclient_args;
 
+	struct dns_lookup *dns_lookup;
 	struct istream *input;
 	struct ostream *output;
 	struct io *io;
@@ -64,6 +69,7 @@
 	struct istream *data_input;
 	unsigned char output_last;
 
+	unsigned int xclient_sent:1;
 	unsigned int rcpt_to_successes:1;
 	unsigned int output_finished:1;
 	unsigned int finish_called:1;
@@ -89,15 +95,21 @@
 	client->set.my_hostname = p_strdup(pool, set->my_hostname);
 	client->set.dns_client_socket_path =
 		p_strdup(pool, set->dns_client_socket_path);
+	client->set.source_ip = set->source_ip;
+	client->set.source_port = set->source_port;
+	client->set.proxy_ttl_plus_1 = set->proxy_ttl_plus_1;
 	client->finish_callback = finish_callback;
 	client->finish_context = context;
 	client->fd = -1;
+	client->input_multiline = str_new(default_pool, 128);
 	p_array_init(&client->recipients, pool, 16);
 	return client;
 }
 
 void lmtp_client_close(struct lmtp_client *client)
 {
+	if (client->dns_lookup != NULL)
+		dns_lookup_abort(&client->dns_lookup);
 	if (client->io != NULL)
 		io_remove(&client->io);
 	if (client->input != NULL)
@@ -138,6 +150,7 @@
 		i_stream_unref(&client->input);
 	if (client->output != NULL)
 		o_stream_unref(&client->output);
+	str_free(&client->input_multiline);
 	pool_unref(&client->pool);
 }
 
@@ -177,6 +190,8 @@
 			return t_strdup_printf("DATA (%"PRIuUOFF_T"/?)",
 					       client->data_input->v_offset);
 		}
+	case LMTP_INPUT_STATE_XCLIENT:
+		return "XCLIENT";
 	}
 	return "??";
 }
@@ -356,7 +371,8 @@
 	}
 }
 
-static int lmtp_input_get_reply_code(const char *line, int *reply_code_r)
+static int lmtp_input_get_reply_code(const char *line, int *reply_code_r,
+				     string_t *multiline)
 {
 	if (!i_isdigit(line[0]) || !i_isdigit(line[1]) || !i_isdigit(line[2]))
 		return -1;
@@ -369,7 +385,9 @@
 		/* final reply */
 		return 1;
 	} else if (line[3] == '-') {
-		/* multiline reply. just ignore it. */
+		/* multiline reply. */
+		str_append(multiline, line);
+		str_append_c(multiline, '\n');
 		return 0;
 	} else {
 		/* invalid input */
@@ -377,11 +395,61 @@
 	}
 }
 
+static void
+lmtp_client_parse_capabilities(struct lmtp_client *client, const char *lines)
+{
+	const char *const *linep;
+
+	for (linep = t_strsplit(lines, "\n"); *linep != NULL; linep++) {
+		const char *line = *linep;
+
+		line += 4; /* already checked this is valid */
+		if (strncasecmp(line, "XCLIENT ", 8) == 0) {
+			client->xclient_args =
+				(void *)p_strsplit(client->pool, line + 8, " ");
+		}
+	}
+}
+
+static bool lmtp_client_send_xclient(struct lmtp_client *client)
+{
+	string_t *str;
+	unsigned int empty_len;
+
+	if (client->xclient_args == NULL) {
+		/* not supported */
+		return FALSE;
+	}
+	if (client->xclient_sent)
+		return FALSE;
+
+	str = t_str_new(64);
+	str_append(str, "XCLIENT");
+	empty_len = str_len(str);
+	if (client->set.source_ip.family != 0 &&
+	    str_array_icase_find(client->xclient_args, "ADDR"))
+		str_printfa(str, " ADDR=%s", net_ip2addr(&client->set.source_ip));
+	if (client->set.source_port != 0 &&
+	    str_array_icase_find(client->xclient_args, "PORT"))
+		str_printfa(str, " PORT=%u", client->set.source_port);
+	if (client->set.proxy_ttl_plus_1 != 0 &&
+	    str_array_icase_find(client->xclient_args, "TTL"))
+		str_printfa(str, " TTL=%u", client->set.proxy_ttl_plus_1-1);
+
+	if (str_len(str) == empty_len)
+		return FALSE;
+
+	str_append(str, "\r\n");
+	o_stream_send(client->output, str_data(str), str_len(str));
+	return TRUE;
+}
+
 static int lmtp_client_input_line(struct lmtp_client *client, const char *line)
 {
 	int ret, reply_code = 0;
 
-	if ((ret = lmtp_input_get_reply_code(line, &reply_code)) <= 0) {
+	if ((ret = lmtp_input_get_reply_code(line, &reply_code,
+					     client->input_multiline)) <= 0) {
 		if (ret == 0)
 			return 0;
 		lmtp_client_fail(client, line);
@@ -390,12 +458,13 @@
 
 	switch (client->input_state) {
 	case LMTP_INPUT_STATE_GREET:
+	case LMTP_INPUT_STATE_XCLIENT:
 		if (reply_code != 220) {
 			lmtp_client_fail(client, line);
 			return -1;
 		}
 		lmtp_client_send_handshake(client);
-		client->input_state++;
+		client->input_state = LMTP_INPUT_STATE_LHLO;
 		break;
 	case LMTP_INPUT_STATE_LHLO:
 	case LMTP_INPUT_STATE_MAIL_FROM:
@@ -403,6 +472,15 @@
 			lmtp_client_fail(client, line);
 			return -1;
 		}
+		str_append(client->input_multiline, line);
+		lmtp_client_parse_capabilities(client,
+			str_c(client->input_multiline));
+		if (client->input_state == LMTP_INPUT_STATE_LHLO &&
+		    lmtp_client_send_xclient(client)) {
+			client->input_state = LMTP_INPUT_STATE_XCLIENT;
+			client->xclient_sent = TRUE;
+			break;
+		}
 		if (client->input_state == LMTP_INPUT_STATE_LHLO) {
 			o_stream_send_str(client->output,
 				t_strdup_printf("MAIL FROM:%s\r\n",
@@ -435,21 +513,27 @@
 			return -1;
 		break;
 	}
-	return 0;
+	return 1;
 }
 
 static void lmtp_client_input(struct lmtp_client *client)
 {
 	const char *line;
+	int ret;
 
 	lmtp_client_ref(client);
 	o_stream_cork(client->output);
 	while ((line = i_stream_read_next_line(client->input)) != NULL) {
-		if (lmtp_client_input_line(client, line) < 0) {
+		T_BEGIN {
+			ret = lmtp_client_input_line(client, line);
+		} T_END;
+		if (ret < 0) {
 			o_stream_uncork(client->output);
 			lmtp_client_unref(&client);
 			return;
 		}
+		if (ret > 0)
+			str_truncate(client->input_multiline, 0);
 	}
 
 	if (client->input->stream_errno != 0) {
@@ -519,6 +603,8 @@
 static void lmtp_client_dns_done(const struct dns_lookup_result *result,
 				 struct lmtp_client *client)
 {
+	client->dns_lookup = NULL;
+
 	if (result->ret != 0) {
 		i_error("lmtp client: DNS lookup of %s failed: %s",
 			client->host, result->error);
@@ -570,7 +656,8 @@
 		client->ip = ips[0];
 	} else {
 		if (dns_lookup(host, &dns_lookup_set,
-			       lmtp_client_dns_done, client) < 0)
+			       lmtp_client_dns_done, client,
+			       &client->dns_lookup) < 0)
 			return -1;
 		return 0;
 	}
--- a/src/lib-lda/lmtp-client.h	Sun May 20 03:08:01 2012 +0300
+++ b/src/lib-lda/lmtp-client.h	Sun May 20 03:25:04 2012 +0300
@@ -1,6 +1,8 @@
 #ifndef LMTP_CLIENT_H
 #define LMTP_CLIENT_H
 
+#include "network.h"
+
 #define ERRSTR_TEMP_REMOTE_FAILURE "451 4.4.0 Remote server not answering"
 
 /* LMTP/SMTP client code. */
@@ -14,6 +16,13 @@
 	const char *my_hostname;
 	const char *mail_from;
 	const char *dns_client_socket_path;
+
+	/* if remote server supports XCLIENT capability,
+	   send the these as ADDR/PORT */
+	struct ip_addr source_ip;
+	unsigned int source_port;
+	/* send TTL as this -1, so the default 0 means "don't send it" */
+	unsigned int proxy_ttl_plus_1;
 };
 
 /* reply contains the reply coming from remote server, or NULL
--- a/src/lib-lda/mail-send.c	Sun May 20 03:08:01 2012 +0300
+++ b/src/lib-lda/mail-send.c	Sun May 20 03:25:04 2012 +0300
@@ -4,6 +4,7 @@
 #include "ioloop.h"
 #include "hostpid.h"
 #include "istream.h"
+#include "ostream.h"
 #include "str.h"
 #include "str-sanitize.h"
 #include "var-expand.h"
@@ -55,12 +56,10 @@
     struct mail *mail = ctx->src_mail;
     struct istream *input;
     struct smtp_client *smtp_client;
-    FILE *f;
+    struct ostream *output;
     const char *return_addr, *hdr;
-    const unsigned char *data;
     const char *value, *msgid, *orig_msgid, *boundary;
     string_t *str;
-    size_t size;
     int ret;
 
     if (mail_get_first_header(mail, "Message-ID", &orig_msgid) < 0)
@@ -87,76 +86,75 @@
 		    str_sanitize(reason, 512));
     }
 
-    smtp_client = smtp_client_open(ctx->set, return_addr, NULL, &f);
+    smtp_client = smtp_client_open(ctx->set, return_addr, NULL, &output);
 
     msgid = mail_deliver_get_new_message_id(ctx);
     boundary = t_strdup_printf("%s/%s", my_pid, ctx->set->hostname);
 
-    fprintf(f, "Message-ID: %s\r\n", msgid);
-    fprintf(f, "Date: %s\r\n", message_date_create(ioloop_time));
-    fprintf(f, "From: Mail Delivery Subsystem <%s>\r\n",
-	    ctx->set->postmaster_address);
-    fprintf(f, "To: <%s>\r\n", return_addr);
-    fprintf(f, "MIME-Version: 1.0\r\n");
-    fprintf(f, "Content-Type: "
-	    "multipart/report; report-type=%s;\r\n"
-	    "\tboundary=\"%s\"\r\n",
-	    ctx->dsn ? "delivery-status" : "disposition-notification",
-	    boundary);
-
-    str = t_str_new(256);
+    str = t_str_new(512);
+    str_printfa(str, "Message-ID: %s\r\n", msgid);
+    str_printfa(str, "Date: %s\r\n", message_date_create(ioloop_time));
+    str_printfa(str, "From: Mail Delivery Subsystem <%s>\r\n",
+		ctx->set->postmaster_address);
+    str_printfa(str, "To: <%s>\r\n", return_addr);
+    str_append(str, "MIME-Version: 1.0\r\n");
+    str_printfa(str, "Content-Type: "
+		"multipart/report; report-type=%s;\r\n"
+		"\tboundary=\"%s\"\r\n",
+		ctx->dsn ? "delivery-status" : "disposition-notification",
+		boundary);
+    str_append(str, "Subject: ");
     var_expand(str, ctx->set->rejection_subject,
 	       get_var_expand_table(mail, reason, recipient));
-    fprintf(f, "Subject: %s\r\n", str_c(str));
+    str_append(str, "\r\n");
 
-    fprintf(f, "Auto-Submitted: auto-replied (rejected)\r\n");
-    fprintf(f, "Precedence: bulk\r\n");
-    fprintf(f, "\r\nThis is a MIME-encapsulated message\r\n\r\n");
+    str_append(str, "Auto-Submitted: auto-replied (rejected)\r\n");
+    str_append(str, "Precedence: bulk\r\n");
+    str_append(str, "\r\nThis is a MIME-encapsulated message\r\n\r\n");
 
     /* human readable status report */
-    fprintf(f, "--%s\r\n", boundary);
-    fprintf(f, "Content-Type: text/plain; charset=utf-8\r\n");
-    fprintf(f, "Content-Disposition: inline\r\n");
-    fprintf(f, "Content-Transfer-Encoding: 8bit\r\n\r\n");
+    str_printfa(str, "--%s\r\n", boundary);
+    str_append(str, "Content-Type: text/plain; charset=utf-8\r\n");
+    str_append(str, "Content-Disposition: inline\r\n");
+    str_append(str, "Content-Transfer-Encoding: 8bit\r\n\r\n");
 
-    str_truncate(str, 0);
     var_expand(str, ctx->set->rejection_reason,
 	       get_var_expand_table(mail, reason, recipient));
-    fprintf(f, "%s\r\n", str_c(str));
+    str_append(str, "\r\n");
 
     if (ctx->dsn) {
 	    /* DSN status report: For LDA rejects. currently only used when
 	       user is out of quota */
-	    fprintf(f, "--%s\r\n"
-		    "Content-Type: message/delivery-status\r\n\r\n",
-		    boundary);
-	    fprintf(f, "Reporting-MTA: dns; %s\r\n",
-		    ctx->set->hostname);
+	    str_printfa(str, "--%s\r\n"
+			"Content-Type: message/delivery-status\r\n\r\n",
+			boundary);
+	    str_printfa(str, "Reporting-MTA: dns; %s\r\n", ctx->set->hostname);
 	    if (mail_get_first_header(mail, "Original-Recipient", &hdr) > 0)
-		    fprintf(f, "Original-Recipient: rfc822; %s\r\n", hdr);
-	    fprintf(f, "Final-Recipient: rfc822; %s\r\n", recipient);
-	    fprintf(f, "Action: failed\r\n");
-	    fprintf(f, "Status: %s\r\n", ctx->mailbox_full ? "5.2.2" : "5.2.0");
+		    str_printfa(str, "Original-Recipient: rfc822; %s\r\n", hdr);
+	    str_printfa(str, "Final-Recipient: rfc822; %s\r\n", recipient);
+	    str_append(str, "Action: failed\r\n");
+	    str_printfa(str, "Status: %s\r\n", ctx->mailbox_full ? "5.2.2" : "5.2.0");
     } else {
 	    /* MDN status report: For Sieve "reject" */
-	    fprintf(f, "--%s\r\n"
-		    "Content-Type: message/disposition-notification\r\n\r\n",
-		    boundary);
-	    fprintf(f, "Reporting-UA: %s; Dovecot Mail Delivery Agent\r\n",
-		    ctx->set->hostname);
+	    str_printfa(str, "--%s\r\n"
+			"Content-Type: message/disposition-notification\r\n\r\n",
+			boundary);
+	    str_printfa(str, "Reporting-UA: %s; Dovecot Mail Delivery Agent\r\n",
+			ctx->set->hostname);
 	    if (mail_get_first_header(mail, "Original-Recipient", &hdr) > 0)
-		    fprintf(f, "Original-Recipient: rfc822; %s\r\n", hdr);
-	    fprintf(f, "Final-Recipient: rfc822; %s\r\n", recipient);
+		    str_printfa(str, "Original-Recipient: rfc822; %s\r\n", hdr);
+	    str_printfa(str, "Final-Recipient: rfc822; %s\r\n", recipient);
 
 	    if (orig_msgid != NULL)
-		    fprintf(f, "Original-Message-ID: %s\r\n", orig_msgid);
-	    fprintf(f, "Disposition: "
-		    "automatic-action/MDN-sent-automatically; deleted\r\n");
+		    str_printfa(str, "Original-Message-ID: %s\r\n", orig_msgid);
+	    str_append(str, "Disposition: "
+		       "automatic-action/MDN-sent-automatically; deleted\r\n");
     }
-    fprintf(f, "\r\n");
+    str_append(str, "\r\n");
 
     /* original message's headers */
-    fprintf(f, "--%s\r\nContent-Type: message/rfc822\r\n\r\n", boundary);
+    str_printfa(str, "--%s\r\nContent-Type: message/rfc822\r\n\r\n", boundary);
+    o_stream_send(output, str_data(str), str_len(str));
 
     if (mail_get_hdr_stream(mail, NULL, &input) == 0) {
 	    /* Note: If you add more headers, they need to be sorted.
@@ -173,17 +171,15 @@
 			N_ELEMENTS(exclude_headers),
 			null_header_filter_callback, NULL);
 
-	    while ((ret = i_stream_read_data(input, &data, &size, 0)) > 0) {
-		    if (fwrite(data, size, 1, f) == 0)
-			    break;
-		    i_stream_skip(input, size);
-	    }
+	    ret = o_stream_send_istream(output, input);
 	    i_stream_unref(&input);
 
 	    i_assert(ret != 0);
     }
 
-    fprintf(f, "\r\n\r\n--%s--\r\n", boundary);
+    str_truncate(str, 0);
+    str_printfa(str, "\r\n\r\n--%s--\r\n", boundary);
+    o_stream_send(output, str_data(str), str_len(str));
     return smtp_client_close(smtp_client);
 }
 
@@ -193,11 +189,9 @@
         "Return-Path"
     };
     struct istream *input;
+    struct ostream *output;
     struct smtp_client *smtp_client;
-    FILE *f;
-    const unsigned char *data;
     const char *return_path;
-    size_t size;
 
     if (mail_get_stream(ctx->src_mail, NULL, NULL, &input) < 0)
 	    return -1;
@@ -208,18 +202,14 @@
 		    forwardto, return_path);
     }
 
-    smtp_client = smtp_client_open(ctx->set, forwardto, return_path, &f);
+    smtp_client = smtp_client_open(ctx->set, forwardto, return_path, &output);
 
     input = i_stream_create_header_filter(input, HEADER_FILTER_EXCLUDE |
                                           HEADER_FILTER_NO_CR, hide_headers,
                                           N_ELEMENTS(hide_headers),
 					  null_header_filter_callback, NULL);
 
-    while (i_stream_read_data(input, &data, &size, 0) > 0) {
-	    if (fwrite(data, size, 1, f) == 0)
-		    break;
-	    i_stream_skip(input, size);
-    }
+    o_stream_send_istream(output, input);
     i_stream_unref(&input);
 
     return smtp_client_close(smtp_client);
--- a/src/lib-lda/smtp-client.c	Sun May 20 03:08:01 2012 +0300
+++ b/src/lib-lda/smtp-client.c	Sun May 20 03:25:04 2012 +0300
@@ -2,11 +2,13 @@
 
 #include "lib.h"
 #include "ioloop.h"
+#include "buffer.h"
 #include "str.h"
 #include "close-keep-errno.h"
 #include "safe-mkstemp.h"
 #include "execv-const.h"
 #include "istream.h"
+#include "ostream.h"
 #include "master-service.h"
 #include "lmtp-client.h"
 #include "lda-settings.h"
@@ -20,7 +22,9 @@
 #define DEFAULT_SUBMISSION_PORT 25
 
 struct smtp_client {
-	FILE *f;
+	struct ostream *output;
+	buffer_t *buf;
+	int temp_fd;
 	pid_t pid;
 
 	bool use_smtp;
@@ -33,15 +37,17 @@
 	char *return_path;
 };
 
-static struct smtp_client *smtp_client_devnull(FILE **file_r)
+static struct smtp_client *smtp_client_devnull(struct ostream **output_r)
 {
 	struct smtp_client *client;
-
+	
 	client = i_new(struct smtp_client, 1);
-	client->f = *file_r = fopen("/dev/null", "w");
-	if (client->f == NULL)
-		i_fatal("fopen() failed: %m");
+	client->buf = buffer_create_dynamic(default_pool, 1);
+	client->output = o_stream_create_buffer(client->buf);
+	o_stream_close(client->output);
 	client->pid = (pid_t)-1;
+
+	*output_r = client->output;
 	return client;
 }
 
@@ -76,7 +82,7 @@
 static struct smtp_client *
 smtp_client_open_sendmail(const struct lda_settings *set,
 			  const char *destination, const char *return_path,
-			  FILE **file_r)
+			  struct ostream **output_r)
 {
 	struct smtp_client *client;
 	int fd[2];
@@ -84,13 +90,13 @@
 
 	if (pipe(fd) < 0) {
 		i_error("pipe() failed: %m");
-		return smtp_client_devnull(file_r);
+		return smtp_client_devnull(output_r);
 	}
 
 	if ((pid = fork()) == (pid_t)-1) {
 		i_error("fork() failed: %m");
 		(void)close(fd[0]); (void)close(fd[1]);
-		return smtp_client_devnull(file_r);
+		return smtp_client_devnull(output_r);
 	}
 	if (pid == 0) {
 		/* child */
@@ -100,10 +106,10 @@
 	(void)close(fd[0]);
 
 	client = i_new(struct smtp_client, 1);
-	client->f = *file_r = fdopen(fd[1], "w");
+	client->output = o_stream_create_fd(fd[1], IO_BLOCK_SIZE, TRUE);
 	client->pid = pid;
-	if (client->f == NULL)
-		i_fatal("fdopen() failed: %m");
+
+	*output_r = client->output;
 	return client;
 }
 
@@ -137,7 +143,7 @@
 
 struct smtp_client *
 smtp_client_open(const struct lda_settings *set, const char *destination,
-		 const char *return_path, FILE **file_r)
+		 const char *return_path, struct ostream **output_r)
 {
 	struct smtp_client *client;
 	const char *path;
@@ -145,21 +151,22 @@
 
 	if (*set->submission_host == '\0') {
 		return smtp_client_open_sendmail(set, destination,
-						 return_path, file_r);
+						 return_path, output_r);
 	}
 
 	if ((fd = create_temp_file(&path)) == -1)
-		return smtp_client_devnull(file_r);
+		return smtp_client_devnull(output_r);
 
 	client = i_new(struct smtp_client, 1);
 	client->set = set;
 	client->temp_path = i_strdup(path);
 	client->destination = i_strdup(destination);
 	client->return_path = i_strdup(return_path);
-	client->f = *file_r = fdopen(fd, "w");
-	if (client->f == NULL)
-		i_fatal("fdopen() failed: %m");
+	client->temp_fd = fd;
+	client->output = o_stream_create_fd(fd, IO_BLOCK_SIZE, TRUE);
 	client->use_smtp = TRUE;
+
+	*output_r = client->output;
 	return client;
 }
 
@@ -167,7 +174,7 @@
 {
 	int ret = EX_TEMPFAIL, status;
 
-	fclose(client->f);
+	o_stream_destroy(&client->output);
 
 	if (client->pid == (pid_t)-1) {
 		/* smtp_client_open() failed already */
@@ -189,6 +196,8 @@
 		i_error("Sendmail process terminated abnormally, "
 			"return status %d", status);
 	}
+	if (client->buf != NULL)
+		buffer_free(&client->buf);
 	i_free(client);
 	return ret;
 }
@@ -247,12 +256,12 @@
 		}
 	}
 
-	if (fflush(smtp_client->f) != 0) {
-		i_error("fflush(%s) failed: %m", smtp_client->temp_path);
+	if (o_stream_flush(smtp_client->output) < 0) {
+		i_error("write(%s) failed: %m", smtp_client->temp_path);
 		return -1;
 	}
 
-	if (lseek(fileno(smtp_client->f), 0, SEEK_SET) < 0) {
+	if (o_stream_seek(smtp_client->output, 0) < 0) {
 		i_error("lseek(%s) failed: %m", smtp_client->temp_path);
 		return -1;
 	}
@@ -276,7 +285,7 @@
 	lmtp_client_add_rcpt(client, smtp_client->destination,
 			     rcpt_to_callback, data_callback, smtp_client);
 
-	input = i_stream_create_fd(fileno(smtp_client->f), (size_t)-1, FALSE);
+	input = i_stream_create_fd(smtp_client->temp_fd, (size_t)-1, FALSE);
 	lmtp_client_send(client, input);
 	i_stream_unref(&input);
 
@@ -296,7 +305,7 @@
 	/* the mail has been written to a file. now actually send it. */
 	ret = smtp_client_send(client);
 
-	fclose(client->f);
+	o_stream_destroy(&client->output);
 	i_free(client->return_path);
 	i_free(client->destination);
 	i_free(client->temp_path);
--- a/src/lib-lda/smtp-client.h	Sun May 20 03:08:01 2012 +0300
+++ b/src/lib-lda/smtp-client.h	Sun May 20 03:25:04 2012 +0300
@@ -5,7 +5,7 @@
 
 struct smtp_client *
 smtp_client_open(const struct lda_settings *set, const char *destination,
-		 const char *return_path, FILE **file_r);
+		 const char *return_path, struct ostream **output_r);
 /* Returns sysexits-compatible return value */
 int smtp_client_close(struct smtp_client *client);
 
--- a/src/lib-mail/message-parser.c	Sun May 20 03:08:01 2012 +0300
+++ b/src/lib-mail/message-parser.c	Sun May 20 03:25:04 2012 +0300
@@ -54,6 +54,8 @@
 				       struct message_block *block_r);
 static int parse_next_body_to_eof(struct message_parser_ctx *ctx,
 				  struct message_block *block_r);
+static int preparsed_parse_epilogue_init(struct message_parser_ctx *ctx,
+					 struct message_block *block_r);
 static int preparsed_parse_next_header_init(struct message_parser_ctx *ctx,
 					    struct message_block *block_r);
 
@@ -271,9 +273,17 @@
 	block_r->size = (ptr - block_r->data) + 1;
 	parse_body_add_block(ctx, block_r);
 
-	/* a new MIME part begins */
-	ctx->parse_next_block = parse_next_mime_header_init;
-	return 1;
+	if (ctx->boundaries == NULL || ctx->boundaries->part != ctx->part) {
+		/* epilogue */
+		if (ctx->boundaries != NULL)
+			ctx->parse_next_block = parse_next_body_to_boundary;
+		else
+			ctx->parse_next_block = parse_next_body_to_eof;
+	} else {
+		/* a new MIME part begins */
+		ctx->parse_next_block = parse_next_mime_header_init;
+	}
+	return ctx->parse_next_block(ctx, block_r);
 }
 
 static int parse_part_finish(struct message_parser_ctx *ctx,
@@ -293,28 +303,24 @@
 	if (boundary->epilogue_found) {
 		/* this boundary isn't needed anymore */
 		ctx->boundaries = boundary->next;
-
-		if (ctx->boundaries != NULL)
-			ctx->parse_next_block = parse_next_body_to_boundary;
-		else
-			ctx->parse_next_block = parse_next_body_to_eof;
-		return ctx->parse_next_block(ctx, block_r);
+	} else {
+		/* forget about the boundaries we possibly skipped */
+		ctx->boundaries = boundary;
 	}
 
-	/* forget about the boundaries we possibly skipped */
-	ctx->boundaries = boundary;
-
 	/* the boundary itself should already be in buffer. add that. */
 	block_r->data = i_stream_get_data(ctx->input, &block_r->size);
 	i_assert(block_r->size >= ctx->skip + 2 + boundary->len +
 		 (first_line ? 0 : 1));
 	block_r->data += ctx->skip;
-	/* [\n]--<boundary> */
-	block_r->size = (first_line ? 0 : 1) + 2 + boundary->len;
+	/* [\n]--<boundary>[--] */
+	block_r->size = (first_line ? 0 : 1) + 2 + boundary->len +
+		(boundary->epilogue_found ? 2 : 0);
 	parse_body_add_block(ctx, block_r);
 
 	ctx->parse_next_block = parse_next_body_skip_boundary_line;
-	return 1;
+
+	return ctx->parse_next_block(ctx, block_r);
 }
 
 static int parse_next_body_to_boundary(struct message_parser_ctx *ctx,
@@ -392,6 +398,11 @@
 	}
 	if (block_r->size != 0) {
 		parse_body_add_block(ctx, block_r);
+
+		if ((ctx->part->flags & MESSAGE_PART_FLAG_MULTIPART) != 0 &&
+		    (ctx->flags & MESSAGE_PARSER_FLAG_INCLUDE_MULTIPART_BLOCKS) == 0)
+			return 0;
+
 		return 1;
 	}
 	return ret <= 0 ? ret :
@@ -408,6 +419,11 @@
 		return ret;
 
 	parse_body_add_block(ctx, block_r);
+
+	if ((ctx->part->flags & MESSAGE_PART_FLAG_MULTIPART) != 0 &&
+	    (ctx->flags & MESSAGE_PARSER_FLAG_INCLUDE_MULTIPART_BLOCKS) == 0)
+		return 0;
+
 	return 1;
 }
 
@@ -583,6 +599,24 @@
 			ctx->part = ctx->part->next;
 			break;
 		}
+
+		/* parse epilogue of multipart parent if requested */
+		if (ctx->part->parent != NULL &&
+		    (ctx->part->parent->flags & MESSAGE_PART_FLAG_MULTIPART) != 0 &&
+		    (ctx->flags & MESSAGE_PARSER_FLAG_INCLUDE_MULTIPART_BLOCKS) != 0) {
+			/* check for presence of epilogue */
+			uoff_t part_end = ctx->part->physical_pos +
+				ctx->part->header_size.physical_size +
+				ctx->part->body_size.physical_size;
+			uoff_t parent_end = ctx->part->parent->physical_pos +
+				ctx->part->parent->header_size.physical_size +
+				ctx->part->parent->body_size.physical_size;
+
+			if (parent_end > part_end) {
+				ctx->parse_next_block = preparsed_parse_epilogue_init;
+				break;
+			}
+		}
 		ctx->part = ctx->part->parent;
 	}
 	if (ctx->part == NULL)
@@ -599,6 +633,17 @@
 	return ctx->parse_next_block(ctx, block_r);
 }
 
+static int preparsed_parse_prologue_finish(struct message_parser_ctx *ctx,
+					   struct message_block *block_r)
+{
+	i_stream_skip(ctx->input, ctx->skip);
+	ctx->skip = 0;
+
+	ctx->parse_next_block = preparsed_parse_next_header_init;
+	ctx->part = ctx->part->children;
+	return ctx->parse_next_block(ctx, block_r);
+}
+
 static int preparsed_parse_body_more(struct message_parser_ctx *ctx,
 				     struct message_block *block_r)
 {
@@ -619,6 +664,143 @@
 	return 1;
 }
 
+static int preparsed_parse_prologue_more(struct message_parser_ctx *ctx,
+					 struct message_block *block_r)
+{
+	uoff_t end_offset = ctx->part->children->physical_pos;
+	uoff_t boundary_min_start;
+	const unsigned char *cur;
+	bool full;
+	int ret;
+
+	if ((ret = message_parser_read_more(ctx, block_r, &full)) <= 0)
+		return ret;
+
+	if (ctx->input->v_offset + block_r->size >= end_offset) {
+		/* we've got the full prologue: clip off the initial boundary */
+		block_r->size = end_offset - ctx->input->v_offset;
+		cur = block_r->data + block_r->size - 1;
+
+		/* [\r]\n--boundary[\r]\n */ 
+		if (block_r->size < 5 || *cur != '\n') {
+			ctx->broken = TRUE;
+			return -1;
+		}
+		
+		cur--;
+		if (*cur == '\r') cur--;
+
+		/* find newline just before boundary */
+		for (; cur >= block_r->data; cur--) {
+			if (*cur == '\n') break;
+		}
+
+		if (cur[0] != '\n' || cur[1] != '-' || cur[2] != '-') {
+			ctx->broken = TRUE;
+			return -1;
+		}
+
+		if (cur != block_r->data && cur[-1] == '\r') cur--;
+
+		/* clip boundary */
+		block_r->size = cur - block_r->data;			
+
+		ctx->parse_next_block = preparsed_parse_prologue_finish;
+		ctx->skip = block_r->size;
+		return 1;
+	}
+		
+	/* retain enough data in the stream buffer to contain initial boundary */
+	if (end_offset > BOUNDARY_END_MAX_LEN)
+		boundary_min_start = end_offset - BOUNDARY_END_MAX_LEN;
+	else
+		boundary_min_start = 0;
+
+	if (ctx->input->v_offset + block_r->size >= boundary_min_start) {
+		if (boundary_min_start <= ctx->input->v_offset)
+			return 0;
+		block_r->size = boundary_min_start - ctx->input->v_offset;
+	}
+	ctx->skip = block_r->size;
+	return 1;
+}
+
+static int preparsed_parse_epilogue_more(struct message_parser_ctx *ctx,
+					 struct message_block *block_r)
+{
+	uoff_t end_offset = ctx->part->physical_pos +
+		ctx->part->header_size.physical_size +
+		ctx->part->body_size.physical_size;
+	bool full;
+	int ret;
+
+	if ((ret = message_parser_read_more(ctx, block_r, &full)) <= 0)
+		return ret;
+
+	if (ctx->input->v_offset + block_r->size >= end_offset) {
+		block_r->size = end_offset - ctx->input->v_offset;
+		ctx->parse_next_block = preparsed_parse_body_finish;
+	}
+	ctx->skip = block_r->size;
+	return 1;
+}
+
+static int preparsed_parse_epilogue_boundary(struct message_parser_ctx *ctx,
+					     struct message_block *block_r)
+{
+	uoff_t end_offset = ctx->part->physical_pos +
+		ctx->part->header_size.physical_size +
+		ctx->part->body_size.physical_size;
+	const unsigned char *data, *cur;
+	size_t size;
+	bool full;
+	int ret;
+
+	if (end_offset - ctx->input->v_offset < 7) {
+		ctx->broken = TRUE;
+		return -1;
+	}
+
+	if ((ret = message_parser_read_more(ctx, block_r, &full)) <= 0)
+		return ret;
+
+	/* [\r]\n--boundary--[\r]\n */
+	if (block_r->size < 7) {
+		ctx->want_count = 7;
+		return 0;
+	}
+
+	data = block_r->data;
+	size = block_r->size;
+	cur = data;
+
+	if (*cur == '\r') cur++;
+
+	if (cur[0] != '\n' || cur[1] != '-' || data[2] != '-') {
+		ctx->broken = TRUE;
+		return -1;
+	}
+
+	/* find the end of the line */
+	cur += 3;
+	if ((cur = memchr(cur, '\n', size - (cur-data))) == NULL) {
+		if (end_offset < ctx->input->v_offset + size) {
+			ctx->broken = TRUE;
+			return -1;
+		} else if (ctx->input->v_offset + size < end_offset &&
+			   size < BOUNDARY_END_MAX_LEN &&
+			   !ctx->input->eof && !full) {
+			ctx->want_count = BOUNDARY_END_MAX_LEN;
+			return 0;
+		}
+	}
+
+	block_r->size = 0;
+	ctx->parse_next_block = preparsed_parse_epilogue_more;
+	ctx->skip = cur - data + 1;
+	return 0;
+}
+
 static int preparsed_parse_body_init(struct message_parser_ctx *ctx,
 				     struct message_block *block_r)
 {
@@ -632,16 +814,45 @@
 	}
 	i_stream_skip(ctx->input, offset - ctx->input->v_offset);
 
-	ctx->parse_next_block = preparsed_parse_body_more;
-	return preparsed_parse_body_more(ctx, block_r);
+	if ((ctx->part->flags & MESSAGE_PART_FLAG_MULTIPART) == 0)
+		ctx->parse_next_block = preparsed_parse_body_more;
+	else
+		ctx->parse_next_block = preparsed_parse_prologue_more;
+	return ctx->parse_next_block(ctx, block_r);
+}
+
+static int preparsed_parse_epilogue_init(struct message_parser_ctx *ctx,
+					 struct message_block *block_r)
+{
+	uoff_t offset = ctx->part->physical_pos +
+		ctx->part->header_size.physical_size +
+		ctx->part->body_size.physical_size;
+
+	ctx->part = ctx->part->parent;
+
+	if (offset < ctx->input->v_offset) {
+		/* last child was actually larger than the cached size
+		   suggested */
+		ctx->broken = TRUE;
+		return -1;
+	}
+	i_stream_skip(ctx->input, offset - ctx->input->v_offset);
+
+	ctx->parse_next_block = preparsed_parse_epilogue_boundary;
+	return ctx->parse_next_block(ctx, block_r);
 }
 
 static int preparsed_parse_finish_header(struct message_parser_ctx *ctx,
 					 struct message_block *block_r)
 {
 	if (ctx->part->children != NULL) {
-		ctx->parse_next_block = preparsed_parse_next_header_init;
-		ctx->part = ctx->part->children;
+		if ((ctx->part->flags & MESSAGE_PART_FLAG_MULTIPART) != 0 &&
+		    (ctx->flags & MESSAGE_PARSER_FLAG_INCLUDE_MULTIPART_BLOCKS) != 0)
+			ctx->parse_next_block = preparsed_parse_body_init;
+		else {
+			ctx->parse_next_block = preparsed_parse_next_header_init;
+			ctx->part = ctx->part->children;
+		}
 	} else if ((ctx->flags & MESSAGE_PARSER_FLAG_SKIP_BODY_BLOCK) == 0) {
 		ctx->parse_next_block = preparsed_parse_body_init;
 	} else {
--- a/src/lib-mail/message-parser.h	Sun May 20 03:08:01 2012 +0300
+++ b/src/lib-mail/message-parser.h	Sun May 20 03:25:04 2012 +0300
@@ -10,7 +10,9 @@
 	/* Buggy software creates Content-Type: headers without Mime-Version:
 	   header. By default we allow this and assume message is MIME if
 	   Content-Type: is found. This flag disables this. */
-	MESSAGE_PARSER_FLAG_MIME_VERSION_STRICT	= 0x02
+	MESSAGE_PARSER_FLAG_MIME_VERSION_STRICT	= 0x02,
+	/* Return multipart (preamble and epilogue) blocks */
+	MESSAGE_PARSER_FLAG_INCLUDE_MULTIPART_BLOCKS = 0x04
 };
 
 /* Note that these flags are used directly by message-parser-serialize, so
--- a/src/lib-storage/index/imapc/imapc-storage.c	Sun May 20 03:08:01 2012 +0300
+++ b/src/lib-storage/index/imapc/imapc-storage.c	Sun May 20 03:25:04 2012 +0300
@@ -705,10 +705,11 @@
 static void imapc_notify_changes(struct mailbox *box)
 {
 	struct imapc_mailbox *mbox = (struct imapc_mailbox *)box;
+	const struct mail_storage_settings *set = box->storage->set;
 	struct imapc_command *cmd;
 	enum imapc_capability capa;
 
-	if (box->notify_min_interval == 0) {
+	if (box->notify_callback == NULL) {
 		if (mbox->to_idle_check != NULL)
 			timeout_remove(&mbox->to_idle_check);
 		return;
@@ -728,7 +729,7 @@
 		   check for changes with NOOP every once in a while. */
 		i_assert(!imapc_client_is_running(mbox->storage->client));
 		mbox->to_idle_check =
-			timeout_add(box->notify_min_interval * 1000,
+			timeout_add(set->mailbox_idle_check_interval * 1000,
 				    imapc_idle_timeout, mbox);
 	}
 }
--- a/src/lib-storage/index/index-mailbox-check.c	Sun May 20 03:08:01 2012 +0300
+++ b/src/lib-storage/index/index-mailbox-check.c	Sun May 20 03:25:04 2012 +0300
@@ -67,12 +67,13 @@
 void index_mailbox_check_add(struct mailbox *box, const char *path)
 {
 	struct index_mailbox_context *ibox = INDEX_STORAGE_CONTEXT(box);
+	const struct mail_storage_settings *set = box->storage->set;
 	struct index_notify_file *file;
 	struct stat st;
 	struct io *io = NULL;
 	struct index_notify_io *aio;
 
-	i_assert(box->notify_min_interval > 0);
+	i_assert(set->mailbox_idle_check_interval > 0);
 
 	(void)io_add_notify(path, notify_callback, box, &io);
 	if (io != NULL) {
@@ -94,7 +95,7 @@
 	 * when the filesystem is remote (NFS, ...) */
 	if (ibox->notify_to == NULL) {
 		ibox->notify_to =
-			timeout_add(box->notify_min_interval * 1000,
+			timeout_add(set->mailbox_idle_check_interval * 1000,
 				    check_timeout, box);
 	}
 }
--- a/src/lib-storage/index/pop3c/pop3c-client.c	Sun May 20 03:08:01 2012 +0300
+++ b/src/lib-storage/index/pop3c/pop3c-client.c	Sun May 20 03:25:04 2012 +0300
@@ -50,6 +50,7 @@
 	struct ostream *output, *raw_output;
 	struct ssl_iostream *ssl_iostream;
 	struct timeout *to;
+	struct dns_lookup *dns_lookup;
 
 	enum pop3c_client_state state;
 	enum pop3c_capability capabilities;
@@ -66,7 +67,8 @@
 };
 
 static void
-pop3c_dns_callback(const struct dns_lookup_result *result, void *context);
+pop3c_dns_callback(const struct dns_lookup_result *result,
+		   struct pop3c_client *client);
 
 struct pop3c_client *
 pop3c_client_init(const struct pop3c_client_settings *set)
@@ -134,6 +136,8 @@
 	if (client->running)
 		io_loop_stop(current_ioloop);
 
+	if (client->dns_lookup != NULL)
+		dns_lookup_abort(&client->dns_lookup);
 	if (client->to != NULL)
 		timeout_remove(&client->to);
 	if (client->io != NULL)
@@ -215,7 +219,8 @@
 			client->set.dns_client_socket_path;
 		dns_set.timeout_msecs = POP3C_DNS_LOOKUP_TIMEOUT_MSECS;
 		(void)dns_lookup(client->set.host, &dns_set,
-				 pop3c_dns_callback, client);
+				 pop3c_dns_callback, client,
+				 &client->dns_lookup);
 	} else if (client->to == NULL) {
 		client->to = timeout_add(POP3C_COMMAND_TIMEOUT_MSECS,
 					 pop3c_client_timeout, client);
@@ -542,9 +547,10 @@
 }
 
 static void
-pop3c_dns_callback(const struct dns_lookup_result *result, void *context)
+pop3c_dns_callback(const struct dns_lookup_result *result,
+		   struct pop3c_client *client)
 {
-	struct pop3c_client *client = context;
+	client->dns_lookup = NULL;
 
 	if (result->ret != 0) {
 		i_error("pop3c(%s): dns_lookup() failed: %s",
--- a/src/lib-storage/mail-storage-private.h	Sun May 20 03:08:01 2012 +0300
+++ b/src/lib-storage/mail-storage-private.h	Sun May 20 03:25:04 2012 +0300
@@ -243,7 +243,6 @@
 	struct mail_index_view *tmp_sync_view;
 
 	/* Mailbox notification settings: */
-	unsigned int notify_min_interval;
 	mailbox_notify_callback_t *notify_callback;
 	void *notify_context;
 
--- a/src/lib-storage/mail-storage.c	Sun May 20 03:08:01 2012 +0300
+++ b/src/lib-storage/mail-storage.c	Sun May 20 03:25:04 2012 +0300
@@ -1370,10 +1370,9 @@
 }
 
 #undef mailbox_notify_changes
-void mailbox_notify_changes(struct mailbox *box, unsigned int min_interval,
+void mailbox_notify_changes(struct mailbox *box,
 			    mailbox_notify_callback_t *callback, void *context)
 {
-	box->notify_min_interval = min_interval;
 	box->notify_callback = callback;
 	box->notify_context = context;
 
@@ -1382,7 +1381,7 @@
 
 void mailbox_notify_changes_stop(struct mailbox *box)
 {
-	mailbox_notify_changes(box, 0, NULL, NULL);
+	mailbox_notify_changes(box, NULL, NULL);
 }
 
 struct mail_search_context *
--- a/src/lib-storage/mail-storage.h	Sun May 20 03:08:01 2012 +0300
+++ b/src/lib-storage/mail-storage.h	Sun May 20 03:25:04 2012 +0300
@@ -491,16 +491,16 @@
 int mailbox_sync(struct mailbox *box, enum mailbox_sync_flags flags);
 
 /* Call given callback function when something changes in the mailbox. */
-void mailbox_notify_changes(struct mailbox *box, unsigned int min_interval,
+void mailbox_notify_changes(struct mailbox *box,
 			    mailbox_notify_callback_t *callback, void *context);
 #ifdef CONTEXT_TYPE_SAFETY
-#  define mailbox_notify_changes(box, min_interval, callback, context) \
+#  define mailbox_notify_changes(box, callback, context) \
 	({(void)(1 ? 0 : callback((struct mailbox *)NULL, context)); \
-	  mailbox_notify_changes(box, min_interval, \
+	  mailbox_notify_changes(box, \
 		(mailbox_notify_callback_t *)callback, context); })
 #else
-#  define mailbox_notify_changes(box, min_interval, callback, context) \
-	  mailbox_notify_changes(box, min_interval, \
+#  define mailbox_notify_changes(box, callback, context) \
+	  mailbox_notify_changes(box, \
 		(mailbox_notify_callback_t *)callback, context)
 #endif
 void mailbox_notify_changes_stop(struct mailbox *box);
--- a/src/lib/network.c	Sun May 20 03:08:01 2012 +0300
+++ b/src/lib/network.c	Sun May 20 03:25:04 2012 +0300
@@ -13,8 +13,10 @@
 #include <ctype.h>
 #include <sys/un.h>
 #include <netinet/tcp.h>
-#ifdef HAVE_UCRED_H
+#if defined(HAVE_UCRED_H)
 #  include <ucred.h> /* for getpeerucred() */
+#elif defined(HAVE_SYS_UCRED_H)
+#  include <sys/ucred.h> /* for FreeBSD struct xucred */
 #endif
 
 union sockaddr_union {
@@ -694,16 +696,14 @@
 
 int net_getunixcred(int fd, struct net_unix_cred *cred_r)
 {
-#if defined(HAVE_GETPEEREID)
-	/* OSX 10.4+, FreeBSD 4.6+, OpenBSD 3.0+, NetBSD 5.0+ */
-	if (getpeereid(fd, &cred_r->uid, &cred_r->gid) < 0) {
-		i_error("getpeereid() failed: %m");
-		return -1;
-	}
-	return 0;
-#elif defined(SO_PEERCRED)
+#if defined(SO_PEERCRED)
+# if defined(HAVE_STRUCT_SOCKPEERCRED)
+	/* OpenBSD (may also provide getpeereid, but we also want pid) */
+	struct sockpeercred ucred;
+# else
 	/* Linux */
 	struct ucred ucred;
+# endif
 	socklen_t len = sizeof(ucred);
 
 	if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &ucred, &len) < 0) {
@@ -712,6 +712,48 @@
 	}
 	cred_r->uid = ucred.uid;
 	cred_r->gid = ucred.gid;
+	cred_r->pid = ucred.pid;
+	return 0;
+#elif defined(LOCAL_PEEREID)
+	/* NetBSD (may also provide getpeereid, but we also want pid) */
+	struct unpcbid ucred;
+	socklen_t len = sizeof(ucred);
+
+	if (getsockopt(s, 0, LOCAL_PEEREID, &ucred, &len) < 0) {
+		i_error("getsockopt(LOCAL_PEEREID) failed: %m");
+		return -1;
+	}
+	
+	cred_r->uid = ucred.unp_euid;
+	cred_r->gid = ucred.unp_egid;
+	cred_r->pid = ucred.unp_pid;
+	return 0;
+#elif defined(HAVE_GETPEEREID)
+	/* OSX 10.4+, FreeBSD 4.6+, OpenBSD 3.0+, NetBSD 5.0+ */
+	if (getpeereid(fd, &cred_r->uid, &cred_r->gid) < 0) {
+		i_error("getpeereid() failed: %m");
+		return -1;
+	}
+	cred_r->pid = (pid_t)-1;
+	return 0;
+#elif defined(LOCAL_PEERCRED)
+	/* Older FreeBSD */
+	struct xucred ucred;
+	socklen_t len = sizeof(ucred);
+
+	if (getsockopt(fd, 0, LOCAL_PEERCRED, &ucred, &len) < 0) {
+		i_error("getsockopt(LOCAL_PEERCRED) failed: %m");
+		return -1;
+	}
+
+	if (ucred.cr_version != XUCRED_VERSION) {
+		errno = EINVAL;
+		return -1;
+	}
+
+	cred_r->uid = ucred.cr_uid;
+	cred_r->gid = ucred.cr_gid;
+	cred_r->pid = (pid_t)-1;
 	return 0;
 #elif defined(HAVE_GETPEERUCRED)
 	/* Solaris */
@@ -723,6 +765,7 @@
 	}
 	cred_r->uid = ucred_geteuid(ucred);
 	cred_r->gid = ucred_getrgid(ucred);
+	cred_r->pid = ucred_getpid(ucred);
 	ucred_free(ucred);
 
 	if (cred_r->uid == (uid_t)-1 ||
--- a/src/lib/network.h	Sun May 20 03:08:01 2012 +0300
+++ b/src/lib/network.h	Sun May 20 03:25:04 2012 +0300
@@ -34,6 +34,7 @@
 struct net_unix_cred {
 	uid_t uid;
 	gid_t gid;
+	pid_t pid;
 };
 
 /* maxmimum string length of IP address */
@@ -115,7 +116,8 @@
 int net_getpeername(int fd, struct ip_addr *addr, unsigned int *port);
 /* Get UNIX socket name. */
 int net_getunixname(int fd, const char **name_r);
-/* Get UNIX socket peer process's credentials. */
+/* Get UNIX socket peer process's credentials. The pid may be (pid_t)-1 if
+   unavailable. */
 int net_getunixcred(int fd, struct net_unix_cred *cred_r);
 
 /* Returns ip_addr as string, or NULL if ip is invalid. */
--- a/src/lmtp/client.c	Sun May 20 03:08:01 2012 +0300
+++ b/src/lmtp/client.c	Sun May 20 03:25:04 2012 +0300
@@ -68,6 +68,8 @@
 		return cmd_rset(client, args);
 	if (strcmp(cmd, "NOOP") == 0)
 		return cmd_noop(client, args);
+	if (strcmp(cmd, "XCLIENT") == 0)
+		return cmd_xclient(client, args);
 
 	client_send_line(client, "502 5.5.2 Unknown command");
 	return 0;
@@ -225,6 +227,7 @@
 	client_generate_session_id(client);
 	client->my_domain = client->set->hostname;
 	client->lhlo = i_strdup("missing");
+	client->proxy_ttl = LMTP_PROXY_DEFAULT_TTL;
 
 	DLLIST_PREPEND(&clients, client);
 	clients_count++;
@@ -342,6 +345,29 @@
 	va_end(args);
 }
 
+bool client_is_trusted(struct client *client)
+{
+	const char *const *net;
+	struct ip_addr net_ip;
+	unsigned int bits;
+
+	if (client->lmtp_set->login_trusted_networks == NULL)
+		return FALSE;
+
+	net = t_strsplit_spaces(client->lmtp_set->login_trusted_networks, ", ");
+	for (; *net != NULL; net++) {
+		if (net_parse_range(*net, &net_ip, &bits) < 0) {
+			i_error("login_trusted_networks: "
+				"Invalid network '%s'", *net);
+			break;
+		}
+
+		if (net_is_in_network(&client->remote_ip, &net_ip, bits))
+			return TRUE;
+	}
+	return FALSE;
+}
+
 void clients_destroy(void)
 {
 	while (clients != NULL) {
--- a/src/lmtp/client.h	Sun May 20 03:08:01 2012 +0300
+++ b/src/lmtp/client.h	Sun May 20 03:25:04 2012 +0300
@@ -62,6 +62,7 @@
 	struct client_state state;
 	struct istream *dot_input;
 	struct lmtp_proxy *proxy;
+	unsigned int proxy_ttl;
 
 	unsigned int disconnected:1;
 };
@@ -82,6 +83,7 @@
 
 void client_send_line(struct client *client, const char *fmt, ...)
 	ATTR_FORMAT(2, 3);
+bool client_is_trusted(struct client *client);
 
 void clients_destroy(void);
 
--- a/src/lmtp/commands.c	Sun May 20 03:08:01 2012 +0300
+++ b/src/lmtp/commands.c	Sun May 20 03:25:04 2012 +0300
@@ -68,6 +68,8 @@
 
 	client_state_reset(client);
 	client_send_line(client, "250-%s", client->my_domain);
+	if (client_is_trusted(client))
+		client_send_line(client, "250-XCLIENT ADDR PORT TTL");
 	client_send_line(client, "250-8BITMIME");
 	client_send_line(client, "250-ENHANCEDSTATUSCODES");
 	client_send_line(client, "250 PIPELINING");
@@ -148,7 +150,7 @@
 }
 
 static bool
-client_proxy_rcpt_parse_fields(struct lmtp_proxy_settings *set,
+client_proxy_rcpt_parse_fields(struct lmtp_proxy_rcpt_settings *set,
 			       const char *const *args, const char **address)
 {
 	const char *p, *key, *value;
@@ -201,7 +203,7 @@
 
 static bool
 client_proxy_is_ourself(const struct client *client,
-			const struct lmtp_proxy_settings *set)
+			const struct lmtp_proxy_rcpt_settings *set)
 {
 	struct ip_addr ip;
 
@@ -235,7 +237,7 @@
 			      const char *username, const char *detail)
 {
 	struct auth_master_connection *auth_conn;
-	struct lmtp_proxy_settings set;
+	struct lmtp_proxy_rcpt_settings set;
 	struct auth_user_info info;
 	struct mail_storage_service_input input;
 	const char *args, *const *fields, *errstr, *orig_username = username;
@@ -294,6 +296,15 @@
 		return TRUE;
 	}
 
+	if (client->proxy_ttl == 0) {
+		i_error("Proxying to <%s> appears to be looping (TTL=0)",
+			username);
+		client_send_line(client, "554 5.4.6 <%s> "
+				 "Proxying appears to be looping (TTL=0)",
+				 username);
+		pool_unref(&pool);
+		return TRUE;
+	}
 	if (array_count(&client->state.rcpt_to) != 0) {
 		client_send_line(client, "451 4.3.0 <%s> "
 			"Can't handle mixed proxy/non-proxy destinations",
@@ -302,9 +313,16 @@
 		return TRUE;
 	}
 	if (client->proxy == NULL) {
-		client->proxy = lmtp_proxy_init(client->set->hostname,
-						dns_client_socket_path,
-						client->output);
+		struct lmtp_proxy_settings proxy_set;
+
+		memset(&proxy_set, 0, sizeof(proxy_set));
+		proxy_set.my_hostname = client->set->hostname;
+		proxy_set.dns_client_socket_path = dns_client_socket_path;
+		proxy_set.source_ip = client->remote_ip;
+		proxy_set.source_port = client->remote_port;
+		proxy_set.proxy_ttl = client->proxy_ttl-1;
+
+		client->proxy = lmtp_proxy_init(&proxy_set, client->output);
 		if (client->state.mail_body_8bitmime)
 			args = " BODY=8BITMIME";
 		else if (client->state.mail_body_7bit)
@@ -909,3 +927,46 @@
 	client_input_data_handle(client);
 	return -1;
 }
+
+int cmd_xclient(struct client *client, const char *args)
+{
+	const char *const *tmp;
+	struct ip_addr remote_ip;
+	unsigned int remote_port = 0, ttl = -1U;
+	bool args_ok = TRUE;
+
+	if (!client_is_trusted(client)) {
+		client_send_line(client, "550 You are not from trusted IP");
+		return 0;
+	}
+	remote_ip.family = 0;
+	for (tmp = t_strsplit(args, " "); *tmp != NULL; tmp++) {
+		if (strncasecmp(*tmp, "ADDR=", 5) == 0) {
+			if (net_addr2ip(*tmp + 5, &remote_ip) < 0)
+				args_ok = FALSE;
+		} else if (strncasecmp(*tmp, "PORT=", 5) == 0) {
+			if (str_to_uint(*tmp + 5, &remote_port) < 0 ||
+			    remote_port == 0 || remote_port > 65535)
+				args_ok = FALSE;
+		} else if (strncasecmp(*tmp, "TTL=", 4) == 0) {
+			if (str_to_uint(*tmp + 4, &ttl) < 0)
+				args_ok = FALSE;
+		}
+	}
+	if (!args_ok) {
+		client_send_line(client, "501 Invalid parameters");
+		return 0;
+	}
+
+	/* args ok, set them and reset the state */
+	client_state_reset(client);
+	if (remote_ip.family != 0)
+		client->remote_ip = remote_ip;
+	if (remote_port != 0)
+		client->remote_port = remote_port;
+	if (ttl != -1U)
+		client->proxy_ttl = ttl;
+	client_send_line(client, "220 %s %s", client->my_domain,
+			 client->lmtp_set->login_greeting);
+	return 0;
+}
--- a/src/lmtp/commands.h	Sun May 20 03:08:01 2012 +0300
+++ b/src/lmtp/commands.h	Sun May 20 03:25:04 2012 +0300
@@ -11,5 +11,6 @@
 int cmd_rset(struct client *client, const char *args);
 int cmd_noop(struct client *client, const char *args);
 int cmd_data(struct client *client, const char *args);
+int cmd_xclient(struct client *client, const char *args);
 
 #endif
--- a/src/lmtp/lmtp-proxy.c	Sun May 20 03:08:01 2012 +0300
+++ b/src/lmtp/lmtp-proxy.c	Sun May 20 03:25:04 2012 +0300
@@ -21,7 +21,7 @@
 
 struct lmtp_proxy_connection {
 	struct lmtp_proxy *proxy;
-	struct lmtp_proxy_settings set;
+	struct lmtp_proxy_rcpt_settings set;
 
 	struct lmtp_client *client;
 	struct istream *data_input;
@@ -33,8 +33,8 @@
 
 struct lmtp_proxy {
 	pool_t pool;
-	const char *mail_from, *my_hostname;
-	const char *dns_client_socket_path;
+	const char *mail_from;
+	struct lmtp_proxy_settings set;
 
 	ARRAY_DEFINE(connections, struct lmtp_proxy_connection *);
 	ARRAY_DEFINE(rcpt_to, struct lmtp_proxy_recipient *);
@@ -55,7 +55,7 @@
 static void lmtp_conn_finish(void *context);
 
 struct lmtp_proxy *
-lmtp_proxy_init(const char *my_hostname, const char *dns_client_socket_path,
+lmtp_proxy_init(const struct lmtp_proxy_settings *set,
 		struct ostream *client_output)
 {
 	struct lmtp_proxy *proxy;
@@ -66,9 +66,13 @@
 	pool = pool_alloconly_create("lmtp proxy", 1024);
 	proxy = p_new(pool, struct lmtp_proxy, 1);
 	proxy->pool = pool;
-	proxy->my_hostname = p_strdup(pool, my_hostname);
 	proxy->client_output = client_output;
-	proxy->dns_client_socket_path = p_strdup(pool, dns_client_socket_path);
+	proxy->set.my_hostname = p_strdup(pool, set->my_hostname);
+	proxy->set.dns_client_socket_path =
+		p_strdup(pool, set->dns_client_socket_path);
+	proxy->set.source_ip = set->source_ip;
+	proxy->set.source_port = set->source_port;
+	proxy->set.proxy_ttl = set->proxy_ttl;
 	i_array_init(&proxy->rcpt_to, 32);
 	i_array_init(&proxy->connections, 32);
 	return proxy;
@@ -110,7 +114,7 @@
 
 static struct lmtp_proxy_connection *
 lmtp_proxy_get_connection(struct lmtp_proxy *proxy,
-			  const struct lmtp_proxy_settings *set)
+			  const struct lmtp_proxy_rcpt_settings *set)
 {
 	struct lmtp_proxy_connection *const *conns, *conn;
 	struct lmtp_client_settings client_set;
@@ -127,8 +131,11 @@
 
 	memset(&client_set, 0, sizeof(client_set));
 	client_set.mail_from = proxy->mail_from;
-	client_set.my_hostname = proxy->my_hostname;
-	client_set.dns_client_socket_path = proxy->dns_client_socket_path;
+	client_set.my_hostname = proxy->set.my_hostname;
+	client_set.dns_client_socket_path = proxy->set.dns_client_socket_path;
+	client_set.source_ip = proxy->set.source_ip;
+	client_set.source_port = proxy->set.source_port;
+	client_set.proxy_ttl_plus_1 = proxy->set.proxy_ttl+1;
 
 	conn = p_new(proxy->pool, struct lmtp_proxy_connection, 1);
 	conn->proxy = proxy;
@@ -236,7 +243,7 @@
 }
 
 int lmtp_proxy_add_rcpt(struct lmtp_proxy *proxy, const char *address,
-			const struct lmtp_proxy_settings *set)
+			const struct lmtp_proxy_rcpt_settings *set)
 {
 	struct lmtp_proxy_connection *conn;
 	struct lmtp_proxy_recipient *rcpt;
@@ -294,4 +301,5 @@
 		lmtp_client_send(conn->client, conn->data_input);
 		lmtp_client_send_more(conn->client);
 	}
+	lmtp_proxy_try_finish(proxy);
 }
--- a/src/lmtp/lmtp-proxy.h	Sun May 20 03:08:01 2012 +0300
+++ b/src/lmtp/lmtp-proxy.h	Sun May 20 03:25:04 2012 +0300
@@ -4,7 +4,19 @@
 #include "network.h"
 #include "lmtp-client.h"
 
+#define LMTP_PROXY_DEFAULT_TTL 5
+
 struct lmtp_proxy_settings {
+	const char *my_hostname;
+	const char *dns_client_socket_path;
+
+	/* the original client's IP/port that connected to the proxy */
+	struct ip_addr source_ip;
+	unsigned int source_port;
+	unsigned int proxy_ttl;
+};
+
+struct lmtp_proxy_rcpt_settings {
 	const char *host;
 	unsigned int port;
 	unsigned int timeout_msecs;
@@ -14,7 +26,7 @@
 typedef void lmtp_proxy_finish_callback_t(void *context);
 
 struct lmtp_proxy *
-lmtp_proxy_init(const char *my_hostname, const char *dns_client_socket_path,
+lmtp_proxy_init(const struct lmtp_proxy_settings *set,
 		struct ostream *client_output);
 void lmtp_proxy_deinit(struct lmtp_proxy **proxy);
 
@@ -23,7 +35,7 @@
 /* Add a new recipient. Returns -1 if we already know that the destination
    host can't be reached. */
 int lmtp_proxy_add_rcpt(struct lmtp_proxy *proxy, const char *address,
-			const struct lmtp_proxy_settings *set);
+			const struct lmtp_proxy_rcpt_settings *set);
 /* Start proxying */
 void lmtp_proxy_start(struct lmtp_proxy *proxy, struct istream *data_input,
 		      const char *header,
--- a/src/lmtp/lmtp-settings.c	Sun May 20 03:08:01 2012 +0300
+++ b/src/lmtp/lmtp-settings.c	Sun May 20 03:25:04 2012 +0300
@@ -60,6 +60,7 @@
 	DEF(SET_BOOL, lmtp_proxy),
 	DEF(SET_BOOL, lmtp_save_to_detail_mailbox),
 	DEF(SET_STR_VARS, login_greeting),
+	DEF(SET_STR, login_trusted_networks),
 
 	SETTING_DEFINE_LIST_END
 };
@@ -67,7 +68,8 @@
 static const struct lmtp_settings lmtp_default_settings = {
 	.lmtp_proxy = FALSE,
 	.lmtp_save_to_detail_mailbox = FALSE,
-	.login_greeting = PACKAGE_NAME" ready."
+	.login_greeting = PACKAGE_NAME" ready.",
+	.login_trusted_networks = ""
 };
 
 static const struct setting_parser_info *lmtp_setting_dependencies[] = {
--- a/src/lmtp/lmtp-settings.h	Sun May 20 03:08:01 2012 +0300
+++ b/src/lmtp/lmtp-settings.h	Sun May 20 03:25:04 2012 +0300
@@ -8,6 +8,7 @@
 	bool lmtp_proxy;
 	bool lmtp_save_to_detail_mailbox;
 	const char *login_greeting;
+	const char *login_trusted_networks;
 };
 
 extern const struct setting_parser_info lmtp_setting_parser_info;
--- a/src/login-common/Makefile.am	Sun May 20 03:08:01 2012 +0300
+++ b/src/login-common/Makefile.am	Sun May 20 03:25:04 2012 +0300
@@ -4,7 +4,6 @@
 	-I$(top_srcdir)/src/lib \
 	-I$(top_srcdir)/src/lib-settings \
 	-I$(top_srcdir)/src/lib-auth \
-	-I$(top_srcdir)/src/lib-dns \
 	-I$(top_srcdir)/src/lib-master \
 	-I$(top_srcdir)/src/lib-ssl-iostream \
 	-I$(top_srcdir)/src/lib-mail \
--- a/src/login-common/client-common-auth.c	Sun May 20 03:08:01 2012 +0300
+++ b/src/login-common/client-common-auth.c	Sun May 20 03:25:04 2012 +0300
@@ -1,10 +1,12 @@
 /* Copyright (c) 2002-2012 Dovecot authors, see the included COPYING file */
 
+#include "hostpid.h"
 #include "login-common.h"
 #include "istream.h"
 #include "ostream.h"
 #include "str.h"
 #include "safe-memset.h"
+#include "time-util.h"
 #include "login-proxy.h"
 #include "auth-client.h"
 #include "client-common.h"
@@ -12,14 +14,13 @@
 #include <stdlib.h>
 
 #define PROXY_FAILURE_MSG "Account is temporarily unavailable."
-#define LOGIN_DNS_CLIENT_SOCKET_PATH "dns-client"
 
 /* If we've been waiting auth server to respond for over this many milliseconds,
    send a "waiting" message. */
 #define AUTH_WAITING_TIMEOUT_MSECS (30*1000)
-#define GREETING_WARNING_TIMEOUT_MSECS (10*1000)
+#define AUTH_WAITING_WARNING_TIMEOUT_MSECS (10*1000)
 
-void client_auth_failed(struct client *client)
+static void client_auth_failed(struct client *client)
 {
 	i_free_and_null(client->master_data_prefix);
 	if (client->auth_response != NULL)
@@ -37,13 +38,12 @@
 
 static void client_auth_waiting_timeout(struct client *client)
 {
-	if (!client->greeting_sent) {
+	if (!client->notified_auth_ready) {
 		client_log_warn(client, "Auth process not responding, "
-				"delayed sending greeting");
+				"delayed sending initial response (greeting)");
 	}
-	client_send_line(client, CLIENT_CMD_REPLY_STATUS,
-			 client->master_tag == 0 ?
-			 AUTH_SERVER_WAITING_MSG : AUTH_MASTER_WAITING_MSG);
+	client_notify_status(client, FALSE, client->master_tag == 0 ?
+			     AUTH_SERVER_WAITING_MSG : AUTH_MASTER_WAITING_MSG);
 	timeout_remove(&client->to_auth_waiting);
 }
 
@@ -51,8 +51,8 @@
 {
 	i_assert(client->to_auth_waiting == NULL);
 	client->to_auth_waiting =
-		timeout_add(!client->greeting_sent ?
-			    GREETING_WARNING_TIMEOUT_MSECS :
+		timeout_add(!client->notified_auth_ready ?
+			    AUTH_WAITING_WARNING_TIMEOUT_MSECS :
 			    AUTH_WAITING_TIMEOUT_MSECS,
 			    client_auth_waiting_timeout, client);
 }
@@ -165,6 +165,11 @@
 	client_destroy_success(client, str_c(str));
 }
 
+static void client_proxy_error(struct client *client, const char *text)
+{
+	client->v.proxy_error(client, text);
+}
+
 void client_proxy_log_failure(struct client *client, const char *line)
 {
 	string_t *str = t_str_new(128);
@@ -188,8 +193,7 @@
 void client_proxy_failed(struct client *client, bool send_line)
 {
 	if (send_line) {
-		client_send_line(client, CLIENT_CMD_REPLY_AUTH_FAIL_TEMP,
-				 PROXY_FAILURE_MSG);
+		client_proxy_error(client, PROXY_FAILURE_MSG);
 	}
 
 	login_proxy_free(&client->login_proxy);
@@ -272,14 +276,12 @@
 
 	if (reply->password == NULL) {
 		client_log_err(client, "proxy: password not given");
-		client_send_line(client, CLIENT_CMD_REPLY_AUTH_FAIL_TEMP,
-				 PROXY_FAILURE_MSG);
+		client_proxy_error(client, PROXY_FAILURE_MSG);
 		return -1;
 	}
 	if (reply->host == NULL || *reply->host == '\0') {
 		client_log_err(client, "proxy: host not given");
-		client_send_line(client, CLIENT_CMD_REPLY_AUTH_FAIL_TEMP,
-				 PROXY_FAILURE_MSG);
+		client_proxy_error(client, PROXY_FAILURE_MSG);
 		return -1;
 	}
 
@@ -293,8 +295,7 @@
 	if (login_proxy_is_ourself(client, reply->host, reply->port,
 				   reply->destuser)) {
 		client_log_err(client, "Proxying loops to itself");
-		client_send_line(client, CLIENT_CMD_REPLY_AUTH_FAIL_TEMP,
-				 PROXY_FAILURE_MSG);
+		client_proxy_error(client, PROXY_FAILURE_MSG);
 		return -1;
 	}
 
@@ -304,14 +305,12 @@
 	    net_addr2ip(reply->hostip, &proxy_set.ip) < 0)
 		proxy_set.ip.family = 0;
 	proxy_set.port = reply->port;
-	proxy_set.dns_client_socket_path = LOGIN_DNS_CLIENT_SOCKET_PATH;
 	proxy_set.connect_timeout_msecs = reply->proxy_timeout_msecs;
 	proxy_set.notify_refresh_secs = reply->proxy_refresh_secs;
 	proxy_set.ssl_flags = reply->ssl_flags;
 
 	if (login_proxy_new(client, &proxy_set, proxy_input) < 0) {
-		client_send_line(client, CLIENT_CMD_REPLY_AUTH_FAIL_TEMP,
-				 PROXY_FAILURE_MSG);
+		client_proxy_error(client, PROXY_FAILURE_MSG);
 		return -1;
 	}
 
@@ -325,6 +324,13 @@
 	return 0;
 }
 
+static void
+client_auth_result(struct client *client, enum client_auth_result result,
+		   const struct client_auth_reply *reply, const char *text)
+{
+	client->v.auth_result(client, result, reply, text);
+}
+
 static bool
 client_auth_handle_reply(struct client *client,
 			 const struct client_auth_reply *reply, bool success)
@@ -341,7 +347,79 @@
 			client_auth_failed(client);
 		return TRUE;
 	}
-	return client->v.auth_handle_reply(client, reply);
+
+	if (reply->host != NULL) {
+		const char *reason;
+
+		if (reply->reason != NULL)
+			reason = reply->reason;
+		else if (reply->nologin)
+			reason = "Try this server instead.";
+		else
+			reason = "Logged in, but you should use this server instead.";
+
+		if (reply->nologin) {
+			client_auth_result(client,
+				CLIENT_AUTH_RESULT_REFERRAL_NOLOGIN,
+				reply, reason);
+		} else {
+			client_auth_result(client,
+				CLIENT_AUTH_RESULT_REFERRAL_SUCCESS,
+				reply, reason);
+			return TRUE;
+		}
+	} else if (reply->nologin) {
+		/* Authentication went ok, but for some reason user isn't
+		   allowed to log in. Shouldn't probably happen. */
+		if (reply->reason != NULL) {
+			client_auth_result(client,
+				CLIENT_AUTH_RESULT_AUTHFAILED_REASON,
+				reply, reply->reason);
+		} else if (reply->temp) {
+			const char *timestamp, *msg;
+
+			timestamp = t_strflocaltime("%Y-%m-%d %H:%M:%S", ioloop_time);
+			msg = t_strdup_printf(AUTH_TEMP_FAILED_MSG" [%s:%s]",
+				      my_hostname, timestamp);
+			client_auth_result(client, CLIENT_AUTH_RESULT_TEMPFAIL,
+					   reply, msg);
+		} else if (reply->authz_failure) {
+			client_auth_result(client,
+				CLIENT_AUTH_RESULT_AUTHZFAILED, reply,
+				"Authorization failed");
+		} else {
+			client_auth_result(client,
+				CLIENT_AUTH_RESULT_AUTHFAILED, reply,
+				AUTH_FAILED_MSG);
+		}
+	} else {
+		/* normal login/failure */
+		return FALSE;
+	}
+
+	i_assert(reply->nologin);
+
+	if (!client->destroyed)
+		client_auth_failed(client);
+	return TRUE;
+}
+
+void client_auth_respond(struct client *client, const char *response)
+{
+	client->auth_waiting = FALSE;
+	client_set_auth_waiting(client);
+	auth_client_request_continue(client->auth_request, response);
+	io_remove(&client->io);
+}
+
+void client_auth_abort(struct client *client)
+{
+	sasl_server_auth_abort(client);
+}
+
+void client_auth_fail(struct client *client, const char *text)
+{
+	sasl_server_auth_failed(client, text);
 }
 
 int client_auth_read_line(struct client *client)
@@ -377,33 +455,24 @@
 	return i < size;
 }
 
-int client_auth_parse_response(struct client *client)
+void client_auth_parse_response(struct client *client)
 {
-	int ret;
-
-	if ((ret = client_auth_read_line(client)) <= 0)
-		return ret;
+	if (client_auth_read_line(client) <= 0)
+		return;
 
 	if (strcmp(str_c(client->auth_response), "*") == 0) {
 		sasl_server_auth_abort(client);
-		return -1;
+		return;
 	}
-	return 1;
+
+	client_auth_respond(client, str_c(client->auth_response));
+	memset(str_c_modifiable(client->auth_response), 0,
+	       str_len(client->auth_response));
 }
 
 static void client_auth_input(struct client *client)
 {
-	if (client->v.auth_parse_response(client) <= 0)
-		return;
-
-	client->auth_waiting = FALSE;
-	client_set_auth_waiting(client);
-	auth_client_request_continue(client->auth_request,
-				     str_c(client->auth_response));
-	io_remove(&client->io);
-
-	memset(str_c_modifiable(client->auth_response), 0,
-	       str_len(client->auth_response));
+	client->v.auth_parse_response(client);
 }
 
 void client_auth_send_challenge(struct client *client, const char *data)
@@ -439,6 +508,8 @@
 			if (client_auth_handle_reply(client, &reply, TRUE))
 				break;
 		}
+		client_auth_result(client, CLIENT_AUTH_RESULT_SUCCESS,
+				   NULL, NULL);
 		client_destroy_success(client, "Login");
 		break;
 	case SASL_SERVER_REPLY_AUTH_FAILED:
@@ -453,15 +524,16 @@
 		}
 
 		if (sasl_reply == SASL_SERVER_REPLY_AUTH_ABORTED) {
-			client_send_line(client, CLIENT_CMD_REPLY_BAD,
-					 "Authentication aborted by client.");
+			client_auth_result(client, CLIENT_AUTH_RESULT_ABORTED,
+				NULL, "Authentication aborted by client.");
 		} else if (data == NULL) {
-			client_send_line(client, CLIENT_CMD_REPLY_AUTH_FAILED,
-					 AUTH_FAILED_MSG);
+			client_auth_result(client,
+				CLIENT_AUTH_RESULT_AUTHFAILED, NULL,
+				AUTH_FAILED_MSG);
 		} else {
-			client_send_line(client,
-					 CLIENT_CMD_REPLY_AUTH_FAIL_REASON,
-					 data);
+			client_auth_result(client,
+				CLIENT_AUTH_RESULT_AUTHFAILED_REASON, NULL,
+				AUTH_FAILED_MSG);
 		}
 
 		if (!client->destroyed)
@@ -471,8 +543,8 @@
 		if (data != NULL) {
 			/* authentication itself succeeded, we just hit some
 			   internal failure. */
-			client_send_line(client,
-					 CLIENT_CMD_REPLY_AUTH_FAIL_TEMP, data);
+			client_auth_result(client, CLIENT_AUTH_RESULT_TEMPFAIL,
+					   NULL, data);
 		}
 
 		/* the fd may still be hanging somewhere in kernel or another
@@ -514,7 +586,7 @@
 				   "SSL required for authentication");
 		}
 		client->auth_attempts++;
-		client_send_line(client, CLIENT_CMD_REPLY_AUTH_FAIL_NOSSL,
+		client_auth_result(client, CLIENT_AUTH_RESULT_SSL_REQUIRED, NULL,
 			"Authentication not allowed until SSL/TLS is enabled.");
 		return 1;
 	}
@@ -545,13 +617,13 @@
 			   "Plaintext authentication disabled");
 	}
 	if (pass_sent) {
-		client_send_line(client, CLIENT_CMD_REPLY_STATUS_BAD,
+		client_notify_status(client, TRUE,
 			 "Plaintext authentication not allowed "
 			 "without SSL/TLS, but your client did it anyway. "
 			 "If anyone was listening, the password was exposed.");
 	}
-	client_send_line(client, CLIENT_CMD_REPLY_AUTH_FAIL_NOSSL,
-			 AUTH_PLAINTEXT_DISABLED_MSG);
+	client_auth_result(client, CLIENT_AUTH_RESULT_SSL_REQUIRED, NULL,
+			   AUTH_PLAINTEXT_DISABLED_MSG);
 	client->auth_tried_disabled_plaintext = TRUE;
 	client->auth_attempts++;
 	return FALSE;
@@ -566,8 +638,9 @@
 
 		if (client->to_auth_waiting != NULL)
 			timeout_remove(&client->to_auth_waiting);
-		if (!client->greeting_sent)
-			client->v.send_greeting(client);
+
+		client_notify_auth_ready(client);
+
 		if (client->input_blocked) {
 			client->input_blocked = FALSE;
 			client_input(client);
--- a/src/login-common/client-common.c	Sun May 20 03:08:01 2012 +0300
+++ b/src/login-common/client-common.c	Sun May 20 03:25:04 2012 +0300
@@ -56,7 +56,7 @@
 		user_reason = "Disconnected for inactivity.";
 		destroy_reason = "Disconnected: Inactivity";
 	}
-	client_send_line(client, CLIENT_CMD_REPLY_BYE, user_reason);
+	client_notify_disconnect(client, CLIENT_DISCONNECT_TIMEOUT, user_reason);
 	client_destroy(client, destroy_reason);
 }
 
@@ -74,6 +74,29 @@
 	}
 }
 
+static bool client_is_trusted(struct client *client)
+{
+	const char *const *net;
+	struct ip_addr net_ip;
+	unsigned int bits;
+
+	if (client->set->login_trusted_networks == NULL)
+		return FALSE;
+
+	net = t_strsplit_spaces(client->set->login_trusted_networks, ", ");
+	for (; *net != NULL; net++) {
+		if (net_parse_range(*net, &net_ip, &bits) < 0) {
+			i_error("login_trusted_networks: "
+				"Invalid network '%s'", *net);
+			break;
+		}
+
+		if (net_is_in_network(&client->ip, &net_ip, bits))
+			return TRUE;
+	}
+	return FALSE;
+}
+
 struct client *
 client_create(int fd, bool ssl, pool_t pool,
 	      const struct login_settings *set, void **other_sets,
@@ -102,6 +125,7 @@
 	client->trusted = client_is_trusted(client);
 	client->secured = ssl || client->trusted ||
 		net_ip_compare(remote_ip, local_ip);
+	client->proxy_ttl = LOGIN_PROXY_TTL;
 
 	if (last_client == NULL)
 		last_client = client;
@@ -116,7 +140,7 @@
 	client->v.create(client, other_sets);
 
 	if (auth_client_is_connected(auth_client))
-		client->v.send_greeting(client);
+		client_notify_auth_ready(client);
 	else
 		client_set_auth_waiting(client);
 
@@ -203,9 +227,9 @@
 
 void client_destroy_internal_failure(struct client *client)
 {
-	client_send_line(client, CLIENT_CMD_REPLY_AUTH_FAIL_TEMP,
-			 "Internal login failure. "
-			 "Refer to server log for more information.");
+	client_notify_disconnect(client, CLIENT_DISCONNECT_INTERNAL_ERROR,
+		"Internal login failure. "
+		"Refer to server log for more information.");
 	client_destroy(client, t_strdup_printf(
 		"Internal login failure (pid=%s id=%u)",
 		my_pid, client->master_auth_id));
@@ -270,6 +294,8 @@
 	if (client == NULL)
 		client = last_client;
 
+	client_notify_disconnect(client, CLIENT_DISCONNECT_RESOURCE_CONSTRAINT,
+				 "Connection queue full");
 	client_destroy(client, "Disconnected: Connection queue full");
 }
 
@@ -279,6 +305,8 @@
 
 	for (client = clients; client != NULL; client = next) {
 		next = client->next;
+		client_notify_disconnect(client,
+			CLIENT_DISCONNECT_SYSTEM_SHUTDOWN, reason);
 		client_destroy(client, reason);
 	}
 }
@@ -299,10 +327,11 @@
 	fd_ssl = ssl_proxy_alloc(client->fd, &client->ip, client->pool,
 				 client->set, &client->ssl_proxy);
 	if (fd_ssl == -1) {
-		client_send_line(client, CLIENT_CMD_REPLY_BYE,
-				 "TLS initialization failed.");
+		client_notify_disconnect(client,
+			CLIENT_DISCONNECT_INTERNAL_ERROR,
+			"TLS initialization failed.");
 		client_destroy(client,
-			       "Disconnected: TLS initialization failed.");
+			"Disconnected: TLS initialization failed.");
 		return;
 	}
 	ssl_proxy_set_client(client->ssl_proxy, client);
@@ -341,14 +370,12 @@
 void client_cmd_starttls(struct client *client)
 {
 	if (client->tls) {
-		client_send_line(client, CLIENT_CMD_REPLY_BAD,
-				 "TLS is already active.");
+		client->v.notify_starttls(client, FALSE, "TLS is already active.");
 		return;
 	}
 
 	if (!ssl_initialized) {
-		client_send_line(client, CLIENT_CMD_REPLY_BAD,
-				 "TLS support isn't enabled.");
+		client->v.notify_starttls(client, FALSE, "TLS support isn't enabled.");
 		return;
 	}
 
@@ -357,8 +384,7 @@
 	if (client->io != NULL)
 		io_remove(&client->io);
 
-	client_send_line(client, CLIENT_CMD_REPLY_OK,
-			 "Begin TLS negotiation now.");
+	client->v.notify_starttls(client, TRUE, "Begin TLS negotiation now.");
 
 	/* uncork the old fd */
 	o_stream_uncork(client->output);
@@ -568,29 +594,6 @@
 	} T_END;
 }
 
-bool client_is_trusted(struct client *client)
-{
-	const char *const *net;
-	struct ip_addr net_ip;
-	unsigned int bits;
-
-	if (client->set->login_trusted_networks == NULL)
-		return FALSE;
-
-	net = t_strsplit_spaces(client->set->login_trusted_networks, ", ");
-	for (; *net != NULL; net++) {
-		if (net_parse_range(*net, &net_ip, &bits) < 0) {
-			i_error("login_trusted_networks: "
-				"Invalid network '%s'", *net);
-			break;
-		}
-
-		if (net_is_in_network(&client->ip, &net_ip, bits))
-			return TRUE;
-	}
-	return FALSE;
-}
-
 const char *client_get_extra_disconnect_reason(struct client *client)
 {
 	unsigned int auth_secs = client->auth_first_started == 0 ? 0 :
@@ -604,9 +607,9 @@
 			return "(client didn't send a cert)";
 	}
 
-	if (!client->greeting_sent)
+	if (!client->notified_auth_ready)
 		return t_strdup_printf(
-			"(disconnected before greeting, waited %u secs)",
+			"(disconnected before auth was ready, waited %u secs)",
 			(unsigned int)(ioloop_time - client->created));
 
 	if (client->auth_attempts == 0) {
@@ -653,10 +656,28 @@
 			       client->auth_attempts, auth_secs);
 }
 
-void client_send_line(struct client *client, enum client_cmd_reply reply,
-		      const char *text)
+void client_notify_disconnect(struct client *client,
+			      enum client_disconnect_reason reason,
+			      const char *text)
 {
-	client->v.send_line(client, reply, text);
+	if (!client->notified_disconnect) {
+		client->v.notify_disconnect(client, reason, text);
+		client->notified_disconnect = TRUE;
+	}
+}
+
+void client_notify_auth_ready(struct client *client)
+{
+	if (!client->notified_auth_ready) {
+		client->v.notify_auth_ready(client);
+		client->notified_auth_ready = TRUE;
+	}
+}
+
+void client_notify_status(struct client *client, bool bad, const char *text)
+{
+	if (client->v.notify_status != NULL)
+		client->v.notify_status(client, bad, text);
 }
 
 void client_send_raw_data(struct client *client, const void *data, size_t size)
@@ -683,8 +704,9 @@
 	switch (i_stream_read(client->input)) {
 	case -2:
 		/* buffer full */
-		client_send_line(client, CLIENT_CMD_REPLY_BYE,
-				 "Input buffer full, aborting");
+		client_notify_disconnect(client,
+			CLIENT_DISCONNECT_RESOURCE_CONSTRAINT,
+			"Input buffer full, aborting");
 		client_destroy(client, "Disconnected: Input buffer full");
 		return FALSE;
 	case -1:
--- a/src/login-common/client-common.h	Sun May 20 03:08:01 2012 +0300
+++ b/src/login-common/client-common.h	Sun May 20 03:25:04 2012 +0300
@@ -33,17 +33,23 @@
 #define AUTH_MASTER_WAITING_MSG \
 	"Waiting for authentication master process to respond.."
 
-enum client_cmd_reply {
-	CLIENT_CMD_REPLY_OK,
-	CLIENT_CMD_REPLY_AUTH_FAILED,
-	CLIENT_CMD_REPLY_AUTHZ_FAILED,
-	CLIENT_CMD_REPLY_AUTH_FAIL_TEMP,
-	CLIENT_CMD_REPLY_AUTH_FAIL_REASON,
-	CLIENT_CMD_REPLY_AUTH_FAIL_NOSSL,
-	CLIENT_CMD_REPLY_BAD,
-	CLIENT_CMD_REPLY_BYE,
-	CLIENT_CMD_REPLY_STATUS,
-	CLIENT_CMD_REPLY_STATUS_BAD
+enum client_disconnect_reason {
+	CLIENT_DISCONNECT_TIMEOUT,
+	CLIENT_DISCONNECT_SYSTEM_SHUTDOWN,
+	CLIENT_DISCONNECT_RESOURCE_CONSTRAINT,
+	CLIENT_DISCONNECT_INTERNAL_ERROR
+};
+
+enum client_auth_result {
+	CLIENT_AUTH_RESULT_SUCCESS,
+	CLIENT_AUTH_RESULT_REFERRAL_SUCCESS,
+	CLIENT_AUTH_RESULT_REFERRAL_NOLOGIN,
+	CLIENT_AUTH_RESULT_ABORTED,
+	CLIENT_AUTH_RESULT_AUTHFAILED,
+	CLIENT_AUTH_RESULT_AUTHFAILED_REASON,
+	CLIENT_AUTH_RESULT_AUTHZFAILED,
+	CLIENT_AUTH_RESULT_TEMPFAIL,
+	CLIENT_AUTH_RESULT_SSL_REQUIRED
 };
 
 struct client_auth_reply {
@@ -65,17 +71,25 @@
 	struct client *(*alloc)(pool_t pool);
 	void (*create)(struct client *client, void **other_sets);
 	void (*destroy)(struct client *client);
-	void (*send_greeting)(struct client *client);
+	void (*notify_auth_ready)(struct client *client);
+	void (*notify_disconnect)(struct client *client,
+				  enum client_disconnect_reason reason,
+				  const char *text);
+	void (*notify_status)(struct client *client,
+			      bool bad, const char *text);
+	void (*notify_starttls)(struct client *client,
+				bool success, const char *text);
 	void (*starttls)(struct client *client);
 	void (*input)(struct client *client);
-	void (*send_line)(struct client *client, enum client_cmd_reply reply,
-			  const char *text);
-	bool (*auth_handle_reply)(struct client *client,
-				  const struct client_auth_reply *reply);
 	void (*auth_send_challenge)(struct client *client, const char *data);
-	int (*auth_parse_response)(struct client *client);
+	void (*auth_parse_response)(struct client *client);
+	void (*auth_result)(struct client *client,
+			    enum client_auth_result result,
+			    const struct client_auth_reply *reply,
+			    const char *text);
 	void (*proxy_reset)(struct client *client);
 	int (*proxy_parse_line)(struct client *client, const char *line);
+	void (*proxy_error)(struct client *client, const char *text);
 };
 
 struct client {
@@ -106,6 +120,7 @@
 	struct login_proxy *login_proxy;
 	char *proxy_user, *proxy_master_user, *proxy_password;
 	unsigned int proxy_state;
+	unsigned int proxy_ttl;
 
 	char *auth_mech_name;
 	struct auth_client_request *auth_request;
@@ -125,7 +140,6 @@
 	unsigned int destroyed:1;
 	unsigned int input_blocked:1;
 	unsigned int login_success:1;
-	unsigned int greeting_sent:1;
 	unsigned int starttls:1;
 	unsigned int tls:1;
 	unsigned int secured:1;
@@ -141,6 +155,8 @@
 	unsigned int auth_waiting:1;
 	unsigned int auth_user_disabled:1;
 	unsigned int auth_pass_expired:1;
+	unsigned int notified_auth_ready:1;
+	unsigned int notified_disconnect:1;
 	/* ... */
 };
 
@@ -166,21 +182,27 @@
 void client_log_err(struct client *client, const char *msg);
 void client_log_warn(struct client *client, const char *msg);
 const char *client_get_extra_disconnect_reason(struct client *client);
-bool client_is_trusted(struct client *client);
-void client_auth_failed(struct client *client);
+
+void client_auth_respond(struct client *client, const char *response);
+void client_auth_abort(struct client *client);
+void client_auth_fail(struct client *client, const char *text);
 const char *client_get_session_id(struct client *client);
 
 bool client_read(struct client *client);
 void client_input(struct client *client);
 
-void client_send_line(struct client *client, enum client_cmd_reply reply,
-		      const char *text);
+void client_notify_auth_ready(struct client *client);
+void client_notify_status(struct client *client, bool bad, const char *text);
+void client_notify_disconnect(struct client *client,
+			      enum client_disconnect_reason reason,
+			      const char *text);
+
 void client_send_raw_data(struct client *client, const void *data, size_t size);
 void client_send_raw(struct client *client, const char *data);
 
 void client_set_auth_waiting(struct client *client);
 void client_auth_send_challenge(struct client *client, const char *data);
-int client_auth_parse_response(struct client *client);
+void client_auth_parse_response(struct client *client);
 int client_auth_begin(struct client *client, const char *mech_name,
 		      const char *init_resp);
 bool client_check_plaintext_auth(struct client *client, bool pass_sent);
--- a/src/login-common/login-proxy.c	Sun May 20 03:08:01 2012 +0300
+++ b/src/login-common/login-proxy.c	Sun May 20 03:25:04 2012 +0300
@@ -9,7 +9,6 @@
 #include "time-util.h"
 #include "master-service.h"
 #include "ipc-server.h"
-#include "dns-lookup.h"
 #include "mail-user-hash.h"
 #include "client-common.h"
 #include "ssl-proxy.h"
@@ -19,7 +18,6 @@
 #define MAX_PROXY_INPUT_SIZE 4096
 #define OUTBUF_THRESHOLD 1024
 #define LOGIN_PROXY_DIE_IDLE_SECS 2
-#define LOGIN_PROXY_DNS_WARN_MSECS 500
 #define LOGIN_PROXY_IPC_PATH "ipc-proxy"
 #define LOGIN_PROXY_IPC_NAME "proxy"
 #define KILLED_BY_ADMIN_REASON "Killed by admin"
@@ -269,32 +267,11 @@
 	return 0;
 }
 
-static void login_proxy_dns_done(const struct dns_lookup_result *result,
-				 struct login_proxy *proxy)
-{
-	if (result->ret != 0) {
-		i_error("proxy(%s): DNS lookup of %s failed: %s",
-			proxy->client->virtual_user, proxy->host,
-			result->error);
-		login_proxy_free(&proxy);
-	} else {
-		if (result->msecs > LOGIN_PROXY_DNS_WARN_MSECS) {
-			i_warning("proxy(%s): DNS lookup for %s took %u.%03u s",
-				  proxy->client->virtual_user, proxy->host,
-				  result->msecs/1000, result->msecs % 1000);
-		}
-
-		proxy->ip = result->ips[0];
-		(void)login_proxy_connect(proxy);
-	}
-}
-
 int login_proxy_new(struct client *client,
 		    const struct login_proxy_settings *set,
 		    proxy_callback_t *callback)
 {
 	struct login_proxy *proxy;
-	struct dns_lookup_settings dns_lookup_set;
 
 	i_assert(client->login_proxy == NULL);
 
@@ -303,6 +280,12 @@
 		return -1;
 	}
 
+	if (client->proxy_ttl == 0) {
+		i_error("proxy(%s): TTL reached zero - "
+			"proxies appear to be looping?", client->virtual_user);
+		return -1;
+	}
+
 	proxy = i_new(struct login_proxy, 1);
 	proxy->client = client;
 	proxy->client_fd = -1;
@@ -316,15 +299,11 @@
 	proxy->ssl_flags = set->ssl_flags;
 	client_ref(client);
 
-	memset(&dns_lookup_set, 0, sizeof(dns_lookup_set));
-	dns_lookup_set.dns_client_socket_path = set->dns_client_socket_path;
-	dns_lookup_set.timeout_msecs = set->connect_timeout_msecs;
-
 	if (set->ip.family == 0 &&
 	    net_addr2ip(set->host, &proxy->ip) < 0) {
-		if (dns_lookup(set->host, &dns_lookup_set,
-			       login_proxy_dns_done, proxy) < 0)
-			return -1;
+		i_error("proxy(%s): BUG: host %s is not an IP "
+			"(auth should have changed it)",
+			client->virtual_user, set->host);
 	} else {
 		if (login_proxy_connect(proxy) < 0)
 			return -1;
--- a/src/login-common/login-proxy.h	Sun May 20 03:08:01 2012 +0300
+++ b/src/login-common/login-proxy.h	Sun May 20 03:25:04 2012 +0300
@@ -3,6 +3,13 @@
 
 #include "network.h"
 
+/* Max. number of embedded proxying connections until proxying fails.
+   This is intended to avoid an accidental configuration where two proxies
+   keep connecting to each others, both thinking the other one is supposed to
+   handle the user. This only works if both proxies support the Dovecot
+   TTL extension feature. */
+#define LOGIN_PROXY_TTL 5
+
 struct client;
 struct login_proxy;
 
@@ -18,7 +25,6 @@
 struct login_proxy_settings {
 	const char *host;
 	struct ip_addr ip;
-	const char *dns_client_socket_path;
 	unsigned int port;
 	unsigned int connect_timeout_msecs;
 	/* send a notification about proxy connection to proxy-notify pipe
--- a/src/plugins/imap-acl/imap-acl-plugin.c	Sun May 20 03:08:01 2012 +0300
+++ b/src/plugins/imap-acl/imap-acl-plugin.c	Sun May 20 03:25:04 2012 +0300
@@ -47,7 +47,7 @@
 const char *imap_acl_plugin_version = DOVECOT_VERSION;
 
 static struct module *imap_acl_module;
-static void (*next_hook_client_created)(struct client **client);
+static imap_client_created_func_t *next_hook_client_created;
 
 static struct mailbox *
 acl_mailbox_open_as_admin(struct client_command_context *cmd, const char *name)
--- a/src/plugins/imap-quota/imap-quota-plugin.c	Sun May 20 03:08:01 2012 +0300
+++ b/src/plugins/imap-quota/imap-quota-plugin.c	Sun May 20 03:25:04 2012 +0300
@@ -17,7 +17,7 @@
 const char *imap_quota_plugin_version = DOVECOT_VERSION;
 
 static struct module *imap_quota_module;
-static void (*next_hook_client_created)(struct client **client);
+static imap_client_created_func_t *next_hook_client_created;
 
 static const char *
 imap_quota_root_get_name(struct mail_user *user, struct mail_user *owner,
--- a/src/plugins/imap-zlib/imap-zlib-plugin.c	Sun May 20 03:08:01 2012 +0300
+++ b/src/plugins/imap-zlib/imap-zlib-plugin.c	Sun May 20 03:25:04 2012 +0300
@@ -25,7 +25,7 @@
 const char *imap_zlib_plugin_version = DOVECOT_VERSION;
 
 static struct module *imap_zlib_module;
-static void (*next_hook_client_created)(struct client **client);
+static imap_client_created_func_t *next_hook_client_created;
 
 static MODULE_CONTEXT_DEFINE_INIT(imap_zlib_imap_module,
 				  &imap_module_register);
--- a/src/plugins/virtual/virtual-storage.c	Sun May 20 03:08:01 2012 +0300
+++ b/src/plugins/virtual/virtual-storage.c	Sun May 20 03:25:04 2012 +0300
@@ -391,10 +391,8 @@
 
 		if (box->notify_callback == NULL)
 			mailbox_notify_changes_stop(bbox);
-		else {
-			mailbox_notify_changes(bbox, box->notify_min_interval,
-					       virtual_notify_callback, box);
-		}
+		else
+			mailbox_notify_changes(bbox, virtual_notify_callback, box);
 	}
 }
 
--- a/src/pop3-login/client-authenticate.c	Sun May 20 03:08:01 2012 +0300
+++ b/src/pop3-login/client-authenticate.c	Sun May 20 03:25:04 2012 +0300
@@ -4,14 +4,12 @@
 #include "base64.h"
 #include "buffer.h"
 #include "hex-binary.h"
-#include "hostpid.h"
 #include "ioloop.h"
 #include "istream.h"
 #include "ostream.h"
 #include "safe-memset.h"
 #include "str.h"
 #include "str-sanitize.h"
-#include "time-util.h"
 #include "auth-client.h"
 #include "../pop3/pop3-capability.h"
 #include "ssl-proxy.h"
@@ -51,30 +49,22 @@
 	return TRUE;
 }
 
-bool pop3_client_auth_handle_reply(struct client *client,
-				   const struct client_auth_reply *reply)
+void pop3_client_auth_result(struct client *client,
+			     enum client_auth_result result,
+			     const struct client_auth_reply *reply ATTR_UNUSED,
+			     const char *text)
 {
-	const char *timestamp, *msg;
-
-	if (!reply->nologin)
-		return FALSE;
-
-	if (reply->reason != NULL) {
-		client_send_line(client, CLIENT_CMD_REPLY_AUTH_FAILED,
-				 reply->reason);
-	} else if (reply->temp) {
-		timestamp = t_strflocaltime("%Y-%m-%d %H:%M:%S", ioloop_time);
-		msg = t_strdup_printf(AUTH_TEMP_FAILED_MSG" [%s:%s]",
-				      my_hostname, timestamp);
-		client_send_line(client, CLIENT_CMD_REPLY_AUTH_FAIL_TEMP, msg);
-	} else {
-		client_send_line(client, CLIENT_CMD_REPLY_AUTH_FAILED,
-				 AUTH_FAILED_MSG);
+	switch (result) {
+	case CLIENT_AUTH_RESULT_SUCCESS:
+		/* nothing to be done for POP3 */
+		break;
+	case CLIENT_AUTH_RESULT_TEMPFAIL:
+		client_send_reply(client, POP3_CMD_REPLY_TEMPFAIL, text);
+		break;
+	default:
+		client_send_reply(client, POP3_CMD_REPLY_ERROR, text);
+		break;
 	}
-
-	if (!client->destroyed)
-		client_auth_failed(client);
-	return TRUE;
 }
 
 bool cmd_auth(struct pop3_client *pop3_client, const char *args)
@@ -134,8 +124,8 @@
 		if (!client_check_plaintext_auth(client, TRUE))
 			return TRUE;
 
-		client_send_line(client, CLIENT_CMD_REPLY_BAD,
-				 "No username given.");
+		client_send_reply(client, POP3_CMD_REPLY_ERROR,
+				  "No username given.");
 		return TRUE;
 	}
 
@@ -166,8 +156,8 @@
 	if (pop3_client->apop_challenge == NULL) {
 		if (client->set->auth_verbose)
 			client_log(client, "APOP failed: APOP not enabled");
-		client_send_line(client, CLIENT_CMD_REPLY_BAD,
-				 "APOP not enabled.");
+		client_send_reply(client, POP3_CMD_REPLY_ERROR,
+				  "APOP not enabled.");
 		return TRUE;
 	}
 
@@ -176,8 +166,8 @@
 	if (p == NULL || strlen(p+1) != 32) {
 		if (client->set->auth_verbose)
 			client_log(client, "APOP failed: Invalid parameters");
-		client_send_line(client, CLIENT_CMD_REPLY_BAD,
-				 "Invalid parameters.");
+		client_send_reply(client, POP3_CMD_REPLY_ERROR,
+				  "Invalid parameters.");
 		return TRUE;
 	}
 
@@ -193,8 +183,8 @@
 			client_log(client, "APOP failed: "
 				   "Invalid characters in MD5 response");
 		}
-		client_send_line(client, CLIENT_CMD_REPLY_BAD,
-				 "Invalid characters in MD5 response.");
+		client_send_reply(client, POP3_CMD_REPLY_ERROR,
+				  "Invalid characters in MD5 response.");
 		return TRUE;
 	}
 
--- a/src/pop3-login/client-authenticate.h	Sun May 20 03:08:01 2012 +0300
+++ b/src/pop3-login/client-authenticate.h	Sun May 20 03:25:04 2012 +0300
@@ -1,8 +1,10 @@
 #ifndef CLIENT_AUTHENTICATE_H
 #define CLIENT_AUTHENTICATE_H
 
-bool pop3_client_auth_handle_reply(struct client *client,
-				   const struct client_auth_reply *reply);
+void pop3_client_auth_result(struct client *client,
+			     enum client_auth_result result,
+			     const struct client_auth_reply *reply,
+			     const char *text);
 
 bool cmd_capa(struct pop3_client *client, const char *args);
 bool cmd_user(struct pop3_client *client, const char *args);
--- a/src/pop3-login/client.c	Sun May 20 03:08:01 2012 +0300
+++ b/src/pop3-login/client.c	Sun May 20 03:25:04 2012 +0300
@@ -24,13 +24,13 @@
 
 static bool cmd_stls(struct pop3_client *client)
 {
-	client_cmd_starttls(&client->common);
+	client_cmd_starttls(&client->common);	
 	return TRUE;
 }
 
 static bool cmd_quit(struct pop3_client *client)
 {
-	client_send_line(&client->common, CLIENT_CMD_REPLY_OK, "Logging out");
+	client_send_reply(&client->common, POP3_CMD_REPLY_OK, "Logging out");
 	client_destroy(&client->common, "Aborted login");
 	return TRUE;
 }
@@ -42,8 +42,8 @@
 	bool args_ok = TRUE;
 
 	if (!client->common.trusted) {
-		client_send_line(&client->common, CLIENT_CMD_REPLY_BAD,
-				 "You are not from trusted IP");
+		client_send_reply(&client->common, POP3_CMD_REPLY_ERROR,
+				  "You are not from trusted IP");
 		return TRUE;
 	}
 	for (tmp = t_strsplit(args, " "); *tmp != NULL; tmp++) {
@@ -59,16 +59,19 @@
 		} else if (strncasecmp(*tmp, "SESSION=", 8) == 0) {
 			client->common.session_id =
 				p_strdup(client->common.pool, *tmp + 8);
+		} else if (strncasecmp(*tmp, "TTL=", 4) == 0) {
+			if (str_to_uint(*tmp + 4, &client->common.proxy_ttl) < 0)
+				args_ok = FALSE;
 		}
 	}
 	if (!args_ok) {
-		client_send_line(&client->common, CLIENT_CMD_REPLY_BAD,
-				 "Invalid parameters");
+		client_send_reply(&client->common, POP3_CMD_REPLY_ERROR,
+				  "Invalid parameters");
 		return TRUE;
 	}
 
 	/* args ok, set them and reset the state */
-	client_send_line(&client->common, CLIENT_CMD_REPLY_OK, "Updated");
+	client_send_reply(&client->common, POP3_CMD_REPLY_OK, "Updated");
 	return TRUE;
 }
 
@@ -93,8 +96,8 @@
 	if (strcmp(cmd, "XCLIENT") == 0)
 		return cmd_xclient(client, args);
 
-	client_send_line(&client->common, CLIENT_CMD_REPLY_BAD,
-			 "Unknown command.");
+	client_send_reply(&client->common, POP3_CMD_REPLY_ERROR,
+			  "Unknown command.");
 	return FALSE;
 }
 
@@ -124,7 +127,7 @@
 					   args != NULL ? args : ""))
 			client->bad_counter = 0;
 		else if (++client->bad_counter > CLIENT_MAX_BAD_COMMANDS) {
-			client_send_line(client, CLIENT_CMD_REPLY_BYE,
+			client_send_reply(client, POP3_CMD_REPLY_ERROR,
 				"Too many invalid bad commands.");
 			client_destroy(client,
 				       "Disconnected: Too many bad commands");
@@ -185,7 +188,7 @@
 			       (const char *)buf.data, my_hostname);
 }
 
-static void pop3_client_send_greeting(struct client *client)
+static void pop3_client_notify_auth_ready(struct client *client)
 {
 	struct pop3_client *pop3_client = (struct pop3_client *)client;
 	string_t *str;
@@ -198,41 +201,41 @@
 		str_append(str, "[XCLIENT] ");
 	}
 	str_append(str, client->set->login_greeting);
+
 	pop3_client->apop_challenge = get_apop_challenge(pop3_client);
 	if (pop3_client->apop_challenge != NULL)
 		str_printfa(str, " %s", pop3_client->apop_challenge);
-	client_send_line(client, CLIENT_CMD_REPLY_OK, str_c(str));
-	client->greeting_sent = TRUE;
+	client_send_reply(client, POP3_CMD_REPLY_OK, str_c(str));
+}
+
+static void
+pop3_client_notify_starttls(struct client *client,
+			    bool success, const char *text)
+{
+	if (success)
+		client_send_reply(client, POP3_CMD_REPLY_OK, text);
+	else
+		client_send_reply(client, POP3_CMD_REPLY_ERROR, text);
 }
 
 static void pop3_client_starttls(struct client *client ATTR_UNUSED)
 {
 }
 
-static void
-pop3_client_send_line(struct client *client, enum client_cmd_reply reply,
-		      const char *text)
+void client_send_reply(struct client *client, enum pop3_cmd_reply reply,
+		       const char *text)
 {
 	const char *prefix = "-ERR";
 
 	switch (reply) {
-	case CLIENT_CMD_REPLY_OK:
+	case POP3_CMD_REPLY_OK:
 		prefix = "+OK";
 		break;
-	case CLIENT_CMD_REPLY_AUTH_FAIL_TEMP:
+	case POP3_CMD_REPLY_TEMPFAIL:
 		prefix = "-ERR [IN-USE]";
 		break;
-	case CLIENT_CMD_REPLY_AUTH_FAILED:
-	case CLIENT_CMD_REPLY_AUTHZ_FAILED:
-	case CLIENT_CMD_REPLY_AUTH_FAIL_REASON:
-	case CLIENT_CMD_REPLY_AUTH_FAIL_NOSSL:
-	case CLIENT_CMD_REPLY_BAD:
-	case CLIENT_CMD_REPLY_BYE:
+	case POP3_CMD_REPLY_ERROR:
 		break;
-	case CLIENT_CMD_REPLY_STATUS:
-	case CLIENT_CMD_REPLY_STATUS_BAD:
-		/* can't send status notifications */
-		return;
 	}
 
 	T_BEGIN {
@@ -243,11 +246,21 @@
 		str_append(line, text);
 		str_append(line, "\r\n");
 
-		client_send_raw_data(client, str_data(line),
-				     str_len(line));
+		client_send_raw_data(client, str_data(line), str_len(line));
 	} T_END;
 }
 
+static void 
+pop3_client_notify_disconnect(struct client *client,
+			      enum client_disconnect_reason reason,
+			      const char *text)
+{
+	if (reason == CLIENT_DISCONNECT_INTERNAL_ERROR)
+		client_send_reply(client, POP3_CMD_REPLY_TEMPFAIL, text);
+	else
+		client_send_reply(client, POP3_CMD_REPLY_ERROR, text);
+}
+
 static void pop3_login_die(void)
 {
 	/* do nothing. pop3 connections typically die pretty quick anyway. */
@@ -273,15 +286,18 @@
 	pop3_client_alloc,
 	pop3_client_create,
 	pop3_client_destroy,
-	pop3_client_send_greeting,
+	pop3_client_notify_auth_ready,
+	pop3_client_notify_disconnect,
+	NULL,
+	pop3_client_notify_starttls,
 	pop3_client_starttls,
 	pop3_client_input,
-	pop3_client_send_line,
-	pop3_client_auth_handle_reply,
 	NULL,
 	NULL,
+	pop3_client_auth_result,
 	pop3_proxy_reset,
-	pop3_proxy_parse_line
+	pop3_proxy_parse_line,
+	pop3_proxy_error
 };
 
 static const struct login_binary pop3_login_binary = {
--- a/src/pop3-login/client.h	Sun May 20 03:08:01 2012 +0300
+++ b/src/pop3-login/client.h	Sun May 20 03:25:04 2012 +0300
@@ -22,4 +22,13 @@
 	bool proxy_xclient;
 };
 
+enum pop3_cmd_reply {
+	POP3_CMD_REPLY_OK,
+	POP3_CMD_REPLY_ERROR,
+	POP3_CMD_REPLY_TEMPFAIL
+};
+
+void client_send_reply(struct client *client, enum pop3_cmd_reply reply,
+		       const char *text);
+
 #endif
--- a/src/pop3-login/pop3-proxy.c	Sun May 20 03:08:01 2012 +0300
+++ b/src/pop3-login/pop3-proxy.c	Sun May 20 03:25:04 2012 +0300
@@ -37,13 +37,15 @@
 {
 	string_t *str;
 
+	i_assert(client->common.proxy_ttl > 0);
 	if (client->proxy_xclient) {
 		/* remote supports XCLIENT, send it */
 		(void)o_stream_send_str(output, t_strdup_printf(
-			"XCLIENT ADDR=%s PORT=%u SESSION=%s\r\n",
+			"XCLIENT ADDR=%s PORT=%u SESSION=%s TTL=%u\r\n",
 			net_ip2addr(&client->common.ip),
 			client->common.remote_port,
-			client_get_session_id(&client->common)));
+			client_get_session_id(&client->common),
+			client->common.proxy_ttl - 1));
 		client->common.proxy_state = POP3_PROXY_XCLIENT;
 	} else {
 		client->common.proxy_state = POP3_PROXY_LOGIN1;
@@ -168,8 +170,8 @@
 	   shouldn't be a real problem since of course everyone will
 	   be using only Dovecot as their backend :) */
 	if (strncmp(line, "-ERR ", 5) != 0) {
-		client_send_line(client, CLIENT_CMD_REPLY_AUTH_FAILED,
-				 AUTH_FAILED_MSG);
+		client_send_reply(client, POP3_CMD_REPLY_ERROR,
+				  AUTH_FAILED_MSG);
 	} else {
 		client_send_raw(client, t_strconcat(line, "\r\n", NULL));
 	}
@@ -188,3 +190,8 @@
 {
 	client->proxy_state = POP3_PROXY_BANNER;
 }
+
+void pop3_proxy_error(struct client *client, const char *text)
+{
+	client_send_reply(client, POP3_CMD_REPLY_ERROR, text);
+}
--- a/src/pop3-login/pop3-proxy.h	Sun May 20 03:08:01 2012 +0300
+++ b/src/pop3-login/pop3-proxy.h	Sun May 20 03:25:04 2012 +0300
@@ -4,4 +4,6 @@
 void pop3_proxy_reset(struct client *client);
 int pop3_proxy_parse_line(struct client *client, const char *line);
 
+void pop3_proxy_error(struct client *client, const char *text);
+
 #endif
--- a/src/pop3/main.c	Sun May 20 03:08:01 2012 +0300
+++ b/src/pop3/main.c	Sun May 20 03:25:04 2012 +0300
@@ -29,7 +29,16 @@
 static struct mail_storage_service_ctx *storage_service;
 static struct master_login *master_login = NULL;
 
-void (*hook_client_created)(struct client **client) = NULL;
+pop3_client_created_func_t *hook_client_created = NULL;
+
+pop3_client_created_func_t *
+pop3_client_created_hook_set(pop3_client_created_func_t *new_hook)
+{
+	pop3_client_created_func_t *old_hook = hook_client_created;
+
+	hook_client_created = new_hook;
+	return old_hook;
+}
 
 void pop3_refresh_proctitle(void)
 {
--- a/src/pop3/pop3-client.c	Sun May 20 03:08:01 2012 +0300
+++ b/src/pop3/pop3-client.c	Sun May 20 03:25:04 2012 +0300
@@ -37,6 +37,10 @@
    transaction. This allows the mailbox to become unlocked. */
 #define CLIENT_COMMIT_TIMEOUT_MSECS (10*1000)
 
+extern struct pop3_client_vfuncs pop3_client_vfuncs;
+
+struct pop3_module_register pop3_module_register = { 0 };
+
 struct client *pop3_clients;
 unsigned int pop3_client_count;
 
@@ -284,21 +288,26 @@
         enum mailbox_flags flags;
 	const char *errmsg;
 	enum mail_error error;
+	pool_t pool;
 
 	/* always use nonblocking I/O */
 	net_set_nonblock(fd_in, TRUE);
 	net_set_nonblock(fd_out, TRUE);
 
-	client = i_new(struct client, 1);
+	pool = pool_alloconly_create("pop3 client", 256);
+	client = p_new(pool, struct client, 1);
+	client->pool = pool;
 	client->service_user = service_user;
+	client->v = pop3_client_vfuncs;
 	client->set = set;
-	client->session_id = i_strdup(session_id);
+	client->session_id = p_strdup(pool, session_id);
 	client->fd_in = fd_in;
 	client->fd_out = fd_out;
 	client->input = i_stream_create_fd(fd_in, MAX_INBUF_SIZE, FALSE);
 	client->output = o_stream_create_fd(fd_out, (size_t)-1, FALSE);
 	o_stream_set_flush_callback(client->output, client_output, client);
 
+	p_array_init(&client->module_contexts, client->pool, 5);
 	client->io = io_add(fd_in, IO_READ, client_input, client);
         client->last_input = ioloop_time;
 	client->to_idle = timeout_add(CLIENT_IDLE_TIMEOUT_MSECS,
@@ -469,6 +478,11 @@
 
 void client_destroy(struct client *client, const char *reason)
 {
+	client->v.destroy(client, reason);
+}
+
+static void client_default_destroy(struct client *client, const char *reason)
+{
 	if (client->seen_change_count > 0)
 		client_update_mails(client);
 
@@ -524,8 +538,7 @@
 	if (client->fd_in != client->fd_out)
 		net_disconnect(client->fd_out);
 	mail_storage_service_user_free(&client->service_user);
-	i_free(client->session_id);
-	i_free(client);
+	pool_unref(&client->pool);
 
 	master_service_client_connection_destroyed(master_service);
 	pop3_refresh_proctitle();
@@ -735,3 +748,7 @@
 		client_destroy(pop3_clients, "Server shutting down.");
 	}
 }
+
+struct pop3_client_vfuncs pop3_client_vfuncs = {
+	client_default_destroy
+};
--- a/src/pop3/pop3-client.h	Sun May 20 03:08:01 2012 +0300
+++ b/src/pop3/pop3-client.h	Sun May 20 03:25:04 2012 +0300
@@ -9,6 +9,11 @@
 #define MSGS_BITMASK_SIZE(client) \
 	(((client)->messages_count + (CHAR_BIT-1)) / CHAR_BIT)
 
+struct pop3_client_vfuncs {
+	void (*destroy)(struct client *client, const char *reason);
+
+};
+
 /*
    pop3_msn = 1..n in the POP3 protocol
    msgnum = 0..n-1 = pop3_msn-1
@@ -18,7 +23,9 @@
 struct client {
 	struct client *prev, *next;
 
-	char *session_id;
+	struct pop3_client_vfuncs v;
+	const char *session_id;
+
 	int fd_in, fd_out;
 	struct io *io;
 	struct istream *input;
@@ -28,6 +35,7 @@
 	command_func_t *cmd;
 	void *cmd_context;
 
+	pool_t pool;
 	struct mail_storage_service_user *service_user;
 	struct mail_user *user;
 	struct mail_namespace *inbox_ns;
@@ -67,6 +75,9 @@
 	pool_t uidl_pool;
 	enum uidl_keys uidl_keymask;
 
+	/* Module-specific contexts. */
+	ARRAY_DEFINE(module_contexts, union pop3_module_context *);
+
 	unsigned int disconnected:1;
 	unsigned int deleted:1;
 	unsigned int waiting_input:1;
@@ -74,6 +85,16 @@
 	unsigned int message_uidls_save:1;
 };
 
+struct pop3_module_register {
+	unsigned int id;
+};
+
+union pop3_module_context {
+	struct pop3_client_vfuncs super;
+	struct pop3_module_register *reg;
+};
+extern struct pop3_module_register pop3_module_register;
+
 extern struct client *pop3_clients;
 extern unsigned int pop3_client_count;
 
--- a/src/pop3/pop3-common.h	Sun May 20 03:08:01 2012 +0300
+++ b/src/pop3/pop3-common.h	Sun May 20 03:25:04 2012 +0300
@@ -13,7 +13,14 @@
 #include "pop3-client.h"
 #include "pop3-settings.h"
 
-extern void (*hook_client_created)(struct client **client);
+typedef void pop3_client_created_func_t(struct client **client);
+
+extern pop3_client_created_func_t *hook_client_created;
+
+/* Sets the hook_client_created and returns the previous hook,
+   which the new_hook should call if it's non-NULL. */
+pop3_client_created_func_t *
+pop3_client_created_hook_set(pop3_client_created_func_t *new_hook);
 
 void pop3_refresh_proctitle(void);