changeset 12860:3984231cd873

Merged changes from v2.0 tree.
author Timo Sirainen <tss@iki.fi>
date Thu, 17 Mar 2011 16:37:22 +0200
parents 6c4a2ca946f3 (current diff) 02829f7f79c7 (diff)
children 2db1d9cbc334
files NEWS TODO configure.in src/auth/auth-penalty.c src/auth/auth-request.c src/auth/db-ldap.c src/auth/passdb-ldap.c src/auth/userdb-ldap.c src/config/old-set-parser.c src/doveadm/doveadm-dump-index.c src/doveadm/doveadm-dump-log.c src/doveadm/doveadm-mail-fetch.c src/doveadm/doveadm-mail-import.c src/doveadm/doveadm-mail-iter.c src/doveadm/doveadm-mail-list-iter.c src/doveadm/doveadm-mail-mailbox-status.c src/doveadm/doveadm-mail-mailbox.c src/doveadm/doveadm-mail-search.c src/doveadm/doveadm-mail.c src/dsync/dsync-worker-local.c src/dsync/dsync.c src/imap/cmd-append.c src/imap/cmd-copy.c src/imap/cmd-create.c src/imap/cmd-delete.c src/imap/cmd-enable.c src/imap/cmd-fetch.c src/imap/cmd-list.c src/imap/cmd-namespace.c src/imap/cmd-rename.c src/imap/cmd-select.c src/imap/cmd-status.c src/imap/cmd-store.c src/imap/cmd-subscribe.c src/imap/imap-client.c src/imap/imap-commands-util.c src/imap/imap-fetch.c src/imap/imap-search.c src/imap/imap-status.c src/imap/imap-sync.c src/lda/main.c src/lib-auth/auth-client-request.c src/lib-charset/charset-iconv.c src/lib-imap/imap-bodystructure.c src/lib-imap/imap-envelope.c src/lib-imap/imap-parser.c src/lib-index/mail-index-transaction.c src/lib-index/mail-index-view.c src/lib-index/mail-index.c src/lib-index/mail-transaction-log-append.c src/lib-index/mail-transaction-log-view.c src/lib-index/mail-transaction-log.c src/lib-lda/duplicate.c src/lib-lda/mail-deliver.c src/lib-master/anvil-client.c src/lib-sql/driver-mysql.c src/lib-sql/driver-pgsql.c src/lib-sql/driver-sqlpool.c src/lib-storage/index/cydir/cydir-storage.c src/lib-storage/index/dbox-common/dbox-file.c src/lib-storage/index/dbox-common/dbox-mail.c src/lib-storage/index/dbox-common/dbox-storage.c src/lib-storage/index/dbox-multi/mdbox-mail.c src/lib-storage/index/dbox-multi/mdbox-map.c src/lib-storage/index/dbox-multi/mdbox-purge.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-multi/mdbox-sync.c src/lib-storage/index/dbox-single/sdbox-file.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/dbox-single/sdbox-sync.c src/lib-storage/index/index-attachment.c src/lib-storage/index/index-mail-headers.c src/lib-storage/index/index-mail.c src/lib-storage/index/index-search.c src/lib-storage/index/index-sort.c src/lib-storage/index/index-status.c src/lib-storage/index/index-storage.c src/lib-storage/index/index-sync.c src/lib-storage/index/index-thread.c src/lib-storage/index/index-transaction.c src/lib-storage/index/maildir/maildir-copy.c src/lib-storage/index/maildir/maildir-filename.c src/lib-storage/index/maildir/maildir-keywords.c src/lib-storage/index/maildir/maildir-mail.c src/lib-storage/index/maildir/maildir-save.c src/lib-storage/index/maildir/maildir-storage.c src/lib-storage/index/maildir/maildir-sync-index.c src/lib-storage/index/maildir/maildir-sync.c src/lib-storage/index/maildir/maildir-uidlist.c src/lib-storage/index/maildir/maildir-util.c src/lib-storage/index/mbox/mbox-file.c src/lib-storage/index/mbox/mbox-lock.c src/lib-storage/index/mbox/mbox-mail.c src/lib-storage/index/mbox/mbox-save.c src/lib-storage/index/mbox/mbox-storage.c src/lib-storage/index/mbox/mbox-sync.c src/lib-storage/index/raw/raw-mail.c src/lib-storage/index/raw/raw-storage.c src/lib-storage/index/shared/shared-list.c src/lib-storage/index/shared/shared-storage.c src/lib-storage/list/index-mailbox-list.c src/lib-storage/list/mailbox-list-fs-iter.c src/lib-storage/list/mailbox-list-fs.c src/lib-storage/list/mailbox-list-maildir-iter.c src/lib-storage/list/mailbox-list-maildir.c src/lib-storage/list/mailbox-list-none.c src/lib-storage/list/mailbox-list-subscriptions.c src/lib-storage/list/subscription-file.c src/lib-storage/mail-copy.c src/lib-storage/mail-namespace.c src/lib-storage/mail-search-register-human.c src/lib-storage/mail-search-register-imap.c src/lib-storage/mail-search.c src/lib-storage/mail-storage-private.h src/lib-storage/mail-storage-settings.c src/lib-storage/mail-storage.c src/lib-storage/mail-storage.h src/lib-storage/mailbox-list.c src/lib-storage/mailbox-tree.c src/lib-storage/mailbox-uidvalidity.c src/lib-storage/test-mailbox.c src/lib/ioloop.c src/lib/lib.c src/lib/printf-format-fix.c src/lib/process-title.c src/lmtp/client.c src/lmtp/commands.c src/login-common/sasl-server.c src/master/main.c src/master/master-settings.c src/master/service-monitor.c src/master/service-process.c src/master/service.c src/plugins/acl/acl-backend-vfile-acllist.c src/plugins/acl/acl-backend-vfile.c src/plugins/acl/acl-mailbox-list.c src/plugins/acl/acl-mailbox.c src/plugins/acl/doveadm-acl.c src/plugins/autocreate/autocreate-plugin.c src/plugins/fts-solr/fts-backend-solr.c src/plugins/fts-squat/fts-backend-squat.c src/plugins/fts/fts-storage.c src/plugins/imap-acl/imap-acl-plugin.c src/plugins/imap-quota/imap-quota-plugin.c src/plugins/lazy-expunge/lazy-expunge-plugin.c src/plugins/listescape/listescape-plugin.c src/plugins/quota/quota-count.c src/plugins/quota/quota-maildir.c src/plugins/quota/quota-storage.c src/plugins/quota/quota.c src/plugins/snarf/snarf-plugin.c src/plugins/trash/trash-plugin.c src/plugins/virtual/virtual-config.c src/plugins/virtual/virtual-mail.c src/plugins/virtual/virtual-save.c src/plugins/virtual/virtual-storage.c src/plugins/virtual/virtual-storage.h src/plugins/virtual/virtual-sync.c src/plugins/virtual/virtual-transaction.c src/plugins/zlib/zlib-plugin.c src/pop3/pop3-client.c src/util/script.c
diffstat 79 files changed, 889 insertions(+), 383 deletions(-) [+]
line wrap: on
line diff
--- a/.hgsigs	Thu Mar 10 18:44:20 2011 +0200
+++ b/.hgsigs	Thu Mar 17 16:37:22 2011 +0200
@@ -23,3 +23,5 @@
 d0d3aca1c9587887a32a890548ec7db76c3bbbe2 0 iEYEABECAAYFAkzYU0oACgkQyUhSUUBVismeNwCaAua5XzOT/BgfeOVrBpscYz4M/jYAnRAc11iVkEeXr32o4YVL37DCPOv/
 51e41fcc78560b1eb5e3e1d026151e2cbe007486 0 iEYEABECAAYFAkz5RxcACgkQyUhSUUBViskcyQCcDetyEXnLHc1nCSFQ5LdlxgoNDE4AoKS8ZtsrUFdeT3/dfnJT1K7b5ski
 440fcf8cb33815e86e5cb9701965000230338ac8 0 iEYEABECAAYFAk0u33kACgkQyUhSUUBVislxBwCeNfszo3Ivr5182ugeAXheEX33qDgAni6UFG9soLT2P5p1wpL2p/Bq4ACG
+755c63ff089f434d18801d1864e5ce98d60f5ca9 0 iEYEABECAAYFAk1xNpAACgkQyUhSUUBVisn4HgCggcSxHEZGjRlCJQubqYquZMrnYckAoIKy+zTG+JMEez2pAtZdKse3uCjQ
+3355b4bbd4acf5eb004cacbaa3bcc873ad818b80 0 iEYEABECAAYFAk10DCoACgkQyUhSUUBVislFAwCfUyZb1gwaGuSweAs1eUwIpIAAWTkAn2M2MDMIjmZduTZgcmbQPlWScNGP
--- a/.hgtags	Thu Mar 10 18:44:20 2011 +0200
+++ b/.hgtags	Thu Mar 17 16:37:22 2011 +0200
@@ -60,3 +60,5 @@
 d0d3aca1c9587887a32a890548ec7db76c3bbbe2 2.0.7
 51e41fcc78560b1eb5e3e1d026151e2cbe007486 2.0.8
 440fcf8cb33815e86e5cb9701965000230338ac8 2.0.9
+755c63ff089f434d18801d1864e5ce98d60f5ca9 2.0.10
+3355b4bbd4acf5eb004cacbaa3bcc873ad818b80 2.0.11
--- a/NEWS	Thu Mar 10 18:44:20 2011 +0200
+++ b/NEWS	Thu Mar 17 16:37:22 2011 +0200
@@ -14,6 +14,26 @@
 	  (e.g. copy multiple messages).
 	- dsync: Saved messages' save-date was set to 1970-01-01.
 
+	+ Added submission_host setting to send mails via SMTP instead of
+	  via sendmail binary.
+	+ Added doveadm acl get/set/delete commands for ACL manipulation,
+	  similar to how IMAP ACL extension works.
+	+ Added doveadm acl debug command to help debug and fix problems
+	  with why shared mailboxes aren't working as expected.
+	- IMAP: Fixed hangs with COMPRESS extension
+	- IMAP: Fixed a hang when trying to COPY to a nonexistent mailbox. 
+	- IMAP: Fixed hang/crash with SEARCHRES + pipelining $.
+	- IMAP: Fixed assert-crash if IDLE+DONE is sent in same TCP packet. 
+	- LMTP: Fixed sending multiple messages in a session.
+	- doveadm: Fixed giving parameters to mail commands. 
+	- doveadm import: Settings weren't correctly used for the
+	  import storage.
+	- dsync: Fixed somewhat random failures with saving messages to
+	  remote dsync.
+	- v2.0.9: Config reload didn't notify running processes with
+	  shutdown_clients=no, so they could have kept serving new clients
+	  with old settings.
+
 v2.0.9 2011-01-13  Timo Sirainen <tss@iki.fi>
 
 	- Linux: Fixed a high system CPU usage / high context switch count
--- a/doc/example-config/conf.d/20-imap.conf	Thu Mar 10 18:44:20 2011 +0200
+++ b/doc/example-config/conf.d/20-imap.conf	Thu Mar 17 16:37:22 2011 +0200
@@ -48,6 +48,11 @@
   #     With mbox storage a mailbox can contain either mails or submailboxes,
   #     but not both. Thunderbird separates these two by forcing server to
   #     accept '/' suffix in mailbox names in subscriptions list.
+  #   tb-lsub-flags:
+  #     Show \Noselect flags for LSUB replies with LAYOUT=fs (e.g. mbox).
+  #     This makes Thunderbird realize they aren't selectable and show them
+  #     greyed out, instead of only later giving "not selectable" popup error.
+  #
   # The list is space-separated.
   #imap_client_workarounds = 
 }
--- a/doc/example-config/dovecot.conf	Thu Mar 10 18:44:20 2011 +0200
+++ b/doc/example-config/dovecot.conf	Thu Mar 17 16:37:22 2011 +0200
@@ -57,6 +57,11 @@
 # UNIX socket or host:port used for connecting to doveadm server
 #doveadm_socket_path = doveadm-server
 
+# Space separated list of environment variables that are preserved on Dovecot
+# startup and passed down to all of its child processes. You can also give
+# key=value pairs to always set specific settings.
+#import_environment = TZ
+
 ##
 ## Dictionary server settings
 ##
--- a/src/auth/auth-penalty.c	Thu Mar 10 18:44:20 2011 +0200
+++ b/src/auth/auth-penalty.c	Thu Mar 17 16:37:22 2011 +0200
@@ -123,7 +123,7 @@
 	const char *ident;
 
 	ident = auth_penalty_get_ident(auth_request);
-	if (penalty->disabled || ident == NULL) {
+	if (penalty->disabled || ident == NULL || auth_request->no_penalty) {
 		callback(0, auth_request);
 		return;
 	}
@@ -155,7 +155,7 @@
 	const char *ident;
 
 	ident = auth_penalty_get_ident(auth_request);
-	if (penalty->disabled || ident == NULL)
+	if (penalty->disabled || ident == NULL || auth_request->no_penalty)
 		return;
 
 	if (value > AUTH_PENALTY_MAX_PENALTY) {
--- a/src/auth/auth-request.c	Thu Mar 10 18:44:20 2011 +0200
+++ b/src/auth/auth-request.c	Thu Mar 17 16:37:22 2011 +0200
@@ -197,6 +197,8 @@
 		auth_stream_reply_add(reply, "skip_password_check", "1");
 	if (request->valid_client_cert)
 		auth_stream_reply_add(reply, "valid-client-cert", "1");
+	if (request->no_penalty)
+		auth_stream_reply_add(reply, "no-penalty", "1");
 	if (request->mech_name != NULL)
 		auth_stream_reply_add(reply, "mech", request->mech_name);
 }
@@ -235,6 +237,8 @@
 		request->no_login = TRUE;
 	else if (strcmp(key, "valid-client-cert") == 0)
 		request->valid_client_cert = TRUE;
+	else if (strcmp(key, "no-penalty") == 0)
+		request->no_penalty = TRUE;
 	else if (strcmp(key, "skip_password_check") == 0) {
 		i_assert(request->master_user !=  NULL);
 		request->skip_password_check = TRUE;
@@ -1308,10 +1312,7 @@
 	if (*values == NULL)
 		return;
 
-	if (strcmp(name, "uid") == 0) {
-		/* there can be only one. use the first one. */
-		auth_request_set_userdb_field(request, name, *values);
-	} else if (strcmp(name, "gid") == 0) {
+	if (strcmp(name, "gid") == 0) {
 		/* convert gids to comma separated list */
 		string_t *value;
 		gid_t gid;
@@ -1332,6 +1333,11 @@
 				      str_c(value));
 	} else {
 		/* add only one */
+		if (values[1] != NULL) {
+			auth_request_log_warning(request, "userdb",
+				"Multiple values found for '%s', "
+				"using value '%s'", name, *values);
+		}
 		auth_request_set_userdb_field(request, name, *values);
 	}
 }
@@ -1672,6 +1678,19 @@
 	va_end(va);
 }
 
+void auth_request_log_warning(struct auth_request *auth_request,
+			      const char *subsystem,
+			      const char *format, ...)
+{
+	va_list va;
+
+	va_start(va, format);
+	T_BEGIN {
+		i_warning("%s", get_log_str(auth_request, subsystem, format, va));
+	} T_END;
+	va_end(va);
+}
+
 void auth_request_log_error(struct auth_request *auth_request,
 			    const char *subsystem,
 			    const char *format, ...)
--- a/src/auth/auth-request.h	Thu Mar 10 18:44:20 2011 +0200
+++ b/src/auth/auth-request.h	Thu Mar 17 16:37:22 2011 +0200
@@ -106,6 +106,7 @@
 	unsigned int proxy:1;
 	unsigned int proxy_maybe:1;
 	unsigned int valid_client_cert:1;
+	unsigned int no_penalty:1;
 	unsigned int cert_username:1;
 	unsigned int userdb_lookup:1;
 	unsigned int userdb_lookup_failed:1;
@@ -192,6 +193,9 @@
 void auth_request_log_info(struct auth_request *auth_request,
 			   const char *subsystem,
 			   const char *format, ...) ATTR_FORMAT(3, 4);
+void auth_request_log_warning(struct auth_request *auth_request,
+			      const char *subsystem,
+			      const char *format, ...);
 void auth_request_log_error(struct auth_request *auth_request,
 			    const char *subsystem,
 			    const char *format, ...) ATTR_FORMAT(3, 4);
--- a/src/auth/db-ldap.c	Thu Mar 10 18:44:20 2011 +0200
+++ b/src/auth/db-ldap.c	Thu Mar 17 16:37:22 2011 +0200
@@ -58,12 +58,11 @@
 	struct var_expand_table *var_table;
 
 	char *attr, **vals;
-	const char *name, *value, *template, *val_1_arr[2];
+	const char *name, *template, *val_1_arr[2];
 	const char *const *static_attrs;
 	BerElement *ber;
 
 	string_t *var, *debug;
-	unsigned int value_idx;
 };
 
 struct db_ldap_sasl_bind_context {
@@ -1111,6 +1110,8 @@
 static void
 db_ldap_result_change_attr(struct db_ldap_result_iterate_context *ctx)
 {
+	i_assert(ctx->vals == NULL);
+
 	ctx->name = hash_table_lookup(ctx->attr_map, ctx->attr);
 	ctx->template = NULL;
 
@@ -1119,10 +1120,8 @@
 			    ctx->name != NULL ? ctx->name : "?unknown?");
 	}
 
-	if (ctx->name == NULL || *ctx->name == '\0') {
-		ctx->value = NULL;
+	if (ctx->name == NULL || *ctx->name == '\0')
 		return;
-	}
 
 	if (strchr(ctx->name, '%') != NULL &&
 	    (ctx->template = strchr(ctx->name, '=')) != NULL) {
@@ -1138,31 +1137,35 @@
 
 	ctx->vals = ldap_get_values(ctx->conn->ld, ctx->entry,
 				    ctx->attr);
-	ctx->value = ctx->vals[0];
-	ctx->value_idx = 0;
 }
 
 static void
 db_ldap_result_return_value(struct db_ldap_result_iterate_context *ctx)
 {
-	bool first = ctx->value_idx == 0;
+	unsigned int i;
 
 	if (ctx->template != NULL) {
-		ctx->var_table[0].value = ctx->value;
+		if (ctx->vals[1] != NULL) {
+			auth_request_log_warning(ctx->auth_request, "ldap",
+				"Multiple values found for '%s', "
+				"using value '%s'", ctx->name, ctx->vals[0]);
+		}
+		ctx->var_table[0].value = ctx->vals[0];
 		str_truncate(ctx->var, 0);
 		var_expand(ctx->var, ctx->template, ctx->var_table);
-		ctx->value = str_c(ctx->var);
+		ctx->val_1_arr[0] = str_c(ctx->var);
 	}
 
-	if (ctx->debug != NULL) {
-		if (!first)
-			str_append_c(ctx->debug, '/');
-		if (ctx->auth_request->set->debug_passwords ||
-		    (strcmp(ctx->name, "password") != 0 &&
-		     strcmp(ctx->name, "password_noscheme") != 0))
-			str_append(ctx->debug, ctx->value);
-		else
-			str_append(ctx->debug, PASSWORD_HIDDEN_STR);
+	if (ctx->debug == NULL) {
+		/* no debugging */
+	} else if (ctx->auth_request->set->debug_passwords ||
+		   (strcmp(ctx->name, "password") != 0 &&
+		    strcmp(ctx->name, "password_noscheme") != 0)) {
+		str_append(ctx->debug, ctx->vals[0]);
+		for (i = 1; ctx->vals[i] != NULL; i++)
+			str_printfa(ctx->debug, ", %s", ctx->vals[i]);
+	} else {
+		str_append(ctx->debug, PASSWORD_HIDDEN_STR);
 	}
 }
 
@@ -1172,17 +1175,11 @@
 
 	while (ctx->attr != NULL) {
 		if (ctx->vals == NULL) {
-			/* a new attribute */
 			db_ldap_result_change_attr(ctx);
-		} else {
-			/* continuing existing attribute */
-			if (ctx->value != NULL)
-				ctx->value = ctx->vals[++ctx->value_idx];
-		}
-
-		if (ctx->value != NULL) {
-			db_ldap_result_return_value(ctx);
-			return TRUE;
+			if (ctx->vals != NULL) {
+				db_ldap_result_return_value(ctx);
+				return TRUE;
+			}
 		}
 
 		ldap_value_free(ctx->vals); ctx->vals = NULL;
@@ -1195,14 +1192,13 @@
 		p = strchr(*ctx->static_attrs, '=');
 		if (p == NULL) {
 			ctx->name = *ctx->static_attrs;
-			ctx->value = "";
+			ctx->val_1_arr[0] = "";
 		} else {
 			ctx->name = t_strdup_until(*ctx->static_attrs, p);
-			ctx->value = p + 1;
+			ctx->val_1_arr[0] = p + 1;
 		}
-		/* make _next_all() return correct values */
+		/* make _next() return correct values */
 		ctx->template = "";
-		ctx->val_1_arr[0] = ctx->value;
 		ctx->static_attrs++;
 		return TRUE;
 	}
@@ -1212,31 +1208,18 @@
 }
 
 bool db_ldap_result_iterate_next(struct db_ldap_result_iterate_context *ctx,
-				 const char **name_r, const char **value_r)
-{
-	if (!db_ldap_result_int_next(ctx))
-		return FALSE;
-
-	*name_r = ctx->name;
-	*value_r = ctx->value;
-	return TRUE;
-}
-
-bool db_ldap_result_iterate_next_all(struct db_ldap_result_iterate_context *ctx,
-				     const char **name_r,
-				     const char *const **values_r)
+				 const char **name_r,
+				 const char *const **values_r)
 {
 	if (!db_ldap_result_int_next(ctx))
 		return FALSE;
 
 	if (ctx->template != NULL) {
 		/* we can use only one value with templates */
-		ctx->val_1_arr[0] = ctx->value;
 		*values_r = ctx->val_1_arr;
 	} else {
 		*values_r = (const char *const *)ctx->vals;
 	}
-	ctx->value = NULL;
 	*name_r = ctx->name;
 	return TRUE;
 }
--- a/src/auth/db-ldap.h	Thu Mar 10 18:44:20 2011 +0200
+++ b/src/auth/db-ldap.h	Thu Mar 17 16:37:22 2011 +0200
@@ -178,9 +178,7 @@
 			    struct auth_request *auth_request,
 			    struct hash_table *attr_map);
 bool db_ldap_result_iterate_next(struct db_ldap_result_iterate_context *ctx,
-				 const char **name_r, const char **value_r);
-bool db_ldap_result_iterate_next_all(struct db_ldap_result_iterate_context *ctx,
-				     const char **name_r,
-				     const char *const **values_r);
+				 const char **name_r,
+				 const char *const **values_r);
 
 #endif
--- a/src/auth/passdb-bsdauth.c	Thu Mar 10 18:44:20 2011 +0200
+++ b/src/auth/passdb-bsdauth.c	Thu Mar 17 16:37:22 2011 +0200
@@ -7,23 +7,28 @@
 
 #include "safe-memset.h"
 #include "auth-cache.h"
+#include "ipwd.h"
 #include "mycrypt.h"
 
 #include <login_cap.h>
 #include <bsd_auth.h>
-#include <pwd.h>
 
 static void
 bsdauth_verify_plain(struct auth_request *request, const char *password,
 		    verify_plain_callback_t *callback)
 {
-	struct passwd *pw;
+	struct passwd pw;
 	int result;
 
 	auth_request_log_debug(request, "bsdauth", "lookup");
 
-	pw = getpwnam(request->user);
-	if (pw == NULL) {
+	switch (i_getpwnam(request->user, &pw)) {
+	case -1:
+		auth_request_log_error(request, "bsdauth",
+				       "getpwnam() failed: %m");
+		callback(PASSDB_RESULT_INTERNAL_FAILURE, request);
+		return;
+	case 0:
 		auth_request_log_info(request, "bsdauth", "unknown user");
 		callback(PASSDB_RESULT_USER_UNKNOWN, request);
 		return;
@@ -34,7 +39,7 @@
 			       t_strdup_noconst(password));
 
 	/* clear the passwords from memory */
-	safe_memset(pw->pw_passwd, 0, strlen(pw->pw_passwd));
+	safe_memset(pw.pw_passwd, 0, strlen(pw.pw_passwd));
 
 	if (result == 0) {
 		auth_request_log_password_mismatch(request, "bsdauth");
@@ -43,7 +48,7 @@
 	}
 
 	/* make sure we're using the username exactly as it's in the database */
-        auth_request_set_field(request, "user", pw->pw_name, NULL);
+        auth_request_set_field(request, "user", pw.pw_name, NULL);
 
 	callback(PASSDB_RESULT_OK, request);
 }
--- a/src/auth/passdb-ldap.c	Thu Mar 10 18:44:20 2011 +0200
+++ b/src/auth/passdb-ldap.c	Thu Mar 17 16:37:22 2011 +0200
@@ -43,12 +43,17 @@
 		       LDAPMessage *entry, struct auth_request *auth_request)
 {
 	struct db_ldap_result_iterate_context *ldap_iter;
-	const char *name, *value;
+	const char *name, *const *values;
 
 	ldap_iter = db_ldap_result_iterate_init(conn, entry, auth_request,
 						conn->pass_attr_map);
-	while (db_ldap_result_iterate_next(ldap_iter, &name, &value)) {
-		auth_request_set_field(auth_request, name, value,
+	while (db_ldap_result_iterate_next(ldap_iter, &name, &values)) {
+		if (values[1] != NULL) {
+			auth_request_log_warning(auth_request, "ldap",
+				"Multiple values found for '%s', "
+				"using value '%s'", name, values[0]);
+		}
+		auth_request_set_field(auth_request, name, values[0],
 				       conn->set.default_pass_scheme);
 	}
 }
--- a/src/auth/passdb-passwd.c	Thu Mar 10 18:44:20 2011 +0200
+++ b/src/auth/passdb-passwd.c	Thu Mar 17 16:37:22 2011 +0200
@@ -6,8 +6,7 @@
 #ifdef PASSDB_PASSWD
 
 #include "safe-memset.h"
-
-#include <pwd.h>
+#include "ipwd.h"
 
 #define PASSWD_CACHE_KEY "%u"
 #define PASSWD_PASS_SCHEME "CRYPT"
@@ -16,35 +15,40 @@
 passwd_verify_plain(struct auth_request *request, const char *password,
 		    verify_plain_callback_t *callback)
 {
-	struct passwd *pw;
+	struct passwd pw;
 	int ret;
 
 	auth_request_log_debug(request, "passwd", "lookup");
 
-	pw = getpwnam(request->user);
-	if (pw == NULL) {
+	switch (i_getpwnam(request->user, &pw)) {
+	case -1:
+		auth_request_log_error(request, "passwd",
+				       "getpwnam() failed: %m");
+		callback(PASSDB_RESULT_INTERNAL_FAILURE, request);
+		return;
+	case 0:
 		auth_request_log_info(request, "passwd", "unknown user");
 		callback(PASSDB_RESULT_USER_UNKNOWN, request);
 		return;
 	}
 
-	if (!IS_VALID_PASSWD(pw->pw_passwd)) {
+	if (!IS_VALID_PASSWD(pw.pw_passwd)) {
 		auth_request_log_info(request, "passwd",
-			"invalid password field '%s'", pw->pw_passwd);
+			"invalid password field '%s'", pw.pw_passwd);
 		callback(PASSDB_RESULT_USER_DISABLED, request);
 		return;
 	}
 
 	/* save the password so cache can use it */
-	auth_request_set_field(request, "password", pw->pw_passwd,
+	auth_request_set_field(request, "password", pw.pw_passwd,
 			       PASSWD_PASS_SCHEME);
 
 	/* check if the password is valid */
-	ret = auth_request_password_verify(request, password, pw->pw_passwd,
+	ret = auth_request_password_verify(request, password, pw.pw_passwd,
 					   PASSWD_PASS_SCHEME, "passwd");
 
 	/* clear the passwords from memory */
-	safe_memset(pw->pw_passwd, 0, strlen(pw->pw_passwd));
+	safe_memset(pw.pw_passwd, 0, strlen(pw.pw_passwd));
 
 	if (ret <= 0) {
 		callback(PASSDB_RESULT_PASSWORD_MISMATCH, request);
@@ -52,7 +56,7 @@
 	}
 
 	/* make sure we're using the username exactly as it's in the database */
-        auth_request_set_field(request, "user", pw->pw_name, NULL);
+        auth_request_set_field(request, "user", pw.pw_name, NULL);
 
 	callback(PASSDB_RESULT_OK, request);
 }
--- a/src/auth/userdb-ldap.c	Thu Mar 10 18:44:20 2011 +0200
+++ b/src/auth/userdb-ldap.c	Thu Mar 17 16:37:22 2011 +0200
@@ -52,7 +52,7 @@
 
 	ldap_iter = db_ldap_result_iterate_init(conn, entry, auth_request,
 						conn->user_attr_map);
-	while (db_ldap_result_iterate_next_all(ldap_iter, &name, &values)) {
+	while (db_ldap_result_iterate_next(ldap_iter, &name, &values)) {
 		auth_request_set_userdb_field_values(auth_request,
 						     name, values);
 	}
@@ -168,7 +168,7 @@
 	ldap_iter = db_ldap_result_iterate_init(conn, res,
 						request->auth_request,
 						conn->iterate_attr_map);
-	while (db_ldap_result_iterate_next_all(ldap_iter, &name, &values)) {
+	while (db_ldap_result_iterate_next(ldap_iter, &name, &values)) {
 		if (strcmp(name, "user") != 0) {
 			i_warning("ldap: iterate: "
 				  "Ignoring field not named 'user': %s", name);
--- a/src/auth/userdb-passwd.c	Thu Mar 10 18:44:20 2011 +0200
+++ b/src/auth/userdb-passwd.c	Thu Mar 17 16:37:22 2011 +0200
@@ -6,10 +6,9 @@
 #ifdef USERDB_PASSWD
 
 #include "ioloop.h"
+#include "ipwd.h"
 #include "userdb-static.h"
 
-#include <pwd.h>
-
 #define USER_CACHE_KEY "%u"
 
 struct passwd_userdb_module {
@@ -32,18 +31,23 @@
 	struct userdb_module *_module = auth_request->userdb->userdb;
 	struct passwd_userdb_module *module =
 		(struct passwd_userdb_module *)_module;
-	struct passwd *pw;
+	struct passwd pw;
 
 	auth_request_log_debug(auth_request, "passwd", "lookup");
 
-	pw = getpwnam(auth_request->user);
-	if (pw == NULL) {
+	switch (i_getpwnam(auth_request->user, &pw)) {
+	case -1:
+		auth_request_log_error(auth_request, "passwd",
+				       "getpwnam() failed: %m");
+		callback(USERDB_RESULT_INTERNAL_FAILURE, auth_request);
+		return;
+	case 0:
 		auth_request_log_info(auth_request, "passwd", "unknown user");
 		callback(USERDB_RESULT_USER_UNKNOWN, auth_request);
 		return;
 	}
 
-	auth_request_set_field(auth_request, "user", pw->pw_name, NULL);
+	auth_request_set_field(auth_request, "user", pw.pw_name, NULL);
 
 	auth_request_init_userdb_reply(auth_request);
 	userdb_static_template_export(module->tmpl, auth_request);
@@ -53,18 +57,18 @@
 	    !userdb_static_template_isset(module->tmpl, "system_user")) {
 		auth_request_set_userdb_field(auth_request,
 					      "system_groups_user",
-					      pw->pw_name);
+					      pw.pw_name);
 	}
 	if (!userdb_static_template_isset(module->tmpl, "uid")) {
 		auth_request_set_userdb_field(auth_request,
-					      "uid", dec2str(pw->pw_uid));
+					      "uid", dec2str(pw.pw_uid));
 	}
 	if (!userdb_static_template_isset(module->tmpl, "gid")) {
 		auth_request_set_userdb_field(auth_request,
-					      "gid", dec2str(pw->pw_gid));
+					      "gid", dec2str(pw.pw_gid));
 	}
 	if (!userdb_static_template_isset(module->tmpl, "home"))
-		auth_request_set_userdb_field(auth_request, "home", pw->pw_dir);
+		auth_request_set_userdb_field(auth_request, "home", pw.pw_dir);
 
 	callback(USERDB_RESULT_OK, auth_request);
 }
--- a/src/auth/userdb.c	Thu Mar 10 18:44:20 2011 +0200
+++ b/src/auth/userdb.c	Thu Mar 17 16:37:22 2011 +0200
@@ -2,12 +2,11 @@
 
 #include "auth-common.h"
 #include "array.h"
+#include "ipwd.h"
 #include "auth-worker-server.h"
 #include "userdb.h"
 
 #include <stdlib.h>
-#include <pwd.h>
-#include <grp.h>
 
 static ARRAY_DEFINE(userdb_interfaces, struct userdb_module_interface *);
 static ARRAY_DEFINE(userdb_modules, struct userdb_module *);
@@ -61,7 +60,7 @@
 
 uid_t userdb_parse_uid(struct auth_request *request, const char *str)
 {
-	struct passwd *pw;
+	struct passwd pw;
 	uid_t uid;
 
 	if (str == NULL)
@@ -70,20 +69,24 @@
 	if (str_to_uid(str, &uid) == 0)
 		return uid;
 
-	pw = getpwnam(str);
-	if (pw == NULL) {
+	switch (i_getpwnam(str, &pw)) {
+	case -1:
+		i_error("getpwnam() failed: %m");
+		return (uid_t)-1;
+	case 0:
 		if (request != NULL) {
 			auth_request_log_error(request, "userdb",
 					       "Invalid UID value '%s'", str);
 		}
 		return (uid_t)-1;
+	default:
+		return pw.pw_uid;
 	}
-	return pw->pw_uid;
 }
 
 gid_t userdb_parse_gid(struct auth_request *request, const char *str)
 {
-	struct group *gr;
+	struct group gr;
 	gid_t gid;
 
 	if (str == NULL)
@@ -92,15 +95,19 @@
 	if (str_to_gid(str, &gid) == 0)
 		return gid;
 
-	gr = getgrnam(str);
-	if (gr == NULL) {
+	switch (i_getgrnam(str, &gr)) {
+	case -1:
+		i_error("getgrnam() failed: %m");
+		return (gid_t)-1;
+	case 0:
 		if (request != NULL) {
 			auth_request_log_error(request, "userdb",
 					       "Invalid GID value '%s'", str);
 		}
 		return (gid_t)-1;
+	default:
+		return gr.gr_gid;
 	}
-	return gr->gr_gid;
 }
 
 static struct userdb_module *
--- a/src/config/old-set-parser.c	Thu Mar 10 18:44:20 2011 +0200
+++ b/src/config/old-set-parser.c	Thu Mar 17 16:37:22 2011 +0200
@@ -357,7 +357,8 @@
 		return TRUE;
 	}
 	if (strcmp(key, "login_process_size") == 0) {
-		config_apply_login_set(ctx, old_section, key, "vsz_limit", value);
+		config_apply_login_set(ctx, old_section, key, "vsz_limit",
+				       t_strconcat(value, " M", NULL));
 		return TRUE;
 	}
 	if (strcmp(key, "login_process_per_connection") == 0) {
@@ -378,7 +379,8 @@
 		return TRUE;
 	}
 	if (strcmp(key, "login_process_size") == 0) {
-		config_apply_login_set(ctx, old_section, key, "vsz_limit", value);
+		config_apply_login_set(ctx, old_section, key, "vsz_limit",
+				       t_strconcat(value, " M", NULL));
 		return TRUE;
 	}
 
@@ -391,7 +393,8 @@
 		return TRUE;
 	}
 	if (strcmp(key, "mail_process_size") == 0) {
-		config_apply_mail_set(ctx, old_section, key, "vsz_limit", value);
+		config_apply_mail_set(ctx, old_section, key, "vsz_limit",
+				      t_strconcat(value, " M", NULL));
 		return TRUE;
 	}
 	if (strcmp(key, "mail_drop_priv_before_exec") == 0) {
@@ -409,7 +412,8 @@
 		return TRUE;
 	}
 	if (strcmp(key, "auth_process_size") == 0) {
-		config_apply_auth_set(ctx, key, "vsz_limit", value);
+		config_apply_auth_set(ctx, key, "vsz_limit",
+				      t_strconcat(value, " M", NULL));
 		return TRUE;
 	}
 	if (strcmp(key, "auth_user") == 0) {
--- a/src/doveadm/doveadm-mail-import.c	Thu Mar 10 18:44:20 2011 +0200
+++ b/src/doveadm/doveadm-mail-import.c	Thu Mar 17 16:37:22 2011 +0200
@@ -187,13 +187,13 @@
 
 static struct doveadm_mail_cmd_context *cmd_import_alloc(void)
 {
-	struct doveadm_mail_cmd_context *ctx;
+	struct import_cmd_context *ctx;
 
-	ctx = doveadm_mail_cmd_alloc(struct doveadm_mail_cmd_context);
-	ctx->v.init = cmd_import_init;
-	ctx->v.deinit = cmd_import_deinit;
-	ctx->v.run = cmd_import_run;
-	return ctx;
+	ctx = doveadm_mail_cmd_alloc(struct import_cmd_context);
+	ctx->ctx.v.init = cmd_import_init;
+	ctx->ctx.v.deinit = cmd_import_deinit;
+	ctx->ctx.v.run = cmd_import_run;
+	return &ctx->ctx;
 }
 
 struct doveadm_mail_cmd cmd_import = {
--- a/src/doveadm/doveadm-mail.c	Thu Mar 10 18:44:20 2011 +0200
+++ b/src/doveadm/doveadm-mail.c	Thu Mar 17 16:37:22 2011 +0200
@@ -248,7 +248,6 @@
 		i_fatal("%s", error);
 	else if (ret == 0)
 		i_fatal("User doesn't exist");
-	mail_storage_service_deinit(&ctx->storage_service);
 }
 
 static void sig_die(const siginfo_t *si, void *context ATTR_UNUSED)
@@ -319,7 +318,6 @@
 	i_set_failure_prefix("doveadm: ");
 	if (ret < 0)
 		i_error("Failed to iterate through some users");
-	mail_storage_service_deinit(&ctx->storage_service);
 }
 
 static void
@@ -420,12 +418,18 @@
 		service_flags |= MAIL_STORAGE_SERVICE_FLAG_TEMP_PRIV_DROP;
 		doveadm_mail_all_users(ctx, argv, wildcard_user, service_flags);
 	}
+	if (ctx->search_args != NULL)
+		mail_search_args_unref(&ctx->search_args);
 	doveadm_mail_server_flush();
 	ctx->v.deinit(ctx);
 	doveadm_print_flush();
 
+	/* service deinit unloads mail plugins, so do it late */
+	mail_storage_service_deinit(&ctx->storage_service);
+
 	if (ctx->failed)
 		exit(FATAL_DEFAULT);
+	pool_unref(&ctx->pool);
 }
 
 static bool
--- a/src/dsync/dsync-brain.c	Thu Mar 10 18:44:20 2011 +0200
+++ b/src/dsync/dsync-brain.c	Thu Mar 17 16:37:22 2011 +0200
@@ -290,6 +290,7 @@
 	case DSYNC_BRAIN_MAILBOX_ACTION_CREATE:
 		new_box = *action_box;
 		new_box.uid_next = action_box->uid_validity == 0 ? 0 : 1;
+		new_box.first_recent_uid = 0;
 		new_box.highest_modseq = 0;
 		dsync_worker_create_mailbox(action_worker, &new_box);
 		break;
@@ -667,8 +668,9 @@
 				brain_box->box = *src_boxes[src];
 				brain_box->src = src_boxes[src];
 				if (brain->verbose) {
-					i_info("%s: only in source",
-					       brain_box->box.name);
+					i_info("%s: only in source (guid=%s)",
+					       brain_box->box.name,
+					       dsync_guid_to_str(&brain_box->box.mailbox_guid));
 				}
 			}
  			src++;
@@ -679,8 +681,9 @@
 				brain_box->box = *dest_boxes[dest];
 				brain_box->dest = dest_boxes[dest];
 				if (brain->verbose) {
-					i_info("%s: only in dest",
-					       brain_box->box.name);
+					i_info("%s: only in dest (guid=%s)",
+					       brain_box->box.name,
+					       dsync_guid_to_str(&brain_box->box.mailbox_guid));
 				}
 			}
 			dest++;
@@ -694,8 +697,11 @@
 		brain_box = array_append_space(brain_boxes);
 		brain_box->box = *src_boxes[src];
 		brain_box->src = src_boxes[src];
-		if (brain->verbose)
-			i_info("%s: only in source", brain_box->box.name);
+		if (brain->verbose) {
+			i_info("%s: only in source (guid=%s)",
+			       brain_box->box.name,
+			       dsync_guid_to_str(&brain_box->box.mailbox_guid));
+		}
 	}
 	for (; dest < dest_count; dest++) {
 		if ((dest_boxes[dest]->flags &
@@ -705,8 +711,11 @@
 		brain_box = array_append_space(brain_boxes);
 		brain_box->box = *dest_boxes[dest];
 		brain_box->dest = dest_boxes[dest];
-		if (brain->verbose)
-			i_info("%s: only in dest", brain_box->box.name);
+		if (brain->verbose) {
+			i_info("%s: only in dest (guid=%s)",
+			       brain_box->box.name,
+			       dsync_guid_to_str(&brain_box->box.mailbox_guid));
+		}
 	}
 }
 
--- a/src/dsync/dsync-data.h	Thu Mar 10 18:44:20 2011 +0200
+++ b/src/dsync/dsync-data.h	Thu Mar 17 16:37:22 2011 +0200
@@ -22,7 +22,7 @@
 	/* Mailbox's GUID. Full of zero with \Noselect mailboxes. */
 	mailbox_guid_t mailbox_guid;
 
-	uint32_t uid_validity, uid_next, message_count;
+	uint32_t uid_validity, uid_next, message_count, first_recent_uid;
 	uint64_t highest_modseq;
 	/* if mailbox is deleted, this is the deletion timestamp.
 	   otherwise it's the last rename timestamp. */
--- a/src/dsync/dsync-proxy-client.c	Thu Mar 10 18:44:20 2011 +0200
+++ b/src/dsync/dsync-proxy-client.c	Thu Mar 17 16:37:22 2011 +0200
@@ -364,8 +364,10 @@
 		/* proxy_client_worker_msg_save() hasn't finished yet. */
 		o_stream_cork(worker->output);
 		proxy_client_send_stream(worker);
-		if (worker->save_input != NULL)
-			return 1;
+		if (worker->save_input != NULL) {
+			/* still unfinished, make sure we get called again */
+			return 0;
+		}
 	}
 
 	if (worker->worker.output_callback != NULL)
@@ -385,7 +387,21 @@
 static void
 proxy_client_worker_timeout(struct proxy_client_dsync_worker *worker)
 {
-	i_error("proxy client timed out");
+	const char *reason;
+
+	if (worker->save_io != NULL)
+		reason = " (waiting for more input from mail being saved)";
+	else if (worker->save_input != NULL) {
+		size_t bytes = o_stream_get_buffer_used_size(worker->output);
+
+		reason = t_strdup_printf(" (waiting for output stream to flush, "
+					 "%"PRIuSIZE_T" bytes left)", bytes);
+	} else if (worker->msg_get_data.input != NULL) {
+		reason = " (waiting for MSG-GET message from remote)";
+	} else {
+		reason = "";
+	}
+	i_error("proxy client timed out%s", reason);
 	proxy_client_fail(worker);
 }
 
@@ -397,7 +413,7 @@
 	worker->worker.v = proxy_client_dsync_worker;
 	worker->fd_in = fd_in;
 	worker->fd_out = fd_out;
-	worker->to = timeout_add(DSYNC_PROXY_TIMEOUT_MSECS,
+	worker->to = timeout_add(DSYNC_PROXY_CLIENT_TIMEOUT_MSECS,
 				 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);
@@ -455,7 +471,7 @@
 	struct proxy_client_dsync_worker *worker =
 		(struct proxy_client_dsync_worker *)_worker;
 
-	if (worker->save_io != NULL) {
+	if (worker->save_input != NULL) {
 		/* we haven't finished sending a message save, so we're full. */
 		return TRUE;
 	}
--- a/src/dsync/dsync-proxy-server.c	Thu Mar 10 18:44:20 2011 +0200
+++ b/src/dsync/dsync-proxy-server.c	Thu Mar 17 16:37:22 2011 +0200
@@ -108,7 +108,6 @@
 		return;
 	}
 
-	timeout_reset(server->to);
 	o_stream_cork(server->output);
 	while (proxy_server_read_line(server, &line) > 0) {
 		T_BEGIN {
@@ -123,6 +122,7 @@
 
 	if (ret < 0)
 		master_service_stop(master_service);
+	timeout_reset(server->to);
 }
 
 static int proxy_server_output(struct dsync_proxy_server *server)
@@ -130,7 +130,6 @@
 	struct ostream *output = server->output;
 	int ret;
 
-	timeout_reset(server->to);
 	if ((ret = o_stream_flush(output)) < 0)
 		ret = 1;
 	else if (server->cur_cmd != NULL) {
@@ -149,6 +148,7 @@
 	}
 	if (output->closed)
 		master_service_stop(master_service);
+	timeout_reset(server->to);
 	return ret;
 }
 
@@ -172,7 +172,7 @@
 	server->io = io_add(fd_in, IO_READ, proxy_server_input, server);
 	server->input = i_stream_create_fd(fd_in, (size_t)-1, FALSE);
 	server->output = o_stream_create_fd(fd_out, (size_t)-1, FALSE);
-	server->to = timeout_add(DSYNC_PROXY_TIMEOUT_MSECS,
+	server->to = timeout_add(DSYNC_PROXY_SERVER_TIMEOUT_MSECS,
 				 dsync_proxy_server_timeout, NULL);
 	o_stream_set_flush_callback(server->output, proxy_server_output,
 				    server);
--- a/src/dsync/dsync-proxy.c	Thu Mar 10 18:44:20 2011 +0200
+++ b/src/dsync/dsync-proxy.c	Thu Mar 17 16:37:22 2011 +0200
@@ -174,9 +174,10 @@
 
 	str_append_c(str, '\t');
 	dsync_proxy_mailbox_guid_export(str, &box->mailbox_guid);
-	str_printfa(str, "\t%u\t%u\t%u\t%llu",
+	str_printfa(str, "\t%u\t%u\t%u\t%llu\t%u",
 		    box->uid_validity, box->uid_next, box->message_count,
-		    (unsigned long long)box->highest_modseq);
+		    (unsigned long long)box->highest_modseq,
+		    box->first_recent_uid);
 	dsync_proxy_strings_export(str, &box->cache_fields);
 }
 
@@ -261,6 +262,12 @@
 		return -1;
 	}
 
+	box_r->first_recent_uid = strtoul(args[i++], &p, 10);
+	if (*p != '\0') {
+		*error_r = "Invalid mailbox first_recent_uid";
+		return -1;
+	}
+
 	args += i;
 	count -= i;
 	p_array_init(&box_r->cache_fields, pool, count + 1);
--- a/src/dsync/dsync-proxy.h	Thu Mar 10 18:44:20 2011 +0200
+++ b/src/dsync/dsync-proxy.h	Thu Mar 17 16:37:22 2011 +0200
@@ -3,7 +3,9 @@
 
 #include "dsync-data.h"
 
-#define DSYNC_PROXY_TIMEOUT_MSECS (15*60*1000)
+#define DSYNC_PROXY_CLIENT_TIMEOUT_MSECS (14*60*1000)
+#define DSYNC_PROXY_SERVER_TIMEOUT_MSECS (15*60*1000)
+
 #define DSYNC_PROXY_CLIENT_GREETING_LINE "dsync-client\t1"
 #define DSYNC_PROXY_SERVER_GREETING_LINE "dsync-server\t1"
 
--- a/src/dsync/dsync-worker-local.c	Thu Mar 10 18:44:20 2011 +0200
+++ b/src/dsync/dsync-worker-local.c	Thu Mar 17 16:37:22 2011 +0200
@@ -514,7 +514,7 @@
 		MAILBOX_FLAG_READONLY | MAILBOX_FLAG_KEEP_RECENT;
 	const enum mailbox_status_items status_items =
 		STATUS_UIDNEXT | STATUS_UIDVALIDITY |
-		STATUS_HIGHESTMODSEQ;
+		STATUS_HIGHESTMODSEQ | STATUS_FIRST_RECENT_UID;
 	const enum mailbox_metadata_items metadata_items =
 		MAILBOX_METADATA_CACHE_FIELDS | MAILBOX_METADATA_GUID;
 	const struct mailbox_info *info;
@@ -579,6 +579,7 @@
 	dsync_box_r->uid_validity = status.uidvalidity;
 	dsync_box_r->uid_next = status.uidnext;
 	dsync_box_r->message_count = status.messages;
+	dsync_box_r->first_recent_uid = status.first_recent_uid;
 	dsync_box_r->highest_modseq = status.highest_modseq;
 
 	p_clear(iter->ret_pool);
@@ -994,6 +995,7 @@
 	       sizeof(update_r->mailbox_guid));
 	update_r->uid_validity = dsync_box->uid_validity;
 	update_r->min_next_uid = dsync_box->uid_next;
+	update_r->min_first_recent_uid = dsync_box->first_recent_uid;
 	update_r->min_highest_modseq = dsync_box->highest_modseq;
 }
 
--- a/src/dsync/test-dsync-brain.c	Thu Mar 10 18:44:20 2011 +0200
+++ b/src/dsync/test-dsync-brain.c	Thu Mar 17 16:37:22 2011 +0200
@@ -111,28 +111,28 @@
 static void test_dsync_brain(void)
 {
 	static struct dsync_mailbox src_boxes[] = {
-		{ "box1", '/', { { 0, } }, { { 0, } }, 1234567890, 5432, 0, 123123123123ULL, 3636, 0, ARRAY_INIT },
-		{ "box2", '/', { { 0, } }, { { 0, } }, 1234567890, 5432, 0, 123123123123ULL, 3636, 0, ARRAY_INIT },
-		{ "box3", '/', { { 0, } }, { { 0, } }, 1234567890, 5432, 0, 123123123123ULL, 3636, 0, ARRAY_INIT },
-		{ "box4", '/', { { 0, } }, { { 0, } }, 1234567890, 5432, 0, 123123123123ULL, 3636, 0, ARRAY_INIT },
-		{ "box5", '/', { { 0, } }, { { 0, } }, 1234567890, 5433, 0, 123123123123ULL, 3636, 0, ARRAY_INIT },
-		{ "box6", '/', { { 0, } }, { { 0, } }, 1234567890, 5432, 0, 123123123124ULL, 3636, 0, ARRAY_INIT },
-		{ "boxx", '/', { { 0, } }, { { 0, } }, 1234567890, 5432, 0, 123123123123ULL, 3636, 0, ARRAY_INIT },
-		{ "boxd1", '/', { { 0, } }, { { 0, } }, 1234567890, 5432, 0, 123123123123ULL, 3636, 0, ARRAY_INIT },
-		{ "boxd2", '/', { { 0, } }, { { 0, } }, 1234567890, 5432, 0, 123123123123ULL, 3636, DSYNC_MAILBOX_FLAG_DELETED_MAILBOX, ARRAY_INIT },
-		{ NULL, 0, { { 0, } }, { { 0, } }, 0, 0, 0, 0, 0, 0, ARRAY_INIT }
+		{ "box1", '/', { { 0, } }, { { 0, } }, 1234567890, 5432, 0, 1, 123123123123ULL, 3636, 0, ARRAY_INIT },
+		{ "box2", '/', { { 0, } }, { { 0, } }, 1234567890, 5432, 0, 1, 123123123123ULL, 3636, 0, ARRAY_INIT },
+		{ "box3", '/', { { 0, } }, { { 0, } }, 1234567890, 5432, 0, 1, 123123123123ULL, 3636, 0, ARRAY_INIT },
+		{ "box4", '/', { { 0, } }, { { 0, } }, 1234567890, 5432, 0, 1, 123123123123ULL, 3636, 0, ARRAY_INIT },
+		{ "box5", '/', { { 0, } }, { { 0, } }, 1234567890, 5433, 0, 1, 123123123123ULL, 3636, 0, ARRAY_INIT },
+		{ "box6", '/', { { 0, } }, { { 0, } }, 1234567890, 5432, 0, 1, 123123123124ULL, 3636, 0, ARRAY_INIT },
+		{ "boxx", '/', { { 0, } }, { { 0, } }, 1234567890, 5432, 0, 1, 123123123123ULL, 3636, 0, ARRAY_INIT },
+		{ "boxd1", '/', { { 0, } }, { { 0, } }, 1234567890, 5432, 0, 1, 123123123123ULL, 3636, 0, ARRAY_INIT },
+		{ "boxd2", '/', { { 0, } }, { { 0, } }, 1234567890, 5432, 0, 1, 123123123123ULL, 3636, DSYNC_MAILBOX_FLAG_DELETED_MAILBOX, ARRAY_INIT },
+		{ NULL, 0, { { 0, } }, { { 0, } }, 0, 0, 0, 0, 0, 0, 0, ARRAY_INIT }
 	};
 	static struct dsync_mailbox dest_boxes[] = {
-		{ "box1", '/', { { 0, } }, { { 0, } }, 1234567890, 5432, 0, 123123123123ULL, 3636, 0, ARRAY_INIT },
-		{ "box2", '/', { { 0, } }, { { 0, } }, 1234567891, 5432, 0, 123123123123ULL, 3636, 0, ARRAY_INIT },
-		{ "box3", '/', { { 0, } }, { { 0, } }, 1234567890, 5433, 0, 123123123123ULL, 3636, 0, ARRAY_INIT },
-		{ "box4", '/', { { 0, } }, { { 0, } }, 1234567890, 5432, 0, 123123123124ULL, 3636, 0, ARRAY_INIT },
-		{ "box5", '/', { { 0, } }, { { 0, } }, 1234567890, 5432, 0, 123123123123ULL, 3636, 0, ARRAY_INIT },
-		{ "box6", '/', { { 0, } }, { { 0, } }, 1234567890, 5432, 0, 123123123123ULL, 3636, 0, ARRAY_INIT },
-		{ "boxy", '/', { { 0, } }, { { 0, } }, 1234567890, 5432, 0, 123123123123ULL, 3636, 0, ARRAY_INIT },
-		{ "boxd1", '/', { { 0, } }, { { 0, } }, 1234567890, 5432, 0, 123123123123ULL, 3636, DSYNC_MAILBOX_FLAG_DELETED_MAILBOX, ARRAY_INIT },
-		{ "boxd2", '/', { { 0, } }, { { 0, } }, 1234567890, 5432, 0, 123123123123ULL, 3636, 0, ARRAY_INIT },
-		{ NULL, 0, { { 0, } }, { { 0, } }, 0, 0, 0, 0, 0, 0, ARRAY_INIT }
+		{ "box1", '/', { { 0, } }, { { 0, } }, 1234567890, 5432, 0, 1, 123123123123ULL, 3636, 0, ARRAY_INIT },
+		{ "box2", '/', { { 0, } }, { { 0, } }, 1234567891, 5432, 0, 1, 123123123123ULL, 3636, 0, ARRAY_INIT },
+		{ "box3", '/', { { 0, } }, { { 0, } }, 1234567890, 5433, 0, 1, 123123123123ULL, 3636, 0, ARRAY_INIT },
+		{ "box4", '/', { { 0, } }, { { 0, } }, 1234567890, 5432, 0, 1, 123123123124ULL, 3636, 0, ARRAY_INIT },
+		{ "box5", '/', { { 0, } }, { { 0, } }, 1234567890, 5432, 0, 1, 123123123123ULL, 3636, 0, ARRAY_INIT },
+		{ "box6", '/', { { 0, } }, { { 0, } }, 1234567890, 5432, 0, 1, 123123123123ULL, 3636, 0, ARRAY_INIT },
+		{ "boxy", '/', { { 0, } }, { { 0, } }, 1234567890, 5432, 0, 1, 123123123123ULL, 3636, 0, ARRAY_INIT },
+		{ "boxd1", '/', { { 0, } }, { { 0, } }, 1234567890, 5432, 0, 1, 123123123123ULL, 3636, DSYNC_MAILBOX_FLAG_DELETED_MAILBOX, ARRAY_INIT },
+		{ "boxd2", '/', { { 0, } }, { { 0, } }, 1234567890, 5432, 0, 1, 123123123123ULL, 3636, 0, ARRAY_INIT },
+		{ NULL, 0, { { 0, } }, { { 0, } }, 0, 0, 0, 0, 0, 0, 0, ARRAY_INIT }
 	};
 	struct dsync_brain *brain;
 	struct dsync_worker *src_worker, *dest_worker;
@@ -225,8 +225,8 @@
 static void test_dsync_brain_full(void)
 {
 	static struct dsync_mailbox boxes[] = {
-		{ "box1", '/', { { 0, } }, { { 0, } }, 1234567890, 5432, 0, 123123123123ULL, 2352, 0, ARRAY_INIT },
-		{ NULL, 0, { { 0, } }, { { 0, } }, 0, 0, 0, 0, 0, 0, ARRAY_INIT }
+		{ "box1", '/', { { 0, } }, { { 0, } }, 1234567890, 5432, 0, 1, 123123123123ULL, 2352, 0, ARRAY_INIT },
+		{ NULL, 0, { { 0, } }, { { 0, } }, 0, 0, 0, 0, 0, 0, 0, ARRAY_INIT }
 	};
 	struct dsync_brain *brain;
 	struct dsync_worker *src_worker, *dest_worker;
--- a/src/dsync/test-dsync-proxy-server-cmd.c	Thu Mar 10 18:44:20 2011 +0200
+++ b/src/dsync/test-dsync-proxy-server-cmd.c	Thu Mar 17 16:37:22 2011 +0200
@@ -92,6 +92,7 @@
 	box.uid_next = 4023233417;
 	box.message_count = 4525;
 	box.highest_modseq = 18080787909545915012ULL;
+	box.first_recent_uid = 353;
 	test_worker->box_iter.next_box = &box;
 
 	test_assert(run_more() == 0);
@@ -101,7 +102,8 @@
 			   "4275878552\t"
 			   "4023233417\t"
 			   "4525\t"
-			   "18080787909545915012\n") == 0);
+			   "18080787909545915012\t"
+			   "353\n") == 0);
 	out_clear();
 
 	/* last mailbox */
@@ -231,7 +233,7 @@
 
 	test_assert(run_cmd("BOX-CREATE", "selectable", "?",
 			    "61", "2", TEST_MAILBOX_GUID2, "1234567890", "9876",
-			    "4610", "28427847284728", NULL) == 1);
+			    "4610", "28427847284728", "853", NULL) == 1);
 	test_assert(test_dsync_worker_next_box_event(test_worker, &event));
 	test_assert(event.type == LAST_BOX_TYPE_CREATE);
 	test_assert(strcmp(event.box.name, "selectable") == 0);
@@ -242,6 +244,7 @@
 	test_assert(event.box.uid_next == 9876);
 	test_assert(event.box.message_count == 4610);
 	test_assert(event.box.highest_modseq == 28427847284728);
+	test_assert(event.box.first_recent_uid == 853);
 	test_assert(event.box.last_change == 61);
 
 	test_end();
@@ -299,7 +302,7 @@
 
 	test_assert(run_cmd("BOX-UPDATE", "updated", "/",
 			    "53", "2", TEST_MAILBOX_GUID1, "34343", "22",
-			    "58293", "2238427847284728", NULL) == 1);
+			    "58293", "2238427847284728", "2482", NULL) == 1);
 	test_assert(test_dsync_worker_next_box_event(test_worker, &event));
 	test_assert(event.type == LAST_BOX_TYPE_UPDATE);
 	test_assert(strcmp(event.box.name, "updated") == 0);
@@ -310,6 +313,7 @@
 	test_assert(event.box.uid_next == 22);
 	test_assert(event.box.message_count == 58293);
 	test_assert(event.box.highest_modseq == 2238427847284728);
+	test_assert(event.box.first_recent_uid == 2482);
 	test_assert(event.box.last_change == 53);
 
 	test_end();
--- a/src/imap-login/client.c	Thu Mar 10 18:44:20 2011 +0200
+++ b/src/imap-login/client.c	Thu Mar 17 16:37:22 2011 +0200
@@ -94,7 +94,8 @@
 		imap_client->client_ignores_capability_resp_code = TRUE;
 	client_send_raw(client, t_strconcat(
 		"* CAPABILITY ", get_capability(client), "\r\n", NULL));
-	client_send_line(client, CLIENT_CMD_REPLY_OK, "Capability completed.");
+	client_send_line(client, CLIENT_CMD_REPLY_OK,
+			 "Pre-login capabilities listed, post-login capabilities have more.");
 	return 1;
 }
 
--- a/src/imap/cmd-list.c	Thu Mar 10 18:44:20 2011 +0200
+++ b/src/imap/cmd-list.c	Thu Mar 17 16:37:22 2011 +0200
@@ -946,10 +946,13 @@
 	}
 
 	if (lsub) {
-		/* LSUB - we don't care about flags */
+		/* LSUB - we don't care about flags except if
+		   tb-lsub-flags workaround is explicitly set */
 		ctx->list_flags |= MAILBOX_LIST_ITER_SELECT_SUBSCRIBED |
-			MAILBOX_LIST_ITER_SELECT_RECURSIVEMATCH |
-			MAILBOX_LIST_ITER_RETURN_NO_FLAGS;
+			MAILBOX_LIST_ITER_SELECT_RECURSIVEMATCH;
+		if ((cmd->client->set->parsed_workarounds &
+		     WORKAROUND_TB_LSUB_FLAGS) == 0)
+			ctx->list_flags |= MAILBOX_LIST_ITER_RETURN_NO_FLAGS;
 	} else if (!ctx->used_listext) {
 		/* non-extended LIST - return children flags always */
 		ctx->list_flags |= MAILBOX_LIST_ITER_RETURN_CHILDREN;
--- a/src/imap/imap-settings.c	Thu Mar 10 18:44:20 2011 +0200
+++ b/src/imap/imap-settings.c	Thu Mar 17 16:37:22 2011 +0200
@@ -116,6 +116,7 @@
 static const struct imap_client_workaround_list imap_client_workaround_list[] = {
 	{ "delay-newmail", WORKAROUND_DELAY_NEWMAIL },
 	{ "tb-extra-mailbox-sep", WORKAROUND_TB_EXTRA_MAILBOX_SEP },
+	{ "tb-lsub-flags", WORKAROUND_TB_LSUB_FLAGS },
 	{ NULL, 0 }
 };
 
--- a/src/imap/imap-settings.h	Thu Mar 10 18:44:20 2011 +0200
+++ b/src/imap/imap-settings.h	Thu Mar 17 16:37:22 2011 +0200
@@ -6,7 +6,8 @@
 /* <settings checks> */
 enum imap_client_workarounds {
 	WORKAROUND_DELAY_NEWMAIL		= 0x01,
-	WORKAROUND_TB_EXTRA_MAILBOX_SEP		= 0x08
+	WORKAROUND_TB_EXTRA_MAILBOX_SEP		= 0x08,
+	WORKAROUND_TB_LSUB_FLAGS		= 0x10
 };
 /* </settings checks> */
 
--- a/src/lda/main.c	Thu Mar 10 18:44:20 2011 +0200
+++ b/src/lda/main.c	Thu Mar 17 16:37:22 2011 +0200
@@ -10,6 +10,7 @@
 #include "abspath.h"
 #include "safe-mkstemp.h"
 #include "eacces-error.h"
+#include "ipwd.h"
 #include "mkdir-parents.h"
 #include "str.h"
 #include "str-sanitize.h"
@@ -30,7 +31,6 @@
 
 #include <stdio.h>
 #include <stdlib.h>
-#include <pwd.h>
 #include <sysexits.h>
 
 #define DEFAULT_ENVELOPE_SENDER "MAILER-DAEMON"
@@ -314,18 +314,21 @@
 		;
 	else if (process_euid != 0) {
 		/* we're non-root. get our username and possibly our home. */
-		struct passwd *pw;
+		struct passwd pw;
 		const char *home;
 
 		home = getenv("HOME");
 		if (user != NULL && home != NULL) {
 			/* no need for a pw lookup */
 			user_source = "USER environment";
-		} else if ((pw = getpwuid(process_euid)) != NULL) {
-			user = t_strdup(pw->pw_name);
+		} else if ((ret = i_getpwuid(process_euid, &pw)) > 0) {
+			user = t_strdup(pw.pw_name);
 			if (home == NULL)
-				env_put(t_strconcat("HOME=", pw->pw_dir, NULL));
+				env_put(t_strconcat("HOME=", pw.pw_dir, NULL));
 			user_source = "passwd lookup for process euid";
+		} else if (ret < 0) {
+			/* temporary failure */
+			i_fatal("getpwuid() failed: %m");
 		} else if (user == NULL) {
 			i_fatal_status(EX_USAGE,
 				       "Couldn't lookup our username (uid=%s)",
--- a/src/lib-auth/auth-client-request.c	Thu Mar 10 18:44:20 2011 +0200
+++ b/src/lib-auth/auth-client-request.c	Thu Mar 17 16:37:22 2011 +0200
@@ -36,6 +36,8 @@
 
 	if ((info->flags & AUTH_REQUEST_FLAG_SECURED) != 0)
 		str_append(str, "\tsecured");
+	if ((info->flags & AUTH_REQUEST_FLAG_NO_PENALTY) != 0)
+		str_append(str, "\tno-penalty");
 	if ((info->flags & AUTH_REQUEST_FLAG_VALID_CLIENT_CERT) != 0)
 		str_append(str, "\tvalid-client-cert");
 
--- a/src/lib-auth/auth-client.h	Thu Mar 10 18:44:20 2011 +0200
+++ b/src/lib-auth/auth-client.h	Thu Mar 17 16:37:22 2011 +0200
@@ -9,7 +9,9 @@
 
 enum auth_request_flags {
 	AUTH_REQUEST_FLAG_SECURED		= 0x01,
-	AUTH_REQUEST_FLAG_VALID_CLIENT_CERT	= 0x02
+	AUTH_REQUEST_FLAG_VALID_CLIENT_CERT	= 0x02,
+	/* Skip penalty checks for this request */
+	AUTH_REQUEST_FLAG_NO_PENALTY		= 0x04
 };
 
 enum auth_request_status {
--- a/src/lib-auth/auth-master.c	Thu Mar 10 18:44:20 2011 +0200
+++ b/src/lib-auth/auth-master.c	Thu Mar 17 16:37:22 2011 +0200
@@ -372,6 +372,7 @@
 	if (conn->output->stream_errno != 0) {
 		errno = conn->output->stream_errno;
 		i_error("write(auth socket) failed: %m");
+		conn->aborted = TRUE;
 	} else {
 		io_loop_run(conn->ioloop);
 	}
--- a/src/lib-charset/charset-iconv.c	Thu Mar 10 18:44:20 2011 +0200
+++ b/src/lib-charset/charset-iconv.c	Thu Mar 17 16:37:22 2011 +0200
@@ -104,10 +104,7 @@
 	else {
 		/* should be EILSEQ */
 		*result = CHARSET_RET_INVALID_INPUT;
-		if (!dtcase)
-			buffer_set_used_size(dest, dest->used - destleft);
-		uni_ucs4_to_utf8_c(UNICODE_REPLACEMENT_CHAR, dest);
-		return TRUE;
+		ret = FALSE;
 	}
 	*src_size -= srcleft;
 
@@ -132,6 +129,7 @@
 	bool dtcase = (t->flags & CHARSET_FLAG_DECOMP_TITLECASE) != 0;
 	enum charset_result result;
 	size_t pos, used, size, prev_pos = 0, prev_used = 0;
+	size_t prev_invalid_pos = (size_t)-1;
 	bool ret;
 
 	for (pos = 0;;) {
@@ -139,12 +137,17 @@
 		ret = charset_to_utf8_try(t, src + pos, &size, dest, &result);
 		pos += size;
 
-		if (ret) {
-			*src_size = pos;
-			return result;
-		}
+		if (ret)
+			break;
 
-		if (!dtcase) {
+		if (result == CHARSET_RET_INVALID_INPUT) {
+			if (prev_invalid_pos != dest->used) {
+				uni_ucs4_to_utf8_c(UNICODE_REPLACEMENT_CHAR,
+						   dest);
+				prev_invalid_pos = dest->used;
+			}
+			pos++;
+		} else if (!dtcase) {
 			/* force buffer to grow */
 			used = dest->used;
 			size = buffer_get_size(dest) - used + 1;
@@ -156,6 +159,12 @@
 			prev_used = dest->used;
 		}
 	}
+
+	if (prev_invalid_pos != (size_t)-1)
+		result = CHARSET_RET_INVALID_INPUT;
+
+	*src_size = pos;
+	return result;
 }
 
 #endif
--- a/src/lib-imap/imap-id.c	Thu Mar 10 18:44:20 2011 +0200
+++ b/src/lib-imap/imap-id.c	Thu Mar 17 16:37:22 2011 +0200
@@ -164,6 +164,7 @@
 			str_append_c(reply, '=');
 			str_append(reply, str_sanitize(value, 80));
 		}
+		args++;
 	}
 	return str_len(reply) == 0 ? NULL : str_c(reply);
 }
--- a/src/lib-master/anvil-client.c	Thu Mar 10 18:44:20 2011 +0200
+++ b/src/lib-master/anvil-client.c	Thu Mar 17 16:37:22 2011 +0200
@@ -21,6 +21,9 @@
 	struct ostream *output;
 	struct io *io;
 
+	struct timeout *to_reconnect;
+	time_t last_reconnect;
+
 	ARRAY_DEFINE(queries_arr, struct anvil_query);
 	struct aqueue *queries;
 
@@ -30,6 +33,7 @@
 
 #define ANVIL_HANDSHAKE "VERSION\tanvil\t1\t0\n"
 #define ANVIL_INBUF_SIZE 1024
+#define ANVIL_RECONNECT_MIN_SECS 5
 
 static void anvil_client_disconnect(struct anvil_client *client);
 
@@ -59,6 +63,7 @@
 	array_free(&client->queries_arr);
 	aqueue_deinit(&client->queries);
 	i_free(client->path);
+	i_assert(client->to_reconnect == NULL);
 	i_free(client);
 }
 
@@ -71,7 +76,17 @@
 			return;
 		}
 	}
-	(void)anvil_client_connect(client, FALSE);
+
+	if (ioloop_time - client->last_reconnect < ANVIL_RECONNECT_MIN_SECS) {
+		if (client->to_reconnect == NULL) {
+			client->to_reconnect =
+				timeout_add(ANVIL_RECONNECT_MIN_SECS,
+					    anvil_reconnect, client);
+		}
+	} else {
+		client->last_reconnect = ioloop_time;
+		(void)anvil_client_connect(client, FALSE);
+	}
 }
 
 static void anvil_input(struct anvil_client *client)
@@ -119,6 +134,9 @@
 		return -1;
 	}
 
+	if (client->to_reconnect != NULL)
+		timeout_remove(&client->to_reconnect);
+
 	client->fd = fd;
 	client->input = i_stream_create_fd(fd, ANVIL_INBUF_SIZE, FALSE);
 	client->output = o_stream_create_fd(fd, (size_t)-1, FALSE);
@@ -142,15 +160,16 @@
 
 static void anvil_client_disconnect(struct anvil_client *client)
 {
-	if (client->fd == -1)
-		return;
-
-	anvil_client_cancel_queries(client);
-	io_remove(&client->io);
-	i_stream_destroy(&client->input);
-	o_stream_destroy(&client->output);
-	net_disconnect(client->fd);
-	client->fd = -1;
+	if (client->fd != -1) {
+		anvil_client_cancel_queries(client);
+		io_remove(&client->io);
+		i_stream_destroy(&client->input);
+		o_stream_destroy(&client->output);
+		net_disconnect(client->fd);
+		client->fd = -1;
+	}
+	if (client->to_reconnect != NULL)
+		timeout_remove(&client->to_reconnect);
 }
 
 static int anvil_client_send(struct anvil_client *client, const char *cmd)
--- a/src/lib-sql/driver-mysql.c	Thu Mar 10 18:44:20 2011 +0200
+++ b/src/lib-sql/driver-mysql.c	Thu Mar 17 16:37:22 2011 +0200
@@ -52,6 +52,11 @@
 extern const struct sql_result driver_mysql_result;
 extern const struct sql_result driver_mysql_error_result;
 
+static const char *mysql_prefix(struct mysql_db *db)
+{
+	return t_strdup_printf("mysql(%s)", db->host);
+}
+
 static int driver_mysql_connect(struct sql_db *_db)
 {
 	struct mysql_db *db = (struct mysql_db *)_db;
@@ -105,15 +110,14 @@
 	alarm(0);
 	if (failed) {
 		sql_db_set_state(&db->api, SQL_DB_STATE_DISCONNECTED);
-		i_error("mysql: Connect failed to %s (%s): %s - "
+		i_error("%s: Connect failed to database (%s): %s - "
 			"waiting for %u seconds before retry",
-			host != NULL ? host : unix_socket, db->dbname,
+			mysql_prefix(db), db->dbname,
 			mysql_error(db->mysql), db->api.connect_delay);
 		return -1;
 	} else {
-		i_info("mysql: Connected to %s%s (%s)",
-		       host != NULL ? host : unix_socket,
-		       db->ssl_set ? " using SSL" : "", db->dbname);
+		i_info("%s: Connected to database %s%s", mysql_prefix(db),
+		       db->dbname, db->ssl_set ? " using SSL" : "");
 
 		sql_db_set_state(&db->api, SQL_DB_STATE_IDLE);
 		return 1;
@@ -266,8 +270,8 @@
 	struct mysql_db *db = (struct mysql_db *)_db;
 
 	if (driver_mysql_do_query(db, query) < 0) {
-		i_error("mysql: Query '%s' failed: %s",
-			query, mysql_error(db->mysql));
+		i_error("%s: Query '%s' failed: %s",
+			mysql_prefix(db), query, mysql_error(db->mysql));
 	}
 }
 
--- a/src/lib-sql/driver-pgsql.c	Thu Mar 10 18:44:20 2011 +0200
+++ b/src/lib-sql/driver-pgsql.c	Thu Mar 17 16:37:22 2011 +0200
@@ -73,6 +73,12 @@
 
 static void result_finish(struct pgsql_result *result);
 
+static const char *pgsql_prefix(struct pgsql_db *db)
+{
+	return db->host == NULL ? "pgsql" :
+		t_strdup_printf("pgsql(%s)", db->host);
+}
+
 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);
@@ -150,8 +156,8 @@
 	case PGRES_POLLING_OK:
 		break;
 	case PGRES_POLLING_FAILED:
-		i_error("pgsql: Connect failed to %s: %s",
-			PQdb(db->pg), last_error(db));
+		i_error("%s: Connect failed to database %s: %s",
+			pgsql_prefix(db), PQdb(db->pg), last_error(db));
 		driver_pgsql_close(db);
 		return;
 	}
@@ -162,7 +168,8 @@
 	}
 
 	if (io_dir == 0) {
-		i_info("pgsql: Connected to %s", PQdb(db->pg));
+		i_info("%s: Connected to database %s",
+		       pgsql_prefix(db), PQdb(db->pg));
 		if (db->to_connect != NULL)
 			timeout_remove(&db->to_connect);
 		driver_pgsql_set_state(db, SQL_DB_STATE_IDLE);
@@ -176,11 +183,10 @@
 
 static void driver_pgsql_connect_timeout(struct pgsql_db *db)
 {
-	const char *dbname = PQdb(db->pg);
 	unsigned int secs = ioloop_time - db->api.last_connect_try;
 
-	i_error("pgsql: Connect failed to %s: Timeout after %u seconds",
-		dbname != NULL ? dbname : db->host, secs);
+	i_error("%s: Connect failed: Timeout after %u seconds",
+		pgsql_prefix(db), secs);
 	driver_pgsql_close(db);
 }
 
@@ -191,19 +197,21 @@
 	i_assert(db->api.state == SQL_DB_STATE_DISCONNECTED);
 
 	db->pg = PQconnectStart(db->connect_string);
-	if (db->pg == NULL)
-		i_fatal("pgsql: PQconnectStart() failed (out of memory)");
+	if (db->pg == NULL) {
+		i_fatal("%s: PQconnectStart() failed (out of memory)",
+			pgsql_prefix(db));
+	}
 
 	if (PQstatus(db->pg) == CONNECTION_BAD) {
-		i_error("pgsql: Connect failed to %s: %s",
-			PQdb(db->pg), last_error(db));
+		i_error("%s: Connect failed to database %s: %s",
+			pgsql_prefix(db), PQdb(db->pg), last_error(db));
 		driver_pgsql_close(db);
 		return -1;
 	}
 
 	/* nonblocking connecting begins. */
 	if (PQsetnonblocking(db->pg, 1) < 0)
-		i_error("pgsql: PQsetnonblocking() failed");
+		i_error("%s: PQsetnonblocking() failed", pgsql_prefix(db));
 	i_assert(db->to_connect == NULL);
 	db->to_connect = timeout_add(SQL_CONNECT_TIMEOUT_SECS * 1000,
 				     driver_pgsql_connect_timeout, db);
@@ -428,7 +436,7 @@
 
 	driver_pgsql_stop_io(db);
 
-	i_error("pgsql: Query timed out, aborting");
+	i_error("%s: Query timed out, aborting", pgsql_prefix(db));
 	result->timeout = TRUE;
 	result_finish(result);
 }
@@ -442,6 +450,7 @@
 	i_assert(db->cur_result == NULL);
 	i_assert(db->io == NULL);
 
+	driver_pgsql_set_state(db, SQL_DB_STATE_BUSY);
 	db->cur_result = result;
 	result->to = timeout_add(SQL_QUERY_TIMEOUT_SECS * 1000,
 				 query_timeout, result);
@@ -453,7 +462,6 @@
 		return;
 	}
 
-	driver_pgsql_set_state(db, SQL_DB_STATE_BUSY);
 	if (ret > 0) {
 		/* write blocks */
 		db->io = io_add(PQsocket(db->pg), IO_WRITE,
@@ -494,7 +502,7 @@
 {
 	struct pgsql_db *db = (struct pgsql_db *)_result->db;
 
-	i_error("pgsql: sql_exec() failed: %s", last_error(db));
+	i_error("%s: sql_exec() failed: %s", pgsql_prefix(db), last_error(db));
 }
 
 static void driver_pgsql_exec(struct sql_db *db, const char *query)
--- a/src/lib-sql/driver-sqlpool.c	Thu Mar 10 18:44:20 2011 +0200
+++ b/src/lib-sql/driver-sqlpool.c	Thu Mar 17 16:37:22 2011 +0200
@@ -356,6 +356,8 @@
 	if (conn == NULL) {
 		/* still nothing. try creating new connections */
 		conn = sqlpool_add_new_connection(db);
+		if (conn != NULL)
+			(void)sql_connect(conn->db);
 		if (conn == NULL || !SQL_DB_IS_READY(conn->db))
 			return FALSE;
 	}
--- a/src/lib-storage/index/dbox-common/dbox-attachment.c	Thu Mar 10 18:44:20 2011 +0200
+++ b/src/lib-storage/index/dbox-common/dbox-attachment.c	Thu Mar 17 16:37:22 2011 +0200
@@ -139,7 +139,8 @@
 static int
 dbox_attachment_file_get_stream_from(struct dbox_file *file,
 				     const char *ext_refs,
-				     struct istream **stream)
+				     struct istream **stream,
+				     const char **error_r)
 {
 	ARRAY_TYPE(mail_attachment_extref) extrefs_arr;
 	ARRAY_DEFINE(streams, struct istream *);
@@ -150,10 +151,14 @@
 	unsigned int i;
 	int ret = 1;
 
+	*error_r = NULL;
+
 	t_array_init(&extrefs_arr, 16);
 	if (!dbox_attachment_parse_extref_real(ext_refs, pool_datastack_create(),
-					       &extrefs_arr))
+					       &extrefs_arr)) {
+		*error_r = "Broken ext-refs string";
 		return 0;
+	}
 	psize = dbox_file_get_plaintext_size(file);
 
 	t_array_init(&streams, 8);
@@ -165,13 +170,17 @@
 		if (extref->start_offset != last_voffset) {
 			uoff_t part_size = extref->start_offset - last_voffset;
 
+			if ((*stream)->v_offset + part_size > psize) {
+				*error_r = t_strdup_printf(
+					"ext-refs point outside message "
+					"(%"PRIuUOFF_T" + %"PRIuUOFF_T" > %"PRIuUOFF_T")",
+					(*stream)->v_offset, part_size, psize);
+				ret = 0;
+			}
+
 			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;
 		}
 
@@ -192,8 +201,11 @@
 	}
 
 	if (psize != (*stream)->v_offset) {
-		if (psize < (*stream)->v_offset) {
-			/* extrefs point outside message */
+		if ((*stream)->v_offset > psize) {
+			*error_r = t_strdup_printf(
+				"ext-refs point outside message "
+				"(%"PRIuUOFF_T" > %"PRIuUOFF_T")",
+				(*stream)->v_offset, psize);
 			ret = 0;
 		} else {
 			uoff_t trailer_size = psize - (*stream)->v_offset;
@@ -215,7 +227,7 @@
 int dbox_attachment_file_get_stream(struct dbox_file *file,
 				    struct istream **stream)
 {
-	const char *ext_refs;
+	const char *ext_refs, *error;
 	int ret;
 
 	/* need to read metadata in case there are external references */
@@ -231,11 +243,12 @@
 	/* we have external references. */
 	T_BEGIN {
 		ret = dbox_attachment_file_get_stream_from(file, ext_refs,
-							   stream);
+							   stream, &error);
+		if (ret == 0) {
+			dbox_file_set_corrupted(file,
+				"Corrupted ext-refs metadata %s: %s",
+				ext_refs, error);
+		}
 	} T_END;
-	if (ret == 0) {
-		dbox_file_set_corrupted(file, "Ext refs metadata corrupted: %s",
-					ext_refs);
-	}
 	return ret;
 }
--- a/src/lib-storage/index/dbox-common/dbox-file.c	Thu Mar 10 18:44:20 2011 +0200
+++ b/src/lib-storage/index/dbox-common/dbox-file.c	Thu Mar 17 16:37:22 2011 +0200
@@ -437,13 +437,13 @@
 			*offset_r = file->cur_offset;
 			return ret;
 		}
+		if (i_stream_is_eof(file->input)) {
+			*last_r = TRUE;
+			return 0;
+		}
 	}
 	*offset_r = offset;
 
-	if (i_stream_is_eof(file->input)) {
-		*last_r = TRUE;
-		return 0;
-	}
 	*last_r = FALSE;
 
 	ret = dbox_file_seek(file, offset);
--- a/src/lib-storage/index/dbox-multi/mdbox-map.c	Thu Mar 10 18:44:20 2011 +0200
+++ b/src/lib-storage/index/dbox-multi/mdbox-map.c	Thu Mar 17 16:37:22 2011 +0200
@@ -1098,6 +1098,23 @@
 	return 0;
 }
 
+static void
+mdbox_map_append_close_if_unneeded(struct mdbox_map *map,
+				   struct dbox_file_append_context *append_ctx)
+{
+	struct mdbox_file *mfile =
+		(struct mdbox_file *)append_ctx->file;
+	uoff_t end_offset = append_ctx->output->offset;
+
+	/* if this file is now large enough not to fit any other
+	   mails and we created it, close its fd since it's not
+	   needed anymore. */
+	if (end_offset > map->set->mdbox_rotate_size &&
+	    mfile->file_id == 0 &&
+	    dbox_file_append_flush(append_ctx) == 0)
+		dbox_file_close(append_ctx->file);
+}
+
 void mdbox_map_append_finish(struct mdbox_map_append_context *ctx)
 {
 	struct mdbox_map_append *appends, *last;
@@ -1113,6 +1130,8 @@
 	i_assert(cur_offset >= last->offset);
 	last->size = cur_offset - last->offset;
 	dbox_file_append_checkpoint(last->file_append);
+
+	mdbox_map_append_close_if_unneeded(ctx->map, last->file_append);
 }
 
 void mdbox_map_append_abort(struct mdbox_map_append_context *ctx)
--- a/src/lib-storage/index/dbox-multi/mdbox-purge.c	Thu Mar 10 18:44:20 2011 +0200
+++ b/src/lib-storage/index/dbox-multi/mdbox-purge.c	Thu Mar 17 16:37:22 2011 +0200
@@ -222,7 +222,13 @@
 					  out_file_append->file->cur_path);
 		return -1;
 	}
-	i_assert(ret == (off_t)msg_size);
+	if (ret != (off_t)msg_size) {
+		i_assert(ret < (off_t)msg_size);
+		i_assert(i_stream_is_eof(file->input));
+
+		dbox_file_set_corrupted(file, "truncated message at EOF");
+		return 0;
+	}
 
 	/* copy metadata */
 	if ((ret = mdbox_file_metadata_copy(file, output)) <= 0)
@@ -366,10 +372,9 @@
 		} else {
 			/* non-expunged message. write it to output file. */
 			i_stream_seek(file->input, offset);
-			if (mdbox_purge_save_msg(ctx, file, &msgs[i]) < 0) {
-				ret = -1;
+			ret = mdbox_purge_save_msg(ctx, file, &msgs[i]);
+			if (ret <= 0)
 				break;
-			}
 			array_append(&copied_map_uids, &msgs[i].map_uid, 1);
 		}
 		offset = file->input->v_offset;
--- a/src/lib-storage/index/dbox-multi/mdbox-storage.c	Thu Mar 10 18:44:20 2011 +0200
+++ b/src/lib-storage/index/dbox-multi/mdbox-storage.c	Thu Mar 17 16:37:22 2011 +0200
@@ -232,6 +232,14 @@
 			offsetof(struct mail_index_header, next_uid),
 			&uid_next, sizeof(uid_next), TRUE);
 	}
+	if (update != NULL && update->min_first_recent_uid != 0 &&
+	    hdr->first_recent_uid < update->min_first_recent_uid) {
+		uint32_t first_recent_uid = update->min_first_recent_uid;
+
+		mail_index_update_header(trans,
+			offsetof(struct mail_index_header, first_recent_uid),
+			&first_recent_uid, sizeof(first_recent_uid), FALSE);
+	}
 	if (update != NULL && update->min_highest_modseq != 0 &&
 	    mail_index_modseq_get_highest(box->view) <
 	    					update->min_highest_modseq) {
--- a/src/lib-storage/index/dbox-single/sdbox-save.c	Thu Mar 10 18:44:20 2011 +0200
+++ b/src/lib-storage/index/dbox-single/sdbox-save.c	Thu Mar 17 16:37:22 2011 +0200
@@ -265,8 +265,12 @@
 
 	i_assert(ctx->ctx.finished);
 
-	if (array_count(&ctx->files) == 0)
+	if (array_count(&ctx->files) == 0) {
+		/* the mail must be freed in the commit_pre() */
+		if (ctx->ctx.mail != NULL)
+			mail_free(&ctx->ctx.mail);
 		return 0;
+	}
 
 	if (sdbox_sync_begin(ctx->mbox, SDBOX_SYNC_FLAG_FORCE |
 			     SDBOX_SYNC_FLAG_FSYNC, &ctx->sync_ctx) < 0) {
--- a/src/lib-storage/index/dbox-single/sdbox-storage.c	Thu Mar 10 18:44:20 2011 +0200
+++ b/src/lib-storage/index/dbox-single/sdbox-storage.c	Thu Mar 17 16:37:22 2011 +0200
@@ -153,6 +153,14 @@
 			offsetof(struct mail_index_header, next_uid),
 			&uid_next, sizeof(uid_next), TRUE);
 	}
+	if (update != NULL && update->min_first_recent_uid != 0 &&
+	    hdr->first_recent_uid < update->min_first_recent_uid) {
+		uint32_t first_recent_uid = update->min_first_recent_uid;
+
+		mail_index_update_header(trans,
+			offsetof(struct mail_index_header, first_recent_uid),
+			&first_recent_uid, sizeof(first_recent_uid), FALSE);
+	}
 	if (update != NULL && update->min_highest_modseq != 0 &&
 	    mail_index_modseq_get_highest(box->view) <
 	    					update->min_highest_modseq) {
@@ -216,6 +224,11 @@
 		return 0;
 	}
 
+	if (box->creating) {
+		/* wait for mailbox creation to initialize the index */
+		return 0;
+	}
+
 	/* get/generate mailbox guid */
 	if (sdbox_read_header(mbox, &hdr, FALSE) < 0) {
 		/* it's possible that this mailbox is just now being created
--- a/src/lib-storage/index/index-status.c	Thu Mar 10 18:44:20 2011 +0200
+++ b/src/lib-storage/index/index-status.c	Thu Mar 17 16:37:22 2011 +0200
@@ -32,6 +32,7 @@
 	status_r->unseen = hdr->messages_count - hdr->seen_messages_count;
 	status_r->uidvalidity = hdr->uid_validity;
 	status_r->uidnext = hdr->next_uid;
+	status_r->first_recent_uid = hdr->first_recent_uid;
 	status_r->nonpermanent_modseqs = mail_index_is_in_memory(box->index);
 	if ((items & STATUS_HIGHESTMODSEQ) != 0) {
 		status_r->highest_modseq =
--- a/src/lib-storage/index/index-storage.c	Thu Mar 10 18:44:20 2011 +0200
+++ b/src/lib-storage/index/index-storage.c	Thu Mar 17 16:37:22 2011 +0200
@@ -429,6 +429,14 @@
 			offsetof(struct mail_index_header, next_uid),
 			&next_uid, sizeof(next_uid), FALSE);
 	}
+	if (update->min_first_recent_uid != 0 &&
+	    hdr->first_recent_uid < update->min_first_recent_uid) {
+		uint32_t first_recent_uid = update->min_first_recent_uid;
+
+		mail_index_update_header(trans,
+			offsetof(struct mail_index_header, first_recent_uid),
+			&first_recent_uid, sizeof(first_recent_uid), FALSE);
+	}
 	if (update->min_highest_modseq != 0 &&
 	    mail_index_modseq_get_highest(view) < update->min_highest_modseq) {
 		mail_index_modseq_enable(box->index);
--- a/src/lib-storage/index/maildir/maildir-save.c	Thu Mar 10 18:44:20 2011 +0200
+++ b/src/lib-storage/index/maildir/maildir-save.c	Thu Mar 17 16:37:22 2011 +0200
@@ -927,8 +927,12 @@
 	i_assert(_ctx->output == NULL);
 	i_assert(ctx->last_save_finished);
 
-	if (ctx->files_count == 0)
+	if (ctx->files_count == 0) {
+		/* the mail must be freed in the commit_pre() */
+		if (ctx->mail != NULL)
+			mail_free(&ctx->mail);
 		return 0;
+	}
 
 	sync_flags = MAILDIR_UIDLIST_SYNC_PARTIAL |
 		MAILDIR_UIDLIST_SYNC_NOREFRESH;
--- a/src/lib-storage/index/mbox/mbox-lock.c	Thu Mar 10 18:44:20 2011 +0200
+++ b/src/lib-storage/index/mbox/mbox-lock.c	Thu Mar 17 16:37:22 2011 +0200
@@ -4,6 +4,7 @@
 #include "eacces-error.h"
 #include "restrict-access.h"
 #include "nfs-workarounds.h"
+#include "ipwd.h"
 #include "mail-index-private.h"
 #include "mbox-storage.h"
 #include "istream-raw-mbox.h"
@@ -15,7 +16,6 @@
 #include <unistd.h>
 #include <fcntl.h>
 #include <sys/stat.h>
-#include <grp.h>
 
 #ifdef HAVE_FLOCK
 #  include <sys/file.h>
@@ -341,9 +341,9 @@
 static void
 mbox_dotlock_log_eacces_error(struct mbox_mailbox *mbox, const char *path)
 {
-	const char *dir, *errmsg;
+	const char *dir, *errmsg, *name;
 	struct stat st;
-	const struct group *group;
+	struct group group;
 	int orig_errno = errno;
 
 	errmsg = eacces_error_get_creating("file_dotlock_create", path);
@@ -365,10 +365,12 @@
 	} else if (stat(dir, &st) == 0 &&
 		   (st.st_mode & 02) == 0 && /* not world-writable */
 		   (st.st_mode & 020) != 0) { /* group-writable */
-		group = getgrgid(st.st_gid);
+		if (i_getgrgid(st.st_gid, &group) <= 0)
+			name = dec2str(st.st_gid);
+		else
+			name = group.gr_name;
 		mail_storage_set_critical(&mbox->storage->storage,
-			"%s (set mail_privileged_group=%s)", errmsg,
-			group == NULL ? dec2str(st.st_gid) : group->gr_name);
+			"%s (set mail_privileged_group=%s)", errmsg, name);
 	} else {
 		mail_storage_set_critical(&mbox->storage->storage,
 			"%s (nonstandard permissions in %s)", errmsg, dir);
--- a/src/lib-storage/mail-storage-service.c	Thu Mar 10 18:44:20 2011 +0200
+++ b/src/lib-storage/mail-storage-service.c	Thu Mar 17 16:37:22 2011 +0200
@@ -7,6 +7,7 @@
 #include "module-dir.h"
 #include "restrict-access.h"
 #include "eacces-error.h"
+#include "ipwd.h"
 #include "str.h"
 #include "var-expand.h"
 #include "dict.h"
@@ -22,8 +23,6 @@
 
 #include <stdlib.h>
 #include <sys/stat.h>
-#include <pwd.h>
-#include <grp.h>
 
 #ifdef HAVE_SYS_TIME_H
 #  include <sys/time.h>
@@ -271,34 +270,44 @@
 	return ret;
 }
 
-static bool parse_uid(const char *str, uid_t *uid_r)
+static bool parse_uid(const char *str, uid_t *uid_r, const char **error_r)
 {
-	struct passwd *pw;
+	struct passwd pw;
 
 	if (str_to_uid(str, uid_r) == 0)
 		return TRUE;
 
-	pw = getpwnam(str);
-	if (pw == NULL)
+	switch (i_getpwnam(str, &pw)) {
+	case -1:
+		*error_r = t_strdup_printf("getpwnam(%s) failed: %m", str);
 		return FALSE;
-
-	*uid_r = pw->pw_uid;
-	return TRUE;
+	case 0:
+		*error_r = t_strconcat("Unknown UNIX UID user: ", str, NULL);
+		return FALSE;
+	default:
+		*uid_r = pw.pw_uid;
+		return TRUE;
+	}
 }
 
-static bool parse_gid(const char *str, gid_t *gid_r)
+static bool parse_gid(const char *str, gid_t *gid_r, const char **error_r)
 {
-	struct group *gr;
+	struct group gr;
 
 	if (str_to_gid(str, gid_r) == 0)
 		return TRUE;
 
-	gr = getgrnam(str);
-	if (gr == NULL)
+	switch (i_getgrnam(str, &gr)) {
+	case -1:
+		*error_r = t_strdup_printf("getgrnam(%s) failed: %m", str);
 		return FALSE;
-
-	*gid_r = gr->gr_gid;
-	return TRUE;
+	case 0:
+		*error_r = t_strconcat("Unknown UNIX GID group: ", str, NULL);
+		return FALSE;
+	default:
+		*gid_r = gr.gr_gid;
+		return TRUE;
+	}
 }
 
 static int
@@ -310,24 +319,24 @@
 {
 	struct restrict_access_settings rset;
 	uid_t current_euid, setuid_uid = 0;
-	const char *cur_chroot;
+	const char *cur_chroot, *error;
 
 	current_euid = geteuid();
 	restrict_access_init(&rset);
 	restrict_access_get_env(&rset);
 	if (*set->mail_uid != '\0') {
-		if (!parse_uid(set->mail_uid, &rset.uid)) {
-			*error_r = t_strdup_printf("Unknown mail_uid user: %s",
-						   set->mail_uid);
+		if (!parse_uid(set->mail_uid, &rset.uid, &error)) {
+			*error_r = t_strdup_printf("%s (from %s)", error,
+						   user->uid_source);
 			return -1;
 		}
 		if (rset.uid < (uid_t)set->first_valid_uid ||
 		    (set->last_valid_uid != 0 &&
 		     rset.uid > (uid_t)set->last_valid_uid)) {
 			*error_r = t_strdup_printf(
-				"Mail access for users with UID %s "
-				"not permitted (see first_valid_uid in config file).",
-				dec2str(rset.uid));
+				"Mail access for users with UID %s not permitted "
+				"(see first_valid_uid in config file, uid from %s).",
+				dec2str(rset.uid), user->uid_source);
 			return -1;
 		}
 		rset.uid_source = user->uid_source;
@@ -337,18 +346,18 @@
 		return -1;
 	}
 	if (*set->mail_gid != '\0') {
-		if (!parse_gid(set->mail_gid, &rset.gid)) {
-			*error_r = t_strdup_printf("Unknown mail_gid group: %s",
-						   set->mail_gid);
+		if (!parse_gid(set->mail_gid, &rset.gid, &error)) {
+			*error_r = t_strdup_printf("%s (from %s)", error,
+						   user->gid_source);
 			return -1;
 		}
 		if (rset.gid < (gid_t)set->first_valid_gid ||
 		    (set->last_valid_gid != 0 &&
 		     rset.gid > (gid_t)set->last_valid_gid)) {
 			*error_r = t_strdup_printf(
-				"Mail access for users with GID %s "
-				"not permitted (see first_valid_gid in config file).",
-				dec2str(rset.gid));
+				"Mail access for users with GID %s not permitted "
+				"(see first_valid_gid in config file, gid from %s).",
+				dec2str(rset.gid), user->gid_source);
 			return -1;
 		}
 		rset.gid_source = user->gid_source;
@@ -358,10 +367,10 @@
 		return -1;
 	}
 	if (*set->mail_privileged_group != '\0') {
-		if (!parse_gid(set->mail_privileged_group, &rset.privileged_gid)) {
+		if (!parse_gid(set->mail_privileged_group, &rset.privileged_gid,
+			       &error)) {
 			*error_r = t_strdup_printf(
-				"Unknown mail_privileged_group: %s",
-				set->mail_gid);
+				"%s (in mail_privileged_group setting)", error);
 			return -1;
 		}
 	}
--- a/src/lib-storage/mail-storage-settings.c	Thu Mar 10 18:44:20 2011 +0200
+++ b/src/lib-storage/mail-storage-settings.c	Thu Mar 17 16:37:22 2011 +0200
@@ -65,7 +65,7 @@
 	.mail_save_crlf = FALSE,
 	.mail_fsync = "optimized:never:always",
 	.mmap_disable = FALSE,
-	.dotlock_use_excl = FALSE,
+	.dotlock_use_excl = TRUE,
 	.mail_nfs_storage = FALSE,
 	.mail_nfs_index = FALSE,
 	.mailbox_list_index = FALSE,
--- a/src/lib-storage/mail-storage.c	Thu Mar 10 18:44:20 2011 +0200
+++ b/src/lib-storage/mail-storage.c	Thu Mar 17 16:37:22 2011 +0200
@@ -803,6 +803,10 @@
 
 int mailbox_update(struct mailbox *box, const struct mailbox_update *update)
 {
+	i_assert(update->min_next_uid == 0 ||
+		 update->min_first_recent_uid == 0 ||
+		 update->min_first_recent_uid <= update->min_next_uid);
+
 	return box->v.update(box, update);
 }
 
--- a/src/lib-storage/mail-storage.h	Thu Mar 10 18:44:20 2011 +0200
+++ b/src/lib-storage/mail-storage.h	Thu Mar 17 16:37:22 2011 +0200
@@ -70,7 +70,8 @@
 	STATUS_UNSEEN		= 0x10,
 	STATUS_FIRST_UNSEEN_SEQ	= 0x20,
 	STATUS_KEYWORDS		= 0x40,
-	STATUS_HIGHESTMODSEQ	= 0x80
+	STATUS_HIGHESTMODSEQ	= 0x80,
+	STATUS_FIRST_RECENT_UID	= 0x400
 };
 
 enum mailbox_metadata_items {
@@ -194,6 +195,7 @@
 	uint32_t uidnext;
 
 	uint32_t first_unseen_seq;
+	uint32_t first_recent_uid;
 	uint64_t highest_modseq;
 
 	const ARRAY_TYPE(keywords) *keywords;
@@ -215,6 +217,7 @@
 	uint8_t mailbox_guid[MAIL_GUID_128_SIZE];
 	uint32_t uid_validity;
 	uint32_t min_next_uid;
+	uint32_t min_first_recent_uid;
 	uint64_t min_highest_modseq;
 	/* Add these fields to be temporarily cached, if they aren't already. */
 	const char *const *cache_fields;
--- a/src/lib/Makefile.am	Thu Mar 10 18:44:20 2011 +0200
+++ b/src/lib/Makefile.am	Thu Mar 17 16:37:22 2011 +0200
@@ -45,6 +45,7 @@
 	home-expand.c \
 	hostpid.c \
 	imem.c \
+	ipwd.c \
 	iostream.c \
 	istream.c \
 	istream-base64-encoder.c \
@@ -157,6 +158,7 @@
 	home-expand.h \
 	hostpid.h \
 	imem.h \
+	ipwd.h \
 	iostream-internal.h \
 	istream.h \
 	istream-base64-encoder.h \
--- a/src/lib/eacces-error.c	Thu Mar 10 18:44:20 2011 +0200
+++ b/src/lib/eacces-error.c	Thu Mar 17 16:37:22 2011 +0200
@@ -3,13 +3,12 @@
 #include "lib.h"
 #include "str.h"
 #include "abspath.h"
+#include "ipwd.h"
 #include "restrict-access.h"
 #include "eacces-error.h"
 
 #include <sys/stat.h>
 #include <unistd.h>
-#include <pwd.h>
-#include <grp.h>
 
 static bool is_in_group(gid_t gid)
 {
@@ -86,8 +85,8 @@
 {
 	const char *prev_path = path, *dir, *p;
 	const char *pw_name = NULL, *gr_name = NULL;
-	const struct passwd *pw;
-	const struct group *group;
+	struct passwd pw;
+	struct group group;
 	string_t *errmsg;
 	struct stat st, dir_st;
 	int orig_errno, ret = -1;
@@ -107,32 +106,43 @@
 	str_printfa(errmsg, " failed: Permission denied (euid=%s",
 		    dec2str(geteuid()));
 
-	pw = getpwuid(geteuid());
-	if (pw != NULL) {
-		pw_name = t_strdup(pw->pw_name);
+	switch (i_getpwuid(geteuid(), &pw)) {
+	case -1:
+		str_append(errmsg, "(<getpwuid() error>)");
+		break;
+	case 0:
+		str_append(errmsg, "(<unknown>)");
+		break;
+	default:
+		pw_name = t_strdup(pw.pw_name);
 		str_printfa(errmsg, "(%s)", pw_name);
-	} else {
-		str_append(errmsg, "(<unknown>)");
+		break;
 	}
 
 	str_printfa(errmsg, " egid=%s", dec2str(getegid()));
-	group = getgrgid(getegid());
-	if (group != NULL) {
-		gr_name = t_strdup(group->gr_name);
+	switch (i_getgrgid(getegid(), &group)) {
+	case -1:
+		str_append(errmsg, "(<getgrgid() error>)");
+		break;
+	case 0:
+		str_append(errmsg, "(<unknown>)");
+		break;
+	default:
+		gr_name = t_strdup(group.gr_name);
 		str_printfa(errmsg, "(%s)", gr_name);
-	} else {
-		str_append(errmsg, "(<unknown>)");
+		break;
 	}
 
 	memset(&dir_st, 0, sizeof(dir_st));
 	while ((p = strrchr(prev_path, '/')) != NULL) {
-		dir = t_strdup_until(prev_path, p);
+		dir = p == prev_path ? "/" : t_strdup_until(prev_path, p);
 		ret = stat(dir, &st);
 		if (ret == 0)
 			break;
-		if (errno == EACCES) {
+		if (errno == EACCES && strcmp(dir, "/") != 0) {
 			/* see if we have access to parent directory */
-		} else if (errno == ENOENT && creating) {
+		} else if (errno == ENOENT && creating &&
+			   strcmp(dir, "/") != 0) {
 			/* probably mkdir_parents() failed here, find the first
 			   parent directory we couldn't create */
 		} else {
@@ -168,9 +178,8 @@
 				    "some security policy wrong?");
 	}
 	if (dir_st.st_uid != geteuid()) {
-		if (pw_name != NULL &&
-		    (pw = getpwuid(dir_st.st_uid)) != NULL &&
-		    strcmp(pw->pw_name, pw_name) == 0) {
+		if (pw_name != NULL && i_getpwuid(dir_st.st_uid, &pw) > 0 &&
+		    strcmp(pw.pw_name, pw_name) == 0) {
 			str_printfa(errmsg, ", conflicting dir uid=%s(%s)",
 				    dec2str(dir_st.st_uid), pw_name);
 		} else {
@@ -180,8 +189,8 @@
 		str_append(errmsg, ", euid is dir owner");
 	}
 	if (gr_name != NULL && dir_st.st_gid != getegid()) {
-		group = getgrgid(dir_st.st_gid);
-		if (group != NULL && strcmp(group->gr_name, gr_name) == 0) {
+		if (i_getgrgid(dir_st.st_gid, &group) > 0 &&
+		    strcmp(group.gr_name, gr_name) == 0) {
 			str_printfa(errmsg, ", conflicting dir gid=%s(%s)",
 				    dec2str(dir_st.st_gid), gr_name);
 		}
--- a/src/lib/home-expand.c	Thu Mar 10 18:44:20 2011 +0200
+++ b/src/lib/home-expand.c	Thu Mar 17 16:37:22 2011 +0200
@@ -1,16 +1,16 @@
 /* Copyright (c) 2003-2011 Dovecot authors, see the included COPYING file */
 
 #include "lib.h"
+#include "ipwd.h"
 #include "home-expand.h"
 
 #include <stdlib.h>
-#include <pwd.h>
 
 int home_try_expand(const char **_path)
 {
 	const char *path = *_path;
-	const char *home, *p;
-	struct passwd *pw;
+	const char *name, *home, *p;
+	struct passwd pw;
 
 	if (path == NULL || *path != '~')
 		return 0;
@@ -22,14 +22,24 @@
 	} else {
 		p = strchr(path, '/');
 		if (p == NULL) {
-			pw = getpwnam(path);
+			name = path;
 			path = "";
 		} else {
-			pw = getpwnam(t_strdup_until(path, p));
+			name = t_strdup_until(path, p);
 			path = p+1;
 		}
-
-		home = pw == NULL ? NULL : pw->pw_dir;
+		switch (i_getpwnam(name, &pw)) {
+		case -1:
+			i_error("getpwnam(%s) failed: %m", name);
+			home = NULL;
+			break;
+		case 0:
+			home = NULL;
+			break;
+		default:
+			home = pw.pw_dir;
+			break;
+		}
 	}
 
 	if (home == NULL)
--- a/src/lib/ioloop-notify-inotify.c	Thu Mar 10 18:44:20 2011 +0200
+++ b/src/lib/ioloop-notify-inotify.c	Thu Mar 17 16:37:22 2011 +0200
@@ -11,11 +11,11 @@
 #include "ioloop-notify-fd.h"
 #include "buffer.h"
 #include "network.h"
+#include "ipwd.h"
 
 #include <stdio.h>
 #include <unistd.h>
 #include <fcntl.h>
-#include <pwd.h>
 #include <sys/ioctl.h>
 #include <sys/inotify.h>
 
@@ -149,16 +149,15 @@
 
 static void ioloop_inotify_user_limit_exceeded(void)
 {
-	const struct passwd *pw;
+	struct passwd pw;
 	const char *name;
 	uid_t uid = geteuid();
 
-	pw = getpwuid(uid);
-	if (pw == NULL)
+	if (i_getpwuid(uid, &pw) <= 0)
 		name = t_strdup_printf("UID %s", dec2str(uid));
 	else {
 		name = t_strdup_printf("%s (UID %s)",
-				       dec2str(uid), pw->pw_name);
+				       dec2str(uid), pw.pw_name);
 	}
 	i_warning("Inotify instance limit for user %s exceeded, disabling. "
 		  "Increase /proc/sys/fs/inotify/max_user_instances", name);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/ipwd.c	Thu Mar 17 16:37:22 2011 +0200
@@ -0,0 +1,90 @@
+/* Copyright (c) 2011 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "ipwd.h"
+
+#include <unistd.h>
+
+#define DEFAULT_PWBUF_SIZE 16384
+#define DEFAULT_GRBUF_SIZE 16384
+
+static void *pwbuf = NULL, *grbuf = NULL;
+static size_t pwbuf_size, grbuf_size;
+
+static void pw_init(void)
+{
+	long size;
+
+	if (pwbuf == NULL) {
+		size = sysconf(_SC_GETPW_R_SIZE_MAX);
+		if (size < 0)
+			size = DEFAULT_PWBUF_SIZE;
+
+		pwbuf_size = size;
+		pwbuf = i_malloc(pwbuf_size);
+	}
+}
+
+static void gr_init(void)
+{
+	long size;
+
+	if (grbuf == NULL) {
+		size = sysconf(_SC_GETGR_R_SIZE_MAX);
+		if (size < 0)
+			size = DEFAULT_GRBUF_SIZE;
+
+		grbuf_size = size;
+		grbuf = i_malloc(grbuf_size);
+	}
+}
+
+void ipwd_deinit(void)
+{
+	i_free_and_null(pwbuf);
+	i_free_and_null(grbuf);
+}
+
+int i_getpwnam(const char *name, struct passwd *pwd_r)
+{
+	struct passwd *result;
+
+	pw_init();
+	errno = getpwnam_r(name, pwd_r, pwbuf, pwbuf_size, &result);
+	if (result != NULL)
+		return 1;
+	return errno == 0 ? 0 : -1;
+}
+
+int i_getpwuid(uid_t uid, struct passwd *pwd_r)
+{
+	struct passwd *result;
+
+	pw_init();
+	errno = getpwuid_r(uid, pwd_r, pwbuf, pwbuf_size, &result);
+	if (result != NULL)
+		return 1;
+	return errno == 0 ? 0 : -1;
+}
+
+int i_getgrnam(const char *name, struct group *grp_r)
+{
+	struct group *result;
+
+	gr_init();
+	errno = getgrnam_r(name, grp_r, grbuf, grbuf_size, &result);
+	if (result != NULL)
+		return 1;
+	return errno == 0 ? 0 : -1;
+}
+
+int i_getgrgid(gid_t gid, struct group *grp_r)
+{
+	struct group *result;
+
+	gr_init();
+	errno = getgrgid_r(gid, grp_r, grbuf, grbuf_size, &result);
+	if (result != NULL)
+		return 1;
+	return errno == 0 ? 0 : -1;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/ipwd.h	Thu Mar 17 16:37:22 2011 +0200
@@ -0,0 +1,23 @@
+#ifndef IPWD_H
+#define IPWD_H
+
+#include <pwd.h>
+#include <grp.h>
+
+/* Replacements for standard getpw/gr*(), fixing their ability to report errors
+   properly. As with standard getpw/gr*(), second call overwrites data used
+   by the first one.
+
+   Functions return 1 if user/group is found, 0 if not or
+   -1 if error (with errno set). */
+
+int i_getpwnam(const char *name, struct passwd *pwd_r);
+int i_getpwuid(uid_t uid, struct passwd *pwd_r);
+
+int i_getgrnam(const char *name, struct group *grp_r);
+int i_getgrgid(gid_t gid, struct group *grp_r);
+
+/* Free memory used by above functions. */
+void ipwd_deinit(void);
+
+#endif
--- a/src/lib/lib.c	Thu Mar 10 18:44:20 2011 +0200
+++ b/src/lib/lib.c	Thu Mar 17 16:37:22 2011 +0200
@@ -3,6 +3,7 @@
 #include "lib.h"
 #include "env-util.h"
 #include "hostpid.h"
+#include "ipwd.h"
 #include "process-title.h"
 
 #include <stdlib.h>
@@ -34,6 +35,7 @@
 
 void lib_deinit(void)
 {
+	ipwd_deinit();
 	data_stack_deinit();
 	env_deinit();
 	failures_deinit();
--- a/src/lib/mkdir-parents.c	Thu Mar 10 18:44:20 2011 +0200
+++ b/src/lib/mkdir-parents.c	Thu Mar 17 16:37:22 2011 +0200
@@ -4,11 +4,10 @@
 #include "str.h"
 #include "eacces-error.h"
 #include "mkdir-parents.h"
+#include "ipwd.h"
 
 #include <sys/stat.h>
 #include <unistd.h>
-#include <pwd.h>
-#include <grp.h>
 
 static int
 mkdir_chown_full(const char *path, mode_t mode, uid_t uid,
@@ -48,19 +47,19 @@
 		str_printfa(str, "chown(%s, %ld", path,
 			    uid == (uid_t)-1 ? -1L : (long)uid);
 		if (uid != (uid_t)-1) {
-			struct passwd *pw = getpwuid(uid);
+			struct passwd pw;
 
-			if (pw != NULL)
-				str_printfa(str, "(%s)", pw->pw_name);
+			if (i_getpwuid(uid, &pw) > 0)
+				str_printfa(str, "(%s)", pw.pw_name);
 
 		}
 		str_printfa(str, ", %ld",
 			    gid == (gid_t)-1 ? -1L : (long)gid);
 		if (gid != (gid_t)-1) {
-			struct group *gr = getgrgid(uid);
+			struct group gr;
 
-			if (gr != NULL)
-				str_printfa(str, "(%s)", gr->gr_name);
+			if (i_getgrgid(uid, &gr) > 0)
+				str_printfa(str, "(%s)", gr.gr_name);
 		}
 		errno = orig_errno;
 		i_error("%s) failed: %m", str_c(str));
--- a/src/lib/restrict-access.c	Thu Mar 10 18:44:20 2011 +0200
+++ b/src/lib/restrict-access.c	Thu Mar 17 16:37:22 2011 +0200
@@ -9,11 +9,10 @@
 #include "str.h"
 #include "restrict-access.h"
 #include "env-util.h"
+#include "ipwd.h"
 
 #include <stdlib.h>
 #include <time.h>
-#include <pwd.h>
-#include <grp.h>
 #ifdef HAVE_PR_SET_DUMPABLE
 #  include <sys/prctl.h>
 #endif
@@ -34,30 +33,28 @@
 
 static const char *get_uid_str(uid_t uid)
 {
-	const struct passwd *pw;
+	struct passwd pw;
 	const char *ret;
 	int old_errno = errno;
 
-	pw = getpwuid(uid);
-	if (pw == NULL)
+	if (i_getpwuid(uid, &pw) <= 0)
 		ret = dec2str(uid);
 	else
-		ret = t_strdup_printf("%s(%s)", dec2str(uid), pw->pw_name);
+		ret = t_strdup_printf("%s(%s)", dec2str(uid), pw.pw_name);
 	errno = old_errno;
 	return ret;
 }
 
 static const char *get_gid_str(gid_t gid)
 {
-	const struct group *group;
+	struct group group;
 	const char *ret;
 	int old_errno = errno;
 
-	group = getgrgid(gid);
-	if (group == NULL)
+	if (i_getgrgid(gid, &group) <= 0)
 		ret = dec2str(gid);
 	else
-		ret = t_strdup_printf("%s(%s)", dec2str(gid), group->gr_name);
+		ret = t_strdup_printf("%s(%s)", dec2str(gid), group.gr_name);
 	errno = old_errno;
 	return ret;
 }
@@ -158,16 +155,20 @@
 
 static gid_t get_group_id(const char *name)
 {
-	struct group *group;
+	struct group group;
 	gid_t gid;
 
 	if (str_to_gid(name, &gid) == 0)
 		return gid;
 
-	group = getgrnam(name);
-	if (group == NULL)
+	switch (i_getgrnam(name, &group)) {
+	case -1:
+		i_fatal("getgrnam(%s) failed: %m", name);
+	case 0:
 		i_fatal("unknown group name in extra_groups: %s", name);
-	return group->gr_gid;
+	default:
+		return group.gr_gid;
+	}
 }
 
 static void fix_groups_list(const struct restrict_access_settings *set,
--- a/src/login-common/sasl-server.c	Thu Mar 10 18:44:20 2011 +0200
+++ b/src/login-common/sasl-server.c	Thu Mar 17 16:37:22 2011 +0200
@@ -67,6 +67,10 @@
 		auth_flags |= AUTH_REQUEST_FLAG_VALID_CLIENT_CERT;
 	if (client->secured)
 		auth_flags |= AUTH_REQUEST_FLAG_SECURED;
+	if (client->trusted) {
+		/* e.g. webmail */
+		auth_flags |= AUTH_REQUEST_FLAG_NO_PENALTY;
+	}
 	return auth_flags;
 }
 
--- a/src/master/common.h	Thu Mar 10 18:44:20 2011 +0200
+++ b/src/master/common.h	Thu Mar 17 16:37:22 2011 +0200
@@ -9,7 +9,7 @@
 extern gid_t master_gid;
 extern bool core_dumps_disabled;
 extern const char *ssl_manual_key_password;
-extern int null_fd, master_dead_pipe_fd[2];
+extern int null_fd, global_master_dead_pipe_fd[2];
 extern struct service_list *services;
 
 void process_exec(const char *cmd, const char *extra_args[]) ATTR_NORETURN;
--- a/src/master/main.c	Thu Mar 10 18:44:20 2011 +0200
+++ b/src/master/main.c	Thu Mar 17 16:37:22 2011 +0200
@@ -9,6 +9,7 @@
 #include "env-util.h"
 #include "hostpid.h"
 #include "abspath.h"
+#include "ipwd.h"
 #include "execv-const.h"
 #include "restrict-process-size.h"
 #include "master-service.h"
@@ -28,8 +29,6 @@
 #include <unistd.h>
 #include <fcntl.h>
 #include <sys/stat.h>
-#include <pwd.h>
-#include <grp.h>
 
 #define DOVECOT_CONFIG_BIN_PATH BINDIR"/doveconf"
 
@@ -42,7 +41,7 @@
 gid_t master_gid;
 bool core_dumps_disabled;
 const char *ssl_manual_key_password;
-int null_fd, master_dead_pipe_fd[2];
+int null_fd, global_master_dead_pipe_fd[2];
 struct service_list *services;
 
 static char *pidfile_path;
@@ -87,7 +86,7 @@
 int get_uidgid(const char *user, uid_t *uid_r, gid_t *gid_r,
 	       const char **error_r)
 {
-	struct passwd *pw;
+	struct passwd pw;
 
 	if (*user == '\0') {
 		*uid_r = (uid_t)-1;
@@ -95,32 +94,40 @@
 		return 0;
 	}
 
-	if ((pw = getpwnam(user)) == NULL) {
+	switch (i_getpwnam(user, &pw)) {
+	case -1:
+		*error_r = t_strdup_printf("getpwnam(%s) failed: %m", user);
+		return -1;
+	case 0:
 		*error_r = t_strdup_printf("User doesn't exist: %s", user);
 		return -1;
+	default:
+		*uid_r = pw.pw_uid;
+		*gid_r = pw.pw_gid;
+		return 0;
 	}
-
-	*uid_r = pw->pw_uid;
-	*gid_r = pw->pw_gid;
-	return 0;
 }
 
 int get_gid(const char *group, gid_t *gid_r, const char **error_r)
 {
-	struct group *gr;
+	struct group gr;
 
 	if (*group == '\0') {
 		*gid_r = (gid_t)-1;
 		return 0;
 	}
 
-	if ((gr = getgrnam(group)) == NULL) {
+	switch (i_getgrnam(group, &gr)) {
+	case -1:
+		*error_r = t_strdup_printf("getgrnam(%s) failed: %m", group);
+		return -1;
+	case 0:
 		*error_r = t_strdup_printf("Group doesn't exist: %s", group);
 		return -1;
+	default:
+		*gid_r = gr.gr_gid;
+		return 0;
 	}
-
-	*gid_r = gr->gr_gid;
-	return 0;
 }
 
 static void ATTR_NORETURN ATTR_FORMAT(2, 0)
@@ -725,10 +732,10 @@
 			i_fatal("Can't open /dev/null: %m");
 		fd_close_on_exec(null_fd, TRUE);
 	} while (null_fd <= STDERR_FILENO);
-	if (pipe(master_dead_pipe_fd) < 0)
+	if (pipe(global_master_dead_pipe_fd) < 0)
 		i_fatal("pipe() failed: %m");
-	fd_close_on_exec(master_dead_pipe_fd[0], TRUE);
-	fd_close_on_exec(master_dead_pipe_fd[1], TRUE);
+	fd_close_on_exec(global_master_dead_pipe_fd[0], TRUE);
+	fd_close_on_exec(global_master_dead_pipe_fd[1], TRUE);
 
 	set = master_settings_read();
 	if (ask_key_pass) {
@@ -774,8 +781,12 @@
 	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");
+	if (strcmp(services->service_set->log_path, "/dev/stderr") != 0 &&
+	    strcmp(services->service_set->info_log_path, "/dev/stderr") != 0 &&
+	    strcmp(services->service_set->debug_log_path, "/dev/stderr") != 0) {
+		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);
 
--- a/src/master/master-settings.c	Thu Mar 10 18:44:20 2011 +0200
+++ b/src/master/master-settings.c	Thu Mar 17 16:37:22 2011 +0200
@@ -457,12 +457,8 @@
 		service_set_login_dump_core(service);
 	}
 	set->protocols_split = p_strsplit_spaces(pool, set->protocols, " ");
-	if (set->protocols_split[0] == NULL) {
-		*error_r = "No protocols defined, "
-			"if you don't want any use protocols=none";
-		return FALSE;
-	}
-	if (strcmp(set->protocols_split[0], "none") == 0 &&
+	if (set->protocols_split[0] != NULL &&
+	    strcmp(set->protocols_split[0], "none") == 0 &&
 	    set->protocols_split[1] == NULL)
 		set->protocols_split[0] = NULL;
 
--- a/src/master/service-monitor.c	Thu Mar 10 18:44:20 2011 +0200
+++ b/src/master/service-monitor.c	Thu Mar 17 16:37:22 2011 +0200
@@ -344,6 +344,11 @@
 	services_log_init(service_list);
 	service_anvil_monitor_start(service_list);
 
+	if (pipe(service_list->master_dead_pipe_fd) < 0)
+		i_error("pipe() failed: %m");
+	fd_close_on_exec(service_list->master_dead_pipe_fd[0], TRUE);
+	fd_close_on_exec(service_list->master_dead_pipe_fd[1], TRUE);
+
 	array_foreach(&service_list->services, services) {
 		struct service *service = *services;
 
@@ -422,6 +427,15 @@
 {
 	struct service *const *services;
 
+	if (service_list->master_dead_pipe_fd[0] != -1) {
+		if (close(service_list->master_dead_pipe_fd[0]) < 0)
+			i_error("close(master dead pipe) failed: %m");
+		if (close(service_list->master_dead_pipe_fd[1]) < 0)
+			i_error("close(master dead pipe) failed: %m");
+		service_list->master_dead_pipe_fd[0] = -1;
+		service_list->master_dead_pipe_fd[1] = -1;
+	}
+
 	array_foreach(&service_list->services, services)
 		service_monitor_stop(*services);
 
--- a/src/master/service-process.c	Thu Mar 10 18:44:20 2011 +0200
+++ b/src/master/service-process.c	Thu Mar 17 16:37:22 2011 +0200
@@ -26,8 +26,6 @@
 #include "service-process-notify.h"
 #include "service-process.h"
 
-#include <grp.h>
-#include <pwd.h>
 #include <stdlib.h>
 #include <unistd.h>
 #include <fcntl.h>
@@ -116,7 +114,13 @@
 		break;
 	}
 	dup2_append(&dups, service->status_fd[1], MASTER_STATUS_FD);
-	dup2_append(&dups, master_dead_pipe_fd[1], MASTER_DEAD_FD);
+	if (service->type != SERVICE_TYPE_ANVIL) {
+		dup2_append(&dups, service->list->master_dead_pipe_fd[1],
+			    MASTER_DEAD_FD);
+	} else {
+		dup2_append(&dups, global_master_dead_pipe_fd[1],
+			    MASTER_DEAD_FD);
+	}
 
 	if (service->type == SERVICE_TYPE_LOG) {
 		/* keep stderr as-is. this is especially important when
--- a/src/master/service.c	Thu Mar 10 18:44:20 2011 +0200
+++ b/src/master/service.c	Thu Mar 17 16:37:22 2011 +0200
@@ -426,18 +426,16 @@
 	return FALSE;
 }
 
-int services_create(const struct master_settings *set,
-		    struct service_list **services_r, const char **error_r)
+static int
+services_create_real(const struct master_settings *set, pool_t pool,
+		     struct service_list **services_r, const char **error_r)
 {
 	struct service_list *service_list;
 	struct service *service;
 	struct service_settings *const *service_settings;
-	pool_t pool;
 	const char *error;
 	unsigned int i, count;
 
-	pool = pool_alloconly_create("services pool", 4096);
-
 	service_list = p_new(pool, struct service_list, 1);
 	service_list->refcount = 1;
 	service_list->pool = pool;
@@ -446,6 +444,8 @@
 	service_list->set = set;
 	service_list->master_log_fd[0] = -1;
 	service_list->master_log_fd[1] = -1;
+	service_list->master_dead_pipe_fd[0] = -1;
+	service_list->master_dead_pipe_fd[1] = -1;
 
 	service_settings = array_get(&set->services, &count);
 	p_array_init(&service_list->services, pool, count);
@@ -504,6 +504,19 @@
 	return 0;
 }
 
+int services_create(const struct master_settings *set,
+		    struct service_list **services_r, const char **error_r)
+{
+	pool_t pool;
+
+	pool = pool_alloconly_create("services pool", 4096);
+	if (services_create_real(set, pool, services_r, error_r) < 0) {
+		pool_unref(&pool);
+		return -1;
+	}
+	return 0;
+}
+
 void service_signal(struct service *service, int signo)
 {
 	struct service_process *process = service->processes;
--- a/src/master/service.h	Thu Mar 10 18:44:20 2011 +0200
+++ b/src/master/service.h	Thu Mar 17 16:37:22 2011 +0200
@@ -119,6 +119,8 @@
 	int master_log_fd[2];
 	struct service_process_notify *log_byes;
 
+	int master_dead_pipe_fd[2];
+
 	ARRAY_DEFINE(services, struct service *);
 
 	unsigned int destroyed:1;
--- a/src/plugins/acl/doveadm-acl.c	Thu Mar 10 18:44:20 2011 +0200
+++ b/src/plugins/acl/doveadm-acl.c	Thu Mar 17 16:37:22 2011 +0200
@@ -218,6 +218,17 @@
 	return ctx;
 }
 
+static bool is_standard_right(const char *name)
+{
+	unsigned int i;
+
+	for (i = 0; all_mailbox_rights[i] != NULL; i++) {
+		if (strcmp(all_mailbox_rights[i], name) == 0)
+			return TRUE;
+	}
+	return FALSE;
+}
+
 static void
 cmd_acl_set_run(struct doveadm_mail_cmd_context *ctx, struct mail_user *user)
 {
@@ -250,9 +261,17 @@
 			right++;
 			dest = &dest_neg_rights;
 		}
-		if (strcmp(right, "all") != 0)
-			array_append(dest, &right, 1);
-		else {
+		if (strcmp(right, "all") != 0) {
+			if (*right == ':') {
+				/* non-standard right */
+				right++;
+				array_append(dest, &right, 1);
+			} else if (is_standard_right(right)) {
+				array_append(dest, &right, 1);
+			} else {
+				i_fatal("Invalid right '%s'", right);
+			}
+		} else {
 			for (j = 0; all_mailbox_rights[j] != NULL; j++)
 				array_append(dest, &all_mailbox_rights[j], 1);
 		}
--- a/src/plugins/expire/expire-plugin.c	Thu Mar 10 18:44:20 2011 +0200
+++ b/src/plugins/expire/expire-plugin.c	Thu Mar 17 16:37:22 2011 +0200
@@ -90,12 +90,34 @@
 	}
 }
 
+static void first_save_timestamp(struct mailbox *box, time_t *stamp_r)
+{
+	struct mailbox_transaction_context *t;
+	const struct mail_index_header *hdr;
+	struct mail *mail;
+
+	*stamp_r = ioloop_time;
+
+	t = mailbox_transaction_begin(box, 0);
+	mail = mail_alloc(t, 0, NULL);
+
+	/* find the first non-expunged mail. we're here because the first
+	   mail was expunged, so don't bother checking it. */
+	hdr = mail_index_get_header(box->view);
+	if (hdr->messages_count > 0) {
+		mail_set_seq(mail, 1);
+		(void)mail_get_save_date(mail, stamp_r);
+	}
+	mail_free(&mail);
+	(void)mailbox_transaction_commit(&t);
+}
+
 static int
 expire_mailbox_transaction_commit(struct mailbox_transaction_context *t,
 				  struct mail_transaction_commit_changes *changes_r)
 {
-	struct expire_mail_user *euser =
-		EXPIRE_USER_CONTEXT(t->box->storage->user);
+	struct mail_user *user = t->box->storage->user;
+	struct expire_mail_user *euser = EXPIRE_USER_CONTEXT(user);
 	struct expire_mailbox *xpr_box = EXPIRE_CONTEXT(t->box);
 	struct expire_transaction_context *xt = EXPIRE_CONTEXT(t);
 	struct mailbox *box = t->box;
@@ -106,6 +128,16 @@
 	if (xt->first_expunged) {
 		/* first mail expunged. dict needs updating. */
 		first_nonexpunged_timestamp(t, &new_stamp);
+		if (new_stamp == 0 && xt->saves) {
+			/* everything was expunged, but also within this
+			   transaction a new message was saved */
+			new_stamp = ioloop_time;
+		}
+		if (user->mail_debug) {
+			i_debug("expire: Expunging first message in %s, "
+				"updating timestamp to %ld",
+				box->vname, (long)new_stamp);
+		}
 		update_dict = TRUE;
 	}
 
@@ -123,31 +155,39 @@
 				  box->storage->user->username, "/",
 				  mailbox_get_vname(box), NULL);
 		if (xt->first_expunged) {
-			if (new_stamp == 0 && xt->saves)
-				new_stamp = ioloop_time;
+			/* new_stamp is already set */
 		} else {
 			i_assert(xt->saves);
 			/* saved new mails. dict needs to be updated only if
 			   this is the first mail in the database */
 			ret = dict_lookup(euser->db, pool_datastack_create(),
 					  key, &value);
-			update_dict = ret == 0 ||
-				(ret > 0 && strtoul(value, NULL, 10) == 0);
-			/* may not be exactly the first message's save time
-			   but a few second difference doesn't matter */
-			new_stamp = ioloop_time;
+			if (ret == 0) {
+				/* first time saving here with expire enabled */
+				first_save_timestamp(box, &new_stamp);
+				update_dict = TRUE;
+			} else if (strcmp(value, "0") == 0) {
+				/* we're saving the first mail to this mailbox.
+				   ioloop_time may not be exactly the first
+				   message's save time, but a few seconds
+				   difference doesn't matter */
+				new_stamp = ioloop_time;
+				update_dict = TRUE;
+			} else {
+				/* already exists */
+			}
+			if (user->mail_debug && update_dict) {
+				i_debug("expire: Saving first message to %s, "
+					"updating timestamp to %ld",
+					box->vname, (long)new_stamp);
+			}
 		}
 
 		if (update_dict) {
 			struct dict_transaction_context *dctx;
 
 			dctx = dict_transaction_begin(euser->db);
-			if (new_stamp == 0) {
-				/* everything expunged */
-				dict_unset(dctx, key);
-			} else {
-				dict_set(dctx, key, dec2str(new_stamp));
-			}
+			dict_set(dctx, key, dec2str(new_stamp));
 			dict_transaction_commit(&dctx);
 		}
 	} T_END;
--- a/src/plugins/virtual/virtual-config.c	Thu Mar 10 18:44:20 2011 +0200
+++ b/src/plugins/virtual/virtual-config.c	Thu Mar 17 16:37:22 2011 +0200
@@ -135,7 +135,7 @@
 	if (strcasecmp(line, "INBOX") == 0)
 		line = "INBOX";
 	bbox->name = p_strdup(ctx->pool, line);
-	if (*line == '-' || *line == '!') line++;
+	if (*line == '-' || *line == '+' || *line == '!') line++;
 	bbox->ns = strcasecmp(line, "INBOX") == 0 ?
 		mail_namespace_find_inbox(user->namespaces) :
 		mail_namespace_find(user->namespaces, line);
@@ -149,6 +149,11 @@
 					   bbox->name);
 		return -1;
 	}
+	if (bbox->name[0] == '+') {
+		bbox->name++;
+		bbox->clear_recent = TRUE;
+	}
+
 	if (strchr(bbox->name, '*') != NULL ||
 	    strchr(bbox->name, '%') != NULL) {
 		name = bbox->name[0] == '-' ? bbox->name + 1 : bbox->name;
--- a/src/plugins/virtual/virtual-storage.c	Thu Mar 10 18:44:20 2011 +0200
+++ b/src/plugins/virtual/virtual-storage.c	Thu Mar 17 16:37:22 2011 +0200
@@ -165,7 +165,8 @@
 
 	i_assert(bbox->box == NULL);
 
-	flags |= MAILBOX_FLAG_KEEP_RECENT;
+	if (!bbox->clear_recent)
+		flags |= MAILBOX_FLAG_KEEP_RECENT;
 
 	mailbox = bbox->name;
 	ns = mail_namespace_find(user->namespaces, mailbox);
--- a/src/plugins/virtual/virtual-storage.h	Thu Mar 10 18:44:20 2011 +0200
+++ b/src/plugins/virtual/virtual-storage.h	Thu Mar 17 16:37:22 2011 +0200
@@ -95,6 +95,7 @@
 
 	unsigned int sync_seen:1;
 	unsigned int wildcard:1;
+	unsigned int clear_recent:1;
 	unsigned int uids_nonsorted:1;
 };
 ARRAY_DEFINE_TYPE(virtual_backend_box, struct virtual_backend_box *);