changeset 12755:baa0314ed610

Merged changes from v2.0 tree.
author Timo Sirainen <tss@iki.fi>
date Tue, 22 Feb 2011 15:31:31 +0200
parents 437e24ac6303 (current diff) e7fa2906f875 (diff)
children 04073884748a
files TODO configure.in src/doveadm/doveadm-mail-import.c src/doveadm/doveadm-mail.c src/dsync/dsync-worker-local.c src/imap/cmd-copy.c src/imap/imap-client.c src/imap/imap-client.h src/lda/main.c src/lib-lda/mail-deliver.c src/lib-storage/index/dbox-common/dbox-storage.c src/lib-storage/index/dbox-multi/mdbox-map.c src/lib-storage/index/dbox-multi/mdbox-save.c src/lib-storage/index/dbox-multi/mdbox-storage-rebuild.c src/lib-storage/index/dbox-multi/mdbox-storage.c src/lib-storage/index/dbox-single/sdbox-save.c src/lib-storage/index/dbox-single/sdbox-storage.c src/lib-storage/index/dbox-single/sdbox-sync-rebuild.c src/lib-storage/index/maildir/maildir-uidlist.c src/lib-storage/list/mailbox-list-maildir.c src/lib-storage/list/mailbox-list-none.c src/lib-storage/mail-storage.c src/lib-storage/mail-storage.h src/lib-storage/mailbox-list.c src/lib/ioloop.c src/lmtp/client.c src/lmtp/commands.c src/plugins/virtual/virtual-save.c src/util/script.c
diffstat 95 files changed, 1073 insertions(+), 270 deletions(-) [+]
line wrap: on
line diff
--- a/.hgsigs	Sat Feb 19 16:56:21 2011 +0200
+++ b/.hgsigs	Tue Feb 22 15:31:31 2011 +0200
@@ -22,3 +22,4 @@
 8a838dcf8e761690806c8df2caabf828fa8028ff 0 iEYEABECAAYFAkzAgVMACgkQyUhSUUBVislqlACgptrkApZDOZOARdi0UtefD/EWVagAn3GEcvGADNkeos2laWvQxGURptCY
 d0d3aca1c9587887a32a890548ec7db76c3bbbe2 0 iEYEABECAAYFAkzYU0oACgkQyUhSUUBVismeNwCaAua5XzOT/BgfeOVrBpscYz4M/jYAnRAc11iVkEeXr32o4YVL37DCPOv/
 51e41fcc78560b1eb5e3e1d026151e2cbe007486 0 iEYEABECAAYFAkz5RxcACgkQyUhSUUBViskcyQCcDetyEXnLHc1nCSFQ5LdlxgoNDE4AoKS8ZtsrUFdeT3/dfnJT1K7b5ski
+440fcf8cb33815e86e5cb9701965000230338ac8 0 iEYEABECAAYFAk0u33kACgkQyUhSUUBVislxBwCeNfszo3Ivr5182ugeAXheEX33qDgAni6UFG9soLT2P5p1wpL2p/Bq4ACG
--- a/.hgtags	Sat Feb 19 16:56:21 2011 +0200
+++ b/.hgtags	Tue Feb 22 15:31:31 2011 +0200
@@ -59,3 +59,4 @@
 8a838dcf8e761690806c8df2caabf828fa8028ff 2.0.6
 d0d3aca1c9587887a32a890548ec7db76c3bbbe2 2.0.7
 51e41fcc78560b1eb5e3e1d026151e2cbe007486 2.0.8
+440fcf8cb33815e86e5cb9701965000230338ac8 2.0.9
--- a/NEWS	Sat Feb 19 16:56:21 2011 +0200
+++ b/NEWS	Tue Feb 22 15:31:31 2011 +0200
@@ -1,3 +1,21 @@
+v2.0.9 2011-01-13  Timo Sirainen <tss@iki.fi>
+
+	- Linux: Fixed a high system CPU usage / high context switch count
+	  performance problem
+	- Maildir: Avoid unnecessarily reading dovecot-uidlist while opening
+	  mailbox.
+	- Maildir: Fixed renaming child mailboxes when namespace had a prefix.
+	- mdbox: Don't leave partially written messages to mdbox files when
+	  aborting saving.
+	- Fixed master user logins when using userdb prefetch
+	- lda: Fixed a crash when trying to send "out of quota" reply
+	- lmtp: If delivering duplicate messages to same user's INBOX,
+	  create different GUIDs for them. This helps to avoid duplicate
+	  POP3 UIDLs when pop3_uidl_format=%g.
+	- virtual storage: Fixed saving multiple mails in a transaction
+	  (e.g. copy multiple messages).
+	- dsync: Saved messages' save-date was set to 1970-01-01.
+
 v2.0.8 2010-12-03  Timo Sirainen <tss@iki.fi>
 
 	* Services' default vsz_limits weren't being enforced correctly in
--- a/configure.in	Sat Feb 19 16:56:21 2011 +0200
+++ b/configure.in	Tue Feb 22 15:31:31 2011 +0200
@@ -2624,7 +2624,7 @@
 dnl IDLE doesn't really belong to banner. It's there just to make Blackberries
 dnl happy, because otherwise BIS server disables push email.
 capability_banner="IMAP4rev1 LITERAL+ SASL-IR LOGIN-REFERRALS ID ENABLE IDLE"
-capability="$capability_banner SORT SORT=DISPLAY THREAD=REFERENCES THREAD=REFS MULTIAPPEND UNSELECT IDLE CHILDREN NAMESPACE UIDPLUS LIST-EXTENDED I18NLEVEL=1 CONDSTORE QRESYNC ESEARCH ESORT SEARCHRES WITHIN CONTEXT=SEARCH LIST-STATUS"
+capability="$capability_banner SORT SORT=DISPLAY THREAD=REFERENCES THREAD=REFS MULTIAPPEND UNSELECT CHILDREN NAMESPACE UIDPLUS LIST-EXTENDED I18NLEVEL=1 CONDSTORE QRESYNC ESEARCH ESORT SEARCHRES WITHIN CONTEXT=SEARCH LIST-STATUS"
 AC_DEFINE_UNQUOTED(CAPABILITY_STRING, "$capability", IMAP capabilities)
 AC_DEFINE_UNQUOTED(CAPABILITY_BANNER_STRING, "$capability_banner", IMAP capabilities advertised in banner) 
 
--- a/doc/example-config/conf.d/15-lda.conf	Sat Feb 19 16:56:21 2011 +0200
+++ b/doc/example-config/conf.d/15-lda.conf	Tue Feb 22 15:31:31 2011 +0200
@@ -17,6 +17,9 @@
 # Binary to use for sending mails.
 #sendmail_path = /usr/sbin/sendmail
 
+# If non-empty, send mails via this SMTP host[:port] instead of sendmail.
+#submission_host =
+
 # Subject: header to use for rejection mails. You can use the same variables
 # as for rejection_reason below.
 #rejection_subject = Rejected: %s
--- a/doc/example-config/conf.d/20-pop3.conf	Sat Feb 19 16:56:21 2011 +0200
+++ b/doc/example-config/conf.d/20-pop3.conf	Tue Feb 22 15:31:31 2011 +0200
@@ -69,7 +69,7 @@
 
   # Maximum number of POP3 connections allowed for a user from each IP address.
   # NOTE: The username is compared case-sensitively.
-  #mail_max_userip_connections = 3
+  #mail_max_userip_connections = 10
 
   # Space separated list of plugins to load (default is global mail_plugins).
   #mail_plugins = $mail_plugins
--- a/doc/man/doveadm-quota.1.in	Sat Feb 19 16:56:21 2011 +0200
+++ b/doc/man/doveadm-quota.1.in	Tue Feb 22 15:31:31 2011 +0200
@@ -1,5 +1,5 @@
-.\" Copyright (c) 2010 Dovecot authors, see the included COPYING file
-.TH DOVEADM\-QUOTA 1 "2010-11-25" "Dovecot v2.0" "Dovecot"
+.\" Copyright (c) 2010-2011 Dovecot authors, see the included COPYING file
+.TH DOVEADM\-QUOTA 1 "2011-02-17" "Dovecot v2.0" "Dovecot"
 .SH NAME
 doveadm\-quota \- Initialize/recalculate or show current quota usage
 .\"------------------------------------------------------------------------
@@ -71,6 +71,7 @@
 The
 .B quota get
 command is used to display the current quota usage.
+The storage values are reported in kilobytes.
 .PP
 This command uses by default the output formatter
 .BR table .
--- a/doc/man/dovecot-lda.1.in	Sat Feb 19 16:56:21 2011 +0200
+++ b/doc/man/dovecot-lda.1.in	Tue Feb 22 15:31:31 2011 +0200
@@ -1,5 +1,5 @@
 .\" Copyright (c) 2010 Dovecot authors, see the included COPYING file
-.TH DOVECOT\-LDA 1 "2010-06-22" "Dovecot v2.0" "Dovecot"
+.TH DOVECOT\-LDA 1 "2011-01-16" "Dovecot v2.0" "Dovecot"
 .SH NAME
 dovecot\-lda \- Dovecot\(aqs local mail delivery agent
 .\"------------------------------------------------------------------------
@@ -11,6 +11,7 @@
 [\fB\-d\fP \fIusername\fP]
 [\fB\-f\fP \fIenvelope_sender\fP]
 [\fB\-m\fP \fImailbox\fP]
+[\fB\-o\fP \fIsetting=value\fP]
 [\fB\-p\fP \fIpath\fP]
 .\"------------------------------------------------------------------------
 .SH DESCRIPTION
@@ -80,6 +81,18 @@
 for any reason, it\(aqs delivered to
 .B INBOX
 instead.
+.\"---------------------------------
+.TP
+.BI \-o\  setting = value
+Overrides the configuration
+.I setting
+from
+.I @pkgsysconfdir@/dovecot.conf
+and from the userdb with the given
+.IR value .
+In order to override multiple settings, the
+.B \-o
+option may be specified multiple times.
 .\"-------------------------------------
 .TP
 .BI \-p\  path
--- a/doc/man/dsync.1.in	Sat Feb 19 16:56:21 2011 +0200
+++ b/doc/man/dsync.1.in	Tue Feb 22 15:31:31 2011 +0200
@@ -1,5 +1,5 @@
 .\" Copyright (c) 2010 Dovecot authors, see the included COPYING file
-.TH DSYNC 1 "2010-07-02" "Dovecot v2.0" "Dovecot"
+.TH DSYNC 1 "2011-01-16" "Dovecot v2.0" "Dovecot"
 .SH NAME
 dsync \- Dovecot\(aqs mailbox synchronization utility
 .\"------------------------------------------------------------------------
@@ -111,7 +111,7 @@
 .I @pkgsysconfdir@/dovecot.conf
 and from the userdb with the given
 .IR value .
-In order to override multiple settings to
+In order to override multiple settings, the
 .B \-o
 option may be specified multiple times.
 .\"---------------------------------
--- a/dovecot-config.in.in	Sat Feb 19 16:56:21 2011 +0200
+++ b/dovecot-config.in.in	Tue Feb 22 15:31:31 2011 +0200
@@ -1,6 +1,7 @@
 DOVECOT_CFLAGS="@CFLAGS@"
 DOVECOT_LIBS="@LIBS@"
 DOVECOT_SSL_LIBS="@SSL_LIBS@"
+DOVECOT_SQL_LIBS="@SQL_LIBS@"
 
 LIBDOVECOT="@LIBDOVECOT@ @MODULE_LIBS@"
 LIBDOVECOT_LOGIN="@LIBDOVECOT_LOGIN@ @SSL_LIBS@"
--- a/dovecot.m4	Sat Feb 19 16:56:21 2011 +0200
+++ b/dovecot.m4	Tue Feb 22 15:31:31 2011 +0200
@@ -6,7 +6,7 @@
 # unlimited permission to copy and/or distribute it, with or without
 # modifications, as long as this notice is preserved.
 
-# serial 3
+# serial 4
 
 AC_DEFUN([DC_DOVECOT_MODULEDIR],[
 	AC_ARG_WITH(moduledir,
@@ -56,10 +56,10 @@
 		AC_MSG_ERROR([dovecot-config not found])
 	fi
 
-	eval `grep -i '^dovecot_[[a-z]]*=' "$dovecotdir"/dovecot-config`
+	eval `grep -i '^dovecot_[[a-z_]]*=' "$dovecotdir"/dovecot-config`
 	eval `grep '^LIBDOVECOT[[A-Z_]]*=' "$dovecotdir"/dovecot-config`
 	AX_SUBST_L([dovecotdir], [dovecot_moduledir], [dovecot_pkgincludedir], [dovecot_pkglibexecdir], [dovecot_pkglibdir], [dovecot_docdir])
-	AX_SUBST_L([DOVECOT_CFLAGS], [DOVECOT_LIBS], [DOVECOT_SSL_LIBS])
+	AX_SUBST_L([DOVECOT_CFLAGS], [DOVECOT_LIBS], [DOVECOT_SSL_LIBS], [DOVECOT_SQL_LIBS])
 	AX_SUBST_L([LIBDOVECOT], [LIBDOVECOT_LOGIN], [LIBDOVECOT_SQL], [LIBDOVECOT_LDA], [LIBDOVECOT_STORAGE])
 	AX_SUBST_L([LIBDOVECOT_DEPS], [LIBDOVECOT_LOGIN_DEPS], [LIBDOVECOT_SQL_DEPS], [LIBDOVECOT_LDA_DEPS], [LIBDOVECOT_STORAGE_DEPS])
 	AX_SUBST_L([LIBDOVECOT_INCLUDE], [LIBDOVECOT_LDA_INCLUDE], [LIBDOVECOT_SERVICE_INCLUDE], [LIBDOVECOT_STORAGE_INCLUDE], [LIBDOVECOT_LOGIN_INCLUDE], [LIBDOVECOT_CONFIG_INCLUDE])
--- a/dovecot.service.in	Sat Feb 19 16:56:21 2011 +0200
+++ b/dovecot.service.in	Tue Feb 22 15:31:31 2011 +0200
@@ -6,3 +6,6 @@
 Type=simple
 ExecStart=@sbindir@/dovecot -F
 NonBlocking=yes
+
+[Install]
+WantedBy=multi-user.target
--- a/src/auth/auth-request.c	Sat Feb 19 16:56:21 2011 +0200
+++ b/src/auth/auth-request.c	Tue Feb 22 15:31:31 2011 +0200
@@ -894,7 +894,8 @@
 		if (set->username_chars_map[*p & 0xff] == 0) {
 			*error_r = t_strdup_printf(
 				"Username contains disallowed character: "
-				"0x%02x", *p);
+				"0x%02x (username: %s)", *p,
+				str_sanitize(username, 128));
 			return NULL;
 		}
 	}
@@ -963,11 +964,8 @@
 	}
 
         request->user = auth_request_fix_username(request, username, error_r);
-	if (request->user == NULL) {
-		auth_request_log_debug(request, "auth",
-			"Invalid username: %s", str_sanitize(username, 128));
+	if (request->user == NULL)
 		return FALSE;
-	}
 	if (request->translated_username == NULL) {
 		/* similar to original_username, but after translations */
 		request->translated_username = request->user;
@@ -1474,7 +1472,8 @@
 	}
 
 	if (request->no_password) {
-		auth_request_log_info(request, subsystem, "No password");
+		auth_request_log_debug(request, subsystem,
+				       "Allowing any password");
 		return 1;
 	}
 
--- a/src/auth/mech-winbind.c	Sat Feb 19 16:56:21 2011 +0200
+++ b/src/auth/mech-winbind.c	Tue Feb 22 15:31:31 2011 +0200
@@ -335,7 +335,7 @@
 	.passdb_need = MECH_PASSDB_NEED_NOTHING,
 
 	mech_winbind_ntlm_auth_new,
-	mech_generic_auth_initial,
+	mech_winbind_auth_initial,
 	mech_winbind_auth_continue,
 	mech_generic_auth_free
 };
--- a/src/auth/passdb-cache.c	Sat Feb 19 16:56:21 2011 +0200
+++ b/src/auth/passdb-cache.c	Tue Feb 22 15:31:31 2011 +0200
@@ -18,7 +18,7 @@
 	    *value != '\0' && *value != '\t') {
 		/* hide the password */
 		p = strchr(value, '\t');
-		value = t_strconcat("<hidden>", p, NULL);
+		value = t_strconcat(PASSWORD_HIDDEN_STR, p, NULL);
 	}
 	auth_request_log_debug(request, "cache", "hit: %s", value);
 }
--- a/src/auth/passdb.c	Sat Feb 19 16:56:21 2011 +0200
+++ b/src/auth/passdb.c	Tue Feb 22 15:31:31 2011 +0200
@@ -146,14 +146,18 @@
 		return;
 	}
 
-	if (password == NULL) {
+	if (password != NULL) {
+		if (!passdb_get_credentials(auth_request, password, scheme,
+					    &credentials, &size))
+			result = PASSDB_RESULT_SCHEME_NOT_AVAILABLE;
+	} else if (*auth_request->credentials_scheme == '\0') {
+		/* We're doing a passdb lookup (not authenticating).
+		   Pass through a NULL password without an error. */
+	} else {
 		auth_request_log_info(auth_request, "password",
 			"Requested %s scheme, but we have a NULL password",
 			auth_request->credentials_scheme);
 		result = PASSDB_RESULT_SCHEME_NOT_AVAILABLE;
-	} else if (!passdb_get_credentials(auth_request, password, scheme,
-					   &credentials, &size)) {
-		result = PASSDB_RESULT_SCHEME_NOT_AVAILABLE;
 	}
 
 	callback(result, credentials, size, auth_request);
--- a/src/config/config-parser.c	Sat Feb 19 16:56:21 2011 +0200
+++ b/src/config/config-parser.c	Tue Feb 22 15:31:31 2011 +0200
@@ -74,6 +74,15 @@
 	i_assert(ret > 0);
 }
 
+static bool
+config_parser_is_in_localremote(struct config_section_stack *section)
+{
+	const struct config_filter *filter = &section->filter;
+
+	return filter->local_name != NULL || filter->local_bits > 0 ||
+		filter->remote_bits > 0;
+}
+
 int config_apply_line(struct config_parser_context *ctx, const char *key,
 		      const char *line, const char *section_name)
 {
@@ -85,6 +94,14 @@
 		ret = settings_parse_line(l->parser, line);
 		if (ret > 0) {
 			found = TRUE;
+			/* FIXME: remove once auth does support these. */
+			if (strcmp(l->root->module_name, "auth") == 0 &&
+			    config_parser_is_in_localremote(ctx->cur_section)) {
+				ctx->error = p_strconcat(ctx->pool,
+					"Auth settings not supported inside local/remote blocks: ",
+					key, NULL);
+				return -1;
+			}
 			if (section_name != NULL)
 				config_add_type(l->parser, line, section_name);
 		} else if (ret < 0) {
--- a/src/config/config-request.c	Sat Feb 19 16:56:21 2011 +0200
+++ b/src/config/config-request.c	Tue Feb 22 15:31:31 2011 +0200
@@ -286,16 +286,14 @@
 				break;
 			}
 			hash_table_insert(ctx->keys, key, key);
-			ctx->callback(key, "0", CONFIG_KEY_LIST, ctx->context);
 
 			strings = array_get(val, &count);
 			i_assert(count % 2 == 0);
 			for (i = 0; i < count; i += 2) {
-				str = p_strdup_printf(ctx->pool, "%s%s%c0%c%s",
+				str = p_strdup_printf(ctx->pool, "%s%s%c%s",
 						      str_c(ctx->prefix),
 						      def->key,
 						      SETTINGS_SEPARATOR,
-						      SETTINGS_SEPARATOR,
 						      strings[i]);
 				ctx->callback(str, strings[i+1],
 					      CONFIG_KEY_NORMAL, ctx->context);
--- a/src/doveadm/doveadm-mail-import.c	Sat Feb 19 16:56:21 2011 +0200
+++ b/src/doveadm/doveadm-mail-import.c	Tue Feb 22 15:31:31 2011 +0200
@@ -167,7 +167,7 @@
 
 	/* create a user for accessing the source storage */
 	memset(&input, 0, sizeof(input));
-	input.module = "module";
+	input.module = "mail";
 	input.username = "doveadm";
 	input.no_userdb_lookup = TRUE;
 	input.userdb_fields = userdb_fields;
--- a/src/doveadm/doveadm-mail.c	Sat Feb 19 16:56:21 2011 +0200
+++ b/src/doveadm/doveadm-mail.c	Tue Feb 22 15:31:31 2011 +0200
@@ -372,7 +372,7 @@
 
 	ctx = doveadm_mail_cmd_init(cmd);
 
-	getopt_args = t_strconcat("As:u:", ctx->getopt_args, NULL);
+	getopt_args = t_strconcat("AS:u:", ctx->getopt_args, NULL);
 	username = getenv("USER");
 	wildcard_user = NULL;
 	while ((c = getopt(argc, argv, getopt_args)) > 0) {
--- a/src/doveadm/doveadm.c	Sat Feb 19 16:56:21 2011 +0200
+++ b/src/doveadm/doveadm.c	Tue Feb 22 15:31:31 2011 +0200
@@ -234,6 +234,7 @@
 	memset(&input, 0, sizeof(input));
 	input.roots = set_roots;
 	input.module = "doveadm";
+	input.preserve_user = TRUE;
 	input.preserve_home = TRUE;
 	if (master_service_settings_read(master_service, &input,
 					 &output, &error) < 0)
--- a/src/dsync/dsync-brain-msgs-new.c	Sat Feb 19 16:56:21 2011 +0200
+++ b/src/dsync/dsync-brain-msgs-new.c	Tue Feb 22 15:31:31 2011 +0200
@@ -182,6 +182,11 @@
 		dest_iter->adding_msgs = FALSE;
 		if (dsync_worker_output_flush(src_iter->worker) < 0)
 			return -1;
+		if (dsync_worker_is_output_full(dest_iter->worker)) {
+			/* see if the output becomes less full by flushing */
+			if (dsync_worker_output_flush(dest_iter->worker) < 0)
+				return -1;
+		}
 	}
 	return dsync_worker_is_output_full(dest_iter->worker) ? 0 : 1;
 }
--- a/src/dsync/dsync-brain-msgs.c	Sat Feb 19 16:56:21 2011 +0200
+++ b/src/dsync/dsync-brain-msgs.c	Tue Feb 22 15:31:31 2011 +0200
@@ -112,17 +112,23 @@
 
 static int dsync_brain_msg_iter_next_pair(struct dsync_brain_mailbox_sync *sync)
 {
-	int ret;
+	int ret1, ret2;
 
 	if (sync->skip_mailbox) {
 		if (dsync_brain_msg_iter_skip_mailbox(sync) == 0)
 			return 0;
 	}
 
-	if ((ret = dsync_brain_msg_iter_next(sync->src_msg_iter)) <= 0)
-		return ret;
-	if ((ret = dsync_brain_msg_iter_next(sync->dest_msg_iter)) <= 0)
-		return ret;
+	ret1 = dsync_brain_msg_iter_next(sync->src_msg_iter);
+	ret2 = dsync_brain_msg_iter_next(sync->dest_msg_iter);
+	if (ret1 == 0 || ret2 == 0) {
+		/* make sure we iterate through everything in both iterators
+		   (even if it might not seem necessary, because proxy
+		   requires it) */
+		return 0;
+	}
+	if (ret1 < 0 || ret2 < 0)
+		return -1;
 	return 1;
 }
 
--- a/src/dsync/dsync-data.c	Sat Feb 19 16:56:21 2011 +0200
+++ b/src/dsync/dsync-data.c	Tue Feb 22 15:31:31 2011 +0200
@@ -19,7 +19,9 @@
 
 	if (array_is_created(&box->cache_fields))
 		cache_fields = array_get(&box->cache_fields, &count);
-	if (count > 0) {
+	if (count == 0)
+		memset(&dest->cache_fields, 0, sizeof(dest->cache_fields));
+	else {
 		p_array_init(&dest->cache_fields, pool, count);
 		for (i = 0; i < count; i++) {
 			dup = p_strdup(pool, cache_fields[i]);
--- a/src/dsync/dsync-proxy-client.c	Sat Feb 19 16:56:21 2011 +0200
+++ b/src/dsync/dsync-proxy-client.c	Tue Feb 22 15:31:31 2011 +0200
@@ -382,10 +382,11 @@
 	return ret;
 }
 
-static void proxy_client_worker_timeout(void *context ATTR_UNUSED)
+static void
+proxy_client_worker_timeout(struct proxy_client_dsync_worker *worker)
 {
 	i_error("proxy client timed out");
-	master_service_stop(master_service);
+	proxy_client_fail(worker);
 }
 
 struct dsync_worker *dsync_worker_init_proxy_client(int fd_in, int fd_out)
@@ -397,7 +398,7 @@
 	worker->fd_in = fd_in;
 	worker->fd_out = fd_out;
 	worker->to = timeout_add(DSYNC_PROXY_TIMEOUT_MSECS,
-				 proxy_client_worker_timeout, NULL);
+				 proxy_client_worker_timeout, worker);
 	worker->io = io_add(fd_in, IO_READ, proxy_client_worker_input, worker);
 	worker->input = i_stream_create_fd(fd_in, (size_t)-1, FALSE);
 	worker->output = o_stream_create_fd(fd_out, (size_t)-1, FALSE);
@@ -977,6 +978,8 @@
 proxy_client_send_stream_real(struct proxy_client_dsync_worker *worker)
 {
 	dsync_worker_save_callback_t *callback;
+	void *context;
+	struct istream *input;
 	const unsigned char *data;
 	size_t size;
 	int ret;
@@ -1019,11 +1022,20 @@
 	}
 
 	callback = worker->save_callback;
+	context = worker->save_context;
 	worker->save_callback = NULL;
-	i_stream_unref(&worker->save_input);
+	worker->save_context = NULL;
+
+	/* a bit ugly way to free the stream. the problem is that local worker
+	   has set a destroy callback, which in turn can call our msg_save()
+	   again before the i_stream_unref() is finished. */
+	input = worker->save_input;
+	worker->save_input = NULL;
+	i_stream_unref(&input);
+
 	(void)proxy_client_worker_output_flush(&worker->worker);
 
-	callback(worker->save_context);
+	callback(context);
 }
 
 static void proxy_client_send_stream(struct proxy_client_dsync_worker *worker)
@@ -1053,7 +1065,7 @@
 		proxy_client_worker_cmd(worker, str);
 	} T_END;
 
-	i_assert(worker->save_io == NULL);
+	i_assert(worker->save_input == NULL);
 	worker->save_callback = callback;
 	worker->save_context = context;
 	worker->save_input = data->input;
--- a/src/dsync/dsync-worker-local.c	Sat Feb 19 16:56:21 2011 +0200
+++ b/src/dsync/dsync-worker-local.c	Tue Feb 22 15:31:31 2011 +0200
@@ -964,6 +964,8 @@
 	msg_r->flags = mail_get_flags(iter->mail);
 	msg_r->keywords = mail_get_keywords(iter->mail);
 	msg_r->modseq = mail_get_modseq(iter->mail);
+	if (mail_get_save_date(iter->mail, &msg_r->save_date) < 0)
+		msg_r->save_date = (time_t)-1;
 	return 1;
 }
 
@@ -1671,7 +1673,6 @@
 		i_error("Can't save message to mailbox %s: %s",
 			mailbox_get_vname(dest_box),
 			mailbox_get_last_error(dest_box, NULL));
-		mailbox_save_cancel(&save_ctx);
 		dsync_worker_set_failure(_worker);
 		callback(context);
 		return;
--- a/src/imap/cmd-idle.c	Sat Feb 19 16:56:21 2011 +0200
+++ b/src/imap/cmd-idle.c	Tue Feb 22 15:31:31 2011 +0200
@@ -55,10 +55,26 @@
 		client_command_free(&ctx->cmd);
 }
 
+static bool
+idle_client_handle_input(struct cmd_idle_context *ctx, bool free_cmd)
+{
+	const char *line;
+
+	while ((line = i_stream_next_line(ctx->client->input)) != NULL) {
+		if (ctx->client->input_skip_line)
+			ctx->client->input_skip_line = FALSE;
+		else {
+			idle_finish(ctx, strcasecmp(line, "DONE") == 0,
+				    free_cmd);
+			return TRUE;
+		}
+	}
+	return FALSE;
+}
+
 static void idle_client_input_more(struct cmd_idle_context *ctx)
 {
 	struct client *client = ctx->client;
-	char *line;
 
 	client->last_input = ioloop_time;
 	timeout_reset(client->to_idle);
@@ -82,15 +98,9 @@
 		return;
 	}
 
-	while ((line = i_stream_next_line(client->input)) != NULL) {
-		if (client->input_skip_line)
-			client->input_skip_line = FALSE;
-		else {
-			idle_finish(ctx, strcasecmp(line, "DONE") == 0, TRUE);
-			if (!client->disconnected)
-				client_continue_pending_input(client);
-			break;
-		}
+	if (idle_client_handle_input(ctx, TRUE)) {
+		if (!client->disconnected)
+			client_continue_pending_input(client);
 	}
 }
 
@@ -264,6 +274,5 @@
 	   added mailbox-notifier, we wouldn't see them otherwise. */
 	if (client->mailbox != NULL)
 		idle_sync_now(client->mailbox, ctx);
-	idle_client_input_more(ctx);
-	return FALSE;
+	return idle_client_handle_input(ctx, FALSE);
 }
--- a/src/imap/imap-client.c	Sat Feb 19 16:56:21 2011 +0200
+++ b/src/imap/imap-client.c	Tue Feb 22 15:31:31 2011 +0200
@@ -432,7 +432,7 @@
 	return NULL;
 }
 
-static bool client_command_check_ambiguity(struct client_command_context *cmd)
+static bool client_command_is_ambiguous(struct client_command_context *cmd)
 {
 	enum command_flags flags;
 	enum client_command_state max_state =
@@ -443,6 +443,17 @@
 	    !imap_sync_is_allowed(cmd->client))
 		return TRUE;
 
+	if (cmd->search_save_result_used) {
+		/* if there are pending commands that update the search
+		   save result, wait */
+		struct client_command_context *old_cmd = cmd->next;
+
+		for (; old_cmd != NULL; old_cmd = old_cmd->next) {
+			if (old_cmd->search_save_result)
+				return TRUE;
+		}
+	}
+
 	if ((cmd->cmd_flags & COMMAND_FLAG_BREAKS_MAILBOX) ==
 	    COMMAND_FLAG_BREAKS_MAILBOX) {
 		/* there must be no other command running that uses the
@@ -578,11 +589,11 @@
 
 		/* the command is waiting for existing ambiguity causing
 		   commands to finish. */
-		if (client_command_check_ambiguity(cmd)) {
+		if (client_command_is_ambiguous(cmd)) {
 			/* we could be waiting for existing sync to finish */
 			if (!cmd_sync_delayed(client))
 				return;
-			if (client_command_check_ambiguity(cmd))
+			if (client_command_is_ambiguous(cmd))
 				return;
 		}
 		cmd->state = CLIENT_COMMAND_STATE_WAIT_INPUT;
@@ -690,7 +701,7 @@
 	} else if ((command = command_find(cmd->name)) != NULL) {
 		cmd->func = command->func;
 		cmd->cmd_flags = command->flags;
-		if (client_command_check_ambiguity(cmd)) {
+		if (client_command_is_ambiguous(cmd)) {
 			/* do nothing until existing commands are finished */
 			i_assert(cmd->state == CLIENT_COMMAND_STATE_WAIT_INPUT);
 			cmd->state = CLIENT_COMMAND_STATE_WAIT_UNAMBIGUITY;
@@ -907,6 +918,7 @@
 	i_assert(cmd->state == CLIENT_COMMAND_STATE_WAIT_INPUT);
 	cmd->client->input_lock = cmd;
 	cmd->state = CLIENT_COMMAND_STATE_WAIT_UNAMBIGUITY;
+	cmd->search_save_result_used = TRUE;
 	io_remove(&cmd->client->io);
 	return TRUE;
 }
--- a/src/imap/imap-client.h	Sat Feb 19 16:56:21 2011 +0200
+++ b/src/imap/imap-client.h	Tue Feb 22 15:31:31 2011 +0200
@@ -82,6 +82,7 @@
 	unsigned int cancel:1; /* command is wanted to be cancelled */
 	unsigned int param_error:1;
 	unsigned int search_save_result:1; /* search result is being updated */
+	unsigned int search_save_result_used:1; /* command uses search save */
 	unsigned int temp_executed:1; /* temporary execution state tracking */
 };
 
--- a/src/lda/main.c	Sat Feb 19 16:56:21 2011 +0200
+++ b/src/lda/main.c	Tue Feb 22 15:31:31 2011 +0200
@@ -254,7 +254,8 @@
 		&argc, &argv, "a:d:ef:km:p:r:");
 
 	memset(&ctx, 0, sizeof(ctx));
-	ctx.pool = pool_alloconly_create("mail deliver context", 256);
+	ctx.session = mail_deliver_session_init();
+	ctx.pool = ctx.session->pool;
 	ctx.dest_mailbox_name = "INBOX";
 	path = NULL;
 
@@ -467,7 +468,7 @@
 
 	mail_user_unref(&ctx.dest_user);
 	mail_user_unref(&raw_mail_user);
-	pool_unref(&ctx.pool);
+	mail_deliver_session_deinit(&ctx.session);
 
 	mail_storage_service_user_free(&service_user);
 	mail_storage_service_deinit(&storage_service);
--- a/src/lib-index/mail-index.c	Sat Feb 19 16:56:21 2011 +0200
+++ b/src/lib-index/mail-index.c	Tue Feb 22 15:31:31 2011 +0200
@@ -423,8 +423,19 @@
 
 	path = *path_r = t_strconcat(index->filepath, ".tmp", NULL);
 	old_mask = umask(0);
-	fd = open(path, O_RDWR|O_CREAT|O_TRUNC, index->mode);
+	fd = open(path, O_RDWR|O_CREAT|O_EXCL, index->mode);
 	umask(old_mask);
+	if (fd == -1 && errno == EEXIST) {
+		/* stale temp file. unlink and recreate rather than overwriting,
+		   just to make sure locking problems won't cause corruption */
+		if (unlink(path) < 0) {
+			i_error("unlink(%s) failed: %m", path);
+			return -1;
+		}
+		old_mask = umask(0);
+		fd = open(path, O_RDWR|O_CREAT|O_EXCL, index->mode);
+		umask(old_mask);
+	}
 	if (fd == -1) {
 		mail_index_file_set_syscall_error(index, path, "creat()");
 		return -1;
--- a/src/lib-index/mail-transaction-log-view.c	Sat Feb 19 16:56:21 2011 +0200
+++ b/src/lib-index/mail-transaction-log-view.c	Tue Feb 22 15:31:31 2011 +0200
@@ -495,13 +495,25 @@
 		array_create_from_buffer(&uids, &uid_buf,
 			sizeof(struct mail_transaction_expunge));
 		break;
-	case MAIL_TRANSACTION_EXPUNGE_GUID:
-		if ((rec_size % sizeof(struct mail_transaction_expunge_guid)) != 0) {
+	case MAIL_TRANSACTION_EXPUNGE_GUID: {
+		const struct mail_transaction_expunge_guid *recs = data;
+		unsigned int i, count;
+
+		if ((rec_size % sizeof(*recs)) != 0) {
 			mail_transaction_log_file_set_corrupted(file,
 				"Invalid expunge guid record size");
 			return FALSE;
 		}
+		count = rec_size / sizeof(*recs);
+		for (i = 0; i < count; i++) {
+			if (recs[i].uid == 0) {
+				mail_transaction_log_file_set_corrupted(file,
+					"Expunge guid record with uid=0");
+				return FALSE;
+			}
+		}
 		break;
+	}
 	case MAIL_TRANSACTION_FLAG_UPDATE:
 		buffer_create_const_data(&uid_buf, data, rec_size);
 		array_create_from_buffer(&uids, &uid_buf,
--- a/src/lib-lda/lda-settings.c	Sat Feb 19 16:56:21 2011 +0200
+++ b/src/lib-lda/lda-settings.c	Tue Feb 22 15:31:31 2011 +0200
@@ -20,6 +20,7 @@
 static const struct setting_define lda_setting_defines[] = {
 	DEF(SET_STR, postmaster_address),
 	DEF(SET_STR, hostname),
+	DEF(SET_STR, submission_host),
 	DEF(SET_STR, sendmail_path),
 	DEF(SET_STR, rejection_subject),
 	DEF(SET_STR, rejection_reason),
@@ -36,6 +37,7 @@
 static const struct lda_settings lda_default_settings = {
 	.postmaster_address = "",
 	.hostname = "",
+	.submission_host = "",
 	.sendmail_path = "/usr/sbin/sendmail",
 	.rejection_subject = "Rejected: %s",
 	.rejection_reason =
--- a/src/lib-lda/lda-settings.h	Sat Feb 19 16:56:21 2011 +0200
+++ b/src/lib-lda/lda-settings.h	Tue Feb 22 15:31:31 2011 +0200
@@ -6,12 +6,14 @@
 struct lda_settings {
 	const char *postmaster_address;
 	const char *hostname;
+	const char *submission_host;
 	const char *sendmail_path;
 	const char *rejection_subject;
 	const char *rejection_reason;
 	const char *deliver_log_format;
 	const char *recipient_delimiter;
 	const char *lda_original_recipient_header;
+
 	bool quota_full_tempfail;
 	bool lda_mailbox_autocreate;
 	bool lda_mailbox_autosubscribe;
--- a/src/lib-lda/mail-deliver.c	Sat Feb 19 16:56:21 2011 +0200
+++ b/src/lib-lda/mail-deliver.c	Tue Feb 22 15:31:31 2011 +0200
@@ -116,6 +116,25 @@
 	va_end(args);
 }
 
+struct mail_deliver_session *mail_deliver_session_init(void)
+{
+	struct mail_deliver_session *session;
+	pool_t pool;
+
+	pool = pool_alloconly_create("mail deliver session", 1024);
+	session = p_new(pool, struct mail_deliver_session, 1);
+	session->pool = pool;
+	return session;
+}
+
+void mail_deliver_session_deinit(struct mail_deliver_session **_session)
+{
+	struct mail_deliver_session *session = *_session;
+
+	*_session = NULL;
+	pool_unref(&session->pool);
+}
+
 int mail_deliver_save_open(struct mail_deliver_save_open_context *ctx,
 			   const char *name, struct mailbox **box_r,
 			   enum mail_error *error_r, const char **error_str_r)
@@ -185,6 +204,47 @@
 	return 0;
 }
 
+static bool mail_deliver_check_duplicate(struct mail_deliver_session *session,
+					 struct mail_user *user)
+{
+	const char *const *usernamep, *username;
+
+	/* there shouldn't be all that many recipients,
+	   so just do a linear search */
+	if (!array_is_created(&session->inbox_users))
+		p_array_init(&session->inbox_users, session->pool, 8);
+	array_foreach(&session->inbox_users, usernamep) {
+		if (strcmp(*usernamep, user->username) == 0)
+			return TRUE;
+	}
+	username = p_strdup(session->pool, user->username);
+	array_append(&session->inbox_users, &username, 1);
+	return FALSE;
+}
+
+void mail_deliver_deduplicate_guid_if_needed(struct mail_deliver_session *session,
+					     struct mail_save_context *save_ctx)
+{
+	struct mailbox_transaction_context *trans =
+		mailbox_save_get_transaction(save_ctx);
+	struct mailbox *box = mailbox_transaction_get_mailbox(trans);
+	struct mail_storage *storage = mailbox_get_storage(box);
+	struct mail_user *user = mail_storage_get_user(storage);
+	uint8_t guid[MAIL_GUID_128_SIZE];
+
+	if (strcmp(mailbox_get_name(box), "INBOX") != 0)
+		return;
+
+	/* avoid storing duplicate GUIDs to delivered mails to INBOX. this
+	   happens if mail is delivered to same user multiple times within a
+	   session. the problem with this is that if GUIDs are used as POP3
+	   UIDLs, some clients can't handle the duplicates well. */
+	if (mail_deliver_check_duplicate(session, user)) {
+		mail_generate_guid_128(guid);
+		mailbox_save_set_guid(save_ctx, mail_guid_128_to_string(guid));
+	}
+}
+
 int mail_deliver_save(struct mail_deliver_context *ctx, const char *mailbox,
 		      enum mail_flags flags, const char *const *keywords,
 		      struct mail_storage **storage_r)
@@ -241,6 +301,7 @@
 	ctx->dest_mail = mail_alloc(t, lda_log_wanted_fetch_fields, NULL);
 	mailbox_header_lookup_unref(&headers_ctx);
 	mailbox_save_set_dest_mail(save_ctx, ctx->dest_mail);
+	mail_deliver_deduplicate_guid_if_needed(ctx->session, save_ctx);
 
 	if (mailbox_copy(&save_ctx, ctx->src_mail) < 0)
 		ret = -1;
--- a/src/lib-lda/mail-deliver.h	Sat Feb 19 16:56:21 2011 +0200
+++ b/src/lib-lda/mail-deliver.h	Tue Feb 22 15:31:31 2011 +0200
@@ -4,11 +4,20 @@
 enum mail_flags;
 enum mail_error;
 struct mail_storage;
+struct mail_save_context;
 struct mailbox;
 
+struct mail_deliver_session {
+	pool_t pool;
+
+	/* List of users who have already saved this mail to their INBOX */
+	ARRAY_TYPE(const_string) inbox_users;
+};
+
 struct mail_deliver_context {
 	pool_t pool;
 	const struct lda_settings *set;
+	struct mail_deliver_session *session;
 
 	struct duplicate_context *dup_ctx;
 
@@ -62,6 +71,9 @@
 const char *mail_deliver_get_return_address(struct mail_deliver_context *ctx);
 const char *mail_deliver_get_new_message_id(struct mail_deliver_context *ctx);
 
+struct mail_deliver_session *mail_deliver_session_init(void);
+void mail_deliver_session_deinit(struct mail_deliver_session **session);
+
 /* Try to open mailbox for saving. Returns 0 if ok, -1 if error. The box may
    be returned even with -1, and the caller must free it then. */
 int mail_deliver_save_open(struct mail_deliver_save_open_context *ctx,
@@ -70,6 +82,8 @@
 int mail_deliver_save(struct mail_deliver_context *ctx, const char *mailbox,
 		      enum mail_flags flags, const char *const *keywords,
 		      struct mail_storage **storage_r);
+void mail_deliver_deduplicate_guid_if_needed(struct mail_deliver_session *session,
+					     struct mail_save_context *save_ctx);
 
 int mail_deliver(struct mail_deliver_context *ctx,
 		 struct mail_storage **storage_r);
--- a/src/lib-lda/smtp-client.c	Sat Feb 19 16:56:21 2011 +0200
+++ b/src/lib-lda/smtp-client.c	Tue Feb 22 15:31:31 2011 +0200
@@ -1,8 +1,14 @@
 /* Copyright (c) 2006-2010 Dovecot authors, see the included COPYING file */
 
 #include "lib.h"
+#include "ioloop.h"
+#include "str.h"
+#include "close-keep-errno.h"
+#include "safe-mkstemp.h"
 #include "execv-const.h"
+#include "istream.h"
 #include "master-service.h"
+#include "lmtp-client.h"
 #include "lda-settings.h"
 #include "mail-deliver.h"
 #include "smtp-client.h"
@@ -11,9 +17,20 @@
 #include <sys/wait.h>
 #include <sysexits.h>
 
+#define DEFAULT_SUBMISSION_PORT 25
+
 struct smtp_client {
 	FILE *f;
 	pid_t pid;
+
+	bool use_smtp;
+	bool success;
+	bool finished;
+
+	const struct lda_settings *set;
+	char *temp_path;
+	char *destination;
+	char *return_path;
 };
 
 static struct smtp_client *smtp_client_devnull(FILE **file_r)
@@ -51,14 +68,15 @@
 	if (dup2(fd, STDIN_FILENO) < 0)
 		i_fatal("dup2() failed: %m");
 
-	master_service_env_clean(TRUE);
+	master_service_env_clean();
 
 	execv_const(sendmail_path, argv);
 }
 
-struct smtp_client *
-smtp_client_open(const struct lda_settings *set, const char *destination,
-		 const char *return_path, FILE **file_r)
+static struct smtp_client *
+smtp_client_open_sendmail(const struct lda_settings *set,
+			  const char *destination, const char *return_path,
+			  FILE **file_r)
 {
 	struct smtp_client *client;
 	int fd[2];
@@ -88,11 +106,68 @@
 	return client;
 }
 
-int smtp_client_close(struct smtp_client *client)
+static int create_temp_file(const char **path_r)
+{
+	string_t *path;
+	int fd;
+
+	path = t_str_new(128);
+	str_append(path, "/tmp/dovecot.");
+	str_append(path, master_service_get_name(master_service));
+	str_append_c(path, '.');
+
+	fd = safe_mkstemp(path, 0600, (uid_t)-1, (gid_t)-1);
+	if (fd == -1) {
+		i_error("safe_mkstemp(%s) failed: %m", str_c(path));
+		return -1;
+	}
+
+	/* we just want the fd, unlink it */
+	if (unlink(str_c(path)) < 0) {
+		/* shouldn't happen.. */
+		i_error("unlink(%s) failed: %m", str_c(path));
+		close_keep_errno(fd);
+		return -1;
+	}
+
+	*path_r = str_c(path);
+	return fd;
+}
+
+struct smtp_client *
+smtp_client_open(const struct lda_settings *set, const char *destination,
+		 const char *return_path, FILE **file_r)
+{
+	struct smtp_client *client;
+	const char *path;
+	int fd;
+
+	if (*set->submission_host == '\0') {
+		return smtp_client_open_sendmail(set, destination,
+						 return_path, file_r);
+	}
+
+	if ((fd = create_temp_file(&path)) == -1)
+		return smtp_client_devnull(file_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->use_smtp = TRUE;
+	return client;
+}
+
+static int smtp_client_close_sendmail(struct smtp_client *client)
 {
 	int ret = EX_TEMPFAIL, status;
 
 	fclose(client->f);
+
 	if (client->pid == (pid_t)-1) {
 		/* smtp_client_open() failed already */
 	} else if (waitpid(client->pid, &status, 0) < 0)
@@ -113,7 +188,118 @@
 		i_error("Sendmail process terminated abnormally, "
 			"return status %d", status);
 	}
-
 	i_free(client);
 	return ret;
 }
+
+static void smtp_client_send_finished(void *context)
+{
+	struct smtp_client *smtp_client = context;
+
+	smtp_client->finished = TRUE;
+	io_loop_stop(current_ioloop);
+}
+
+static void
+rcpt_to_callback(bool success, const char *reply, void *context)
+{
+	struct smtp_client *smtp_client = context;
+
+	if (!success) {
+		i_error("smtp(%s): RCPT TO failed: %s",
+			smtp_client->set->submission_host, reply);
+		smtp_client_send_finished(smtp_client);
+	}
+}
+
+static void
+data_callback(bool success, const char *reply, void *context)
+{
+	struct smtp_client *smtp_client = context;
+
+	if (!success) {
+		i_error("smtp(%s): DATA failed: %s",
+			smtp_client->set->submission_host, reply);
+		smtp_client_send_finished(smtp_client);
+	} else {
+		smtp_client->success = TRUE;
+	}
+}
+
+static int smtp_client_send(struct smtp_client *smtp_client)
+{
+	struct lmtp_client_settings client_set;
+	struct lmtp_client *client;
+	struct ioloop *ioloop;
+	struct istream *input;
+	const char *host, *p;
+	unsigned int port = DEFAULT_SUBMISSION_PORT;
+
+	host = smtp_client->set->submission_host;
+	p = strchr(host, ':');
+	if (p != NULL) {
+		host = t_strdup_until(host, p);
+		if (str_to_uint(p + 1, &port) < 0 ||
+		    port == 0 || port > 65535) {
+			i_error("Invalid port in submission_host: %s", p+1);
+			return -1;
+		}
+	}
+
+	if (fflush(smtp_client->f) != 0) {
+		i_error("fflush(%s) failed: %m", smtp_client->temp_path);
+		return -1;
+	}
+
+	if (lseek(fileno(smtp_client->f), 0, SEEK_SET) < 0) {
+		i_error("lseek(%s) failed: %m", smtp_client->temp_path);
+		return -1;
+	}
+
+	memset(&client_set, 0, sizeof(client_set));
+	client_set.mail_from = smtp_client->return_path == NULL ? "<>" :
+		t_strconcat("<", smtp_client->return_path, ">", NULL);
+	client_set.my_hostname = smtp_client->set->hostname;
+	client_set.dns_client_socket_path = "dns-client";
+
+	ioloop = io_loop_create();
+	client = lmtp_client_init(&client_set, smtp_client_send_finished,
+				  smtp_client);
+
+	if (lmtp_client_connect_tcp(client, LMTP_CLIENT_PROTOCOL_SMTP,
+				    host, port) < 0) {
+		lmtp_client_deinit(&client);
+		io_loop_destroy(&ioloop);
+		return -1;
+	}
+
+	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);
+	lmtp_client_send(client, input);
+	i_stream_unref(&input);
+
+	if (!smtp_client->finished)
+		io_loop_run(ioloop);
+	io_loop_destroy(&ioloop);
+	return smtp_client->success ? 0 : -1;
+}
+
+int smtp_client_close(struct smtp_client *client)
+{
+	int ret;
+
+	if (!client->use_smtp)
+		return smtp_client_close_sendmail(client);
+
+	/* the mail has been written to a file. now actually send it. */
+	ret = smtp_client_send(client);
+
+	fclose(client->f);
+	i_free(client->return_path);
+	i_free(client->destination);
+	i_free(client->temp_path);
+	i_free(client);
+	return ret < 0 ? EX_TEMPFAIL : 0;
+}
--- a/src/lib-mail/istream-header-filter.c	Sat Feb 19 16:56:21 2011 +0200
+++ b/src/lib-mail/istream-header-filter.c	Tue Feb 22 15:31:31 2011 +0200
@@ -318,7 +318,9 @@
 
 	if (ret == -1 && stream->parent->eof && !last_lf) {
 		/* missing LF, need to add it */
+		i_assert(!mstream->last_lf_added);
 		i_assert(size == 0 || data[size-1] != '\n');
+
 		buffer_reset(mstream->hdr_buf);
 		buffer_append(mstream->hdr_buf, data, size);
 		if (mstream->crlf)
@@ -328,7 +330,7 @@
 		mstream->last_lf_added = TRUE;
 
 		stream->skip = 0;
-		stream->pos = size + 1;
+		stream->pos = mstream->hdr_buf->used;
 		stream->buffer = mstream->hdr_buf->data;
 		return mstream->crlf ? 2 : 1;
 	} else {
@@ -425,8 +427,6 @@
 	struct header_filter_istream *mstream =
 		(struct header_filter_istream *)stream;
 
-	mstream->last_lf_added = FALSE;
-
 	if (stream->istream.v_offset == v_offset) {
 		/* just reset the input buffer */
 		stream_reset_to(mstream, v_offset);
@@ -434,6 +434,9 @@
 			      mstream->istream.parent_expected_offset);
 		return;
 	}
+	/* if last_lf_added=TRUE, we're currently at EOF. So reset it only if
+	   we're seeking backwards, otherwise we would just add a duplicate */
+	mstream->last_lf_added = FALSE;
 
 	if (v_offset == 0) {
 		/* seeking to beginning of headers. */
--- a/src/lib-master/master-auth.c	Sat Feb 19 16:56:21 2011 +0200
+++ b/src/lib-master/master-auth.c	Tue Feb 22 15:31:31 2011 +0200
@@ -116,7 +116,8 @@
 				return;
 			i_error("read(%s) failed: %m", conn->auth->path);
 		} else {
-			i_error("read(%s) failed: Remote closed connection",
+			i_error("read(%s) failed: Remote closed connection "
+				"(process_limit reached?)",
 				conn->auth->path);
 		}
 		master_auth_connection_deinit(&conn);
--- a/src/lib-master/master-interface.h	Sat Feb 19 16:56:21 2011 +0200
+++ b/src/lib-master/master-interface.h	Tue Feb 22 15:31:31 2011 +0200
@@ -59,6 +59,10 @@
    if dovecot was started with -p parameter. */
 #define MASTER_SSL_KEY_PASSWORD_ENV "SSL_KEY_PASSWORD"
 
+/* getenv(DOVECOT_PRESERVE_ENVS_ENV) returns a space separated list of
+   environments that should be preserved. */
+#define DOVECOT_PRESERVE_ENVS_ENV "DOVECOT_PRESERVE_ENVS"
+
 /* Write pipe to anvil. */
 #define MASTER_ANVIL_FD 3
 /* Anvil reads new log fds from this fd */
--- a/src/lib-master/master-service-settings.c	Sat Feb 19 16:56:21 2011 +0200
+++ b/src/lib-master/master-service-settings.c	Tue Feb 22 15:31:31 2011 +0200
@@ -90,12 +90,22 @@
 			   const struct master_service_settings_input *input)
 {
 	const char **conf_argv, *binary_path = service->argv[0];
+	const char *home = NULL, *user = NULL;
 	unsigned int i, argv_max_count;
 
 	(void)t_binary_abspath(&binary_path);
 
-	if (!service->keep_environment)
-		master_service_env_clean(input->preserve_home);
+	if (!service->keep_environment && !input->preserve_environment) {
+		if (input->preserve_home)
+			home = getenv("HOME");
+		if (input->preserve_user)
+			user = getenv("USER");
+		master_service_env_clean();
+		if (home != NULL)
+			env_put(t_strconcat("HOME=", home, NULL));
+		if (user != NULL)
+			env_put(t_strconcat("USER=", user, NULL));
+	}
 	if (input->use_sysexits)
 		env_put("USE_SYSEXITS=1");
 
--- a/src/lib-master/master-service-settings.h	Sat Feb 19 16:56:21 2011 +0200
+++ b/src/lib-master/master-service-settings.h	Tue Feb 22 15:31:31 2011 +0200
@@ -21,6 +21,8 @@
 struct master_service_settings_input {
 	const struct setting_parser_info *const *roots;
 	const char *config_path;
+	bool preserve_environment;
+	bool preserve_user;
 	bool preserve_home;
 	bool never_exec;
 	bool use_sysexits;
--- a/src/lib-master/master-service.c	Sat Feb 19 16:56:21 2011 +0200
+++ b/src/lib-master/master-service.c	Tue Feb 22 15:31:31 2011 +0200
@@ -391,22 +391,16 @@
 	master_status_update(service);
 }
 
-void master_service_env_clean(bool preserve_home)
+void master_service_env_clean(void)
 {
-	static const char *preserve_envs[] = {
-		"HOME", /* keep as the first element */
-		"USER",
-		"TZ",
-#ifdef DEBUG
-		"GDB",
-#endif
-#ifdef HAVE_SYSTEMD
-		"LISTEN_PID",
-		"LISTEN_FDS",
-#endif
-		NULL
-	};
-	env_clean_except(preserve_envs + (preserve_home ? 0 : 1));
+	const char *value = getenv(DOVECOT_PRESERVE_ENVS_ENV);
+
+	if (value == NULL || *value == '\0')
+		env_clean();
+	else T_BEGIN {
+		value = t_strconcat(value, " "DOVECOT_PRESERVE_ENVS_ENV, NULL);
+		env_clean_except(t_strsplit_spaces(value, " "));
+	} T_END;
 }
 
 void master_service_set_client_limit(struct master_service *service,
--- a/src/lib-master/master-service.h	Sat Feb 19 16:56:21 2011 +0200
+++ b/src/lib-master/master-service.h	Tue Feb 22 15:31:31 2011 +0200
@@ -59,8 +59,9 @@
    before calling this. */
 void master_service_init_finish(struct master_service *service);
 
-/* Clean environment from everything except TZ, USER and optionally HOME. */
-void master_service_env_clean(bool preserve_home);
+/* Clean environment from everything except the ones listed in
+   DOVECOT_PRESERVE_ENVS environment. */
+void master_service_env_clean(void);
 
 /* Initialize logging. */
 void master_service_init_log(struct master_service *service,
--- a/src/lib-settings/settings-parser.c	Sat Feb 19 16:56:21 2011 +0200
+++ b/src/lib-settings/settings-parser.c	Tue Feb 22 15:31:31 2011 +0200
@@ -66,6 +66,9 @@
 	.parent_offset = (size_t)-1
 };
 
+static int settings_parse_keyvalue(struct setting_parser_context *ctx,
+				   const char *key, const char *value);
+
 static void
 setting_parser_copy_defaults(struct setting_parser_context *ctx, 
 			     const struct setting_parser_info *info,
@@ -657,7 +660,7 @@
 {
 	const struct setting_define *def;
 	struct setting_link *link;
-	const char *end;
+	const char *end, *parent_key;
 	unsigned int i;
 
 	/* try to find from roots */
@@ -679,9 +682,27 @@
 	if (end == NULL)
 		return FALSE;
 
-	link = hash_table_lookup(ctx->links, t_strdup_until(key, end));
-	if (link == NULL)
-		return FALSE;
+	parent_key = t_strdup_until(key, end);
+	link = hash_table_lookup(ctx->links, parent_key);
+	if (link == NULL) {
+		/* maybe this is the first strlist value */
+		unsigned int parent_n = 0;
+		const struct setting_define *parent_def;
+		struct setting_link *parent_link;
+
+		if (!settings_find_key_nth(ctx, parent_key, &parent_n,
+					   &parent_def, &parent_link))
+			return FALSE;
+		if (parent_def->type != SET_STRLIST)
+			return FALSE;
+
+		/* setting parent_key=0 adds it to links list */
+		if (settings_parse_keyvalue(ctx, parent_key, "0") <= 0)
+			return FALSE;
+
+		link = hash_table_lookup(ctx->links, parent_key);
+		i_assert(link != NULL);
+	}
 
 	*link_r = link;
 	if (link->info == &strlist_info) {
--- a/src/lib-sql/Makefile.am	Sat Feb 19 16:56:21 2011 +0200
+++ b/src/lib-sql/Makefile.am	Tue Feb 22 15:31:31 2011 +0200
@@ -64,6 +64,12 @@
 libdriver_sqlite_la_SOURCES = driver-sqlite.c
 endif
 
+pkglib_LTLIBRARIES = libdovecot-sql.la
+libdovecot_sql_la_SOURCES = 
+libdovecot_sql_la_LIBADD = libsql.la $(MODULE_LIBS)
+libdovecot_sql_la_DEPENDENCIES = libsql.la
+libdovecot_sql_la_LDFLAGS = -export-dynamic
+
 headers = \
 	sql-api.h \
 	sql-api-private.h \
--- a/src/lib-sql/driver-mysql.c	Sat Feb 19 16:56:21 2011 +0200
+++ b/src/lib-sql/driver-mysql.c	Tue Feb 22 15:31:31 2011 +0200
@@ -31,7 +31,7 @@
 	struct sql_result api;
 
 	MYSQL_RES *result;
-        MYSQL_ROW row;
+	MYSQL_ROW row;
 
 	MYSQL_FIELD *fields;
 	unsigned int fields_count;
@@ -406,12 +406,16 @@
 }
 
 static const unsigned char *
-driver_mysql_result_get_field_value_binary(struct sql_result *_result ATTR_UNUSED,
-					   unsigned int idx ATTR_UNUSED,
-					   size_t *size_r ATTR_UNUSED)
+driver_mysql_result_get_field_value_binary(struct sql_result *_result,
+					   unsigned int idx, size_t *size_r)
 {
-	/* FIXME */
-	return NULL;
+	struct mysql_result *result = (struct mysql_result *)_result;
+	unsigned long *lengths;
+
+	lengths = mysql_fetch_lengths(result->result);
+
+	*size_r = lengths[idx];
+	return (const void *)result->row[idx];
 }
 
 static const char *
@@ -591,6 +595,8 @@
 	.failed_try_retry = TRUE
 };
 
+const char *driver_mysql_version = DOVECOT_VERSION;
+
 void driver_mysql_init(void);
 void driver_mysql_deinit(void);
 
--- a/src/lib-sql/driver-pgsql.c	Sat Feb 19 16:56:21 2011 +0200
+++ b/src/lib-sql/driver-pgsql.c	Tue Feb 22 15:31:31 2011 +0200
@@ -75,6 +75,8 @@
 
 static void driver_pgsql_set_state(struct pgsql_db *db, enum sql_db_state state)
 {
+	i_assert(state == SQL_DB_STATE_BUSY || db->cur_result == NULL);
+
 	/* switch back to original ioloop in case the caller wants to
 	   add/remove timeouts */
 	if (db->ioloop != NULL)
@@ -422,6 +424,10 @@
 
 static void query_timeout(struct pgsql_result *result)
 {
+        struct pgsql_db *db = (struct pgsql_db *)result->api.db;
+
+	driver_pgsql_stop_io(db);
+
 	i_error("pgsql: Query timed out, aborting");
 	result->timeout = TRUE;
 	result_finish(result);
@@ -526,6 +532,8 @@
 
 static void driver_pgsql_sync_init(struct pgsql_db *db)
 {
+	bool add_to_connect;
+
 	db->orig_ioloop = current_ioloop;
 	if (db->io == NULL) {
 		db->ioloop = io_loop_create();
@@ -536,12 +544,18 @@
 
 	/* have to move our existing I/O and timeout handlers to new I/O loop */
 	io_remove(&db->io);
-	if (db->to_connect != NULL)
+	if (db->to_connect != NULL) {
 		timeout_remove(&db->to_connect);
+		add_to_connect = TRUE;
+	} else {
+		add_to_connect = FALSE;
+	}
 
 	db->ioloop = io_loop_create();
-	db->to_connect = timeout_add(SQL_CONNECT_TIMEOUT_SECS * 1000,
-				     driver_pgsql_connect_timeout, db);
+	if (add_to_connect) {
+		db->to_connect = timeout_add(SQL_CONNECT_TIMEOUT_SECS * 1000,
+					     driver_pgsql_connect_timeout, db);
+	}
 	db->io = io_add(PQsocket(db->pg), db->io_dir, connect_callback, db);
 	/* wait for connecting to finish */
 	io_loop_run(db->ioloop);
@@ -1041,6 +1055,8 @@
 	}
 };
 
+const char *driver_pgsql_version = DOVECOT_VERSION;
+
 void driver_pgsql_init(void);
 void driver_pgsql_deinit(void);
 
--- a/src/lib-sql/driver-sqlite.c	Sat Feb 19 16:56:21 2011 +0200
+++ b/src/lib-sql/driver-sqlite.c	Tue Feb 22 15:31:31 2011 +0200
@@ -444,6 +444,8 @@
 	}
 };
 
+const char *driver_sqlite_version = DOVECOT_VERSION;
+
 void driver_sqlite_init(void);
 void driver_sqlite_deinit(void);
 
--- a/src/lib-storage/index/dbox-common/dbox-attachment.c	Sat Feb 19 16:56:21 2011 +0200
+++ b/src/lib-storage/index/dbox-common/dbox-attachment.c	Tue Feb 22 15:31:31 2011 +0200
@@ -148,11 +148,13 @@
 	const char *path, *path_suffix;
 	uoff_t psize, last_voffset = 0;
 	unsigned int i;
+	int ret = 1;
 
 	t_array_init(&extrefs_arr, 16);
 	if (!dbox_attachment_parse_extref_real(ext_refs, pool_datastack_create(),
 					       &extrefs_arr))
 		return 0;
+	psize = dbox_file_get_plaintext_size(file);
 
 	t_array_init(&streams, 8);
 	array_foreach(&extrefs_arr, extref) {
@@ -166,6 +168,10 @@
 			input = i_stream_create_limit(*stream, part_size);
 			array_append(&streams, &input, 1);
 			i_stream_seek(*stream, (*stream)->v_offset + part_size);
+			if ((*stream)->v_offset + part_size > psize) {
+				/* extrefs point outside message */
+				ret = 0;
+			}
 			last_voffset += part_size;
 		}
 
@@ -185,13 +191,17 @@
 		array_append(&streams, &input, 1);
 	}
 
-	psize = dbox_file_get_plaintext_size(file);
 	if (psize != (*stream)->v_offset) {
-		uoff_t trailer_size = psize - (*stream)->v_offset;
+		if (psize < (*stream)->v_offset) {
+			/* extrefs point outside message */
+			ret = 0;
+		} else {
+			uoff_t trailer_size = psize - (*stream)->v_offset;
 
-		input = i_stream_create_limit(*stream, trailer_size);
-		array_append(&streams, &input, 1);
-		(void)array_append_space(&streams);
+			input = i_stream_create_limit(*stream, trailer_size);
+			array_append(&streams, &input, 1);
+			(void)array_append_space(&streams);
+		}
 	}
 
 	inputs = array_idx_modifiable(&streams, 0);
@@ -199,7 +209,7 @@
 	*stream = i_stream_create_concat(inputs);
 	for (i = 0; inputs[i] != NULL; i++)
 		i_stream_unref(&inputs[i]);
-	return 1;
+	return ret;
 }
 
 int dbox_attachment_file_get_stream(struct dbox_file *file,
--- a/src/lib-storage/index/dbox-common/dbox-file.c	Sat Feb 19 16:56:21 2011 +0200
+++ b/src/lib-storage/index/dbox-common/dbox-file.c	Tue Feb 22 15:31:31 2011 +0200
@@ -479,6 +479,13 @@
 	*_ctx = NULL;
 
 	ret = dbox_file_append_flush(ctx);
+	if (ctx->last_checkpoint_offset != ctx->output->offset) {
+		o_stream_close(ctx->output);
+		if (ftruncate(ctx->file->fd, ctx->last_checkpoint_offset) < 0) {
+			dbox_file_set_syscall_error(ctx->file, "ftruncate()");
+			return -1;
+		}
+	}
 	o_stream_unref(&ctx->output);
 	ctx->file->appending = FALSE;
 	i_free(ctx);
@@ -538,6 +545,11 @@
 	return 0;
 }
 
+void dbox_file_append_checkpoint(struct dbox_file_append_context *ctx)
+{
+	ctx->last_checkpoint_offset = ctx->output->offset;
+}
+
 int dbox_file_get_append_stream(struct dbox_file_append_context *ctx,
 				struct ostream **output_r)
 {
@@ -548,6 +560,11 @@
 		/* file creation had failed */
 		return -1;
 	}
+	if (ctx->last_checkpoint_offset != ctx->output->offset) {
+		/* a message was aborted. don't try appending to this
+		   file anymore. */
+		return -1;
+	}
 
 	if (file->file_version == 0) {
 		/* newly created file, write the file header */
--- a/src/lib-storage/index/dbox-common/dbox-file.h	Sat Feb 19 16:56:21 2011 +0200
+++ b/src/lib-storage/index/dbox-common/dbox-file.h	Tue Feb 22 15:31:31 2011 +0200
@@ -126,7 +126,7 @@
 struct dbox_file_append_context {
 	struct dbox_file *file;
 
-	uoff_t first_append_offset, last_flush_offset;
+	uoff_t first_append_offset, last_checkpoint_offset, last_flush_offset;
 	struct ostream *output;
 };
 
@@ -173,6 +173,9 @@
    can't be appended to (old file version or corruption) or -1 if error. */
 int dbox_file_get_append_stream(struct dbox_file_append_context *ctx,
 				struct ostream **output_r);
+/* Call after message has been fully saved. If this isn't done, the writes
+   since the last checkpoint are truncated. */
+void dbox_file_append_checkpoint(struct dbox_file_append_context *ctx);
 /* Flush output buffer. */
 int dbox_file_append_flush(struct dbox_file_append_context *ctx);
 
--- a/src/lib-storage/index/dbox-common/dbox-sync-rebuild.c	Sat Feb 19 16:56:21 2011 +0200
+++ b/src/lib-storage/index/dbox-common/dbox-sync-rebuild.c	Tue Feb 22 15:31:31 2011 +0200
@@ -3,6 +3,7 @@
 #include "lib.h"
 #include "array.h"
 #include "mail-index-modseq.h"
+#include "mailbox-list-private.h"
 #include "index-storage.h"
 #include "dbox-storage.h"
 #include "dbox-sync-rebuild.h"
@@ -190,3 +191,29 @@
 	}
 	i_free(ctx);
 }
+
+int dbox_sync_rebuild_verify_alt_storage(struct mailbox_list *list)
+{
+	const char *alt_path;
+	struct stat st;
+
+	alt_path = mailbox_list_get_path(list, NULL,
+					 MAILBOX_LIST_PATH_TYPE_ALT_DIR);
+	if (alt_path == NULL)
+		return 0;
+
+	/* make sure alt storage is mounted. if it's not, abort the rebuild. */
+	if (stat(alt_path, &st) == 0)
+		return 0;
+	if (errno != ENOENT) {
+		i_error("stat(%s) failed: %m", alt_path);
+		return -1;
+	}
+
+	/* try to create the alt directory. if it fails, it means alt
+	   storage isn't mounted. */
+	if (mailbox_list_mkdir(list, alt_path,
+			       MAILBOX_LIST_PATH_TYPE_ALT_DIR) < 0)
+		return -1;
+	return 0;
+}
--- a/src/lib-storage/index/dbox-common/dbox-sync-rebuild.h	Sat Feb 19 16:56:21 2011 +0200
+++ b/src/lib-storage/index/dbox-common/dbox-sync-rebuild.h	Tue Feb 22 15:31:31 2011 +0200
@@ -1,6 +1,8 @@
 #ifndef DBOX_SYNC_REBUILD_H
 #define DBOX_SYNC_REBUILD_H
 
+struct mailbox_list;
+
 struct dbox_sync_rebuild_context {
 	struct mailbox *box;
 
@@ -23,5 +25,6 @@
 
 void dbox_sync_rebuild_index_metadata(struct dbox_sync_rebuild_context *ctx,
 				      uint32_t new_seq, uint32_t uid);
+int dbox_sync_rebuild_verify_alt_storage(struct mailbox_list *list);
 
 #endif
--- a/src/lib-storage/index/dbox-multi/mdbox-map.c	Sat Feb 19 16:56:21 2011 +0200
+++ b/src/lib-storage/index/dbox-multi/mdbox-map.c	Tue Feb 22 15:31:31 2011 +0200
@@ -1100,15 +1100,19 @@
 
 void mdbox_map_append_finish(struct mdbox_map_append_context *ctx)
 {
-	struct mdbox_map_append *appends;
+	struct mdbox_map_append *appends, *last;
 	unsigned int count;
 	uoff_t cur_offset;
 
 	appends = array_get_modifiable(&ctx->appends, &count);
-	i_assert(count > 0 && appends[count-1].size == (uint32_t)-1);
-	cur_offset = appends[count-1].file_append->output->offset;
-	i_assert(cur_offset >= appends[count-1].offset);
-	appends[count-1].size = cur_offset - appends[count-1].offset;
+	i_assert(count > 0);
+	last = &appends[count-1];
+	i_assert(last->size == (uint32_t)-1);
+
+	cur_offset = last->file_append->output->offset;
+	i_assert(cur_offset >= last->offset);
+	last->size = cur_offset - last->offset;
+	dbox_file_append_checkpoint(last->file_append);
 }
 
 void mdbox_map_append_abort(struct mdbox_map_append_context *ctx)
--- a/src/lib-storage/index/dbox-multi/mdbox-save.c	Sat Feb 19 16:56:21 2011 +0200
+++ b/src/lib-storage/index/dbox-multi/mdbox-save.c	Tue Feb 22 15:31:31 2011 +0200
@@ -410,7 +410,8 @@
 	ctx->ctx.finished = TRUE;
 
 	if (mail->box->storage != _ctx->transaction->box->storage ||
-	    _ctx->transaction->box->disable_reflink_copy_to)
+	    _ctx->transaction->box->disable_reflink_copy_to ||
+	    _ctx->guid != NULL)
 		return mail_storage_copy(_ctx, mail);
 	src_mbox = (struct mdbox_mailbox *)mail->box;
 
--- a/src/lib-storage/index/dbox-multi/mdbox-storage-rebuild.c	Sat Feb 19 16:56:21 2011 +0200
+++ b/src/lib-storage/index/dbox-multi/mdbox-storage-rebuild.c	Tue Feb 22 15:31:31 2011 +0200
@@ -9,6 +9,7 @@
 #include "mail-cache.h"
 #include "dbox-sync-rebuild.h"
 #include "mail-namespace.h"
+#include "mailbox-list-private.h"
 #include "mdbox-storage.h"
 #include "mdbox-file.h"
 #include "mdbox-map-private.h"
@@ -890,6 +891,14 @@
 	struct mdbox_storage_rebuild_context *ctx;
 	int ret;
 
+	if (dbox_sync_rebuild_verify_alt_storage(storage->map->root_list) < 0) {
+		mail_storage_set_critical(&storage->storage.storage,
+			"mdbox rebuild: Alt storage %s not mounted, aborting",
+			storage->alt_storage_dir);
+		mdbox_map_atomic_set_failed(atomic);
+		return -1;
+	}
+
 	ctx = mdbox_storage_rebuild_init(storage, atomic);
 	ret = mdbox_storage_rebuild_scan(ctx);
 	mdbox_storage_rebuild_deinit(ctx);
--- a/src/lib-storage/index/dbox-single/sdbox-copy.c	Sat Feb 19 16:56:21 2011 +0200
+++ b/src/lib-storage/index/dbox-single/sdbox-copy.c	Tue Feb 22 15:31:31 2011 +0200
@@ -148,7 +148,8 @@
 	i_assert((_t->flags & MAILBOX_TRANSACTION_FLAG_EXTERNAL) != 0);
 
 	ctx->finished = TRUE;
-	if (mail_storage_copy_can_use_hardlink(mail->box, &mbox->box)) {
+	if (mail_storage_copy_can_use_hardlink(mail->box, &mbox->box) &&
+	    _ctx->guid == NULL) {
 		T_BEGIN {
 			ret = sdbox_copy_hardlink(_ctx, mail);
 		} T_END;
--- a/src/lib-storage/index/dbox-single/sdbox-save.c	Sat Feb 19 16:56:21 2011 +0200
+++ b/src/lib-storage/index/dbox-single/sdbox-save.c	Tue Feb 22 15:31:31 2011 +0200
@@ -186,8 +186,11 @@
 
 	if (ctx->ctx.failed)
 		dbox_file_append_rollback(&ctx->append_ctx);
-	else if (dbox_file_append_commit(&ctx->append_ctx) < 0)
-		ctx->ctx.failed = TRUE;
+	else {
+		dbox_file_append_checkpoint(ctx->append_ctx);
+		if (dbox_file_append_commit(&ctx->append_ctx) < 0)
+			ctx->ctx.failed = TRUE;
+	}
 
 	i_stream_unref(&ctx->ctx.input);
 	dbox_file_close(*files);
--- a/src/lib-storage/index/dbox-single/sdbox-sync-rebuild.c	Sat Feb 19 16:56:21 2011 +0200
+++ b/src/lib-storage/index/dbox-single/sdbox-sync-rebuild.c	Tue Feb 22 15:31:31 2011 +0200
@@ -178,6 +178,13 @@
 		}
 	}
 
+	if (dbox_sync_rebuild_verify_alt_storage(mbox->box.list) < 0) {
+		mail_storage_set_critical(mbox->box.storage,
+			"sdbox %s: Alt storage not mounted, "
+			"aborting index rebuild", mailbox_get_path(&mbox->box));
+		return -1;
+	}
+
 	mail_cache_reset(mbox->box.cache);
 
 	view = mail_index_view_open(mbox->box.index);
--- a/src/lib-storage/index/maildir/maildir-uidlist.c	Sat Feb 19 16:56:21 2011 +0200
+++ b/src/lib-storage/index/maildir/maildir-uidlist.c	Tue Feb 22 15:31:31 2011 +0200
@@ -1074,6 +1074,10 @@
 int maildir_uidlist_get_mailbox_guid(struct maildir_uidlist *uidlist,
 				     uint8_t mailbox_guid[MAIL_GUID_128_SIZE])
 {
+	if (!uidlist->initial_hdr_read) {
+		if (maildir_uidlist_refresh(uidlist) < 0)
+			return -1;
+	}
 	if (!uidlist->have_mailbox_guid) {
 		uidlist->recreate = TRUE;
 		if (maildir_uidlist_update(uidlist) < 0)
--- a/src/lib-storage/list/mailbox-list-maildir.c	Sat Feb 19 16:56:21 2011 +0200
+++ b/src/lib-storage/list/mailbox-list-maildir.c	Tue Feb 22 15:31:31 2011 +0200
@@ -129,16 +129,6 @@
 	return TRUE;
 }
 
-static bool ATTR_NORETURN
-maildir_is_valid_pattern(struct mailbox_list *list ATTR_UNUSED,
-			 const char *pattern ATTR_UNUSED)
-{
-	i_unreached();
-#ifndef ATTRS_DEFINED
-	return FALSE;
-#endif
-}
-
 static bool
 maildir_is_valid_existing_name(struct mailbox_list *_list, const char *name)
 {
@@ -156,6 +146,15 @@
 }
 
 static bool
+maildir_is_valid_pattern(struct mailbox_list *list, const char *pattern)
+{
+	/* maildir code itself doesn't care about this, but we may get here
+	   from listing subscriptions to LAYOUT=fs namespace containing
+	   entries for a subscriptions=no LAYOUT=maildir++ namespace */
+	return maildir_is_valid_existing_name(list, pattern);
+}
+
+static bool
 maildir_is_valid_create_name(struct mailbox_list *_list, const char *name)
 {
 	struct maildir_mailbox_list *list =
@@ -477,7 +476,7 @@
 	i_array_init(&names_arr, 64);
 
 	old_vname = mailbox_list_get_vname(oldlist, oldname);
-	old_vnamelen = strlen(oldname);
+	old_vnamelen = strlen(old_vname);
 
 	new_vname = mailbox_list_get_vname(newlist, newname);
 
--- a/src/lib-storage/list/mailbox-list-none.c	Sat Feb 19 16:56:21 2011 +0200
+++ b/src/lib-storage/list/mailbox-list-none.c	Tue Feb 22 15:31:31 2011 +0200
@@ -8,6 +8,12 @@
 #define MAILBOX_LIST_NAME_NONE "none"
 #define GLOBAL_TEMP_PREFIX ".temp."
 
+struct noop_list_iterate_context {
+	struct mailbox_list_iterate_context ctx;
+	struct mailbox_info inbox_info;
+	unsigned int list_inbox:1;
+};
+
 extern struct mailbox_list none_mailbox_list;
 
 static struct mailbox_list *none_list_alloc(void)
@@ -126,15 +132,20 @@
 		    const char *const *patterns,
 		    enum mailbox_list_iter_flags flags)
 {
-	struct mailbox_list_iterate_context *ctx;
+	struct noop_list_iterate_context *ctx;
 
-	ctx = i_new(struct mailbox_list_iterate_context, 1);
-	ctx->list = list;
-	ctx->flags = flags;
-	ctx->glob = imap_match_init_multiple(default_pool, patterns, TRUE,
-					     mail_namespace_get_sep(list->ns));
-	array_create(&ctx->module_contexts, default_pool, sizeof(void *), 5);
-	return ctx;
+	ctx = i_new(struct noop_list_iterate_context, 1);
+	ctx->ctx.list = list;
+	ctx->ctx.flags = flags;
+	ctx->ctx.glob = imap_match_init_multiple(default_pool, patterns, TRUE,
+						 mail_namespace_get_sep(list->ns));
+	array_create(&ctx->ctx.module_contexts, default_pool, sizeof(void *), 5);
+	if ((list->ns->flags & NAMESPACE_FLAG_INBOX_USER) != 0) {
+		ctx->list_inbox = TRUE;
+		ctx->inbox_info.ns = list->ns;
+		ctx->inbox_info.name = "INBOX";
+	}
+	return &ctx->ctx;
 }
 
 static int
@@ -147,8 +158,15 @@
 }
 
 static const struct mailbox_info *
-none_list_iter_next(struct mailbox_list_iterate_context *ctx ATTR_UNUSED)
+none_list_iter_next(struct mailbox_list_iterate_context *_ctx)
 {
+	struct noop_list_iterate_context *ctx =
+		(struct noop_list_iterate_context *)_ctx;
+
+	if (ctx->list_inbox) {
+		ctx->list_inbox = FALSE;
+		return &ctx->inbox_info;
+	}
 	return NULL;
 }
 
--- a/src/lib-storage/mail-storage-service.c	Sat Feb 19 16:56:21 2011 +0200
+++ b/src/lib-storage/mail-storage-service.c	Tue Feb 22 15:31:31 2011 +0200
@@ -58,7 +58,7 @@
 	pool_t pool;
 	struct mail_storage_service_input input;
 
-	const char *system_groups_user;
+	const char *system_groups_user, *uid_source, *gid_source;
 	const struct mail_user_settings *user_set;
 	const struct setting_parser_info *user_info;
 	struct setting_parser_context *set_parser;
@@ -178,10 +178,13 @@
 			*error_r = "userdb returned 0 as uid";
 			return -1;
 		}
+		user->uid_source = "userdb lookup";
 		set_keyval(ctx, user, "mail_uid", dec2str(reply->uid));
 	}
-	if (reply->gid != (uid_t)-1)
+	if (reply->gid != (uid_t)-1) {
+		user->gid_source = "userdb lookup";
 		set_keyval(ctx, user, "mail_gid", dec2str(reply->gid));
+	}
 
 	if (home != NULL && chroot == NULL &&
 	    *user->user_set->valid_chroot_dirs != '\0' &&
@@ -299,8 +302,8 @@
 }
 
 static int
-service_drop_privileges(const struct mail_user_settings *set,
-			const char *system_groups_user,
+service_drop_privileges(struct mail_storage_service_user *user,
+			const struct mail_user_settings *set,
 			const char *home, const char *chroot,
 			bool disallow_root, bool keep_setuid_root,
 			bool setenv_only, const char **error_r)
@@ -327,6 +330,7 @@
 				dec2str(rset.uid));
 			return -1;
 		}
+		rset.uid_source = user->uid_source;
 	} else if (rset.uid == (uid_t)-1 &&
 		   disallow_root && current_euid == 0) {
 		*error_r = "User is missing UID (see mail_uid setting)";
@@ -347,6 +351,7 @@
 				dec2str(rset.gid));
 			return -1;
 		}
+		rset.gid_source = user->gid_source;
 	} else if (rset.gid == (gid_t)-1 && disallow_root &&
 		   set->first_valid_gid > 0 && getegid() == 0) {
 		*error_r = "User is missing GID (see mail_gid setting)";
@@ -370,7 +375,7 @@
 	/* we can't chroot if we want to switch between users. there's not
 	   much point either (from security point of view) */
 	rset.chroot_dir = *chroot == '\0' || keep_setuid_root ? NULL : chroot;
-	rset.system_groups_user = system_groups_user;
+	rset.system_groups_user = user->system_groups_user;
 
 	cur_chroot = restrict_access_get_current_chroot();
 	if (cur_chroot != NULL) {
@@ -647,6 +652,7 @@
 
 	memset(&set_input, 0, sizeof(set_input));
 	set_input.roots = ctx->set_roots;
+	set_input.preserve_user = TRUE;
 	/* settings reader may exec doveconf, which is going to clear
 	   environment, and if we're not doing a userdb lookup we want to
 	   use $HOME */
@@ -820,6 +826,8 @@
 		i_panic("settings_parser_check() failed: %s", error);
 
 	user->user_set = settings_parser_get_list(user->set_parser)[1];
+	user->gid_source = "mail_gid setting";
+	user->uid_source = "mail_uid setting";
 
 	if (!userdb_lookup) {
 		const char *home = getenv("HOME");
@@ -891,17 +899,21 @@
 	} else if (len > 0 && temp_priv_drop) {
 		/* we're dropping privileges only temporarily, so we can't
 		   chroot. fix home directory so we can access it. */
-		set_keyval(ctx, user, "mail_home",
-			   t_strconcat(chroot, "/", home, NULL));
+		if (*home == '\0' || strcmp(home, "/") == 0)
+			home = chroot;
+		else
+			home = t_strconcat(chroot, home, NULL);
+		chroot = "";
+		set_keyval(ctx, user, "mail_home", home);
 	}
 
 	if ((ctx->flags & MAIL_STORAGE_SERVICE_FLAG_NO_LOG_INIT) == 0)
 		mail_storage_service_init_log(ctx, user);
 
 	if ((ctx->flags & MAIL_STORAGE_SERVICE_FLAG_NO_RESTRICT_ACCESS) == 0) {
-		if (service_drop_privileges(user_set, user->system_groups_user,
-					    home, chroot, disallow_root,
-					    temp_priv_drop, FALSE, &error) < 0) {
+		if (service_drop_privileges(user, user_set, home, chroot,
+					    disallow_root, temp_priv_drop,
+					    FALSE, &error) < 0) {
 			i_error("user %s: Couldn't drop privileges: %s",
 				user->input.username, error);
 			return -1;
@@ -935,8 +947,8 @@
 	chroot = user_expand_varstr(ctx->service, &user->input,
 				    user_set->mail_chroot);
 
-	if (service_drop_privileges(user_set, user->system_groups_user,
-				    home, chroot, FALSE, FALSE, TRUE,
+	if (service_drop_privileges(user, user_set, home, chroot,
+				    FALSE, FALSE, TRUE,
 				    &error) < 0)
 		i_fatal("%s", error);
 }
--- a/src/lib-storage/mail-storage.c	Sat Feb 19 16:56:21 2011 +0200
+++ b/src/lib-storage/mail-storage.c	Tue Feb 22 15:31:31 2011 +0200
@@ -948,8 +948,9 @@
 			"Can't rename mailboxes across specified storages.");
 		return -1;
 	}
-	if (src->list->ns->type != NAMESPACE_PRIVATE ||
-	    dest->list->ns->type != NAMESPACE_PRIVATE) {
+	if (src->list != dest->list &&
+	    (src->list->ns->type != NAMESPACE_PRIVATE ||
+	     dest->list->ns->type != NAMESPACE_PRIVATE)) {
 		mail_storage_set_error(src->storage, MAIL_ERROR_NOTPOSSIBLE,
 			"Renaming not supported across non-private namespaces.");
 		return -1;
@@ -1445,6 +1446,12 @@
 		mailbox_keywords_unref(&keywords);
 }
 
+struct mailbox_transaction_context *
+mailbox_save_get_transaction(struct mail_save_context *ctx)
+{
+	return ctx->transaction;
+}
+
 int mailbox_copy(struct mail_save_context **_ctx, struct mail *mail)
 {
 	struct mail_save_context *ctx = *_ctx;
--- a/src/lib-storage/mail-storage.h	Sat Feb 19 16:56:21 2011 +0200
+++ b/src/lib-storage/mail-storage.h	Tue Feb 22 15:31:31 2011 +0200
@@ -606,6 +606,9 @@
 int mailbox_save_finish(struct mail_save_context **ctx);
 void mailbox_save_cancel(struct mail_save_context **ctx);
 
+struct mailbox_transaction_context *
+mailbox_save_get_transaction(struct mail_save_context *ctx);
+
 /* Copy the given message. You'll need to specify the flags etc. using the
    mailbox_save_*() functions. */
 int mailbox_copy(struct mail_save_context **ctx, struct mail *mail);
--- a/src/lib-storage/mailbox-list.c	Sat Feb 19 16:56:21 2011 +0200
+++ b/src/lib-storage/mailbox-list.c	Tue Feb 22 15:31:31 2011 +0200
@@ -264,6 +264,12 @@
 		*error_r = t_strconcat(error, "mail root dir in: ", data, NULL);
 		return -1;
 	}
+	if (strncmp(set_r->root_dir, "INBOX=", 6) == 0) {
+		/* probably mbox user trying to avoid root_dir */
+		*error_r = t_strconcat("Mail root directory not given: ",
+				       data, NULL);
+		return -1;
+	}
 
 	while (*tmp != NULL) {
 		str = split_next_arg(&tmp);
--- a/src/lib/file-dotlock.c	Sat Feb 19 16:56:21 2011 +0200
+++ b/src/lib/file-dotlock.c	Tue Feb 22 15:31:31 2011 +0200
@@ -316,11 +316,12 @@
 }
 
 static int try_create_lock_hardlink(struct lock_info *lock_info, bool write_pid,
-				    string_t *tmp_path)
+				    string_t *tmp_path, time_t now)
 {
 	const char *temp_prefix = lock_info->set->temp_prefix;
 	const char *p;
 	mode_t old_mask;
+	struct stat st;
 
 	if (lock_info->temp_path == NULL) {
 		/* we'll need our temp file first. */
@@ -366,6 +367,14 @@
 		}
 
                 lock_info->temp_path = str_c(tmp_path);
+	} else if (fstat(lock_info->fd, &st) < 0) {
+		i_error("fstat(%s) failed: %m", lock_info->temp_path);
+		return -1;
+	} else if (st.st_ctime < now) {
+		/* we've been waiting for a while.
+		   refresh the file's timestamp. */
+		if (utime(lock_info->temp_path, NULL) < 0)
+			i_error("utime(%s) failed: %m", lock_info->temp_path);
 	}
 
 	if (nfs_safe_link(lock_info->temp_path,
@@ -506,6 +515,7 @@
 				lock_info.wait_usecs += lock_info.wait_usecs/2;
 			}
 			dotlock_wait(&lock_info);
+			now = time(NULL);
 		}
 
 		ret = check_lock(now, &lock_info);
@@ -519,7 +529,7 @@
 			ret = set->use_excl_lock ?
 				try_create_lock_excl(&lock_info, write_pid) :
 				try_create_lock_hardlink(&lock_info, write_pid,
-							 tmp_path);
+							 tmp_path, now);
 			if (ret != 0)
 				break;
 		}
--- a/src/lib/istream-limit.c	Sat Feb 19 16:56:21 2011 +0200
+++ b/src/lib/istream-limit.c	Tue Feb 22 15:31:31 2011 +0200
@@ -74,10 +74,6 @@
 static void i_stream_limit_seek(struct istream_private *stream, uoff_t v_offset,
 				bool mark ATTR_UNUSED)
 {
-	struct limit_istream *lstream = (struct limit_istream *) stream;
-
-	i_assert(v_offset <= lstream->v_size);
-
 	stream->istream.v_offset = v_offset;
 	stream->skip = stream->pos = 0;
 }
--- a/src/lib/istream-seekable.c	Sat Feb 19 16:56:21 2011 +0200
+++ b/src/lib/istream-seekable.c	Tue Feb 22 15:31:31 2011 +0200
@@ -132,7 +132,7 @@
 			/* full / error */
 			sstream->istream.istream.stream_errno =
 				sstream->cur_input->stream_errno;
-			return ret;
+			return -1;
 		}
 
 		/* go to next stream */
@@ -157,8 +157,9 @@
 	const unsigned char *data;
 	size_t size, pos, offset;
 
-	if (stream->istream.v_offset +
-	    (stream->pos - stream->skip) >= sstream->buffer->used) {
+	i_assert(stream->skip == 0);
+
+	if (stream->istream.v_offset + stream->pos >= sstream->buffer->used) {
 		/* need to read more */
 		if (sstream->buffer->used >= stream->max_buffer_size)
 			return FALSE;
@@ -176,6 +177,7 @@
 
 		/* we should have more now. */
 		data = i_stream_get_data(sstream->cur_input, &size);
+		i_assert(size > 0);
 		buffer_append(sstream->buffer, data, size);
 		i_stream_skip(sstream->cur_input, size);
 	}
--- a/src/lib/istream.c	Sat Feb 19 16:56:21 2011 +0200
+++ b/src/lib/istream.c	Tue Feb 22 15:31:31 2011 +0200
@@ -136,6 +136,7 @@
 		break;
 	default:
 		i_assert(ret > 0);
+		i_assert(_stream->skip < _stream->pos);
 		i_assert((size_t)ret+old_size == _stream->pos - _stream->skip);
 		break;
 	}
--- a/src/lib/mkdir-parents.c	Sat Feb 19 16:56:21 2011 +0200
+++ b/src/lib/mkdir-parents.c	Tue Feb 22 15:31:31 2011 +0200
@@ -33,12 +33,16 @@
 		return -1;
 	}
 	if (chown(path, uid, gid) < 0) {
+		orig_errno = errno;
+		if (rmdir(path) < 0)
+			i_error("rmdir(%s) failed: %m", path);
+		errno = orig_errno;
+
 		if (errno == EPERM && uid == (uid_t)-1) {
 			i_error("%s", eperm_error_get_chgrp("chown", path, gid,
 							    gid_origin));
 			return -1;
 		}
-		orig_errno = errno;
 
 		str = t_str_new(256);
 		str_printfa(str, "chown(%s, %ld", path,
--- a/src/lib/module-dir.c	Sat Feb 19 16:56:21 2011 +0200
+++ b/src/lib/module-dir.c	Tue Feb 22 15:31:31 2011 +0200
@@ -432,7 +432,7 @@
 		/* make sure all modules were found */
 		for (; *module_names != NULL; module_names++) {
 			if (**module_names != '\0') {
-				i_fatal("Plugin %s not found from directory %s",
+				i_fatal("Plugin '%s' not found from directory %s",
 					*module_names, dir);
 			}
 		}
--- a/src/lib/restrict-access.c	Sat Feb 19 16:56:21 2011 +0200
+++ b/src/lib/restrict-access.c	Tue Feb 22 15:31:31 2011 +0200
@@ -6,6 +6,7 @@
 #include <unistd.h>
 
 #include "lib.h"
+#include "str.h"
 #include "restrict-access.h"
 #include "env-util.h"
 
@@ -61,24 +62,31 @@
 	return ret;
 }
 
-static void restrict_init_groups(gid_t primary_gid, gid_t privileged_gid)
+static void restrict_init_groups(gid_t primary_gid, gid_t privileged_gid,
+				 const char *gid_source)
 {
+	string_t *str;
+
 	if (privileged_gid == (gid_t)-1) {
 		if (primary_gid == getgid() && primary_gid == getegid()) {
 			/* everything is already set */
 			return;
 		}
 
-		if (setgid(primary_gid) != 0) {
-			i_fatal("setgid(%s) failed with "
-				"euid=%s, gid=%s, egid=%s: %m "
-				"(This binary should probably be called with "
-				"process group set to %s instead of %s)",
-				get_gid_str(primary_gid), get_uid_str(geteuid()),
-				get_gid_str(getgid()), get_gid_str(getegid()),
-				get_gid_str(primary_gid), get_gid_str(getegid()));
-		}
-		return;
+		if (setgid(primary_gid) == 0)
+			return;
+
+		str = t_str_new(128);
+		str_printfa(str, "setgid(%s", get_gid_str(primary_gid));
+		if (gid_source != NULL)
+			str_printfa(str, " from %s", gid_source);
+		str_printfa(str, ") failed with euid=%s, gid=%s, egid=%s: %m "
+			    "(This binary should probably be called with "
+			    "process group set to %s instead of %s)",
+			    get_uid_str(geteuid()),
+			    get_gid_str(getgid()), get_gid_str(getegid()),
+			    get_gid_str(primary_gid), get_gid_str(getegid()));
+		i_fatal("%s", str_c(str));
 	}
 
 	if (getegid() != 0 && primary_gid == getgid() &&
@@ -245,7 +253,7 @@
 		if (process_primary_gid == (gid_t)-1)
 			process_primary_gid = getegid();
 		restrict_init_groups(process_primary_gid,
-				     process_privileged_gid);
+				     process_privileged_gid, set->gid_source);
 	} else {
 		if (process_primary_gid == (gid_t)-1)
 			process_primary_gid = getegid();
@@ -295,11 +303,17 @@
 	/* uid last */
 	if (set->uid != (uid_t)-1) {
 		if (setuid(set->uid) != 0) {
-			i_fatal("setuid(%s) failed with euid=%s: %m "
+			string_t *str = t_str_new(128);
+
+			str_printfa(str, "setuid(%s", get_uid_str(set->uid));
+			if (set->uid_source != NULL)
+				str_printfa(str, " from %s", set->uid_source);
+			str_printfa(str, ") failed with euid=%s: %m "
 				"(This binary should probably be called with "
 				"process user set to %s instead of %s)",
-				get_uid_str(set->uid), get_uid_str(geteuid()),
+				get_uid_str(geteuid()),
 				get_uid_str(set->uid), get_uid_str(geteuid()));
+			i_fatal("%s", str_c(str));
 		}
 	}
 
--- a/src/lib/restrict-access.h	Sat Feb 19 16:56:21 2011 +0200
+++ b/src/lib/restrict-access.h	Tue Feb 22 15:31:31 2011 +0200
@@ -19,6 +19,10 @@
 	   group user contains other GIDs, they're silently dropped. */
 	gid_t first_valid_gid, last_valid_gid;
 
+	/* Human readable "source" of UID and GID values. If non-NULL,
+	   displayed on error messages about failing to change uid/gid. */
+	const char *uid_source, *gid_source;
+
 	/* Chroot directory */
 	const char *chroot_dir;
 };
--- a/src/lib/unichar.c	Sat Feb 19 16:56:21 2011 +0200
+++ b/src/lib/unichar.c	Tue Feb 22 15:31:31 2011 +0200
@@ -388,3 +388,10 @@
 	return uni_utf8_find_invalid_pos((const unsigned char *)str,
 					 strlen(str), &i) == 0;
 }
+
+bool uni_utf8_data_is_valid(const unsigned char *data, size_t size)
+{
+	size_t i;
+
+	return uni_utf8_find_invalid_pos(data, size, &i) == 0;
+}
--- a/src/lib/unichar.h	Sat Feb 19 16:56:21 2011 +0200
+++ b/src/lib/unichar.h	Tue Feb 22 15:31:31 2011 +0200
@@ -75,5 +75,7 @@
 			     buffer_t *buf);
 /* Returns TRUE if string is valid UTF-8 input. */
 bool uni_utf8_str_is_valid(const char *str);
+/* Returns TRUE if data contains only valid UTF-8 input. */
+bool uni_utf8_data_is_valid(const unsigned char *data, size_t size);
 
 #endif
--- a/src/lmtp/client.c	Sat Feb 19 16:56:21 2011 +0200
+++ b/src/lmtp/client.c	Tue Feb 22 15:31:31 2011 +0200
@@ -231,7 +231,7 @@
 	client_raw_user_create(client);
 	client_generate_session_id(client);
 	client->my_domain = client->set->hostname;
-	client->state.lhlo = "missing";
+	client->lhlo = i_strdup("missing");
 
 	DLLIST_PREPEND(&clients, client);
 	clients_count++;
@@ -265,6 +265,7 @@
 	if (client->fd_in != client->fd_out)
 		net_disconnect(client->fd_out);
 	client_state_reset(client);
+	i_free(client->lhlo);
 	pool_unref(&client->state_pool);
 	pool_unref(&client->pool);
 
--- a/src/lmtp/client.h	Sat Feb 19 16:56:21 2011 +0200
+++ b/src/lmtp/client.h	Tue Feb 22 15:31:31 2011 +0200
@@ -12,7 +12,6 @@
 };
 
 struct client_state {
-	const char *lhlo;
 	const char *session_id;
 	const char *mail_from;
 	ARRAY_DEFINE(rcpt_to, struct mail_recipient);
@@ -58,6 +57,7 @@
 
 	struct mail_user *raw_mail_user;
 	const char *my_domain;
+	char *lhlo;
 
 	pool_t state_pool;
 	struct client_state state;
--- a/src/lmtp/commands.c	Sat Feb 19 16:56:21 2011 +0200
+++ b/src/lmtp/commands.c	Tue Feb 22 15:31:31 2011 +0200
@@ -71,7 +71,8 @@
 	client_send_line(client, "250-ENHANCEDSTATUSCODES");
 	client_send_line(client, "250 PIPELINING");
 
-	client->state.lhlo = p_strdup(client->state_pool, str_c(domain));
+	i_free(client->lhlo);
+	client->lhlo = i_strdup(str_c(domain));
 	return 0;
 }
 
@@ -323,7 +324,7 @@
 static void rcpt_address_parse(struct client *client, const char *address,
 			       const char **username_r, const char **detail_r)
 {
-	const char *p, *p2;
+	const char *p, *domain;
 
 	*username_r = address;
 	*detail_r = "";
@@ -331,16 +332,16 @@
 	if (*client->set->recipient_delimiter == '\0')
 		return;
 
+	domain = strchr(address, '@');
 	p = strstr(address, client->set->recipient_delimiter);
-	if (p != NULL) {
+	if (p != NULL && (domain == NULL || p < domain)) {
 		/* user+detail@domain */
 		*username_r = t_strdup_until(*username_r, p);
-		p2 = strchr(p, '@');
-		if (p2 == NULL)
+		if (domain == NULL)
 			*detail_r = p+1;
 		else {
-			*detail_r = t_strdup_until(p+1, p2);
-			*username_r = t_strconcat(*username_r, p2, NULL);
+			*detail_r = t_strdup_until(p+1, domain);
+			*username_r = t_strconcat(*username_r, domain, NULL);
 		}
 	}
 }
@@ -446,7 +447,7 @@
 
 static int
 client_deliver(struct client *client, const struct mail_recipient *rcpt,
-	       struct mail *src_mail)
+	       struct mail *src_mail, struct mail_deliver_session *session)
 {
 	struct mail_deliver_context dctx;
 	struct mail_storage *storage;
@@ -470,7 +471,8 @@
 	sets = mail_storage_service_user_get_set(rcpt->service_user);
 
 	memset(&dctx, 0, sizeof(dctx));
-	dctx.pool = pool_alloconly_create("mail delivery", 1024);
+	dctx.session = session;
+	dctx.pool = session->pool;
 	dctx.set = sets[1];
 	dctx.session_id = client->state.session_id;
 	dctx.src_mail = src_mail;
@@ -516,11 +518,11 @@
 		}
 		ret = -1;
 	}
-	pool_unref(&dctx.pool);
 	return ret;
 }
 
-static bool client_deliver_next(struct client *client, struct mail *src_mail)
+static bool client_deliver_next(struct client *client, struct mail *src_mail,
+				struct mail_deliver_session *session)
 {
 	const struct mail_recipient *rcpts;
 	unsigned int count;
@@ -529,7 +531,7 @@
 	rcpts = array_get(&client->state.rcpt_to, &count);
 	while (client->state.rcpt_idx < count) {
 		ret = client_deliver(client, &rcpts[client->state.rcpt_idx],
-				     src_mail);
+				     src_mail, session);
 		i_set_failure_prefix(t_strdup_printf("lmtp(%s): ", my_pid));
 
 		client->state.rcpt_idx++;
@@ -618,15 +620,17 @@
 static void
 client_input_data_write_local(struct client *client, struct istream *input)
 {
+	struct mail_deliver_session *session;
 	struct mail *src_mail;
 	uid_t old_uid, first_uid = (uid_t)-1;
 
 	if (client_open_raw_mail(client, input) < 0)
 		return;
 
+	session = mail_deliver_session_init();
 	old_uid = geteuid();
 	src_mail = client->state.raw_mail;
-	while (client_deliver_next(client, src_mail)) {
+	while (client_deliver_next(client, src_mail, session)) {
 		if (client->state.first_saved_mail == NULL ||
 		    client->state.first_saved_mail == src_mail)
 			mail_user_unref(&client->state.dest_user);
@@ -639,6 +643,7 @@
 			i_assert(first_uid != 0);
 		}
 	}
+	mail_deliver_session_deinit(&session);
 
 	if (client->state.first_saved_mail != NULL) {
 		struct mail *mail = client->state.first_saved_mail;
@@ -695,30 +700,28 @@
 static const char *client_get_added_headers(struct client *client)
 {
 	string_t *str = t_str_new(200);
-	const char *host, *address = NULL, *username = NULL;
+	const char *host, *rcpt_to = NULL;
 
 	if (array_count(&client->state.rcpt_to) == 1) {
 		const struct mail_recipient *rcpt =
 			array_idx(&client->state.rcpt_to, 0);
-		const char *detail;
 
-		address = rcpt->address;
-		rcpt_address_parse(client, address, &username, &detail);
+		rcpt_to = rcpt->address;
 	}
 
 	str_printfa(str, "Return-Path: <%s>\r\n", client->state.mail_from);
-	if (username != NULL)
-		str_printfa(str, "Delivered-To: <%s>\r\n", username);
+	if (rcpt_to != NULL)
+		str_printfa(str, "Delivered-To: <%s>\r\n", rcpt_to);
 
-	str_printfa(str, "Received: from %s", client->state.lhlo);
+	str_printfa(str, "Received: from %s", client->lhlo);
 	if ((host = net_ip2addr(&client->remote_ip)) != NULL)
 		str_printfa(str, " ([%s])", host);
 	str_printfa(str, "\r\n\tby %s ("PACKAGE_NAME") with LMTP id %s",
 		    client->my_domain, client->state.session_id);
 
 	str_append(str, "\r\n\t");
-	if (address != NULL)
-		str_printfa(str, "for <%s>", address);
+	if (rcpt_to != NULL)
+		str_printfa(str, "for <%s>", rcpt_to);
 	str_printfa(str, "; %s\r\n", message_date_create(ioloop_time));
 	return str_c(str);
 }
--- a/src/login-common/client-common.c	Sat Feb 19 16:56:21 2011 +0200
+++ b/src/login-common/client-common.c	Tue Feb 22 15:31:31 2011 +0200
@@ -497,7 +497,8 @@
 
 const char *client_get_extra_disconnect_reason(struct client *client)
 {
-	if (client->set->ssl_require_client_cert && client->ssl_proxy != NULL) {
+	if (client->set->auth_ssl_require_client_cert &&
+	    client->ssl_proxy != NULL) {
 		if (ssl_proxy_has_broken_client_cert(client->ssl_proxy))
 			return "(client sent an invalid cert)";
 		if (!ssl_proxy_has_valid_client_cert(client->ssl_proxy))
@@ -510,7 +511,7 @@
 	/* some auth attempts without SSL/TLS */
 	if (client->auth_tried_disabled_plaintext)
 		return "(tried to use disabled plaintext auth)";
-	if (client->set->ssl_require_client_cert)
+	if (client->set->auth_ssl_require_client_cert)
 		return "(cert required, client didn't start TLS)";
 	if (client->auth_tried_unsupported_mech)
 		return "(tried to use unsupported auth mechanism)";
--- a/src/login-common/login-settings.c	Sat Feb 19 16:56:21 2011 +0200
+++ b/src/login-common/login-settings.c	Tue Feb 22 15:31:31 2011 +0200
@@ -34,8 +34,8 @@
 	DEF(SET_STR, ssl_cipher_list),
 	DEF(SET_STR, ssl_cert_username_field),
 	DEF(SET_BOOL, ssl_verify_client_cert),
-	DEF(SET_BOOL, ssl_require_client_cert),
-	DEF(SET_BOOL, ssl_username_from_cert),
+	DEF(SET_BOOL, auth_ssl_require_client_cert),
+	DEF(SET_BOOL, auth_ssl_username_from_cert),
 	DEF(SET_BOOL, verbose_ssl),
 
 	DEF(SET_BOOL, disable_plaintext_auth),
@@ -64,8 +64,8 @@
 	.ssl_cipher_list = "ALL:!LOW:!SSLv2:!EXP:!aNULL",
 	.ssl_cert_username_field = "commonName",
 	.ssl_verify_client_cert = FALSE,
-	.ssl_require_client_cert = FALSE,
-	.ssl_username_from_cert = FALSE,
+	.auth_ssl_require_client_cert = FALSE,
+	.auth_ssl_username_from_cert = FALSE,
 	.verbose_ssl = FALSE,
 
 	.disable_plaintext_auth = TRUE,
@@ -131,7 +131,8 @@
 	set->log_format_elements_split =
 		p_strsplit(pool, set->login_log_format_elements, " ");
 
-	if (set->ssl_require_client_cert || set->ssl_username_from_cert) {
+	if (set->auth_ssl_require_client_cert ||
+	    set->auth_ssl_username_from_cert) {
 		/* if we require valid cert, make sure we also ask for it */
 		set->ssl_verify_client_cert = TRUE;
 	}
--- a/src/login-common/login-settings.h	Sat Feb 19 16:56:21 2011 +0200
+++ b/src/login-common/login-settings.h	Tue Feb 22 15:31:31 2011 +0200
@@ -16,8 +16,8 @@
 	const char *ssl_cipher_list;
 	const char *ssl_cert_username_field;
 	bool ssl_verify_client_cert;
-	bool ssl_require_client_cert;
-	bool ssl_username_from_cert;
+	bool auth_ssl_require_client_cert;
+	bool auth_ssl_username_from_cert;
 	bool verbose_ssl;
 
 	bool disable_plaintext_auth;
--- a/src/master/main.c	Sat Feb 19 16:56:21 2011 +0200
+++ b/src/master/main.c	Tue Feb 22 15:31:31 2011 +0200
@@ -48,7 +48,6 @@
 static char *pidfile_path;
 static failure_callback_t *orig_fatal_callback;
 static failure_callback_t *orig_error_callback;
-static const char *child_process_env[3]; /* @UNSAFE */
 
 static const struct setting_parser_info *set_roots[] = {
 	&master_setting_parser_info,
@@ -314,8 +313,7 @@
 	sets = master_service_settings_get_others(master_service);
 	set = sets[0];
 
-	if (services_create(set, child_process_env,
-			    &new_services, &error) < 0) {
+	if (services_create(set, &new_services, &error) < 0) {
 		/* new configuration is invalid, keep the old */
 		i_error("Config reload failed: %s", error);
 		return;
@@ -377,12 +375,39 @@
 	input.roots = set_roots;
 	input.module = "master";
 	input.parse_full_config = TRUE;
+	input.preserve_environment = TRUE;
 	if (master_service_settings_read(master_service, &input, &output,
 					 &error) < 0)
 		i_fatal("Error reading configuration: %s", error);
 	return master_service_settings_get_others(master_service)[0];
 }
 
+static void master_set_import_environment(const struct master_settings *set)
+{
+	const char *const *envs, *key, *value;
+	ARRAY_TYPE(const_string) keys;
+
+	if (*set->import_environment == '\0')
+		return;
+
+	t_array_init(&keys, 8);
+	envs = t_strsplit_spaces(set->import_environment, " ");
+	for (; *envs != NULL; envs++) {
+		value = strchr(*envs, '=');
+		if (value == NULL)
+			key = *envs;
+		else {
+			key = t_strdup_until(*envs, value);
+			env_put(*envs);
+		}
+		array_append(&keys, &key, 1);
+	}
+	(void)array_append_space(&keys);
+
+	value = t_strarray_join(array_idx(&keys, 0), " ");
+	env_put(t_strconcat(DOVECOT_PRESERVE_ENVS_ENV"=", value, NULL));
+}
+
 static void main_log_startup(void)
 {
 #define STARTUP_STRING PACKAGE_NAME" v"DOVECOT_VERSION_FULL" starting up"
@@ -598,18 +623,8 @@
 
 int main(int argc, char *argv[])
 {
-	static const char *preserve_envs[] = {
-		/* AIX depends on TZ to get the timezone correctly. */
-		"TZ",
-#ifdef HAVE_SYSTEMD
-		"LISTEN_PID",
-		"LISTEN_FDS",
-#endif
-		NULL
-	};
 	struct master_settings *set;
-	unsigned int child_process_env_idx = 0;
-	const char *error, *env_tz, *doveconf_arg = NULL;
+	const char *error, *doveconf_arg = NULL;
 	failure_callback_t *orig_info_callback, *orig_debug_callback;
 	bool foreground = FALSE, ask_key_pass = FALSE;
 	bool doubleopts[argc];
@@ -618,8 +633,6 @@
 #ifdef DEBUG
 	if (getenv("GDB") == NULL)
 		fd_debug_verify_leaks(3, 1024);
-	else
-		child_process_env[child_process_env_idx++] = "GDB=1";
 #endif
 	/* drop -- prefix from all --args. ugly, but the only way that it
 	   works with standard getopt() in all OSes.. */
@@ -740,23 +753,16 @@
 	master_settings_do_fixes(set);
 	fatal_log_check(set);
 
-	/* clean up the environment */
-	env_clean_except(preserve_envs);
-
-	env_tz = getenv("TZ");
-	if (env_tz != NULL) {
-		child_process_env[child_process_env_idx++] =
-			t_strconcat("TZ=", env_tz, NULL);
-	}
-	i_assert(child_process_env_idx <
-		 sizeof(child_process_env) / sizeof(child_process_env[0]));
-	child_process_env[child_process_env_idx] = NULL;
+	T_BEGIN {
+		master_set_import_environment(set);
+	} T_END;
+	master_service_env_clean();
 
 	/* create service structures from settings. if there are any errors in
 	   service configuration we'll catch it here. */
 	service_pids_init();
 	service_anvil_global_init();
-	if (services_create(set, child_process_env, &services, &error) < 0)
+	if (services_create(set, &services, &error) < 0)
 		i_fatal("%s", error);
 
 	services->config->config_file_path = get_full_config_path(services);
@@ -765,14 +771,17 @@
 	if (services_listen(services) <= 0)
 		i_fatal("Failed to start listeners");
 
-	if (!foreground)
-		daemonize();
 	if (chdir(set->base_dir) < 0)
 		i_fatal("chdir(%s) failed: %m", set->base_dir);
 
+	if (dup2(null_fd, STDERR_FILENO) < 0)
+		i_fatal("dup2(null_fd) failed: %m");
 	i_set_fatal_handler(master_fatal_callback);
 	i_set_error_handler(orig_error_callback);
 
+	if (!foreground)
+		daemonize();
+
 	main_init(set);
 	master_service_run(master_service, NULL);
 	main_deinit();
--- a/src/master/master-settings.c	Sat Feb 19 16:56:21 2011 +0200
+++ b/src/master/master-settings.c	Tue Feb 22 15:31:31 2011 +0200
@@ -170,6 +170,7 @@
 static const struct setting_define master_setting_defines[] = {
 	DEF(SET_STR, base_dir),
 	DEF(SET_STR, libexec_dir),
+	DEF(SET_STR, import_environment),
 	DEF(SET_STR, protocols),
 	DEF(SET_STR, listen),
 	DEF(SET_ENUM, ssl),
@@ -192,9 +193,23 @@
 	SETTING_DEFINE_LIST_END
 };
 
+/* <settings checks> */
+#ifdef HAVE_SYSTEMD
+#  define ENV_SYSTEMD " LISTEN_PID LISTEN_FDS"
+#else
+#  define ENV_SYSTEMD ""
+#endif
+#ifdef DEBUG
+#  define ENV_GDB " GDB"
+#else
+#  define ENV_GDB ""
+#endif
+/* </settings checks> */
+
 static const struct master_settings master_default_settings = {
 	.base_dir = PKG_RUNDIR,
 	.libexec_dir = PKG_LIBEXECDIR,
+	.import_environment = "TZ" ENV_SYSTEMD ENV_GDB,
 	.protocols = "imap pop3 lmtp",
 	.listen = "*, ::",
 	.ssl = "yes:no:required",
--- a/src/master/master-settings.h	Sat Feb 19 16:56:21 2011 +0200
+++ b/src/master/master-settings.h	Tue Feb 22 15:31:31 2011 +0200
@@ -6,6 +6,7 @@
 struct master_settings {
 	const char *base_dir;
 	const char *libexec_dir;
+	const char *import_environment;
 	const char *protocols;
 	const char *listen;
 	const char *ssl;
--- a/src/master/service-monitor.c	Sat Feb 19 16:56:21 2011 +0200
+++ b/src/master/service-monitor.c	Tue Feb 22 15:31:31 2011 +0200
@@ -195,8 +195,11 @@
 	service_throttle(service, SERVICE_STARTUP_FAILURE_THROTTLE_SECS);
 }
 
-static void service_drop_connections(struct service *service)
+static void service_drop_connections(struct service_listener *l)
 {
+	struct service *service = l->service;
+	int fd;
+
 	if (service->last_drop_warning +
 	    SERVICE_DROP_WARN_INTERVAL_SECS < ioloop_time) {
 		service->last_drop_warning = ioloop_time;
@@ -206,25 +209,35 @@
 			  service->process_limit > 1 ?
 			  "process_limit" : "client_limit");
 	}
-	service->listen_pending = TRUE;
-	service_monitor_listen_stop(service);
 
 	if (service->type == SERVICE_TYPE_LOGIN) {
 		/* reached process limit, notify processes that they
 		   need to start killing existing connections if they
 		   reach connection limit */
 		service_login_notify(service, TRUE);
+
+		service->listen_pending = TRUE;
+		service_monitor_listen_stop(service);
+	} else {
+		/* just accept and close the connection, so it's clear that
+		   this is happening because of the limit, rather than because
+		   the service processes aren't answering fast enough */
+		fd = net_accept(l->fd, NULL, NULL);
+		if (fd > 0)
+			net_disconnect(fd);
 	}
 }
 
-static void service_accept(struct service *service)
+static void service_accept(struct service_listener *l)
 {
+	struct service *service = l->service;
+
 	i_assert(service->process_avail == 0);
 
 	if (service->process_count == service->process_limit) {
 		/* we've reached our limits, new clients will have to
 		   wait until there are more processes available */
-		service_drop_connections(service);
+		service_drop_connections(l);
 		return;
 	}
 
@@ -274,7 +287,7 @@
 		struct service_listener *l = *listeners;
 
 		if (l->io == NULL && l->fd != -1)
-			l->io = io_add(l->fd, IO_READ, service_accept, service);
+			l->io = io_add(l->fd, IO_READ, service_accept, l);
 	}
 }
 
--- a/src/master/service-process.c	Sat Feb 19 16:56:21 2011 +0200
+++ b/src/master/service-process.c	Tue Feb 22 15:31:31 2011 +0200
@@ -180,12 +180,8 @@
 service_process_setup_environment(struct service *service, unsigned int uid)
 {
 	const struct master_service_settings *set = service->list->service_set;
-	const char *const *p;
 
-	/* remove all environment, and put back what we need */
-	env_clean();
-	for (p = service->list->child_process_env; *p != NULL; p++)
-		env_put(*p);
+	master_service_env_clean();
 
 	switch (service->type) {
 	case SERVICE_TYPE_CONFIG:
--- a/src/master/service.c	Sat Feb 19 16:56:21 2011 +0200
+++ b/src/master/service.c	Tue Feb 22 15:31:31 2011 +0200
@@ -427,7 +427,6 @@
 }
 
 int services_create(const struct master_settings *set,
-		    const char *const *child_process_env,
 		    struct service_list **services_r, const char **error_r)
 {
 	struct service_list *service_list;
@@ -445,7 +444,6 @@
 	service_list->service_set = master_service_settings_get(master_service);
 	service_list->set_pool = master_service_settings_detach(master_service);
 	service_list->set = set;
-	service_list->child_process_env = child_process_env;
 	service_list->master_log_fd[0] = -1;
 	service_list->master_log_fd[1] = -1;
 
--- a/src/master/service.h	Sat Feb 19 16:56:21 2011 +0200
+++ b/src/master/service.h	Tue Feb 22 15:31:31 2011 +0200
@@ -114,7 +114,6 @@
 	struct service *config;
 	struct service *log;
 	struct service *anvil;
-	const char *const *child_process_env;
 
 	/* nonblocking log fds usd by master */
 	int master_log_fd[2];
@@ -131,7 +130,6 @@
 
 /* Create all services from settings */
 int services_create(const struct master_settings *set,
-		    const char *const *child_process_env,
 		    struct service_list **services_r, const char **error_r);
 
 /* Destroy services */
--- a/src/plugins/expire/doveadm-expire.c	Sat Feb 19 16:56:21 2011 +0200
+++ b/src/plugins/expire/doveadm-expire.c	Tue Feb 22 15:31:31 2011 +0200
@@ -64,7 +64,8 @@
 
 static bool
 doveadm_expire_mail_want(struct doveadm_mail_cmd_context *ctx,
-			 const char *key, time_t stamp, const char **username_r)
+			 const char *key, time_t oldest_savedate,
+			 const char **username_r)
 {
 	struct doveadm_expire_mail_cmd_context *ectx =
 		DOVEADM_EXPIRE_MAIL_CMD_CONTEXT(ctx);
@@ -76,6 +77,7 @@
 	mailbox = strchr(username, '/');
 	if (mailbox == NULL) {
 		/* invalid record, ignore */
+		i_error("expire: Invalid key: %s", key);
 		return FALSE;
 	}
 	username = t_strdup_until(username, mailbox++);
@@ -85,7 +87,8 @@
 		return FALSE;
 	}
 
-	if (!doveadm_expire_mail_match_mailbox(ectx, mailbox, stamp)) {
+	if (!doveadm_expire_mail_match_mailbox(ectx, mailbox,
+					       oldest_savedate)) {
 		/* this mailbox doesn't have any matching messages */
 		return FALSE;
 	}
@@ -103,19 +106,27 @@
 	struct doveadm_expire_mail_cmd_context *ectx =
 		DOVEADM_EXPIRE_MAIL_CMD_CONTEXT(ctx);
 	const char *key, *value;
-	unsigned long stamp;
+	unsigned long oldest_savedate;
 	bool ret;
 
 	while (dict_iterate(ectx->iter, &key, &value)) {
-		if (str_to_ulong(value, &stamp) < 0) {
+		if (str_to_ulong(value, &oldest_savedate) < 0) {
 			/* invalid record */
+			i_error("expire: Invalid timestamp: %s", value);
 			continue;
 		}
-		if ((time_t)stamp > ectx->oldest_before_time)
+		if ((time_t)oldest_savedate > ectx->oldest_before_time) {
+			if (doveadm_debug) {
+				i_debug("expire: Stopping iteration on key %s "
+					"(%lu > %ld)", key, oldest_savedate,
+					(long)ectx->oldest_before_time);
+			}
 			break;
+		}
 
 		T_BEGIN {
-			ret = doveadm_expire_mail_want(ctx, key, stamp,
+			ret = doveadm_expire_mail_want(ctx, key,
+						       oldest_savedate,
 						       username_r);
 		} T_END;
 		if (ret)
--- a/src/plugins/virtual/virtual-save.c	Sat Feb 19 16:56:21 2011 +0200
+++ b/src/plugins/virtual/virtual-save.c	Tue Feb 22 15:31:31 2011 +0200
@@ -21,18 +21,20 @@
 	struct mailbox_transaction_context *backend_trans;
 	struct virtual_save_context *ctx;
 
-	if (_t->save_ctx != NULL)
-		return _t->save_ctx;
-
-	ctx = i_new(struct virtual_save_context, 1);
-	ctx->ctx.transaction = &t->t;
+	if (_t->save_ctx == NULL) {
+		ctx = i_new(struct virtual_save_context, 1);
+		ctx->ctx.transaction = &t->t;
+		_t->save_ctx = &ctx->ctx;
+	} else {
+		ctx = (struct virtual_save_context *)_t->save_ctx;
+	}
 
 	if (mbox->save_bbox != NULL) {
+		i_assert(ctx->backend_save_ctx == NULL);
 		backend_trans =
 			virtual_transaction_get(_t, mbox->save_bbox->box);
 		ctx->backend_save_ctx = mailbox_save_alloc(backend_trans);
 	}
-	_t->save_ctx = &ctx->ctx;
 	return _t->save_ctx;
 }
 
--- a/src/plugins/zlib/Makefile.am	Sat Feb 19 16:56:21 2011 +0200
+++ b/src/plugins/zlib/Makefile.am	Tue Feb 22 15:31:31 2011 +0200
@@ -2,6 +2,7 @@
 
 AM_CPPFLAGS = \
 	-I$(top_srcdir)/src/lib \
+	-I$(top_srcdir)/src/lib-master \
 	-I$(top_srcdir)/src/lib-mail \
 	-I$(top_srcdir)/src/lib-index \
 	-I$(top_srcdir)/src/lib-storage \
--- a/src/plugins/zlib/doveadm-zlib.c	Sat Feb 19 16:56:21 2011 +0200
+++ b/src/plugins/zlib/doveadm-zlib.c	Tue Feb 22 15:31:31 2011 +0200
@@ -1,9 +1,13 @@
 /* Copyright (c) 2010 Dovecot authors, see the included COPYING file */
 
 #include "lib.h"
+#include "network.h"
 #include "istream.h"
+#include "ostream.h"
 #include "istream-zlib.h"
+#include "ostream-zlib.h"
 #include "module-dir.h"
+#include "master-service.h"
 #include "doveadm-dump.h"
 
 #include <stdio.h>
@@ -12,6 +16,8 @@
 
 const char *doveadm_zlib_plugin_version = DOVECOT_VERSION;
 
+extern struct doveadm_cmd doveadm_cmd_zlibconnect;
+
 void doveadm_zlib_plugin_init(struct module *module);
 void doveadm_zlib_plugin_deinit(void);
 
@@ -77,15 +83,120 @@
 	return match;
 }
 
+struct client {
+	int fd;
+	struct io *io_client, *io_server;
+	struct istream *input;
+	struct ostream *output;
+	bool compressed;
+};
+
+static void client_input(struct client *client)
+{
+	struct istream *input;
+	struct ostream *output;
+	unsigned char buf[1024];
+	ssize_t ret;
+
+	ret = read(STDIN_FILENO, buf, sizeof(buf));
+	if (ret == 0) {
+		if (client->compressed) {
+			master_service_stop(master_service);
+			return;
+		}
+		/* start compression */
+		i_info("<Compression started>");
+		input = i_stream_create_deflate(client->input, TRUE);
+		output = o_stream_create_deflate(client->output, 6);
+		i_stream_unref(&client->input);
+		o_stream_unref(&client->output);
+		client->input = input;
+		client->output = output;
+		client->compressed = TRUE;
+		return;
+	}
+	if (ret < 0)
+		i_fatal("read(stdin) failed: %m");
+
+	o_stream_send(client->output, buf, ret);
+}
+
+static void server_input(struct client *client)
+{
+	const unsigned char *data;
+	size_t size;
+
+	if (i_stream_read(client->input) == -1) {
+		if (client->input->stream_errno != 0) {
+			errno = client->input->stream_errno;
+			i_fatal("read(server) failed: %m");
+		}
+
+		i_info("Server disconnected");
+		master_service_stop(master_service);
+		return;
+	}
+
+	data = i_stream_get_data(client->input, &size);
+	if (write(STDOUT_FILENO, data, size) < 0)
+		i_fatal("write(stdout) failed: %m");
+	i_stream_skip(client->input, size);
+}
+
+static void cmd_zlibconnect(int argc ATTR_UNUSED, char *argv[])
+{
+	struct client client;
+	struct ip_addr *ips;
+	unsigned int ips_count, port;
+	int fd, ret;
+
+	if (argv[1] == NULL || argv[2] == NULL ||
+	    str_to_uint(argv[2], &port) < 0)
+		help(&doveadm_cmd_zlibconnect);
+
+	ret = net_gethostbyname(argv[1], &ips, &ips_count);
+	if (ret != 0) {
+		i_fatal("Host %s lookup failed: %s", argv[1],
+			net_gethosterror(ret));
+	}
+
+	if ((fd = net_connect_ip(&ips[0], port, NULL)) == -1)
+		i_fatal("connect(%s, %u) failed: %m", argv[1], port);
+
+	i_info("Connected to %s port %u. Ctrl-D starts compression",
+	       net_ip2addr(&ips[0]), port);
+
+	memset(&client, 0, sizeof(client));
+	client.fd = fd;
+	client.input = i_stream_create_fd(fd, (size_t)-1, FALSE);
+	client.output = o_stream_create_fd(fd, 0, FALSE);
+	client.io_client = io_add(STDIN_FILENO, IO_READ, client_input, &client);
+	client.io_server = io_add(fd, IO_READ, server_input, &client);
+	master_service_run(master_service, NULL);
+	io_remove(&client.io_client);
+	io_remove(&client.io_server);
+	i_stream_unref(&client.input);
+	o_stream_unref(&client.output);
+	if (close(fd) < 0)
+		i_fatal("close() failed: %m");
+}
+
 struct doveadm_cmd_dump doveadm_cmd_dump_zlib = {
 	"imapzlib",
 	test_dump_imapzlib,
 	cmd_dump_imapzlib
 };
 
+struct doveadm_cmd doveadm_cmd_zlibconnect = {
+	cmd_zlibconnect,
+	"zlibconnect",
+	"<host> [<port>]"
+};
+
 void doveadm_zlib_plugin_init(struct module *module ATTR_UNUSED)
 {
 	doveadm_dump_register(&doveadm_cmd_dump_zlib);
+	doveadm_register_cmd(&doveadm_cmd_zlibconnect);
 }
 
 void doveadm_zlib_plugin_deinit(void)
--- a/src/plugins/zlib/ostream-zlib.c	Sat Feb 19 16:56:21 2011 +0200
+++ b/src/plugins/zlib/ostream-zlib.c	Tue Feb 22 15:31:31 2011 +0200
@@ -129,7 +129,8 @@
 	}
 	zstream->crc = crc32_data_more(zstream->crc, data, size);
 	zstream->bytes32 += size;
-	zstream->flushed = FALSE;
+	zstream->flushed = flush == Z_SYNC_FLUSH &&
+		zs->avail_out == sizeof(zstream->outbuf);
 	return 0;
 }
 
@@ -222,8 +223,12 @@
 			return -1;
 		bytes += iov[i].iov_len;
 	}
+	stream->ostream.offset += bytes;
 
-	stream->ostream.offset += bytes;
+	if (!zstream->ostream.corked) {
+		if (o_stream_zlib_send_flush(zstream) < 0)
+			return -1;
+	}
 	return bytes;
 }