changeset 15578:13e74bd5ac8c

Merged changes from v2.1 tree.
author Timo Sirainen <tss@iki.fi>
date Sat, 05 Jan 2013 01:14:11 +0200
parents 922049229f7f (current diff) 3eeb5270963b (diff)
children d1eaa348482f
files .hgsigs .hgtags Makefile.am NEWS configure.ac src/auth/auth-master-connection.c src/auth/auth-request.c src/auth/auth-stream.c src/auth/auth-stream.h src/auth/db-ldap.c src/auth/mech.c src/auth/password-scheme.c src/auth/userdb-passwd.c src/config/main.c src/doveadm/client-connection.c src/doveadm/doveadm-mail-server.c src/doveadm/doveadm-mail.h src/doveadm/doveadm-print-flow.c src/doveadm/doveadm-print-pager.c src/doveadm/doveadm-print-table.c src/doveadm/server-connection.c src/imap/cmd-append.c src/imap/cmd-copy.c src/imap/imap-sync.c src/indexer/master-connection.c src/lib-auth/auth-master.c src/lib-auth/auth-master.h src/lib-dict/dict-file.c src/lib-index/mail-cache-compress.c src/lib-index/mail-cache-fields.c src/lib-index/mail-cache-private.h src/lib-index/mail-cache-transaction.c src/lib-index/mail-cache.c src/lib-index/mail-index-alloc-cache.c src/lib-index/mail-index-transaction-view.c src/lib-lda/mail-deliver.c src/lib-master/mountpoint-list.c src/lib-ssl-iostream/iostream-openssl.c src/lib-storage/index/dbox-multi/mdbox-map.c src/lib-storage/index/dbox-multi/mdbox-save.c src/lib-storage/index/dbox-multi/mdbox-storage-rebuild.c src/lib-storage/index/index-sync.c src/lib-storage/index/raw/raw-mail.c src/lib-storage/index/shared/shared-storage.c src/lib-storage/mail-storage.h src/lib-storage/mail-user.c src/lib-storage/mail-user.h src/lib-storage/mailbox-list.c src/lib/Makefile.am src/lib/buffer.c src/lib/buffer.h src/lib/mkdir-parents.c src/lib/mkdir-parents.h src/lib/str.c src/lib/test-lib.c src/lib/test-lib.h src/lib/unichar.c src/lib/unichar.h src/lib/var-expand.c src/login-common/login-proxy.c src/login-common/ssl-proxy-openssl.c src/plugins/acl/acl-mailbox.c src/plugins/expire/doveadm-expire.c src/plugins/fts-solr/fts-backend-solr-old.c src/plugins/fts-solr/fts-backend-solr.c src/plugins/fts-solr/solr-connection.c src/plugins/fts/fts-api-private.h src/plugins/fts/fts-build-mail.c src/plugins/fts/fts-parser-script.c src/plugins/lazy-expunge/lazy-expunge-plugin.c src/plugins/mailbox-alias/mailbox-alias-plugin.c src/plugins/quota/quota-storage.c src/plugins/stats/stats-plugin.c src/plugins/virtual/virtual-config.c src/plugins/virtual/virtual-mail.c src/stats/mail-session.c
diffstat 64 files changed, 501 insertions(+), 170 deletions(-) [+]
line wrap: on
line diff
--- a/.hgsigs	Sat Jan 05 00:37:26 2013 +0200
+++ b/.hgsigs	Sat Jan 05 01:14:11 2013 +0200
@@ -51,3 +51,5 @@
 bc86680293d256d5a8009690caeb73ab2e34e359 0 iEYEABECAAYFAlAZaTUACgkQyUhSUUBVisnTAACfU1pB34RrXEyLnpnL4Ee/oeNBYcoAnRWxTqx870Efjwf+eBPzafO0C/NU
 1a6c3b4e92e4174d3b1eb0a7c841f97e8fb9e590 0 iEYEABECAAYFAlBYwJMACgkQyUhSUUBVisn2PwCeIJxfB5ebXlAbtMcjrZBCmB8Kg1sAn39BC9rQoR/wjD2/ix1JaxH7gHOT
 f5941f3ac7622361634b6cba464da79cc883d1bb 0 iEYEABECAAYFAlCO62QACgkQyUhSUUBViskUtQCffWRQpSqaf+iCOipsTWE1D93TwVEAnAhxx1aezuqDVAsjWoYZkrIufO28
+741d800a192fa23572bb14196df2a8917cf20614 0 iEYEABECAAYFAlC3A5EACgkQyUhSUUBVisnmlACcCm6jc7NRoTkBtrJLcz+P325U1xcAn2+0eghqEMiP+rzRJC55oQxV00Zy
+75bfda4a7c6c9aa04b6a6ef233fc527356171a06 0 iEYEABECAAYFAlC4WKwACgkQyUhSUUBViskaOACgmcwWV8hgsCOWvkbdh0OIw1ImSQYAn1RcTL0CG3M8+XG7QrrxSfQ7+V99
--- a/.hgtags	Sat Jan 05 00:37:26 2013 +0200
+++ b/.hgtags	Sat Jan 05 01:14:11 2013 +0200
@@ -88,3 +88,5 @@
 bc86680293d256d5a8009690caeb73ab2e34e359 2.1.9
 1a6c3b4e92e4174d3b1eb0a7c841f97e8fb9e590 2.1.10
 f5941f3ac7622361634b6cba464da79cc883d1bb 2.2.alpha1
+741d800a192fa23572bb14196df2a8917cf20614 2.1.11
+75bfda4a7c6c9aa04b6a6ef233fc527356171a06 2.1.12
--- a/Makefile.am	Sat Jan 05 00:37:26 2013 +0200
+++ b/Makefile.am	Sat Jan 05 01:14:11 2013 +0200
@@ -64,12 +64,12 @@
 	grep -v '^LIBDOVECOT_.*_INCLUDE' dovecot-config | \
 	grep -v '^LIBDOVECOT.*_DEPS' | sed \
 	-e "s|^\(LIBDOVECOT\)=.*$$|\1='-L$(pkglibdir) -ldovecot'|" \
-	-e "s|^\(LIBDOVECOT_LOGIN\)=.*$$|\1=-ldovecot-login|" \
+	-e "s|^\(LIBDOVECOT_LOGIN\)=.*$$|\1='-ldovecot-login $(SSL_LIBS)'|" \
 	-e "s|^\(LIBDOVECOT_SQL\)=.*$$|\1=-ldovecot-sql|" \
 	-e "s|^\(LIBDOVECOT_SSL\)=.*$$|\1=-ldovecot-ssl|" \
 	-e "s|^\(LIBDOVECOT_COMPRESS\)=.*$$|\1=-ldovecot-compression|" \
 	-e "s|^\(LIBDOVECOT_LDA\)=.*$$|\1=-ldovecot-lda|" \
-	-e "s|^\(LIBDOVECOT_STORAGE\)=.*$$|\1=-ldovecot-storage|" \
+	-e "s|^\(LIBDOVECOT_STORAGE\)=.*$$|\1='-ldovecot-storage $(LINKED_STORAGE_LDADD)'|" \
 	-e "s|^\(LIBDOVECOT_INCLUDE\)=.*$$|\1=-I$(pkgincludedir)|" \
 	> $(DESTDIR)$(pkglibdir)/dovecot-config
 
--- a/NEWS	Sat Jan 05 00:37:26 2013 +0200
+++ b/NEWS	Sat Jan 05 01:14:11 2013 +0200
@@ -30,6 +30,29 @@
 	+ LMTP proxy: Implemented XCLIENT extension for passing remote IP
 	  address through proxy.
 
+v2.1.12 2012-11-30  Timo Sirainen <tss@iki.fi>
+
+	- dovecot-config in v2.1.11 caused build problems with Pigeonhole
+
+v2.1.11 2012-11-29  Timo Sirainen <tss@iki.fi>
+
+	* lmtp/lda: dovecot.index.cache file is no longer fully mapped to
+	  memory, allowing mail deliveries to work even if the file is huge.
+	* auth: userdb passwd lookups are now done by auth worker processes
+	  instead of auth master process (as it was documented, but
+	  accidentally didn't work that way).
+
+	+ lmtp: lmtp_rcpt_check_quota=yes setting checks quota on RCPT TO.
+	- lmtp: After successful proxying RCPT TO, the next one to a
+	  nonexistent user gave tempfail error instead of "user not found".
+	- lmtp proxy: Fixed hanging if remote server was down.
+	- imap: Fixed crash when SEARCH contained multiple KEYWORD parameters.
+	- doveadm: Various fixes to handling doveadm-server connections.
+	- -i <instance name> parameter for Dovecot tools didn't work correctly.
+	- director was somewhat broken in v2.1.10. This version also includes
+	  various reliability enhancements.
+	- auth: passdb imap was broken in v2.1.10.
+
 v2.1.10 2012-09-18  Timo Sirainen <tss@iki.fi>
 
 	+ imap: Implemented THREAD=ORDEREDSUBJECT extension.
--- a/doc/example-config/Makefile.am	Sat Jan 05 00:37:26 2013 +0200
+++ b/doc/example-config/Makefile.am	Sat Jan 05 01:14:11 2013 +0200
@@ -13,6 +13,7 @@
 example_DATA = \
 	dovecot.conf \
 	dovecot-db.conf.ext \
+	dovecot-dict-auth.conf.ext \
 	dovecot-dict-sql.conf.ext \
 	dovecot-ldap.conf.ext \
 	dovecot-sql.conf.ext
--- a/doc/man/doveadm-expunge.1.in	Sat Jan 05 00:37:26 2013 +0200
+++ b/doc/man/doveadm-expunge.1.in	Sat Jan 05 01:14:11 2013 +0200
@@ -1,5 +1,5 @@
-.\" Copyright (c) 2010 Dovecot authors, see the included COPYING file
-.TH DOVEADM\-EXPUNGE 1 "2010-11-25" "Dovecot v2.1" "Dovecot"
+.\" Copyright (c) 2010-2012 Dovecot authors, see the included COPYING file
+.TH DOVEADM\-EXPUNGE 1 "2012-11-27" "Dovecot v2.1" "Dovecot"
 .SH NAME
 doveadm\-expunge \- Expunge messages matching given search query
 .\"------------------------------------------------------------------------
@@ -47,6 +47,10 @@
 .\"-------------------------------------
 @INCLUDE:option-A@
 .\"-------------------------------------
+.TP
+.B \-d
+Delete the mailbox if it is empty after expunging.
+.\"-------------------------------------
 @INCLUDE:option-S-socket@
 .\"-------------------------------------
 @INCLUDE:option-u-user@
--- a/src/auth/auth-master-connection.c	Sat Jan 05 00:37:26 2013 +0200
+++ b/src/auth/auth-master-connection.c	Sat Jan 05 01:14:11 2013 +0200
@@ -240,7 +240,8 @@
 
 	auth_request_log_error(auth_request, "userdb",
 		"client doesn't have lookup permissions for this user: %s "
-		"(change userdb socket permissions)", reason);
+		"(to bypass this check, set: service auth { unix_listener %s { mode=0777 } })",
+		reason, conn->path);
 	return -1;
 }
 
--- a/src/auth/db-ldap.c	Sat Jan 05 00:37:26 2013 +0200
+++ b/src/auth/db-ldap.c	Sat Jan 05 01:14:11 2013 +0200
@@ -990,7 +990,7 @@
 	char *ldap_attr;
 
 	if (*data != '\0') {
-		ldap_attr = p_strdup(ctx->pool, data);
+		ldap_attr = p_strdup(ctx->pool, t_strcut(data, ':'));
 		array_append(&ctx->attr_names, &ldap_attr, 1);
 	}
 	return NULL;
@@ -1198,28 +1198,42 @@
 	return ctx;
 }
 
+static const char *db_ldap_field_get_default(const char *data)
+{
+	const char *p;
+
+	p = strchr(data, ':');
+	if (p == NULL)
+		return "";
+	else {
+		/* default value given */
+		return p+1;
+	}
+}
+
 static const char *db_ldap_field_expand(const char *data, void *context)
 {
 	struct db_ldap_result_iterate_context *ctx = context;
 	struct db_ldap_value *ldap_value;
+	const char *field_name = t_strcut(data, ':');
 
-	ldap_value = hash_table_lookup(ctx->ldap_attrs, data);
+	ldap_value = hash_table_lookup(ctx->ldap_attrs, field_name);
 	if (ldap_value == NULL) {
-		/* ldap attribute wasn't requested */
+		/* requested ldap attribute wasn't returned at all */
 		if (ctx->debug)
-			str_printfa(ctx->debug, "; %s missing", data);
-		return "";
+			str_printfa(ctx->debug, "; %s missing", field_name);
+		return db_ldap_field_get_default(data);
 	}
 	ldap_value->used = TRUE;
 
 	if (ldap_value->values[0] == NULL) {
 		/* no value for ldap attribute */
-		return "";
+		return db_ldap_field_get_default(data);
 	}
 	if (ldap_value->values[1] != NULL) {
 		auth_request_log_warning(ctx->auth_request, "ldap",
 			"Multiple values found for '%s', using value '%s'",
-			data, ldap_value->values[0]);
+			field_name, ldap_value->values[0]);
 	}
 	return ldap_value->values[0];
 }
--- a/src/auth/password-scheme.c	Sat Jan 05 00:37:26 2013 +0200
+++ b/src/auth/password-scheme.c	Sat Jan 05 01:14:11 2013 +0200
@@ -818,6 +818,7 @@
 	{ "SSHA256", PW_ENCODING_BASE64, 0, ssha256_verify, ssha256_generate },
 	{ "SSHA512", PW_ENCODING_BASE64, 0, ssha512_verify, ssha512_generate },
 	{ "PLAIN", PW_ENCODING_NONE, 0, NULL, plain_generate },
+	{ "CLEAR", PW_ENCODING_NONE, 0, NULL, plain_generate },
 	{ "CLEARTEXT", PW_ENCODING_NONE, 0, NULL, plain_generate },
 	{ "PLAIN-TRUNC", PW_ENCODING_NONE, 0, plain_trunc_verify, plain_generate },
 	{ "CRAM-MD5", PW_ENCODING_HEX, CRAM_MD5_CONTEXTLEN,
--- a/src/auth/userdb-passwd.c	Sat Jan 05 00:37:26 2013 +0200
+++ b/src/auth/userdb-passwd.c	Sat Jan 05 01:14:11 2013 +0200
@@ -217,11 +217,10 @@
 	module = p_new(pool, struct passwd_userdb_module, 1);
 	module->module.cache_key = USER_CACHE_KEY;
 	module->tmpl = userdb_template_build(pool, "passwd", args);
+	module->module.blocking = TRUE;
 
-	if (userdb_template_remove(module->tmpl, "blocking", &value)) {
-		module->module.blocking = value == NULL ||
-			strcasecmp(value, "yes") == 0;
-	}
+	if (userdb_template_remove(module->tmpl, "blocking", &value))
+		module->module.blocking = strcasecmp(value, "yes") == 0;
 	/* FIXME: backwards compatibility */
 	if (!userdb_template_is_empty(module->tmpl))
 		i_warning("userdb passwd: Move templates args to override_fields setting");
--- a/src/config/main.c	Sat Jan 05 00:37:26 2013 +0200
+++ b/src/config/main.c	Sat Jan 05 01:14:11 2013 +0200
@@ -26,13 +26,17 @@
 	restrict_access_by_env(NULL, FALSE);
 	restrict_access_allow_coredumps(TRUE);
 
-	master_service_init_finish(master_service);
 	config_parse_load_modules();
 
 	path = master_service_get_config_path(master_service);
 	if (config_parse_file(path, TRUE, NULL, &error) <= 0)
 		i_fatal("%s", error);
 
+	/* notify about our success only after successfully parsing the
+	   config file, so if the parsing fails, master won't immediately
+	   just recreate this process (and fail again and so on). */
+	master_service_init_finish(master_service);
+
 	master_service_run(master_service, client_connected);
 	config_connections_destroy_all();
 
--- a/src/doveadm/client-connection.c	Sat Jan 05 00:37:26 2013 +0200
+++ b/src/doveadm/client-connection.c	Sat Jan 05 01:14:11 2013 +0200
@@ -57,6 +57,7 @@
 
 	ctx = doveadm_mail_cmd_init(cmd, set);
 	ctx->full_args = (const void *)(argv + 1);
+	ctx->proxying = TRUE;
 
 	ctx->service_flags |=
 		MAIL_STORAGE_SERVICE_FLAG_NO_LOG_INIT |
@@ -139,6 +140,10 @@
 		o_stream_nsend(conn->output, "\n+\n", 3);
 	}
 	pool_unref(&ctx->pool);
+
+	/* clear all headers */
+	doveadm_print_deinit();
+	doveadm_print_init(DOVEADM_PRINT_TYPE_SERVER);
 }
 
 static bool client_is_allowed_command(const struct doveadm_settings *set,
@@ -239,8 +244,11 @@
 	const unsigned char *data;
 	size_t size;
 
-	if ((line = i_stream_read_next_line(conn->input)) == NULL)
+	if ((line = i_stream_read_next_line(conn->input)) == NULL) {
+		if (conn->input->eof)
+			return -1;
 		return 0;
+	}
 
 	if (*conn->set->doveadm_password == '\0') {
 		i_error("doveadm_password not set, "
--- a/src/doveadm/doveadm-mail-server.c	Sat Jan 05 00:37:26 2013 +0200
+++ b/src/doveadm/doveadm-mail-server.c	Sat Jan 05 01:14:11 2013 +0200
@@ -22,6 +22,11 @@
 #define DOVEADM_MAIL_SERVER_FAILED() \
 	(internal_failure || master_service_is_killed(master_service))
 
+struct doveadm_mail_server_cmd {
+	struct server_connection *conn;
+	char *username;
+};
+
 static HASH_TABLE(char *, struct doveadm_server *) servers;
 static pool_t server_pool;
 static struct doveadm_mail_cmd_context *cmd_ctx;
@@ -78,16 +83,22 @@
 
 static void doveadm_cmd_callback(enum server_cmd_reply reply, void *context)
 {
-	struct server_connection *conn = context;
-	struct doveadm_server *server;
+	struct doveadm_mail_server_cmd *servercmd = context;
+	struct doveadm_server *server =
+		server_connection_get_server(servercmd->conn);
+	const char *username = t_strdup(servercmd->username);
+
+	i_free(servercmd->username);
+	i_free(servercmd);
 
 	switch (reply) {
 	case SERVER_CMD_REPLY_INTERNAL_FAILURE:
+		i_error("%s: Internal failure for %s", server->name, username);
 		internal_failure = TRUE;
 		master_service_stop(master_service);
 		return;
 	case SERVER_CMD_REPLY_UNKNOWN_USER:
-		i_error("No such user");
+		i_error("%s: No such user: %s", server->name, username);
 		if (cmd_ctx->exit_code == 0)
 			cmd_ctx->exit_code = EX_NOUSER;
 		break;
@@ -98,8 +109,8 @@
 		break;
 	}
 
-	server = server_connection_get_server(conn);
 	if (array_count(&server->queue) > 0) {
+		struct server_connection *conn;
 		char *const *usernamep = array_idx(&server->queue, 0);
 		char *username = *usernamep;
 
@@ -117,6 +128,7 @@
 static void doveadm_mail_server_handle(struct server_connection *conn,
 				       const char *username)
 {
+	struct doveadm_mail_server_cmd *servercmd;
 	string_t *cmd;
 	unsigned int i;
 
@@ -136,7 +148,12 @@
 		str_append_tabescaped(cmd, cmd_ctx->full_args[i]);
 	}
 	str_append_c(cmd, '\n');
-	server_connection_cmd(conn, str_c(cmd), doveadm_cmd_callback, conn);
+
+	servercmd = i_new(struct doveadm_mail_server_cmd, 1);
+	servercmd->conn = conn;
+	servercmd->username = i_strdup(username);
+	server_connection_cmd(conn, str_c(cmd),
+			      doveadm_cmd_callback, servercmd);
 }
 
 static void doveadm_server_flush_one(struct doveadm_server *server)
@@ -158,7 +175,7 @@
 	struct auth_master_connection *auth_conn;
 	struct auth_user_info info;
 	pool_t pool;
-	const char *proxy_host, *const *fields;
+	const char *auth_socket_path, *proxy_host, *const *fields;
 	unsigned int i;
 	bool proxying;
 	int ret;
@@ -176,14 +193,15 @@
 
 	pool = pool_alloconly_create("auth lookup", 1024);
 	auth_conn = mail_storage_service_get_auth_conn(ctx->storage_service);
+	auth_socket_path = auth_master_get_socket_path(auth_conn);
 	ret = auth_master_pass_lookup(auth_conn, input->username, &info,
 				      pool, &fields);
 	if (ret < 0) {
 		*error_r = fields[0] != NULL ?
 			t_strdup(fields[0]) : "passdb lookup failed";
-		*error_r = t_strdup_printf("%s (to see if user is proxied, "
+		*error_r = t_strdup_printf("%s: %s (to see if user is proxied, "
 					   "because doveadm_proxy_port is set)",
-					   *error_r);
+					   auth_socket_path, *error_r);
 	} else if (ret == 0) {
 		/* user not found from passdb. it could be in userdb though,
 		   so just continue with the default host */
@@ -199,7 +217,13 @@
 		if (!proxying)
 			ret = 0;
 		else if (proxy_host == NULL) {
-			*error_r = "Proxy is missing destination host";
+			*error_r = t_strdup_printf("%s: Proxy is missing destination host",
+						   auth_socket_path);
+			if (strstr(auth_socket_path, "/auth-userdb") != NULL) {
+				*error_r = t_strdup_printf(
+					"%s (maybe set auth_socket_path=director-userdb)",
+					*error_r);
+			}
 			ret = -1;
 		} else {
 			*host_r = t_strdup_printf("%s:%u", proxy_host,
--- a/src/doveadm/doveadm-mail.h	Sat Jan 05 00:37:26 2013 +0200
+++ b/src/doveadm/doveadm-mail.h	Sat Jan 05 01:14:11 2013 +0200
@@ -62,6 +62,8 @@
 	/* if non-zero, exit with this code */
 	int exit_code;
 
+	/* This command is being called by a remote doveadm client. */
+	unsigned int proxying:1;
 	/* We're handling only a single user */
 	unsigned int iterate_single_user:1;
 	/* We're going through all users (not set for wildcard usernames) */
--- a/src/doveadm/doveadm-print-flow.c	Sat Jan 05 00:37:26 2013 +0200
+++ b/src/doveadm/doveadm-print-flow.c	Sat Jan 05 01:14:11 2013 +0200
@@ -63,7 +63,7 @@
 		if ((hdr->flags & DOVEADM_PRINT_HEADER_FLAG_HIDE_TITLE) == 0)
 			printf("%s=", hdr->title);
 	}
-	printf("%.*s", (int)size, value);
+	fwrite(value, 1, size, stdout);
 	if (size == 0) {
 		flow_next_hdr();
 		ctx->streaming = FALSE;
--- a/src/doveadm/doveadm-print-pager.c	Sat Jan 05 00:37:26 2013 +0200
+++ b/src/doveadm/doveadm-print-pager.c	Sat Jan 05 01:14:11 2013 +0200
@@ -56,7 +56,7 @@
 		ctx->streaming = TRUE;
 		printf("%s:\n", hdr->title);
 	}
-	printf("%.*s", (int)size, value);
+	fwrite(value, 1, size, stdout);
 	if (size == 0) {
 		pager_next_hdr();
 		ctx->streaming = FALSE;
--- a/src/doveadm/doveadm-print-tab.c	Sat Jan 05 00:37:26 2013 +0200
+++ b/src/doveadm/doveadm-print-tab.c	Sat Jan 05 01:14:11 2013 +0200
@@ -51,7 +51,7 @@
 	}
 	if (ctx.header_idx > 0)
 		printf("\t");
-	printf("%.*s", (int)size, value);
+	fwrite(value, 1, size, stdout);
 }
 
 static void doveadm_print_tab_flush(void)
--- a/src/doveadm/doveadm-print-table.c	Sat Jan 05 00:37:26 2013 +0200
+++ b/src/doveadm/doveadm-print-table.c	Sat Jan 05 01:14:11 2013 +0200
@@ -2,6 +2,7 @@
 
 #include "lib.h"
 #include "array.h"
+#include "str.h"
 #include "doveadm-print-private.h"
 
 #include <stdio.h>
@@ -24,6 +25,7 @@
 	pool_t pool;
 	ARRAY(struct doveadm_print_table_header) headers;
 	ARRAY_TYPE(const_string) buffered_values;
+	string_t *stream;
 	unsigned int hdr_idx;
 	unsigned int columns;
 
@@ -179,10 +181,17 @@
 }
 
 static void
-doveadm_print_table_print_stream(const unsigned char *value ATTR_UNUSED,
-				 size_t size ATTR_UNUSED)
+doveadm_print_table_print_stream(const unsigned char *value, size_t size)
 {
-	i_fatal("table formatter doesn't support multi-line values");
+	if (memchr(value, '\n', size) != NULL)
+		i_fatal("table formatter doesn't support multi-line values");
+
+	if (size != 0)
+		str_append_n(ctx->stream, value, size);
+	else {
+		doveadm_print_table_print(str_c(ctx->stream));
+		str_truncate(ctx->stream, 0);
+	}
 }
 
 static void doveadm_print_table_flush(void)
@@ -199,6 +208,7 @@
 	pool = pool_alloconly_create("doveadm print table", 2048);
 	ctx = p_new(pool, struct doveadm_print_table_context, 1);
 	ctx->pool = pool;
+	ctx->stream = str_new(default_pool, 128);
 	p_array_init(&ctx->headers, pool, 16);
 	i_array_init(&ctx->buffered_values, 64);
 	ctx->columns = DEFAULT_COLUMNS;
@@ -211,6 +221,7 @@
 
 static void doveadm_print_table_deinit(void)
 {
+	str_free(&ctx->stream);
 	array_free(&ctx->buffered_values);
 	pool_unref(&ctx->pool);
 	ctx = NULL;
--- a/src/doveadm/server-connection.c	Sat Jan 05 00:37:26 2013 +0200
+++ b/src/doveadm/server-connection.c	Sat Jan 05 01:14:11 2013 +0200
@@ -269,8 +269,10 @@
 			server_connection_callback(conn, reply);
 		} else
 			i_error("doveadm server sent broken input");
-		/* we're finished, close the connection */
-		server_connection_destroy(&conn);
+		if (conn->callback == NULL) {
+			/* we're finished, close the connection */
+			server_connection_destroy(&conn);
+		}
 		break;
 	}
 }
--- a/src/imap/cmd-append.c	Sat Jan 05 00:37:26 2013 +0200
+++ b/src/imap/cmd-append.c	Sat Jan 05 01:14:11 2013 +0200
@@ -597,7 +597,7 @@
 
 	msg = t_str_new(256);
 	save_count = seq_range_count(&changes.saved_uids);
-	if (save_count == 0) {
+	if (save_count == 0 || changes.no_read_perm) {
 		/* not supported by backend (virtual) */
 		str_append(msg, "OK Append completed.");
 	} else {
--- a/src/imap/cmd-copy.c	Sat Jan 05 00:37:26 2013 +0200
+++ b/src/imap/cmd-copy.c	Sat Jan 05 01:14:11 2013 +0200
@@ -132,8 +132,10 @@
 	else if (copy_count == 0) {
 		str_append(msg, "OK No messages found.");
 		pool_unref(&changes.pool);
-	} else if (seq_range_count(&changes.saved_uids) == 0) {
-		/* not supported by backend (virtual) */
+	} else if (seq_range_count(&changes.saved_uids) == 0 ||
+		   changes.no_read_perm) {
+		/* not supported by backend (virtual) or no read permissions
+		   for mailbox */
 		str_append(msg, move ? "OK Move completed." :
 			   "OK Copy completed.");
 		pool_unref(&changes.pool);
--- a/src/indexer/master-connection.c	Sat Jan 05 00:37:26 2013 +0200
+++ b/src/indexer/master-connection.c	Sat Jan 05 01:14:11 2013 +0200
@@ -134,7 +134,7 @@
 				mailbox, mailbox_get_last_error(box, NULL));
 			return -1;
 		}
-		i_info("Indexes disabled for Mailbox %s, skipping", mailbox);
+		i_info("Indexes disabled for mailbox %s, skipping", mailbox);
 		return 0;
 	}
 	ret = 0;
--- a/src/lib-auth/auth-master.c	Sat Jan 05 00:37:26 2013 +0200
+++ b/src/lib-auth/auth-master.c	Sat Jan 05 01:14:11 2013 +0200
@@ -107,6 +107,11 @@
 	i_free(conn);
 }
 
+const char *auth_master_get_socket_path(struct auth_master_connection *conn)
+{
+	return conn->auth_socket_path;
+}
+
 static void auth_request_lookup_abort(struct auth_master_connection *conn)
 {
 	io_loop_stop(conn->ioloop);
--- a/src/lib-auth/auth-master.h	Sat Jan 05 00:37:26 2013 +0200
+++ b/src/lib-auth/auth-master.h	Sat Jan 05 01:14:11 2013 +0200
@@ -28,6 +28,9 @@
 auth_master_init(const char *auth_socket_path, enum auth_master_flags flags);
 void auth_master_deinit(struct auth_master_connection **conn);
 
+/* Returns the auth_socket_path */
+const char *auth_master_get_socket_path(struct auth_master_connection *conn);
+
 /* Do a USER lookup. Returns -1 = error, 0 = user not found, 1 = ok.
    When returning -1 and fields[0] isn't NULL, it contains an error message
    that should be shown to user. */
--- a/src/lib-dict/dict-file.c	Sat Jan 05 00:37:26 2013 +0200
+++ b/src/lib-dict/dict-file.c	Sat Jan 05 01:14:11 2013 +0200
@@ -3,6 +3,7 @@
 #include "lib.h"
 #include "array.h"
 #include "hash.h"
+#include "mkdir-parents.h"
 #include "file-lock.h"
 #include "file-dotlock.h"
 #include "nfs-workarounds.h"
@@ -420,6 +421,33 @@
 	return fd_copy_stat_permissions(&src_st, dest_fd, dest_path);
 }
 
+static int file_dict_mkdir(struct file_dict *dict)
+{
+	const char *path, *p, *root;
+	struct stat st;
+	mode_t mode = 0700;
+
+	p = strrchr(dict->path, '/');
+	if (p == NULL)
+		return 0;
+	path = t_strdup_until(dict->path, p);
+
+	if (stat_first_parent(path, &root, &st) < 0) {
+		i_error("stat(%s) failed: %m", root);
+		return -1;
+	}
+	if ((st.st_mode & S_ISGID) != 0) {
+		/* preserve parent's permissions when it has setgid bit */
+		mode = st.st_mode;
+	}
+
+	if (mkdir_parents(path, mode) < 0) {
+		i_error("mkdir_parents(%s) failed: %m", path);
+		return -1;
+	}
+	return 0;
+}
+
 static int
 file_dict_lock(struct file_dict *dict, struct file_lock **lock_r)
 {
@@ -431,6 +459,11 @@
 	if (dict->fd == -1) {
 		/* quota file doesn't exist yet, we need to create it */
 		dict->fd = open(dict->path, O_CREAT | O_RDWR, 0600);
+		if (dict->fd == -1 && errno == ENOENT) {
+			if (file_dict_mkdir(dict) < 0)
+				return -1;
+			dict->fd = open(dict->path, O_CREAT | O_RDWR, 0600);
+		}
 		if (dict->fd == -1) {
 			i_error("creat(%s) failed: %m", dict->path);
 			return -1;
@@ -484,6 +517,12 @@
 	case FILE_LOCK_METHOD_DOTLOCK:
 		fd = file_dotlock_open(&file_dict_dotlock_settings, dict->path, 0,
 				       &dotlock);
+		if (fd == -1 && errno == ENOENT) {
+			if (file_dict_mkdir(dict) < 0)
+				return -1;
+			fd = file_dotlock_open(&file_dict_dotlock_settings,
+					       dict->path, 0, &dotlock);
+		}
 		if (fd == -1) {
 			i_error("file dict commit: file_dotlock_open(%s) failed: %m",
 				dict->path);
--- a/src/lib-index/mail-cache-compress.c	Sat Jan 05 00:37:26 2013 +0200
+++ b/src/lib-index/mail-cache-compress.c	Sat Jan 05 01:14:11 2013 +0200
@@ -458,7 +458,13 @@
 		return 0;
 
 	/* compression isn't very efficient with small read()s */
-	cache->map_with_read = FALSE;
+	if (cache->map_with_read) {
+		cache->map_with_read = FALSE;
+		if (cache->read_buf != NULL)
+			buffer_set_used_size(cache->read_buf, 0);
+		cache->hdr = NULL;
+		cache->mmap_length = 0;
+	}
 
 	if (cache->index->lock_method == FILE_LOCK_METHOD_DOTLOCK) {
 		/* we're using dotlocking, cache file creation itself creates
--- a/src/lib-index/mail-cache-fields.c	Sat Jan 05 00:37:26 2013 +0200
+++ b/src/lib-index/mail-cache-fields.c	Sat Jan 05 01:14:11 2013 +0200
@@ -204,7 +204,7 @@
 	const struct mail_cache_header_fields *field_hdr;
 	struct mail_cache_header_fields tmp_field_hdr;
 	const void *data;
-	uint32_t offset = 0, next_offset;
+	uint32_t offset = 0, next_offset, field_hdr_size;
 	unsigned int next_count = 0;
 	int ret;
 
@@ -272,13 +272,15 @@
 		cache->need_compress_file_seq = cache->hdr->file_seq;
 
 	if (field_hdr_r != NULL) {
+		/* detect corrupted size later */
+		field_hdr_size = I_MAX(field_hdr->size, sizeof(*field_hdr));
 		if (cache->file_cache != NULL) {
 			/* invalidate the cache fields area to make sure we
 			   get the latest cache decisions/last_used fields */
 			file_cache_invalidate(cache->file_cache, offset,
-					      field_hdr->size);
+					      field_hdr_size);
 		}
-		ret = mail_cache_map(cache, offset, field_hdr->size, &data);
+		ret = mail_cache_map(cache, offset, field_hdr_size, &data);
 		if (ret < 0)
 			return -1;
 		if (ret == 0) {
--- a/src/lib-index/mail-cache.c	Sat Jan 05 00:37:26 2013 +0200
+++ b/src/lib-index/mail-cache.c	Sat Jan 05 01:14:11 2013 +0200
@@ -294,6 +294,7 @@
 				!MAIL_CACHE_IS_UNUSABLE(cache) &&
 				cache->hdr->file_seq != 0 ?
 				cache->hdr->file_seq : 0;
+			cache->hdr = NULL;
 			return -1;
 		}
 	}
@@ -309,6 +310,7 @@
 	} else {
 		i_assert(cache->hdr != NULL);
 	}
+	i_assert(cache->hdr->file_seq != 0);
 
 	if (offset + size > cache->mmap_length)
 		return 0;
@@ -357,11 +359,12 @@
 	buffer_set_used_size(cache->read_buf, ret);
 
 	cache->read_offset = offset;
-	cache->mmap_length = offset + size;
+	cache->mmap_length = offset + cache->read_buf->used;
 
 	*data_r = data;
 	hdr_data = offset == 0 ? *data_r : NULL;
-	return mail_cache_map_finish(cache, offset, size, hdr_data, TRUE);
+	return mail_cache_map_finish(cache, offset,
+				     cache->read_buf->used, hdr_data, TRUE);
 }
 
 int mail_cache_map(struct mail_cache *cache, size_t offset, size_t size,
@@ -378,8 +381,6 @@
 		return mail_cache_map_with_read(cache, offset, size, data_r);
 
 	if (cache->file_cache != NULL) {
-		cache->hdr = NULL;
-
 		ret = file_cache_read(cache->file_cache, offset, size);
 		if (ret < 0) {
                         /* In case of ESTALE we'll simply fail without error
@@ -391,6 +392,7 @@
                            offsets. */
                         if (errno != ESTALE)
                                 mail_cache_set_syscall_error(cache, "read()");
+			cache->hdr = NULL;
 			return -1;
 		}
 
@@ -398,12 +400,14 @@
 					  &cache->mmap_length);
 		*data_r = offset > cache->mmap_length ? NULL :
 			CONST_PTR_OFFSET(data, offset);
-		return mail_cache_map_finish(cache, offset, size, data, TRUE);
+		return mail_cache_map_finish(cache, offset, size,
+					     offset == 0 ? data : NULL, TRUE);
 	}
 
 	if (offset < cache->mmap_length &&
 	    size <= cache->mmap_length - offset) {
 		/* already mapped */
+		i_assert(cache->mmap_base != NULL);
 		*data_r = CONST_PTR_OFFSET(cache->mmap_base, offset);
 		return 1;
 	}
@@ -547,6 +551,8 @@
 	mail_index_unregister_expunge_handler(cache->index, cache->ext_id);
 	mail_cache_file_close(cache);
 
+	if (cache->read_buf != NULL)
+		buffer_free(&cache->read_buf);
 	hash_table_destroy(&cache->field_name_hash);
 	pool_unref(&cache->field_pool);
 	i_free(cache->field_file_map);
@@ -748,6 +754,8 @@
 
 	if (cache->file_cache != NULL)
 		file_cache_write(cache->file_cache, data, size, offset);
+	if (cache->read_buf != NULL)
+		buffer_set_used_size(cache->read_buf, 0);
 	return 0;
 }
 
--- a/src/lib-index/mail-index-alloc-cache.c	Sat Jan 05 00:37:26 2013 +0200
+++ b/src/lib-index/mail-index-alloc-cache.c	Sat Jan 05 01:14:11 2013 +0200
@@ -22,6 +22,7 @@
 	struct mail_index *index;
 	char *mailbox_path;
 	int refcount;
+	bool referenced;
 
 	dev_t index_dir_dev;
 	ino_t index_dir_ino;
@@ -58,7 +59,7 @@
 static void
 mail_index_alloc_cache_list_free(struct mail_index_alloc_cache_list *list)
 {
-	if (list->index->open_count > 0)
+	if (list->referenced)
 		mail_index_close(list->index);
 	mail_index_free(&list->index);
 	i_free(list->mailbox_path);
@@ -166,6 +167,15 @@
 		} else {
 			if (rec->refcount == 0)
 				seen_ref0 = TRUE;
+			if (all && rec->index->open_count == 1 &&
+			    rec->referenced) {
+				/* we're the only one keeping this index open.
+				   we might be here, because the caller is
+				   deleting this mailbox and wants its indexes
+				   to be closed. so close it. */
+				rec->referenced = FALSE;
+				mail_index_close(rec->index);
+			}
 			list = &(*list)->next;
 		}
 	}
@@ -229,8 +239,9 @@
 			list->index_dir_dev = st.st_dev;
 		}
 	}
-	if (list != NULL) {
+	if (list != NULL && !list->referenced) {
 		/* keep it referenced for ourself */
+		list->referenced = TRUE;
 		index->open_count++;
 	}
 }
--- a/src/lib-index/mail-index-transaction-view.c	Sat Jan 05 00:37:26 2013 +0200
+++ b/src/lib-index/mail-index-transaction-view.c	Sat Jan 05 01:14:11 2013 +0200
@@ -203,11 +203,17 @@
 			if (first_uid <= rec->uid)
 				break;
 		}
-		if (seq > tview->t->last_new_seq) {
+		if (seq > tview->t->last_new_seq || rec->uid > last_uid) {
 			/* no messages in range */
 			return;
 		}
 		*first_seq_r = seq;
+
+		if (rec->uid == last_uid) {
+			/* one seq in range */
+			*last_seq_r = seq;
+			return;
+		}
 	}
 
 	seq = tview->t->last_new_seq;
--- a/src/lib-lda/mail-deliver.c	Sat Jan 05 00:37:26 2013 +0200
+++ b/src/lib-lda/mail-deliver.c	Sat Jan 05 01:14:11 2013 +0200
@@ -325,7 +325,8 @@
 		ctx->saved_mail = TRUE;
 		mail_deliver_log(ctx, "saved mail to %s", mailbox_name);
 
-		if (ctx->save_dest_mail && mailbox_sync(box, 0) == 0) {
+		if (ctx->save_dest_mail &&
+		    mailbox_sync(box, MAILBOX_SYNC_FLAG_FAST) == 0) {
 			range = array_idx(&changes.saved_uids, 0);
 			i_assert(range[0].seq1 == range[0].seq2);
 
--- a/src/lib-master/mountpoint-list.c	Sat Jan 05 00:37:26 2013 +0200
+++ b/src/lib-master/mountpoint-list.c	Sat Jan 05 01:14:11 2013 +0200
@@ -57,6 +57,10 @@
 	"/proc",
 	"/var/run",
 	"/run",
+#ifdef __APPLE__
+	"/Volumes",
+	"/private/tmp",
+#endif
 	NULL
 };
 
--- a/src/lib-storage/index/dbox-multi/mdbox-map.c	Sat Jan 05 00:37:26 2013 +0200
+++ b/src/lib-storage/index/dbox-multi/mdbox-map.c	Sat Jan 05 01:14:11 2013 +0200
@@ -208,7 +208,8 @@
 int mdbox_map_refresh(struct mdbox_map *map)
 {
 	struct mail_index_view_sync_ctx *ctx;
-	bool delayed_expunges;
+	bool delayed_expunges, fscked;
+	int ret = 0;
 
 	/* some open files may have read partially written mails. now that
 	   map syncing makes the new mails visible, we need to make sure the
@@ -227,14 +228,15 @@
 
 	ctx = mail_index_view_sync_begin(map->view,
 				MAIL_INDEX_VIEW_SYNC_FLAG_FIX_INCONSISTENT);
-	if (mail_index_reset_fscked(map->view->index))
-		mdbox_storage_set_corrupted(map->storage);
+	fscked = mail_index_reset_fscked(map->view->index);
 	if (mail_index_view_sync_commit(&ctx, &delayed_expunges) < 0) {
 		mail_storage_set_internal_error(MAP_STORAGE(map));
 		mail_index_reset_error(map->index);
-		return -1;
+		ret = -1;
 	}
-	return 0;
+	if (fscked)
+		mdbox_storage_set_corrupted(map->storage);
+	return ret;
 }
 
 static void
--- a/src/lib-storage/index/dbox-multi/mdbox-save.c	Sat Jan 05 00:37:26 2013 +0200
+++ b/src/lib-storage/index/dbox-multi/mdbox-save.c	Sat Jan 05 01:14:11 2013 +0200
@@ -295,16 +295,6 @@
 		mdbox_transaction_save_rollback(_ctx);
 		return -1;
 	}
-
-	/* assign map UIDs for newly saved messages. they're written to
-	   transaction log immediately within this function, but the map
-	   is left locked. */
-	if (mdbox_map_append_assign_map_uids(ctx->append_ctx, &first_map_uid,
-					     &last_map_uid) < 0) {
-		mdbox_transaction_save_rollback(_ctx);
-		return -1;
-	}
-
 	/* lock the mailbox after map to avoid deadlocks. if we've noticed
 	   any corruption, deal with it later, otherwise we won't have
 	   up-to-date atomic->sync_view */
@@ -317,6 +307,16 @@
 		return -1;
 	}
 
+	/* assign map UIDs for newly saved messages after we've successfully
+	   acquired all the locks. the transaction is now very unlikely to
+	   fail. the UIDs are written to the transaction log immediately within
+	   this function, but the map is left locked. */
+	if (mdbox_map_append_assign_map_uids(ctx->append_ctx, &first_map_uid,
+					     &last_map_uid) < 0) {
+		mdbox_transaction_save_rollback(_ctx);
+		return -1;
+	}
+
 	/* assign UIDs for new messages */
 	hdr = mail_index_get_header(ctx->sync_ctx->sync_view);
 	mail_index_append_finish_uids(ctx->ctx.trans, hdr->next_uid,
--- a/src/lib-storage/index/dbox-multi/mdbox-storage-rebuild.c	Sat Jan 05 00:37:26 2013 +0200
+++ b/src/lib-storage/index/dbox-multi/mdbox-storage-rebuild.c	Sat Jan 05 01:14:11 2013 +0200
@@ -838,6 +838,12 @@
 	if (mdbox_map_atomic_lock(ctx->atomic) < 0)
 		return -1;
 
+	/* fsck the map just in case its UIDs are broken */
+	if (mail_index_fsck(ctx->storage->map->index) < 0) {
+		mail_storage_set_internal_error(&ctx->storage->storage.storage);
+		return -1;
+	}
+
 	/* get old map header */
 	mail_index_get_header_ext(ctx->atomic->sync_view,
 				  ctx->storage->map->map_ext_id,
--- a/src/lib-storage/index/index-sync.c	Sat Jan 05 00:37:26 2013 +0200
+++ b/src/lib-storage/index/index-sync.c	Sat Jan 05 01:14:11 2013 +0200
@@ -31,6 +31,17 @@
 	    ioloop_time < ibox->sync_last_check + MAILBOX_FULL_SYNC_INTERVAL)
 		return FALSE;
 
+	if ((flags & MAILBOX_SYNC_FLAG_FAST) != 0 &&
+	    (box->flags & MAILBOX_FLAG_SAVEONLY) != 0) {
+		/* lib-lda is syncing the mailbox after saving a mail.
+		   it only wants to find the new mail for potentially copying
+		   to other mailboxes. that's mainly an optimization, and since
+		   the mail was most likely already added to index we don't
+		   need to do a full sync to find it. the main benefit here is
+		   to avoid a very costly sync with a large Maildir/new/ */
+		return FALSE;
+	}
+
 	if (ibox->notify_to != NULL)
 		timeout_reset(ibox->notify_to);
 	ibox->sync_last_check = ioloop_time;
--- a/src/lib-storage/index/raw/raw-mail.c	Sat Jan 05 00:37:26 2013 +0200
+++ b/src/lib-storage/index/raw/raw-mail.c	Sat Jan 05 01:14:11 2013 +0200
@@ -102,7 +102,8 @@
 
 	switch (field) {
 	case MAIL_FETCH_FROM_ENVELOPE:
-		*value_r = mbox->envelope_sender;
+		*value_r = mbox->envelope_sender != NULL ?
+			mbox->envelope_sender : "";
 		return 0;
 	case MAIL_FETCH_UIDL_FILE_NAME:
 		*value_r = mbox->have_filename ?
--- a/src/lib-storage/mail-storage.h	Sat Jan 05 00:37:26 2013 +0200
+++ b/src/lib-storage/mail-storage.h	Sat Jan 05 01:14:11 2013 +0200
@@ -291,6 +291,9 @@
 
 	/* TRUE if anything actually changed with this commit */
 	bool changed;
+	/* User doesn't have read ACL for the mailbox, so don't show the
+	   uid_validity / saved_uids. */
+	bool no_read_perm;
 };
 
 struct mailbox_sync_rec {
--- a/src/lib-storage/mailbox-list.c	Sat Jan 05 00:37:26 2013 +0200
+++ b/src/lib-storage/mailbox-list.c	Sat Jan 05 01:14:11 2013 +0200
@@ -921,27 +921,6 @@
 	}
 }
 
-static int
-mailbox_list_stat_parent(const char *path, const char **root_dir_r,
-			 struct stat *st_r, const char **error_r)
-{
-	const char *p;
-
-	while (stat(path, st_r) < 0) {
-		if (errno != ENOENT || strcmp(path, "/") == 0) {
-			*error_r = t_strdup_printf("stat(%s) failed: %m", path);
-			return -1;
-		}
-		p = strrchr(path, '/');
-		if (p == NULL)
-			path = "/";
-		else
-			path = t_strdup_until(path, p);
-	}
-	*root_dir_r = path;
-	return 0;
-}
-
 static const char *
 get_expanded_path(const char *unexpanded_start, const char *unexpanded_stop,
 		  const char *expanded_full)
@@ -1020,8 +999,10 @@
 	}
 
 	/* get the first existing parent directory's permissions */
-	if (mailbox_list_stat_parent(expanded, &root_dir, &st, error_r) < 0)
+	if (stat_first_parent(expanded, &root_dir, &st) < 0) {
+		*error_r = t_strdup_printf("stat(%s) failed: %m", root_dir);
 		return -1;
+	}
 
 	/* if the parent directory doesn't have setgid-bit enabled, we don't
 	   copy any permissions from it. */
--- a/src/lib/Makefile.am	Sat Jan 05 00:37:26 2013 +0200
+++ b/src/lib/Makefile.am	Sat Jan 05 01:14:11 2013 +0200
@@ -291,6 +291,7 @@
 	test-str-find.c \
 	test-str-sanitize.c \
 	test-time-util.c \
+	test-unichar.c \
 	test-utc-mktime.c \
 	test-var-expand.c
 
--- a/src/lib/buffer.c	Sat Jan 05 00:37:26 2013 +0200
+++ b/src/lib/buffer.c	Sat Jan 05 01:14:11 2013 +0200
@@ -319,3 +319,17 @@
 
 	return memcmp(buf1->data, buf2->data, buf1->used) == 0;
 }
+
+void buffer_verify_pool(buffer_t *_buf)
+{
+	const struct real_buffer *buf = (const struct real_buffer *)_buf;
+	void *ret;
+
+	if (buf->pool->datastack_pool) {
+		/* this doesn't really do anything except verify the
+		   stack frame */
+		ret = p_realloc(buf->pool, buf->w_buffer,
+				buf->alloc, buf->alloc);
+		i_assert(ret == buf->w_buffer);
+	}
+}
--- a/src/lib/buffer.h	Sat Jan 05 00:37:26 2013 +0200
+++ b/src/lib/buffer.h	Sat Jan 05 01:14:11 2013 +0200
@@ -104,4 +104,10 @@
 	return buf->used;
 }
 
+/* Crash if buffer was allocated from data stack and stack frame has changed.
+   This can be used as an assert-like check to verify that it's valid to
+   increase the buffer size here, instead of crashing only randomly when the
+   buffer needs to be increased. */
+void buffer_verify_pool(buffer_t *buf);
+
 #endif
--- a/src/lib/mkdir-parents.c	Sat Jan 05 00:37:26 2013 +0200
+++ b/src/lib/mkdir-parents.c	Sat Jan 05 01:14:11 2013 +0200
@@ -137,3 +137,23 @@
 {
 	return mkdir_parents_chown(path, mode, (uid_t)-1, (gid_t)-1);
 }
+
+int stat_first_parent(const char *path, const char **root_dir_r,
+		      struct stat *st_r)
+{
+	const char *p;
+
+	while (stat(path, st_r) < 0) {
+		if (errno != ENOENT || strcmp(path, "/") == 0) {
+			*root_dir_r = path;
+			return -1;
+		}
+		p = strrchr(path, '/');
+		if (p == NULL)
+			path = "/";
+		else
+			path = t_strdup_until(path, p);
+	}
+	*root_dir_r = path;
+	return 0;
+}
--- a/src/lib/mkdir-parents.h	Sat Jan 05 00:37:26 2013 +0200
+++ b/src/lib/mkdir-parents.h	Sat Jan 05 01:14:11 2013 +0200
@@ -1,6 +1,8 @@
 #ifndef MKDIR_PARENTS_H
 #define MKDIR_PARENTS_H
 
+#include <sys/stat.h>
+
 /* Create path and all the directories under it if needed. Permissions for
    existing directories isn't changed. Returns 0 if ok. If directory already
    exists, returns -1 with errno=EEXIST. */
@@ -22,4 +24,10 @@
 int mkdir_chgrp(const char *path, mode_t mode,
 		gid_t gid, const char *gid_origin);
 
+/* stat() the path or its first parent that exists. Returns 0 if ok, -1 if
+   failed. root_dir is set to the last stat()ed directory (on success and
+   on failure). */
+int stat_first_parent(const char *path, const char **root_dir_r,
+		      struct stat *st_r);
+
 #endif
--- a/src/lib/str.c	Sat Jan 05 00:37:26 2013 +0200
+++ b/src/lib/str.c	Sat Jan 05 01:14:11 2013 +0200
@@ -45,6 +45,9 @@
 	size_t len = str_len(str);
 	size_t alloc = buffer_get_size(str);
 
+#ifdef DEBUG
+	buffer_verify_pool(str);
+#endif
 	if (len == alloc || data[len] != '\0') {
 		buffer_write(str, len, "", 1);
 		/* remove the \0 - we don't want to keep it */
--- a/src/lib/test-lib.c	Sat Jan 05 00:37:26 2013 +0200
+++ b/src/lib/test-lib.c	Sat Jan 05 01:14:11 2013 +0200
@@ -34,6 +34,7 @@
 		test_str_find,
 		test_str_sanitize,
 		test_time_util,
+		test_unichar,
 		test_utc_mktime,
 		test_var_expand,
 		NULL
--- a/src/lib/test-lib.h	Sat Jan 05 00:37:26 2013 +0200
+++ b/src/lib/test-lib.h	Sat Jan 05 01:14:11 2013 +0200
@@ -33,6 +33,7 @@
 void test_str_find(void);
 void test_str_sanitize(void);
 void test_time_util(void);
+void test_unichar(void);
 void test_utc_mktime(void);
 void test_var_expand(void);
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/test-unichar.c	Sat Jan 05 01:14:11 2013 +0200
@@ -0,0 +1,24 @@
+/* Copyright (c) 2007-2012 Dovecot authors, see the included COPYING file */
+
+#include "test-lib.h"
+#include "str.h"
+#include "unichar.h"
+
+void test_unichar(void)
+{
+	static const char *overlong_utf8 = "\xf8\x80\x95\x81\xa1";
+	unichar_t chr, chr2;
+	string_t *str = t_str_new(16);
+
+	test_begin("unichars");
+	for (chr = 0; chr <= 0x10ffff; chr++) {
+		str_truncate(str, 0);
+		uni_ucs4_to_utf8_c(chr, str);
+		test_assert(uni_utf8_str_is_valid(str_c(str)));
+		test_assert(uni_utf8_get_char(str_c(str), &chr2) > 0);
+		test_assert(chr2 == chr);
+	}
+	test_assert(!uni_utf8_str_is_valid(overlong_utf8));
+	test_assert(uni_utf8_get_char(overlong_utf8, &chr2) < 0);
+	test_end();
+}
--- a/src/lib/unichar.c	Sat Jan 05 00:37:26 2013 +0200
+++ b/src/lib/unichar.c	Sat Jan 05 01:14:11 2013 +0200
@@ -37,8 +37,10 @@
 
 int uni_utf8_get_char_n(const void *_input, size_t max_len, unichar_t *chr_r)
 {
+	static unichar_t lowest_valid_chr_table[] =
+		{ 0, 0, 0x80, 0x800, 0x10000, 0x20000, 0x40000 };
 	const unsigned char *input = _input;
-	unichar_t chr;
+	unichar_t chr, lowest_valid_chr;
 	unsigned int i, len;
 	int ret;
 
@@ -75,10 +77,12 @@
 		return -1;
 	}
 
-	if (len <= max_len)
+	if (len <= max_len) {
+		lowest_valid_chr = lowest_valid_chr_table[len];
 		ret = 1;
-	else {
+	} else {
 		/* check first if the input is invalid before returning 0 */
+		lowest_valid_chr = 0;
 		ret = 0;
 		len = max_len;
 	}
@@ -91,6 +95,10 @@
 		chr <<= 6;
 		chr |= input[i] & 0x3f;
 	}
+	if (chr < lowest_valid_chr) {
+		/* overlong encoding */
+		return -1;
+	}
 
 	*chr_r = chr;
 	return ret;
@@ -340,19 +348,11 @@
 static inline unsigned int
 is_valid_utf8_seq(const unsigned char *input, unsigned int size)
 {
-	unsigned int i, len;
-
-	len = uni_utf8_char_bytes(input[0]);
-	if (unlikely(len > size || len == 1))
-		return 0;
+	unichar_t chr;
 
-	/* the rest of the chars should be in 0x80..0xbf range.
-	   anything else is start of a sequence or invalid */
-	for (i = 1; i < len; i++) {
-		if (unlikely(input[i] < 0x80 || input[i] > 0xbf))
-			return 0;
-	}
-	return len;
+	if (uni_utf8_get_char_n(input, size, &chr) <= 0)
+		return 0;
+	return uni_utf8_char_bytes(input[0]);
 }
 
 static int uni_utf8_find_invalid_pos(const unsigned char *input, size_t size,
--- a/src/lib/var-expand.c	Sat Jan 05 00:37:26 2013 +0200
+++ b/src/lib/var-expand.c	Sat Jan 05 01:14:11 2013 +0200
@@ -160,7 +160,7 @@
 		const void *key_start, unsigned int key_len, void *context)
 {
         const struct var_expand_table *t;
-	const char *value = NULL;
+	const char *key, *value = NULL;
 
 	if (table != NULL) {
 		for (t = table; !TABLE_LAST(t); t++) {
@@ -171,35 +171,33 @@
 			}
 		}
 	}
+	key = t_strndup(key_start, key_len);
 
 	/* built-in variables: */
-	T_BEGIN {
-		const char *key = t_strndup(key_start, key_len);
+	switch (key_len) {
+	case 3:
+		if (strcmp(key, "pid") == 0)
+			value = my_pid;
+		else if (strcmp(key, "uid") == 0)
+			value = dec2str(geteuid());
+		else if (strcmp(key, "gid") == 0)
+			value = dec2str(getegid());
+		break;
+	case 8:
+		if (strcmp(key, "hostname") == 0)
+			value = my_hostname;
+		break;
+	}
 
-		switch (key_len) {
-		case 3:
-			if (strcmp(key, "pid") == 0)
-				value = my_pid;
-			else if (strcmp(key, "uid") == 0)
-				value = dec2str(geteuid());
-			else if (strcmp(key, "gid") == 0)
-				value = dec2str(getegid());
-			break;
-		case 8:
-			if (strcmp(key, "hostname") == 0)
-				value = my_hostname;
-			break;
-		}
-		if (value == NULL) {
-			const char *data = strchr(key, ':');
+	if (value == NULL) {
+		const char *data = strchr(key, ':');
 
-			if (data != NULL)
-				key = t_strdup_until(key, data++);
-			else
-				data = "";
-			value = var_expand_func(func_table, key, data, context);
-		}
-	} T_END;
+		if (data != NULL)
+			key = t_strdup_until(key, data++);
+		else
+			data = "";
+		value = var_expand_func(func_table, key, data, context);
+	}
 	return value;
 }
 
--- a/src/login-common/login-proxy.c	Sat Jan 05 00:37:26 2013 +0200
+++ b/src/login-common/login-proxy.c	Sat Jan 05 01:14:11 2013 +0200
@@ -5,6 +5,7 @@
 #include "istream.h"
 #include "ostream.h"
 #include "llist.h"
+#include "str.h"
 #include "str-sanitize.h"
 #include "time-util.h"
 #include "master-service.h"
@@ -194,16 +195,33 @@
 	proxy->state_rec = NULL;
 }
 
+static void
+proxy_log_connect_error(struct login_proxy *proxy)
+{
+	string_t *str = t_str_new(128);
+	struct ip_addr local_ip;
+	unsigned int local_port;
+
+	str_printfa(str, "proxy(%s): connect(%s, %u) failed: %m (after %u secs",
+		    proxy->client->virtual_user,
+		    proxy->host, proxy->port,
+		    (unsigned int)(ioloop_time - proxy->created.tv_sec));
+
+	if (proxy->server_fd != -1 &&
+	    net_getsockname(proxy->server_fd, &local_ip, &local_port) == 0) {
+		str_printfa(str, ", local=%s:%u",
+			    net_ip2addr(&local_ip), local_port);
+	}
+
+	str_append_c(str, ')');
+	i_error("%s", str_c(str));
+}
+
 static void proxy_wait_connect(struct login_proxy *proxy)
 {
-	int err;
-
-	err = net_geterror(proxy->server_fd);
-	if (err != 0) {
-		i_error("proxy(%s): connect(%s, %u) failed: %s (after %u secs)",
-			proxy->client->virtual_user,
-			proxy->host, proxy->port, strerror(err),
-			(unsigned int)(ioloop_time - proxy->created.tv_sec));
+	errno = net_geterror(proxy->server_fd);
+	if (errno != 0) {
+		proxy_log_connect_error(proxy);
 		proxy_fail_connect(proxy);
                 login_proxy_free(&proxy);
 		return;
@@ -229,8 +247,8 @@
 
 static void proxy_connect_timeout(struct login_proxy *proxy)
 {
-	i_error("proxy(%s): connect(%s, %u) timed out",
-		proxy->client->virtual_user, proxy->host, proxy->port);
+	errno = ETIMEDOUT;
+	proxy_log_connect_error(proxy);
 	proxy_fail_connect(proxy);
 	login_proxy_free(&proxy);
 }
@@ -252,8 +270,7 @@
 
 	proxy->server_fd = net_connect_ip(&proxy->ip, proxy->port, NULL);
 	if (proxy->server_fd == -1) {
-		i_error("proxy(%s): connect(%s, %u) failed: %m",
-			proxy->client->virtual_user, proxy->host, proxy->port);
+		proxy_log_connect_error(proxy);
 		login_proxy_free(&proxy);
 		return -1;
 	}
--- a/src/plugins/acl/acl-mailbox.c	Sat Jan 05 00:37:26 2013 +0200
+++ b/src/plugins/acl/acl-mailbox.c	Sat Jan 05 01:14:11 2013 +0200
@@ -21,6 +21,7 @@
 	struct acl_object *aclobj;
 	bool skip_acl_checks;
 	bool acl_enabled;
+	bool no_read_right;
 };
 
 struct acl_transaction_context {
@@ -412,13 +413,19 @@
 {
 	struct acl_mailbox *abox = ACL_CONTEXT(ctx->box);
 	void *at = ACL_CONTEXT(ctx);
+	int ret;
 
 	if (at != NULL) {
 		abox->module_ctx.super.transaction_rollback(ctx);
 		return -1;
 	}
 
-	return abox->module_ctx.super.transaction_commit(ctx, changes_r);
+	ret = abox->module_ctx.super.transaction_commit(ctx, changes_r);
+	if (abox->no_read_right) {
+		/* don't allow IMAP client to see what UIDs the messages got */
+		changes_r->no_read_perm = TRUE;
+	}
+	return ret;
 }
 
 static int acl_mailbox_exists(struct mailbox *box, bool auto_boxes,
@@ -477,6 +484,14 @@
 		}
 		return -1;
 	}
+	if (open_right != ACL_STORAGE_RIGHT_READ) {
+		ret = acl_object_have_right(abox->aclobj,
+					    idx_arr[ACL_STORAGE_RIGHT_READ]);
+		if (ret < 0)
+			return -1;
+		if (ret == 0)
+			abox->no_read_right = TRUE;
+	}
 	return 0;
 }
 
--- a/src/plugins/acl/acl-storage.c	Sat Jan 05 00:37:26 2013 +0200
+++ b/src/plugins/acl/acl-storage.c	Sat Jan 05 01:14:11 2013 +0200
@@ -52,7 +52,7 @@
 	const char *env;
 
 	env = mail_user_plugin_getenv(user, "acl");
-	if (env != NULL)
+	if (env != NULL && *env != '\0')
 		acl_mail_user_create(user, env);
 	else {
 		if (user->mail_debug)
--- a/src/plugins/expire/doveadm-expire.c	Sat Jan 05 00:37:26 2013 +0200
+++ b/src/plugins/expire/doveadm-expire.c	Sat Jan 05 01:14:11 2013 +0200
@@ -381,7 +381,10 @@
 	if (expire_dict == NULL)
 		return;
 
-	if (ctx->iterate_single_user) {
+	/* doveadm proxying uses expire database only locally. the remote
+	   doveadm handles each user one at a time (even though
+	   iterate_single_user=FALSE) */
+	if (ctx->iterate_single_user || ctx->proxying) {
 		if (doveadm_debug) {
 			i_debug("expire: Iterating only a single user, "
 				"ignoring expire database");
--- a/src/plugins/fts-solr/fts-backend-solr.c	Sat Jan 05 00:37:26 2013 +0200
+++ b/src/plugins/fts-solr/fts-backend-solr.c	Sat Jan 05 01:14:11 2013 +0200
@@ -305,7 +305,7 @@
 	}
 	array_foreach_modifiable(&ctx->fields, field) {
 		str_printfa(ctx->cmd, "<field name=\"%s\">", field->key);
-		str_append_str(ctx->cmd, field->value);
+		xml_encode_data(ctx->cmd, str_data(field->value), str_len(field->value));
 		str_append(ctx->cmd, "</field>");
 		str_truncate(field->value, 0);
 	}
@@ -352,8 +352,7 @@
 		   visible to the following search */
 		if (ctx->expunges)
 			fts_backend_solr_expunge_flush(ctx);
-		str = t_strdup_printf("<commit waitFlush=\"false\" "
-				      "waitSearcher=\"%s\"/>",
+		str = t_strdup_printf("<commit waitSearcher=\"%s\"/>",
 				      ctx->documents_added ? "true" : "false");
 		if (solr_connection_post(solr_conn, str) < 0)
 			ret = -1;
--- a/src/plugins/fts-solr/solr-connection.c	Sat Jan 05 00:37:26 2013 +0200
+++ b/src/plugins/fts-solr/solr-connection.c	Sat Jan 05 01:14:11 2013 +0200
@@ -192,6 +192,7 @@
 	curl_slist_free_all(conn->headers_post);
 	curl_multi_cleanup(conn->curlm);
 	curl_easy_cleanup(conn->curl);
+	XML_ParserFree(conn->xml_parser);
 	i_free(conn->last_sent_url);
 	i_free(conn->url);
 	i_free(conn);
--- a/src/plugins/fts/fts-parser-script.c	Sat Jan 05 00:37:26 2013 +0200
+++ b/src/plugins/fts/fts-parser-script.c	Sat Jan 05 01:14:11 2013 +0200
@@ -99,10 +99,13 @@
 		content->content_type = args[0];
 		content->extensions = (const void *)(args+1);
 	}
+	if (!eof_seen) {
+		if (input->v_offset == 0)
+			i_error("parser script didn't send any data");
+		else
+			i_error("parser script didn't send empty EOF line");
+	}
 	i_stream_destroy(&input);
-
-	if (!eof_seen)
-		i_error("parser script didn't send empty EOF line");
 	return 0;
 }
 
--- a/src/plugins/lazy-expunge/lazy-expunge-plugin.c	Sat Jan 05 00:37:26 2013 +0200
+++ b/src/plugins/lazy-expunge/lazy-expunge-plugin.c	Sat Jan 05 01:14:11 2013 +0200
@@ -121,9 +121,20 @@
 	union mail_module_context *mmail = LAZY_EXPUNGE_MAIL_CONTEXT(mail);
 	struct lazy_expunge_transaction *lt =
 		LAZY_EXPUNGE_CONTEXT(_mail->transaction);
+	struct lazy_expunge_mailbox_list *llist;
+	struct mailbox *real_box;
 	struct mail_save_context *save_ctx;
 	const char *error;
 
+	/* don't copy the mail if we're expunging from lazy_expunge
+	   namespace (even if it's via a virtual mailbox) */
+	real_box = mail_get_real_mail(_mail)->box;
+	llist = LAZY_EXPUNGE_LIST_CONTEXT(real_box->list);
+	if (llist != NULL && llist->internal_namespace) {
+		mmail->super.expunge(_mail);
+		return;
+	}
+
 	if (lt->dest_box == NULL) {
 		lt->dest_box = mailbox_open_or_create(luser->lazy_ns->list,
 						      _mail->box, &error);
--- a/src/plugins/mailbox-alias/mailbox-alias-plugin.c	Sat Jan 05 00:37:26 2013 +0200
+++ b/src/plugins/mailbox-alias/mailbox-alias-plugin.c	Sat Jan 05 01:14:11 2013 +0200
@@ -119,7 +119,7 @@
 			if (mailbox_symlink_exists(list, alias->new_vname,
 						   &existence) < 0)
 				ret = -1;
-			if (existence == MAILBOX_SYMLINK_EXISTENCE_SYMLINK)
+			else if (existence == MAILBOX_SYMLINK_EXISTENCE_SYMLINK)
 				return 1;
 		}
 	}
--- a/src/plugins/quota/quota-storage.c	Sat Jan 05 00:37:26 2013 +0200
+++ b/src/plugins/quota/quota-storage.c	Sat Jan 05 01:14:11 2013 +0200
@@ -87,7 +87,7 @@
 		if ((items & ~STATUS_CHECK_OVER_QUOTA) == 0) {
 			/* don't bother calling parent, it may unnecessarily
 			   try to open the mailbox */
-			return ret;
+			return ret < 0 ? -1 : 0;
 		}
 	}
 
@@ -533,6 +533,7 @@
 	struct quota_mailbox_list *qlist;
 	struct quota *quota = NULL;
 	struct quota_root *root;
+	struct mail_user *quota_user;
 	bool add;
 
 	if (QUOTA_USER_CONTEXT(list->ns->user) == NULL)
@@ -541,8 +542,14 @@
 	/* see if we have a quota explicitly defined for this namespace */
 	quota = quota_get_mail_user_quota(list->ns->user);
 	root = quota_find_root_for_ns(quota, list->ns);
-	if (root != NULL)
+	if (root != NULL) {
+		/* explicit quota root */
 		root->ns = list->ns;
+		quota_user = list->ns->user;
+	} else {
+		quota_user = list->ns->owner != NULL ?
+			list->ns->owner : list->ns->user;
+	}
 
 	if ((list->ns->flags & NAMESPACE_FLAG_NOQUOTA) != 0)
 		add = FALSE;
@@ -551,7 +558,9 @@
 		   explicitly defined for it */
 		add = root != NULL;
 	} else {
-		add = TRUE;
+		/* for shared namespaces add only if the owner has quota
+		   enabled */
+		add = QUOTA_USER_CONTEXT(quota_user) != NULL;
 	}
 
 	if (add) {
@@ -563,10 +572,7 @@
 		v->deinit = quota_mailbox_list_deinit;
 		MODULE_CONTEXT_SET(list, quota_mailbox_list_module, qlist);
 
-		/* register to owner's quota roots */
-		quota = list->ns->owner != NULL ?
-			quota_get_mail_user_quota(list->ns->owner) :
-			quota_get_mail_user_quota(list->ns->user);
+		quota = quota_get_mail_user_quota(quota_user);
 		quota_add_user_namespace(quota, list->ns);
 	}
 }
--- a/src/plugins/stats/stats-plugin.c	Sat Jan 05 00:37:26 2013 +0200
+++ b/src/plugins/stats/stats-plugin.c	Sat Jan 05 01:14:11 2013 +0200
@@ -587,9 +587,13 @@
 		stats_global_user = user;
 	} else if (stats_user_count == 1) {
 		/* second user connection. we'll need to start doing
-		   per-io callback tracking now. */
-		stats_add_session(stats_global_user);
-		stats_global_user = NULL;
+		   per-io callback tracking now. (we might have been doing it
+		   also previously but just temporarily quickly dropped to
+		   having 1 user, in which case stats_global_user=NULL) */
+		if (stats_global_user != NULL) {
+			stats_add_session(stats_global_user);
+			stats_global_user = NULL;
+		}
 	}
 	stats_user_count++;
 
--- a/src/plugins/virtual/virtual-config.c	Sat Jan 05 00:37:26 2013 +0200
+++ b/src/plugins/virtual/virtual-config.c	Sat Jan 05 01:14:11 2013 +0200
@@ -28,6 +28,7 @@
 
 	char sep;
 	bool have_wildcards;
+	bool have_mailbox_defines;
 };
 
 static struct mail_search_args *
@@ -170,6 +171,7 @@
 		bbox->name++;
 		ctx->mbox->save_bbox = bbox;
 	}
+	ctx->have_mailbox_defines = TRUE;
 	array_append(&ctx->mbox->backend_boxes, &bbox, 1);
 	return 0;
 }
@@ -420,7 +422,7 @@
 	if (ret == 0 && ctx.have_wildcards)
 		ret = virtual_config_expand_wildcards(&ctx);
 
-	if (ret == 0 && array_count(&mbox->backend_boxes) == 0) {
+	if (ret == 0 && !ctx.have_mailbox_defines) {
 		mail_storage_set_critical(storage,
 					  "%s: No mailboxes defined", path);
 		ret = -1;
--- a/src/plugins/virtual/virtual-mail.c	Sat Jan 05 00:37:26 2013 +0200
+++ b/src/plugins/virtual/virtual-mail.c	Sat Jan 05 01:14:11 2013 +0200
@@ -293,15 +293,17 @@
 {
 	struct virtual_mail *vmail = (struct virtual_mail *)mail;
 	struct mail_private *p = (struct mail_private *)vmail->backend_mail;
+	int ret;
 
 	if (virtual_mail_handle_lost(vmail) < 0)
 		return -1;
-	if (p->v.get_first_header(vmail->backend_mail, field,
-				  decode_to_utf8, value_r) < 0) {
+	ret = p->v.get_first_header(vmail->backend_mail, field,
+				    decode_to_utf8, value_r);
+	if (ret < 0) {
 		virtual_box_copy_error(mail->box, vmail->backend_mail->box);
 		return -1;
 	}
-	return 0;
+	return ret;
 }
 
 static int
--- a/src/stats/mail-session.c	Sat Jan 05 00:37:26 2013 +0200
+++ b/src/stats/mail-session.c	Sat Jan 05 01:14:11 2013 +0200
@@ -264,13 +264,16 @@
 		return -1;
 
 	if (mail_stats_parse(args+1, &stats, error_r) < 0) {
-		*error_r = t_strconcat("UPDATE-SESSION: ", *error_r, NULL);
+		*error_r = t_strdup_printf("UPDATE-SESSION %s %s: %s",
+					   session->user->name,
+					   session->service, *error_r);
 		return -1;
 	}
 
 	if (!mail_stats_diff(&session->stats, &stats, &diff_stats, &error)) {
-		*error_r = t_strconcat("UPDATE-SESSION: stats shrank: ",
-				       error, NULL);
+		*error_r = t_strdup_printf("UPDATE-SESSION %s %s: stats shrank: %s",
+					   session->user->name,
+					   session->service, error);
 		return -1;
 	}
 	mail_session_refresh(session, &diff_stats);