changeset 15162:e3175ee39483

Merged changes from v2.1 tree.
author Timo Sirainen <tss@iki.fi>
date Wed, 26 Sep 2012 18:01:01 +0300
parents 981e0bc71742 (current diff) 4d82c74f702c (diff)
children c7a99d41e39e
files NEWS configure.in src/auth/auth-request.c src/auth/auth-request.h src/auth/db-ldap.c src/auth/userdb-static.c src/config/config-parser.c src/director/director-connection.c src/director/director.c src/director/director.h src/doveadm/Makefile.am src/doveadm/doveadm-settings.c src/doveadm/doveadm-settings.h src/doveadm/doveadm.c src/imap/cmd-list.c src/lib-imap-client/imapc-client.h src/lib-imap-client/imapc-connection.c src/lib-imap/imap-parser.c src/lib-imap/imap-parser.h src/lib-master/master-service-settings-cache.c src/lib-settings/settings-parser.c src/lib-storage/index/dbox-common/dbox-storage.c src/lib-storage/index/dbox-multi/mdbox-file.c src/lib-storage/index/dbox-multi/mdbox-map-private.h src/lib-storage/index/dbox-multi/mdbox-map.c src/lib-storage/index/dbox-multi/mdbox-storage.c src/lib-storage/index/dbox-single/sdbox-storage.c src/lib-storage/index/index-status.c src/lib-storage/index/maildir/maildir-storage.c src/lib-storage/index/mbox/mbox-storage.c src/lib-storage/index/pop3c/pop3c-mail.c src/lib-storage/list/mailbox-list-fs-iter.c src/lib-storage/list/mailbox-list-maildir-iter.c src/lib-storage/mail-namespace.c src/lib-storage/mail-storage-private.h src/lib-storage/mail-storage-service.c src/lib-storage/mail-storage-settings.c src/lib-storage/mail-storage-settings.h src/lib-storage/mail-storage.h src/lib-storage/mail-user.c src/lib-storage/mail-user.h src/lib-storage/mailbox-guid-cache.c src/lib-storage/mailbox-list-iter.h src/lib-storage/mailbox-list-private.h src/lib-storage/mailbox-list.c src/lib-storage/mailbox-list.h src/lib/hash.c src/lib/unichar.h src/lmtp/client.c src/lmtp/commands.c src/lmtp/lmtp-settings.c src/lmtp/lmtp-settings.h src/plugins/acl/acl-lookup-dict.c src/plugins/fts-lucene/Snowball.cc src/plugins/fts-lucene/lucene-wrapper.cc src/plugins/fts-solr/fts-backend-solr.c src/plugins/mailbox-alias/mailbox-alias-plugin.c src/plugins/quota/quota-count.c src/plugins/quota/quota-maildir.c src/plugins/quota/quota-private.h src/plugins/quota/quota-storage.c src/plugins/quota/quota.c
diffstat 63 files changed, 908 insertions(+), 126 deletions(-) [+]
line wrap: on
line diff
--- a/.hgsigs	Wed Sep 26 17:17:08 2012 +0300
+++ b/.hgsigs	Wed Sep 26 18:01:01 2012 +0300
@@ -49,3 +49,4 @@
 c92fb8b928f69ca01681a2c2976304b7e4bc3afc 0 iEYEABECAAYFAk/FIeIACgkQyUhSUUBVisk4IgCfUiXVXntqzPjJcALYRpqw4Zc7a/0An3HKWwgb6PBCbmvxBfTezNkqjqVK
 7e5f36fd989d27a2fb48250adbab8fa54ddb6083 0 iEYEABECAAYFAk/yVakACgkQyUhSUUBVismekwCfSEVQjd6fwdChjd53LSt03b4UWKoAoIxd/IjLatTISlHm44iiQwzRKByo
 bc86680293d256d5a8009690caeb73ab2e34e359 0 iEYEABECAAYFAlAZaTUACgkQyUhSUUBVisnTAACfU1pB34RrXEyLnpnL4Ee/oeNBYcoAnRWxTqx870Efjwf+eBPzafO0C/NU
+1a6c3b4e92e4174d3b1eb0a7c841f97e8fb9e590 0 iEYEABECAAYFAlBYwJMACgkQyUhSUUBVisn2PwCeIJxfB5ebXlAbtMcjrZBCmB8Kg1sAn39BC9rQoR/wjD2/ix1JaxH7gHOT
--- a/.hgtags	Wed Sep 26 17:17:08 2012 +0300
+++ b/.hgtags	Wed Sep 26 18:01:01 2012 +0300
@@ -86,3 +86,4 @@
 c92fb8b928f69ca01681a2c2976304b7e4bc3afc 2.1.7
 7e5f36fd989d27a2fb48250adbab8fa54ddb6083 2.1.8
 bc86680293d256d5a8009690caeb73ab2e34e359 2.1.9
+1a6c3b4e92e4174d3b1eb0a7c841f97e8fb9e590 2.1.10
--- a/NEWS	Wed Sep 26 17:17:08 2012 +0300
+++ b/NEWS	Wed Sep 26 18:01:01 2012 +0300
@@ -19,6 +19,32 @@
 	+ LMTP proxy: Implemented XCLIENT extension for passing remote IP
 	  address through proxy.
 
+v2.1.10 2012-09-18  Timo Sirainen <tss@iki.fi>
+
+	+ imap: Implemented THREAD=ORDEREDSUBJECT extension.
+	+ Added "doveadm exec" command to easily execute commands from
+	  libexec_dir, e.g. "doveadm exec imap -u user@domain"
+	+ Added "doveadm copy" command.
+	+ doveadm copy/move: Added optional user parameter to specify the
+	  source username. This allows easily copying mails between different
+	  users.
+	+ Added namespace { disabled } setting to quickly enable/disable
+	  namespaces. This is especially useful when its value is returned by
+	  userdb.
+	+ Added mailbox_alias plugin. It allows creating mailbox aliases using
+	  symlinks.
+	+ imapc storage: Added imapc_max_idle_time setting to force activity
+	  on connection.
+	+ fts-solr: Expunging multiple messages is now faster.
+	- director: In some conditions director may have disconnected from
+	  another director (without logging about it), thinking it was sending
+	  invalid data.
+	- imap: Various fixes to listing mailboxes.
+	- pop3-migration plugin: Avoid disconnection from POP3 server due
+	  to idling.
+	- login processes crashed if there were a lot of local {} or remote {}
+	  settings blocks.
+
 v2.1.9 2012-08-01  Timo Sirainen <tss@iki.fi>
 
 	* mail-log plugin: Log mailbox names with UTF-8 everywhere 
--- a/configure.in	Wed Sep 26 17:17:08 2012 +0300
+++ b/configure.in	Wed Sep 26 18:01:01 2012 +0300
@@ -2820,6 +2820,7 @@
 src/plugins/lazy-expunge/Makefile
 src/plugins/listescape/Makefile
 src/plugins/mail-log/Makefile
+src/plugins/mailbox-alias/Makefile
 src/plugins/notify/Makefile
 src/plugins/pop3-migration/Makefile
 src/plugins/quota/Makefile
--- a/doc/example-config/conf.d/20-lmtp.conf	Wed Sep 26 17:17:08 2012 +0300
+++ b/doc/example-config/conf.d/20-lmtp.conf	Wed Sep 26 18:01:01 2012 +0300
@@ -10,7 +10,11 @@
 # lda_mailbox_autocreate settings.
 #lmtp_save_to_detail_mailbox = no
 
+# Verify quota before replying to RCPT TO. This adds a small overhead.
+#lmtp_rcpt_check_quota = no
+
 protocol lmtp {
   # Space separated list of plugins to load (default is global mail_plugins).
   #mail_plugins = $mail_plugins
 }
+			  
\ No newline at end of file
--- a/src/auth/auth-request.c	Wed Sep 26 17:17:08 2012 +0300
+++ b/src/auth/auth-request.c	Wed Sep 26 18:01:01 2012 +0300
@@ -1820,18 +1820,27 @@
 	{ '\0', NULL, NULL }
 };
 
-const struct var_expand_table *
-auth_request_get_var_expand_table(const struct auth_request *auth_request,
-				  auth_request_escape_func_t *escape_func)
+struct var_expand_table *
+auth_request_get_var_expand_table_full(const struct auth_request *auth_request,
+				       auth_request_escape_func_t *escape_func,
+				       unsigned int *count)
 {
-	struct var_expand_table *tab;
+	const unsigned int auth_count =
+		N_ELEMENTS(auth_request_var_expand_static_tab);
+	struct var_expand_table *tab, *ret_tab;
 
 	if (escape_func == NULL)
 		escape_func = escape_none;
 
-	tab = t_malloc(sizeof(auth_request_var_expand_static_tab));
+	/* keep the extra fields at the beginning. the last static_tab field
+	   contains the ending NULL-fields. */
+	tab = ret_tab = t_malloc((*count + auth_count) * sizeof(*tab));
+	memset(tab, 0, *count * sizeof(*tab));
+	tab += *count;
+	*count += auth_count;
+
 	memcpy(tab, auth_request_var_expand_static_tab,
-	       sizeof(auth_request_var_expand_static_tab));
+	       auth_count * sizeof(*tab));
 
 	tab[0].value = escape_func(auth_request->user, auth_request);
 	tab[1].value = escape_func(t_strcut(auth_request->user, '@'),
@@ -1878,7 +1887,17 @@
 	}
 	tab[18].value = auth_request->session_id == NULL ? NULL :
 		escape_func(auth_request->session_id, auth_request);
-	return tab;
+	return ret_tab;
+}
+
+const struct var_expand_table *
+auth_request_get_var_expand_table(const struct auth_request *auth_request,
+				  auth_request_escape_func_t *escape_func)
+{
+	unsigned int count = 0;
+
+	return auth_request_get_var_expand_table_full(auth_request, escape_func,
+						      &count);
 }
 
 static void get_log_prefix(string_t *str, struct auth_request *auth_request,
--- a/src/auth/auth-request.h	Wed Sep 26 17:17:08 2012 +0300
+++ b/src/auth/auth-request.h	Wed Sep 26 18:01:01 2012 +0300
@@ -212,6 +212,10 @@
 auth_request_get_var_expand_table(const struct auth_request *auth_request,
 				  auth_request_escape_func_t *escape_func)
 	ATTR_NULL(2);
+struct var_expand_table *
+auth_request_get_var_expand_table_full(const struct auth_request *auth_request,
+				       auth_request_escape_func_t *escape_func,
+				       unsigned int *count) ATTR_NULL(2);
 const char *auth_request_str_escape(const char *string,
 				    const struct auth_request *request);
 
--- a/src/auth/db-ldap.c	Wed Sep 26 17:17:08 2012 +0300
+++ b/src/auth/db-ldap.c	Wed Sep 26 18:01:01 2012 +0300
@@ -63,7 +63,6 @@
 
 	/* attribute name => value */
 	HASH_TABLE(char *, struct db_ldap_value *) ldap_attrs;
-	struct var_expand_table *var_table;
 
 	const char *val_1_arr[2];
 	string_t *var, *debug;
@@ -1068,21 +1067,17 @@
 	*attr_names_r = array_idx_modifiable(&ctx.attr_names, 0);
 }
 
-static struct var_expand_table *
-db_ldap_value_get_var_expand_table(pool_t pool,
-				   struct auth_request *auth_request)
+static const struct var_expand_table *
+db_ldap_value_get_var_expand_table(struct auth_request *auth_request,
+				   const char *ldap_value)
 {
-	const struct var_expand_table *auth_table = NULL;
 	struct var_expand_table *table;
-	unsigned int count;
+	unsigned int count = 1;
 
-	auth_table = auth_request_get_var_expand_table(auth_request, NULL);
-	for (count = 0; auth_table[count].key != '\0'; count++) ;
-	count++;
-
-	table = p_new(pool, struct var_expand_table, count + 2);
+	table = auth_request_get_var_expand_table_full(auth_request, NULL,
+						       &count);
 	table[0].key = '$';
-	memcpy(table + 1, auth_table, sizeof(*table) * count);
+	table[0].value = ldap_value;
 	return table;
 }
 
@@ -1238,6 +1233,7 @@
 		{ "ldap", db_ldap_field_expand },
 		{ NULL, NULL }
 	};
+	const struct var_expand_table *var_table;
 	const char *const *values;
 
 	if (ldap_value != NULL)
@@ -1263,14 +1259,18 @@
 				"using value '%s'",
 				field->name, values[0]);
 		}
-		if (ctx->var_table == NULL) {
-			ctx->var_table = db_ldap_value_get_var_expand_table(
-						ctx->pool, ctx->auth_request);
+
+		/* do this lookup separately for each expansion, because:
+		   1) the values are allocated from data stack
+		   2) if "user" field is updated, we want %u/%n/%d updated
+		      (and less importantly the same for other variables) */
+		var_table = db_ldap_value_get_var_expand_table(ctx->auth_request,
+							       values[0]);
+		if (ctx->var == NULL)
 			ctx->var = str_new(ctx->pool, 256);
-		}
-		ctx->var_table[0].value = values[0];
-		str_truncate(ctx->var, 0);
-		var_expand_with_funcs(ctx->var, field->value, ctx->var_table,
+		else
+			str_truncate(ctx->var, 0);
+		var_expand_with_funcs(ctx->var, field->value, var_table,
 				      var_funcs_table, ctx);
 		ctx->val_1_arr[0] = str_c(ctx->var);
 		values = ctx->val_1_arr;
--- a/src/auth/passdb-imap.c	Wed Sep 26 17:17:08 2012 +0300
+++ b/src/auth/passdb-imap.c	Wed Sep 26 18:01:01 2012 +0300
@@ -85,6 +85,7 @@
 		t_strconcat(auth_request->set->base_dir, "/",
 			    DNS_CLIENT_SOCKET_NAME, NULL);
 	set.password = password;
+	set.max_idle_time = IMAPC_DEFAULT_MAX_IDLE_TIME;
 
 	if (module->set_have_vars) {
 		str = t_str_new(128);
--- a/src/auth/userdb-static.c	Wed Sep 26 17:17:08 2012 +0300
+++ b/src/auth/userdb-static.c	Wed Sep 26 18:01:01 2012 +0300
@@ -42,6 +42,8 @@
 {
 	struct static_context *ctx = auth_request->context;
 
+	auth_request->userdb_lookup = TRUE;
+
 	auth_request->private_callback.userdb = ctx->old_callback;
 	auth_request->context = ctx->old_context;
 	auth_request_set_state(auth_request, AUTH_REQUEST_STATE_USERDB);
@@ -92,6 +94,10 @@
 
 		auth_request->context = ctx;
 		if (auth_request->passdb != NULL) {
+			/* kludge: temporarily work as if we weren't doing
+			   a userdb lookup. this is to get auth cache to use
+			   passdb caching instead of userdb caching. */
+			auth_request->userdb_lookup = FALSE;
 			auth_request_lookup_credentials(auth_request, "",
 				static_credentials_callback);
 		} else {
--- a/src/config/config-parser.c	Wed Sep 26 17:17:08 2012 +0300
+++ b/src/config/config-parser.c	Wed Sep 26 18:01:01 2012 +0300
@@ -255,10 +255,14 @@
 	}
 
 	max_bits = IPADDR_IS_V4(&ips[0]) ? 32 : 128;
-	if (p == NULL || str_to_uint(p, &bits) < 0 || bits > max_bits)
+	if (p == NULL)
 		*bits_r = max_bits;
-	else
+	else if (str_to_uint(p, &bits) == 0 && bits <= max_bits)
 		*bits_r = bits;
+	else {
+		*error_r = t_strdup_printf("Invalid network mask: %s", p);
+		return -1;
+	}
 	return 0;
 }
 
--- a/src/director/director-connection.c	Wed Sep 26 17:17:08 2012 +0300
+++ b/src/director/director-connection.c	Wed Sep 26 18:01:01 2012 +0300
@@ -71,6 +71,8 @@
    mark the host as failed so we won't try to reconnect to it immediately */
 #define DIRECTOR_SUCCESS_MIN_CONNECT_SECS 40
 #define DIRECTOR_WAIT_DISCONNECT_SECS 10
+#define DIRECTOR_HANDSHAKE_WARN_SECS 29
+#define DIRECTOR_HANDSHAKE_BYTES_LOG_MIN_SECS (60*30)
 
 #if DIRECTOR_CONNECTION_DONE_TIMEOUT_MSECS <= DIRECTOR_CONNECTION_PING_TIMEOUT_MSECS
 #  error DIRECTOR_CONNECTION_DONE_TIMEOUT_MSECS is too low
@@ -92,6 +94,8 @@
 	/* for incoming connections the director host isn't known until
 	   ME-line is received */
 	struct director_host *host;
+	/* this is set only for wrong connections: */
+	struct director_host *connect_request_to;
 
 	int fd;
 	struct io *io;
@@ -118,7 +122,9 @@
 };
 
 static void director_connection_disconnected(struct director_connection **conn);
-static void director_connection_reconnect(struct director_connection **conn);
+static void director_connection_reconnect(struct director_connection **conn,
+					  const char *reason);
+static void director_connection_log_disconnect(struct director_connection *conn);
 
 static void ATTR_FORMAT(2, 3)
 director_cmd_error(struct director_connection *conn, const char *fmt, ...)
@@ -169,7 +175,9 @@
 
 static void director_connection_wait_timeout(struct director_connection *conn)
 {
-	director_connection_deinit(&conn);
+	director_connection_log_disconnect(conn);
+	director_connection_deinit(&conn,
+		"Timeout waiting for disconnect after CONNECT");
 }
 
 static void director_connection_send_connect(struct director_connection *conn,
@@ -185,6 +193,12 @@
 	director_connection_send(conn, connect_str);
 	o_stream_uncork(conn->output);
 
+	/* wait for a while for the remote to disconnect, so it will hopefully
+	   see our CONNECT command. we'll also log the warning later to avoid
+	   multiple log lines about it. */
+	conn->connect_request_to = host;
+	director_host_ref(conn->connect_request_to);
+
 	conn->to_disconnect =
 		timeout_add(DIRECTOR_WAIT_DISCONNECT_SECS*1000,
 			    director_connection_wait_timeout, conn);
@@ -219,10 +233,10 @@
 	} else if (dir->left == NULL) {
 		/* no conflicts yet */
 	} else if (dir->left->host == conn->host) {
-		i_info("Dropping existing connection %s "
-		       "in favor of its new connection %s",
-		       dir->left->host->name, conn->host->name);
-		director_connection_deinit(&dir->left);
+		i_warning("Replacing left director connection %s with %s",
+			  dir->left->host->name, conn->host->name);
+		director_connection_deinit(&dir->left, t_strdup_printf(
+			"Replacing with %s", conn->host->name));
 	} else if (dir->left->verifying_left) {
 		/* we're waiting to verify if our current left is still
 		   working. if we don't receive a PONG, the current left
@@ -234,9 +248,6 @@
 					     dir->self_host) < 0) {
 		/* the old connection is the correct one.
 		   refer the client there (FIXME: do we ever get here?) */
-		i_warning("Director connection %s tried to connect to "
-			  "us, should use %s instead",
-			  conn->name, dir->left->host->name);
 		director_connection_send_connect(conn, dir->left->host);
 		return TRUE;
 	} else {
@@ -266,7 +277,8 @@
 			/* either use this or disconnect it */
 			if (!director_connection_assign_left(conn)) {
 				/* we don't want this */
-				director_connection_deinit(&conn);
+				director_connection_deinit(&conn,
+					"Unwanted incoming connection");
 				director_assign_left(dir);
 				break;
 			}
@@ -303,9 +315,10 @@
 			conn->wrong_host = TRUE;
 			return FALSE;
 		}
-		i_info("Replacing right director connection %s with %s",
-		       dir->right->host->name, conn->host->name);
-		director_connection_deinit(&dir->right);
+		i_warning("Replacing right director connection %s with %s",
+			  dir->right->host->name, conn->host->name);
+		director_connection_deinit(&dir->right, t_strdup_printf(
+			"Replacing with %s", conn->host->name));
 	}
 	dir->right = conn;
 	i_free(conn->name);
@@ -409,7 +422,8 @@
 	} else if (dir->left->host == conn->host) {
 		/* b) */
 		i_assert(dir->left != conn);
-		director_connection_deinit(&dir->left);
+		director_connection_deinit(&dir->left,
+			"Replacing with new incoming connection");
 	} else if (director_host_cmp_to_self(conn->host, dir->left->host,
 					     dir->self_host) < 0) {
 		/* c) */
@@ -712,7 +726,7 @@
 	int ret;
 
 	if ((ret = director_cmd_is_seen(conn, &args, &dir_host)) != 0)
-		return FALSE;
+		return ret > 0;
 
 	if (str_array_length(args) != 2 ||
 	    str_to_uint(args[0], &username_hash) < 0 ||
@@ -919,14 +933,19 @@
 static bool director_handshake_cmd_done(struct director_connection *conn)
 {
 	struct director *dir = conn->dir;
-
-	if (dir->debug) {
-		unsigned int secs = time(NULL)-conn->created;
+	unsigned int handshake_secs = time(NULL) - conn->created;
+	string_t *str;
 
-		i_debug("director(%s): Handshake took %u secs, "
-			"bytes in=%"PRIuUOFF_T" out=%"PRIuUOFF_T,
-			conn->name, secs, conn->input->v_offset,
-			conn->output->offset);
+	if (handshake_secs >= DIRECTOR_HANDSHAKE_WARN_SECS || dir->debug) {
+		str = t_str_new(128);
+		str_printfa(str, "director(%s): Handshake took %u secs, "
+			    "bytes in=%"PRIuUOFF_T" out=%"PRIuUOFF_T,
+			    conn->name, handshake_secs, conn->input->v_offset,
+			    conn->output->offset);
+		if (handshake_secs >= DIRECTOR_HANDSHAKE_WARN_SECS)
+			i_warning("%s", str_c(str));
+		else
+			i_debug("%s", str_c(str));
 	}
 
 	/* the host is up now, make sure we can connect to it immediately
@@ -1143,12 +1162,8 @@
 		if (conn->in && conn != dir->left && conn->me_received &&
 		    conn->to_disconnect == NULL &&
 		    director_host_cmp_to_self(dir->left->host, conn->host,
-					      dir->self_host) < 0) {
-			i_warning("Director connection %s tried to connect to "
-				  "us, should use %s instead",
-				  conn->name, dir->left->host->name);
+					      dir->self_host) < 0)
 			director_connection_send_connect(conn, dir->left->host);
-		}
 	}
 }
 
@@ -1220,6 +1235,11 @@
 		return director_connection_sync(conn, args);
 	if (strcmp(cmd, "CONNECT") == 0)
 		return director_cmd_connect(conn, args);
+	if (strcmp(cmd, "QUIT") == 0) {
+		i_warning("Director %s disconnected us with reason: %s",
+			  conn->name, t_strarray_join(args, " "));
+		return FALSE;
+	}
 
 	director_cmd_error(conn, "Unknown command %s", cmd);
 	return FALSE;
@@ -1247,6 +1267,37 @@
 	return ret;
 }
 
+static void
+director_connection_log_disconnect(struct director_connection *conn)
+{
+	unsigned int secs = ioloop_time - conn->created;
+	string_t *str = t_str_new(128);
+
+	i_assert(conn->connected);
+
+	if (conn->connect_request_to != NULL) {
+		i_warning("Director %s tried to connect to us, "
+			  "should use %s instead",
+			  conn->name, conn->connect_request_to->name);
+		return;
+	}
+
+	str_printfa(str, "Director %s disconnected: ", conn->name);
+	str_append(str, "Connection closed");
+	if (errno != 0 && errno != EPIPE)
+		str_printfa(str, ": %m");
+
+	str_printfa(str, " (connected %u secs, "
+		    "in=%"PRIuUOFF_T" out=%"PRIuUOFF_T,
+		    secs, conn->input->v_offset, conn->output->offset);
+
+	if (!conn->me_received)
+		str_append(str, ", handshake ME not received");
+	else if (!conn->handshake_received)
+		str_append(str, ", handshake DONE not received");
+	i_error("%s", str_c(str));
+}
+
 static void director_connection_input(struct director_connection *conn)
 {
 	struct director *dir = conn->dir;
@@ -1258,16 +1309,14 @@
 		return;
 	case -1:
 		/* disconnected */
-		i_error("Director %s disconnected%s", conn->name,
-			conn->handshake_received ? "" :
-			" before handshake finished");
+		director_connection_log_disconnect(conn);
 		director_connection_disconnected(&conn);
 		return;
 	case -2:
 		/* buffer full */
 		director_cmd_error(conn, "Director sent us more than %d bytes",
 				   MAX_INBUF_SIZE);
-		director_connection_reconnect(&conn);
+		director_connection_reconnect(&conn, "Too long input line");
 		return;
 	}
 
@@ -1286,7 +1335,8 @@
 		} T_END;
 
 		if (!ret) {
-			director_connection_reconnect(&conn);
+			director_connection_reconnect(&conn, t_strdup_printf(
+				"Invalid input: %s", line));
 			break;
 		}
 	}
@@ -1472,7 +1522,8 @@
 	return conn;
 }
 
-void director_connection_deinit(struct director_connection **_conn)
+void director_connection_deinit(struct director_connection **_conn,
+				const char *remote_reason)
 {
 	struct director_connection *const *conns, *conn = *_conn;
 	struct director *dir = conn->dir;
@@ -1480,8 +1531,15 @@
 
 	*_conn = NULL;
 
-	if (dir->debug && conn->host != NULL)
-		i_debug("Disconnecting from %s", conn->host->name);
+	if (dir->debug && conn->host != NULL) {
+		i_debug("Disconnecting from %s: %s",
+			conn->host->name, remote_reason);
+	}
+	if (*remote_reason != '\0' &&
+	    conn->minor_version >= DIRECTOR_VERSION_QUIT) {
+		o_stream_send_str(conn->output, t_strdup_printf(
+			"QUIT\t%s\n", remote_reason));
+	}
 
 	conns = array_get(&dir->connections, &count);
 	for (i = 0; i < count; i++) {
@@ -1502,6 +1560,8 @@
 	if (conn->host != NULL)
 		director_host_unref(conn->host);
 
+	if (conn->connect_request_to != NULL)
+		director_host_unref(conn->connect_request_to);
 	if (conn->user_iter != NULL)
 		user_directory_iter_deinit(&conn->user_iter);
 	if (conn->to_disconnect != NULL)
@@ -1540,17 +1600,18 @@
 		conn->host->last_network_failure = ioloop_time;
 	}
 
-	director_connection_deinit(_conn);
+	director_connection_deinit(_conn, "");
 	if (dir->right == NULL)
 		director_connect(dir);
 }
 
-void director_connection_reconnect(struct director_connection **_conn)
+static void director_connection_reconnect(struct director_connection **_conn,
+					  const char *reason)
 {
 	struct director_connection *conn = *_conn;
 	struct director *dir = conn->dir;
 
-	director_connection_deinit(_conn);
+	director_connection_deinit(_conn, reason);
 	if (dir->right == NULL)
 		director_connect(dir);
 }
--- a/src/director/director-connection.h	Wed Sep 26 17:17:08 2012 +0300
+++ b/src/director/director-connection.h	Wed Sep 26 18:01:01 2012 +0300
@@ -10,7 +10,8 @@
 struct director_connection *
 director_connection_init_out(struct director *dir, int fd,
 			     struct director_host *host);
-void director_connection_deinit(struct director_connection **conn);
+void director_connection_deinit(struct director_connection **conn,
+				const char *remote_reason);
 
 void director_connection_send(struct director_connection *conn,
 			      const char *data);
--- a/src/director/director-request.c	Wed Sep 26 17:17:08 2012 +0300
+++ b/src/director/director-request.c	Wed Sep 26 18:01:01 2012 +0300
@@ -136,6 +136,15 @@
 		   its existing connections have been killed */
 		return FALSE;
 	}
+	if (dir->right == NULL && dir->ring_synced) {
+		/* looks like all the other directors have died. we can do
+		   whatever we want without breaking anything. remove the
+		   user's weakness just in case it was set to TRUE when we
+		   had more directors. */
+		user->weak = FALSE;
+		return TRUE;
+	}
+
 	if (user->weak) {
 		/* wait for user to become non-weak */
 		return FALSE;
--- a/src/director/director.c	Wed Sep 26 17:17:08 2012 +0300
+++ b/src/director/director.c	Wed Sep 26 18:01:01 2012 +0300
@@ -216,7 +216,10 @@
 	if (dir->left != NULL) {
 		/* since we couldn't connect to it,
 		   it must have failed recently */
-		director_connection_deinit(&dir->left);
+		i_warning("director: Assuming %s is dead, disconnecting",
+			  director_connection_get_name(dir->left));
+		director_connection_deinit(&dir->left,
+					   "This connection is dead?");
 	}
 	dir->ring_min_version = DIRECTOR_VERSION_MINOR;
 	if (!dir->ring_handshaked)
@@ -482,7 +485,7 @@
 		if (director_connection_get_host(conn) != removed_host)
 			i++;
 		else {
-			director_connection_deinit(&conn);
+			director_connection_deinit(&conn, "Removing from ring");
 			conns = array_get(&dir->connections, &count);
 		}
 	}
@@ -845,7 +848,7 @@
 	while (array_count(&dir->connections) > 0) {
 		connp = array_idx(&dir->connections, 0);
 		conn = *connp;
-		director_connection_deinit(&conn);
+		director_connection_deinit(&conn, "Shutting down");
 	}
 
 	user_directory_deinit(&dir->users);
--- a/src/director/director.h	Wed Sep 26 17:17:08 2012 +0300
+++ b/src/director/director.h	Wed Sep 26 18:01:01 2012 +0300
@@ -6,12 +6,14 @@
 
 #define DIRECTOR_VERSION_NAME "director"
 #define DIRECTOR_VERSION_MAJOR 1
-#define DIRECTOR_VERSION_MINOR 2
+#define DIRECTOR_VERSION_MINOR 3
 
 /* weak users supported in protocol v1.1+ */
 #define DIRECTOR_VERSION_WEAK_USERS 1
 /* director removes supported in v1.2+ */
 #define DIRECTOR_VERSION_RING_REMOVE 2
+/* quit reason supported in v1.3+ */
+#define DIRECTOR_VERSION_QUIT 3
 
 /* Minimum time between even attempting to communicate with a director that
    failed due to a protocol error. */
--- a/src/doveadm/Makefile.am	Wed Sep 26 17:17:08 2012 +0300
+++ b/src/doveadm/Makefile.am	Wed Sep 26 18:01:01 2012 +0300
@@ -22,6 +22,7 @@
 	-DDOVEADM_MODULEDIR=\""$(doveadm_moduledir)"\" \
 	-DPKG_RUNDIR=\""$(rundir)"\" \
 	-DPKG_STATEDIR=\""$(statedir)"\" \
+	-DPKG_LIBEXECDIR=\""$(pkglibexecdir)"\" \
 	-DBINDIR=\""$(bindir)"\" \
 	-DMANDIR=\""$(mandir)"\"
 
--- a/src/doveadm/doveadm-settings.c	Wed Sep 26 17:17:08 2012 +0300
+++ b/src/doveadm/doveadm-settings.c	Wed Sep 26 18:01:01 2012 +0300
@@ -53,6 +53,7 @@
 
 static const struct setting_define doveadm_setting_defines[] = {
 	DEF(SET_STR, base_dir),
+	DEF(SET_STR, libexec_dir),
 	DEF(SET_STR, mail_plugins),
 	DEF(SET_STR, mail_plugin_dir),
 	DEF(SET_STR, doveadm_socket_path),
@@ -70,6 +71,7 @@
 
 const struct doveadm_settings doveadm_default_settings = {
 	.base_dir = PKG_RUNDIR,
+	.libexec_dir = PKG_LIBEXECDIR,
 	.mail_plugins = "",
 	.mail_plugin_dir = MODULEDIR,
 	.doveadm_socket_path = "doveadm-server",
--- a/src/doveadm/doveadm-settings.h	Wed Sep 26 17:17:08 2012 +0300
+++ b/src/doveadm/doveadm-settings.h	Wed Sep 26 18:01:01 2012 +0300
@@ -3,6 +3,7 @@
 
 struct doveadm_settings {
 	const char *base_dir;
+	const char *libexec_dir;
 	const char *mail_plugins;
 	const char *mail_plugin_dir;
 	const char *doveadm_socket_path;
--- a/src/doveadm/doveadm.c	Wed Sep 26 17:17:08 2012 +0300
+++ b/src/doveadm/doveadm.c	Wed Sep 26 18:01:01 2012 +0300
@@ -174,6 +174,21 @@
 	cmd_config, "config", "[doveconf parameters]"
 };
 
+static void cmd_exec(int argc ATTR_UNUSED, char *argv[])
+{
+	const char *path, *binary = argv[1];
+
+	path = t_strdup_printf("%s/%s", doveadm_settings->libexec_dir, binary);
+	argv++;
+	argv[0] = t_strdup_noconst(path);
+	(void)execv(argv[0], argv);
+	i_fatal("execv(%s) failed: %m", argv[0]);
+}
+
+static struct doveadm_cmd doveadm_cmd_exec = {
+	cmd_exec, "exec", "<binary> [binary parameters]"
+};
+
 static bool
 doveadm_try_run_multi_word(const struct doveadm_cmd *cmd,
 			   const char *cmdname, int argc, char *argv[])
@@ -273,6 +288,7 @@
 static struct doveadm_cmd *doveadm_commands[] = {
 	&doveadm_cmd_help,
 	&doveadm_cmd_config,
+	&doveadm_cmd_exec,
 	&doveadm_cmd_stop,
 	&doveadm_cmd_reload,
 	&doveadm_cmd_dump,
--- a/src/lib-imap-client/imapc-client.h	Wed Sep 26 17:17:08 2012 +0300
+++ b/src/lib-imap-client/imapc-client.h	Wed Sep 26 18:01:01 2012 +0300
@@ -1,6 +1,9 @@
 #ifndef IMAPC_CLIENT_H
 #define IMAPC_CLIENT_H
 
+/* IMAP RFC defines this to be at least 30 minutes. */
+#define IMAPC_DEFAULT_MAX_IDLE_TIME (60*29)
+
 enum imapc_command_state {
 	IMAPC_COMMAND_STATE_OK,
 	IMAPC_COMMAND_STATE_NO,
--- a/src/lib-imap-client/imapc-connection.c	Wed Sep 26 17:17:08 2012 +0300
+++ b/src/lib-imap-client/imapc-connection.c	Wed Sep 26 18:01:01 2012 +0300
@@ -496,7 +496,8 @@
 
 	ret = imap_parser_read_args(conn->parser, 0,
 				    IMAP_PARSE_FLAG_LITERAL_SIZE |
-				    IMAP_PARSE_FLAG_ATOM_ALLCHARS, imap_args_r);
+				    IMAP_PARSE_FLAG_ATOM_ALLCHARS |
+				    IMAP_PARSE_FLAG_SERVER_TEXT, imap_args_r);
 	if (ret == -2) {
 		/* need more data */
 		return 0;
@@ -816,6 +817,9 @@
 
 	if ((ret = imapc_connection_read_line(conn, &imap_args)) <= 0)
 		return ret;
+	/* we already verified that the banner beigns with OK */
+	i_assert(imap_arg_atom_equals(imap_args, "OK"));
+	imap_args++;
 
 	if (imapc_connection_handle_imap_resp_text(conn, imap_args,
 						   &key, &value) < 0)
@@ -839,6 +843,8 @@
 static int imapc_connection_input_untagged(struct imapc_connection *conn)
 {
 	const struct imap_arg *imap_args;
+	const unsigned char *data;
+	size_t size;
 	const char *name, *value;
 	struct imap_parser *parser;
 	struct imapc_untagged_reply reply;
@@ -846,13 +852,13 @@
 
 	if (conn->state == IMAPC_CONNECTION_STATE_CONNECTING) {
 		/* input banner */
-		name = imap_parser_read_word(conn->parser);
-		if (name == NULL)
+		data = i_stream_get_data(conn->input, &size);
+		if (size < 3 && memchr(data, '\n', size) == NULL)
 			return 0;
-
-		if (strcasecmp(name, "OK") != 0) {
+		if (i_memcasecmp(data, "OK ", 3) != 0) {
 			imapc_connection_input_error(conn,
-				"Banner doesn't begin with OK: %s", name);
+				"Banner doesn't begin with OK: %s",
+				t_strcut(t_strndup(data, size), '\n'));
 			return -1;
 		}
 		conn->input_callback = imapc_connection_input_banner;
--- a/src/lib-imap/imap-parser.c	Wed Sep 26 17:17:08 2012 +0300
+++ b/src/lib-imap/imap-parser.c	Wed Sep 26 18:01:01 2012 +0300
@@ -18,7 +18,8 @@
 	ARG_PARSE_LITERAL,
 	ARG_PARSE_LITERAL8,
 	ARG_PARSE_LITERAL_DATA,
-	ARG_PARSE_LITERAL_DATA_FORCED
+	ARG_PARSE_LITERAL_DATA_FORCED,
+	ARG_PARSE_TEXT
 };
 
 struct imap_parser {
@@ -38,6 +39,7 @@
 
 	enum arg_parse_type cur_type;
 	size_t cur_pos; /* parser position in input buffer */
+	bool cur_resp_text; /* we're parsing [resp-text-code] */
 
 	int str_first_escape; /* ARG_PARSE_STRING: index to first '\' */
 	uoff_t literal_size; /* ARG_PARSE_LITERAL: string size */
@@ -62,7 +64,7 @@
 	parser = i_new(struct imap_parser, 1);
 	parser->refcount = 1;
 	parser->pool = pool_alloconly_create(MEMPOOL_GROWING"IMAP parser",
-					     1024*10);
+					     1024);
 	parser->input = input;
 	parser->output = output;
 	parser->max_line_size = max_line_size;
@@ -103,6 +105,7 @@
 
 	parser->cur_type = ARG_PARSE_NONE;
 	parser->cur_pos = 0;
+	parser->cur_resp_text = FALSE;
 
 	parser->str_first_escape = 0;
 	parser->literal_size = 0;
@@ -219,6 +222,7 @@
 
 	switch (parser->cur_type) {
 	case ARG_PARSE_ATOM:
+	case ARG_PARSE_TEXT:
 		if (size == 3 && memcmp(data, "NIL", 3) == 0) {
 			/* NIL argument */
 			arg->type = IMAP_ARG_NIL;
@@ -480,6 +484,56 @@
 	}
 }
 
+static bool imap_parser_is_next_resp_text(struct imap_parser *parser)
+{
+	const struct imap_arg *arg;
+
+	if (parser->cur_list != &parser->root_list ||
+	    array_count(parser->cur_list) != 1)
+		return FALSE;
+
+	arg = array_idx(&parser->root_list, 0);
+	if (arg->type != IMAP_ARG_ATOM)
+		return FALSE;
+
+	return strcasecmp(arg->_data.str, "OK") == 0 ||
+		strcasecmp(arg->_data.str, "NO") == 0 ||
+		strcasecmp(arg->_data.str, "BAD") == 0 ||
+		strcasecmp(arg->_data.str, "BYE") == 0;
+}
+
+static bool imap_parser_is_next_text(struct imap_parser *parser)
+{
+	const struct imap_arg *arg;
+	unsigned int len;
+
+	if (parser->cur_list != &parser->root_list)
+		return FALSE;
+
+	arg = array_idx(&parser->root_list, array_count(&parser->root_list)-1);
+	if (arg->type != IMAP_ARG_ATOM)
+		return FALSE;
+
+	len = strlen(arg->_data.str);
+	return len > 0 && arg->_data.str[len-1] == ']';
+}
+
+static bool imap_parser_read_text(struct imap_parser *parser,
+				  const unsigned char *data, size_t data_size)
+{
+	size_t i;
+
+	/* read until end of line */
+	for (i = parser->cur_pos; i < data_size; i++) {
+		if (is_linebreak(data[i])) {
+			imap_parser_save_arg(parser, data, i);
+			break;
+		}
+	}
+	parser->cur_pos = i;
+	return parser->cur_type == ARG_PARSE_NONE;
+}
+
 /* Returns TRUE if argument was fully processed. Also returns TRUE if
    an argument inside a list was processed. */
 static int imap_parser_read_arg(struct imap_parser *parser)
@@ -497,6 +551,13 @@
 			return FALSE;
 		i_assert(parser->cur_pos == 0);
 
+		if (parser->cur_resp_text &&
+		    imap_parser_is_next_text(parser)) {
+			/* we just parsed [resp-text-code] */
+			parser->cur_type = ARG_PARSE_TEXT;
+			break;
+		}
+
 		switch (data[0]) {
 		case '\r':
 			if (data_size == 1) {
@@ -565,6 +626,16 @@
 	case ARG_PARSE_ATOM:
 		if (!imap_parser_read_atom(parser, data, data_size))
 			return FALSE;
+		if ((parser->flags & IMAP_PARSE_FLAG_SERVER_TEXT) == 0)
+			break;
+
+		if (imap_parser_is_next_resp_text(parser)) {
+			/* we just parsed OK/NO/BAD/BYE. after parsing the
+			   [resp-text-code] the rest of the message can contain
+			   pretty much any random text, which we can't parse
+			   as if it was valid IMAP input */
+			parser->cur_resp_text = TRUE;
+		}
 		break;
 	case ARG_PARSE_STRING:
 		if (!imap_parser_read_string(parser, data, data_size))
@@ -594,6 +665,10 @@
 		if (!imap_parser_read_literal_data(parser, data, data_size))
 			return FALSE;
 		break;
+	case ARG_PARSE_TEXT:
+		if (!imap_parser_read_text(parser, data, data_size))
+			return FALSE;
+		break;
 	default:
                 i_unreached();
 	}
@@ -616,6 +691,7 @@
 	parser->line_size += parser->cur_pos;
 	i_stream_skip(parser->input, parser->cur_pos);
 	parser->cur_pos = 0;
+	parser->cur_resp_text = FALSE;
 
 	if (parser->list_arg != NULL && !parser->literal_size_return) {
 		parser->error = "Missing ')'";
--- a/src/lib-imap/imap-parser.h	Wed Sep 26 17:17:08 2012 +0300
+++ b/src/lib-imap/imap-parser.h	Wed Sep 26 18:01:01 2012 +0300
@@ -20,7 +20,11 @@
 	/* Parse in list context; ')' parses as EOL */
 	IMAP_PARSE_FLAG_INSIDE_LIST	= 0x20,
 	/* Parse literal8 and set it as flag to imap_arg. */
-	IMAP_PARSE_FLAG_LITERAL8	= 0x40
+	IMAP_PARSE_FLAG_LITERAL8	= 0x40,
+	/* We're parsing IMAP server replies. Parse the "text" after
+	   OK/NO/BAD/BYE replies as a single atom. We assume that the initial
+	   "*" or tag was already skipped over. */
+	IMAP_PARSE_FLAG_SERVER_TEXT	= 0x80
 };
 
 struct imap_parser;
--- a/src/lib-master/master-service-settings-cache.c	Wed Sep 26 17:17:08 2012 +0300
+++ b/src/lib-master/master-service-settings-cache.c	Wed Sep 26 18:01:01 2012 +0300
@@ -59,7 +59,8 @@
 	struct master_service_settings_cache *cache;
 	pool_t pool;
 
-	pool = pool_alloconly_create("master service settings cache", 1024*32);
+	pool = pool_alloconly_create(MEMPOOL_GROWING"master service settings cache",
+				     1024*12);
 	cache = p_new(pool, struct master_service_settings_cache, 1);
 	cache->pool = pool;
 	cache->service = service;
--- a/src/lib-settings/settings-parser.c	Wed Sep 26 17:17:08 2012 +0300
+++ b/src/lib-settings/settings-parser.c	Wed Sep 26 18:01:01 2012 +0300
@@ -204,7 +204,7 @@
 	i_assert(count > 0);
 
 	parser_pool = pool_alloconly_create(MEMPOOL_GROWING"settings parser",
-					    8192);
+					    1024);
 	ctx = p_new(parser_pool, struct setting_parser_context, 1);
 	ctx->set_pool = set_pool;
 	ctx->parser_pool = parser_pool;
@@ -1745,7 +1745,7 @@
 
 	pool_ref(new_pool);
 	parser_pool = pool_alloconly_create(MEMPOOL_GROWING"dup settings parser",
-					    8192);
+					    1024);
 	new_ctx = p_new(parser_pool, struct setting_parser_context, 1);
 	new_ctx->set_pool = new_pool;
 	new_ctx->parser_pool = parser_pool;
--- a/src/lib-storage/index/dbox-common/dbox-storage.c	Wed Sep 26 17:17:08 2012 +0300
+++ b/src/lib-storage/index/dbox-common/dbox-storage.c	Wed Sep 26 18:01:01 2012 +0300
@@ -111,7 +111,8 @@
 		}
 	}
 
-	dbox_verify_alt_path(ns->list);
+	if (!ns->list->set.alt_dir_nocheck)
+		dbox_verify_alt_path(ns->list);
 	return 0;
 }
 
--- a/src/lib-storage/index/dbox-multi/mdbox-file.c	Wed Sep 26 17:17:08 2012 +0300
+++ b/src/lib-storage/index/dbox-multi/mdbox-file.c	Wed Sep 26 18:01:01 2012 +0300
@@ -302,11 +302,14 @@
 {
 	struct mdbox_file *mfile = (struct mdbox_file *)file;
 	struct mdbox_map *map = mfile->storage->map;
+	struct mailbox_permissions perm;
 	mode_t old_mask;
 	const char *p, *dir;
 	int fd;
 
-	old_mask = umask(0666 & ~map->perm.file_create_mode);
+	mailbox_list_get_root_permissions(map->root_list, &perm);
+
+	old_mask = umask(0666 & ~perm.file_create_mode);
 	fd = open(path, O_RDWR | O_CREAT | O_TRUNC, 0666);
 	umask(old_mask);
 	if (fd == -1 && errno == ENOENT && parents &&
@@ -321,25 +324,25 @@
 			return -1;
 		}
 		/* try again */
-		old_mask = umask(0666 & ~map->perm.file_create_mode);
+		old_mask = umask(0666 & ~perm.file_create_mode);
 		fd = open(path, O_RDWR | O_CREAT | O_TRUNC, 0666);
 		umask(old_mask);
 	}
 	if (fd == -1) {
 		mail_storage_set_critical(&file->storage->storage,
 			"open(%s, O_CREAT) failed: %m", path);
-	} else if (map->perm.file_create_gid == (gid_t)-1) {
+	} else if (perm.file_create_gid == (gid_t)-1) {
 		/* no group change */
-	} else if (fchown(fd, (uid_t)-1, map->perm.file_create_gid) < 0) {
+	} else if (fchown(fd, (uid_t)-1, perm.file_create_gid) < 0) {
 		if (errno == EPERM) {
 			mail_storage_set_critical(&file->storage->storage, "%s",
 				eperm_error_get_chgrp("fchown", path,
-					map->perm.file_create_gid,
-					map->perm.file_create_gid_origin));
+					perm.file_create_gid,
+					perm.file_create_gid_origin));
 		} else {
 			mail_storage_set_critical(&file->storage->storage,
 				"fchown(%s, -1, %ld) failed: %m",
-				path, (long)map->perm.file_create_gid);
+				path, (long)perm.file_create_gid);
 		}
 		/* continue anyway */
 	}
--- a/src/lib-storage/index/dbox-multi/mdbox-map-private.h	Wed Sep 26 17:17:08 2012 +0300
+++ b/src/lib-storage/index/dbox-multi/mdbox-map-private.h	Wed Sep 26 18:01:01 2012 +0300
@@ -20,7 +20,6 @@
 	uint32_t map_ext_id, ref_ext_id;
 
 	struct mailbox_list *root_list;
-	struct mailbox_permissions perm;
 
 	unsigned int verify_existing_file_ids:1;
 };
--- a/src/lib-storage/index/dbox-multi/mdbox-map.c	Wed Sep 26 17:17:08 2012 +0300
+++ b/src/lib-storage/index/dbox-multi/mdbox-map.c	Wed Sep 26 18:01:01 2012 +0300
@@ -74,11 +74,6 @@
 				sizeof(uint32_t));
 	map->ref_ext_id = mail_index_ext_register(map->index, "ref", 0,
 				sizeof(uint16_t), sizeof(uint16_t));
-
-	mailbox_list_get_root_permissions(root_list, &map->perm);
-	mail_index_set_permissions(map->index, map->perm.file_create_mode,
-				   map->perm.file_create_gid,
-				   map->perm.file_create_gid_origin);
 	return map;
 }
 
@@ -140,6 +135,7 @@
 static int mdbox_map_open_internal(struct mdbox_map *map, bool create_missing)
 {
 	enum mail_index_open_flags open_flags;
+	struct mailbox_permissions perm;
 	int ret = 0;
 
 	if (map->view != NULL) {
@@ -147,6 +143,11 @@
 		return 1;
 	}
 
+	mailbox_list_get_root_permissions(map->root_list, &perm);
+	mail_index_set_permissions(map->index, perm.file_create_mode,
+				   perm.file_create_gid,
+				   perm.file_create_gid_origin);
+
 	open_flags = MAIL_INDEX_OPEN_FLAG_NEVER_IN_MEMORY |
 		mail_storage_settings_to_index_flags(MAP_STORAGE(map)->set);
 	if (create_missing) {
--- a/src/lib-storage/index/dbox-multi/mdbox-storage.c	Wed Sep 26 17:17:08 2012 +0300
+++ b/src/lib-storage/index/dbox-multi/mdbox-storage.c	Wed Sep 26 18:01:01 2012 +0300
@@ -87,7 +87,8 @@
 	bool debug = ns->mail_set->mail_debug;
 	const char *home, *path;
 
-	if (mail_user_get_home(ns->owner, &home) > 0) {
+	if (ns->owner != NULL &&
+	    mail_user_get_home(ns->owner, &home) > 0) {
 		path = t_strconcat(home, "/mdbox", NULL);
 		if (access(path, R_OK|W_OK|X_OK) == 0) {
 			if (debug)
--- a/src/lib-storage/index/dbox-single/sdbox-storage.c	Wed Sep 26 17:17:08 2012 +0300
+++ b/src/lib-storage/index/dbox-single/sdbox-storage.c	Wed Sep 26 18:01:01 2012 +0300
@@ -34,7 +34,8 @@
 	bool debug = ns->mail_set->mail_debug;
 	const char *home, *path;
 
-	if (mail_user_get_home(ns->owner, &home) > 0) {
+	if (ns->owner != NULL &&
+	    mail_user_get_home(ns->owner, &home) > 0) {
 		path = t_strconcat(home, "/sdbox", NULL);
 		if (access(path, R_OK|W_OK|X_OK) == 0) {
 			if (debug)
--- a/src/lib-storage/index/imapc/imapc-settings.c	Wed Sep 26 17:17:08 2012 +0300
+++ b/src/lib-storage/index/imapc/imapc-settings.c	Wed Sep 26 18:01:01 2012 +0300
@@ -131,6 +131,10 @@
 		return FALSE;
 	}
 #endif
+	if (set->imapc_max_idle_time == 0) {
+		*error_r = "imapc_max_idle_time must not be 0";
+		return FALSE;
+	}
 	if (imapc_settings_parse_features(set, error_r) < 0)
 		return FALSE;
 	return TRUE;
--- a/src/lib-storage/index/index-status.c	Wed Sep 26 17:17:08 2012 +0300
+++ b/src/lib-storage/index/index-status.c	Wed Sep 26 18:01:01 2012 +0300
@@ -293,6 +293,7 @@
 	}
 	if (mailbox_search_deinit(&search_ctx) < 0)
 		ret = -1;
+	mail_search_args_unref(&search_args);
 
 	if (ret == 0) {
 		/* success, cache all */
--- a/src/lib-storage/index/maildir/maildir-storage.c	Wed Sep 26 17:17:08 2012 +0300
+++ b/src/lib-storage/index/maildir/maildir-storage.c	Wed Sep 26 18:01:01 2012 +0300
@@ -96,7 +96,8 @@
 
 	/* we'll need to figure out the maildir location ourself.
 	   It's ~/Maildir unless we are chrooted. */
-	if (mail_user_get_home(ns->owner, &home) > 0) {
+	if (ns->owner != NULL &&
+	    mail_user_get_home(ns->owner, &home) > 0) {
 		path = t_strconcat(home, "/Maildir", NULL);
 		if (access(path, R_OK|W_OK|X_OK) == 0) {
 			if (debug)
--- a/src/lib-storage/index/mbox/mbox-storage.c	Wed Sep 26 17:17:08 2012 +0300
+++ b/src/lib-storage/index/mbox/mbox-storage.c	Wed Sep 26 18:01:01 2012 +0300
@@ -244,7 +244,8 @@
 	bool debug = ns->mail_set->mail_debug;
 	const char *home, *path;
 
-	if (mail_user_get_home(ns->owner, &home) <= 0) {
+	if (ns->owner == NULL ||
+	    mail_user_get_home(ns->owner, &home) <= 0) {
 		if (debug)
 			i_debug("maildir: Home directory not set");
 		home = "";
--- a/src/lib-storage/index/pop3c/pop3c-mail.c	Wed Sep 26 17:17:08 2012 +0300
+++ b/src/lib-storage/index/pop3c/pop3c-mail.c	Wed Sep 26 18:01:01 2012 +0300
@@ -30,7 +30,7 @@
 	struct message_size hdr_size, body_size;
 	struct istream *input;
 
-	if (mail->data.virtual_size != 0) {
+	if (mail->data.virtual_size != (uoff_t)-1) {
 		/* virtual size is already known. it's the same as our
 		   (correct) physical size */
 		*size_r = mail->data.virtual_size;
--- a/src/lib-storage/list/mailbox-list-fs-iter.c	Wed Sep 26 17:17:08 2012 +0300
+++ b/src/lib-storage/list/mailbox-list-fs-iter.c	Wed Sep 26 18:01:01 2012 +0300
@@ -183,6 +183,12 @@
 		/* mailbox doesn't match any patterns, we don't care about it */
 		return 0;
 	}
+	if ((ctx->ctx.flags & MAILBOX_LIST_ITER_SKIP_ALIASES) != 0) {
+		ret = mailbox_list_dirent_is_alias_symlink(ctx->ctx.list,
+							   dir_path, d);
+		if (ret != 0)
+			return ret < 0 ? -1 : 0;
+	}
 	ret = ctx->ctx.list->v.
 		get_mailbox_flags(ctx->ctx.list, dir_path, d->d_name,
 				  mailbox_list_get_file_type(d), &info_flags);
@@ -756,6 +762,13 @@
 	if (ret <= 0)
 		return NULL;
 
+	if (_ctx->list->ns->type == MAIL_NAMESPACE_TYPE_SHARED &&
+	    !_ctx->list->ns->list->mail_set->mail_shared_explicit_inbox &&
+	    strlen(ctx->info.vname) < _ctx->list->ns->prefix_len) {
+		/* shared/user INBOX, IMAP code already lists it */
+		return fs_list_iter_next(_ctx);
+	}
+
 	if ((ctx->ctx.flags & MAILBOX_LIST_ITER_RETURN_SUBSCRIBED) != 0) {
 		mailbox_list_set_subscription_flags(ctx->ctx.list,
 						    ctx->info.vname,
--- a/src/lib-storage/list/mailbox-list-maildir-iter.c	Wed Sep 26 17:17:08 2012 +0300
+++ b/src/lib-storage/list/mailbox-list-maildir-iter.c	Wed Sep 26 18:01:01 2012 +0300
@@ -323,6 +323,11 @@
 	if (maildir_delete_trash_dir(ctx, fname))
 		return 0;
 
+	if ((ctx->ctx.flags & MAILBOX_LIST_ITER_SKIP_ALIASES) != 0) {
+		ret = mailbox_list_dirent_is_alias_symlink(list, ctx->dir, d);
+		if (ret != 0)
+			return ret < 0 ? -1 : 0;
+	}
 	T_BEGIN {
 		ret = list->v.get_mailbox_flags(list, ctx->dir, fname,
 				mailbox_list_get_file_type(d), &flags);
--- a/src/lib-storage/mail-storage-service.c	Wed Sep 26 17:17:08 2012 +0300
+++ b/src/lib-storage/mail-storage-service.c	Wed Sep 26 18:01:01 2012 +0300
@@ -962,7 +962,7 @@
 	pool_t user_pool, temp_pool;
 	int ret = 1;
 
-	user_pool = pool_alloconly_create("mail storage service user", 1024*8);
+	user_pool = pool_alloconly_create("mail storage service user", 1024*6);
 
 	if (mail_storage_service_read_settings(ctx, input, user_pool,
 					       &user_info, &set_parser,
--- a/src/lib-storage/mail-storage.h	Wed Sep 26 17:17:08 2012 +0300
+++ b/src/lib-storage/mail-storage.h	Wed Sep 26 18:01:01 2012 +0300
@@ -75,7 +75,8 @@
 	STATUS_HIGHESTMODSEQ	= 0x80,
 	STATUS_PERMANENT_FLAGS	= 0x200,
 	STATUS_FIRST_RECENT_UID	= 0x400,
-	STATUS_LAST_CACHED_SEQ	= 0x800
+	STATUS_LAST_CACHED_SEQ	= 0x800,
+	STATUS_CHECK_OVER_QUOTA	= 0x1000 /* return error if over quota */
 };
 
 enum mailbox_metadata_items {
--- a/src/lib-storage/mailbox-guid-cache.c	Wed Sep 26 17:17:08 2012 +0300
+++ b/src/lib-storage/mailbox-guid-cache.c	Wed Sep 26 18:01:01 2012 +0300
@@ -56,6 +56,7 @@
 	list->guid_cache_errors = FALSE;
 
 	ctx = mailbox_list_iter_init(list, "*",
+				     MAILBOX_LIST_ITER_SKIP_ALIASES |
 				     MAILBOX_LIST_ITER_NO_AUTO_BOXES);
 	while ((info = mailbox_list_iter_next(ctx)) != NULL) {
 		if ((info->flags &
--- a/src/lib-storage/mailbox-list-iter.h	Wed Sep 26 17:17:08 2012 +0300
+++ b/src/lib-storage/mailbox-list-iter.h	Wed Sep 26 18:01:01 2012 +0300
@@ -11,7 +11,9 @@
 	   physically exist */
 	MAILBOX_LIST_ITER_NO_AUTO_BOXES		= 0x000004,
 
-	/* For mailbox_list_iter_init_namespaces(): Skip namespaces that
+	/* Skip all kinds of mailbox aliases. This typically includes symlinks
+	   that point to the same directory. Also when iterating with
+	   mailbox_list_iter_init_namespaces() skip namespaces that
 	   have alias_for set. */
 	MAILBOX_LIST_ITER_SKIP_ALIASES		= 0x000008,
 	/* For mailbox_list_iter_init_namespaces(): '*' in a pattern doesn't
--- a/src/lib-storage/mailbox-list-private.h	Wed Sep 26 17:17:08 2012 +0300
+++ b/src/lib-storage/mailbox-list-private.h	Wed Sep 26 18:01:01 2012 +0300
@@ -184,6 +184,9 @@
 
 bool mailbox_list_name_is_too_large(const char *name, char sep);
 enum mailbox_list_file_type mailbox_list_get_file_type(const struct dirent *d);
+int mailbox_list_dirent_is_alias_symlink(struct mailbox_list *list,
+					 const char *dir_path,
+					 const struct dirent *d);
 bool mailbox_list_try_get_absolute_path(struct mailbox_list *list,
 					const char **name);
 
--- a/src/lib-storage/mailbox-list.c	Wed Sep 26 17:17:08 2012 +0300
+++ b/src/lib-storage/mailbox-list.c	Wed Sep 26 18:01:01 2012 +0300
@@ -2,6 +2,7 @@
 
 #include "lib.h"
 #include "array.h"
+#include "abspath.h"
 #include "ioloop.h"
 #include "mkdir-parents.h"
 #include "str.h"
@@ -165,6 +166,7 @@
 	list->set.mailbox_dir_name =
 		p_strdup(list->pool, set->mailbox_dir_name);
 	list->set.alt_dir = p_strdup(list->pool, set->alt_dir);
+	list->set.alt_dir_nocheck = set->alt_dir_nocheck;
 
 	if (*set->mailbox_dir_name == '\0')
 		list->set.mailbox_dir_name = "";
@@ -299,7 +301,10 @@
 			dest = &set_r->control_dir;
 		else if (strcmp(key, "ALT") == 0)
 			dest = &set_r->alt_dir;
-		else if (strcmp(key, "LAYOUT") == 0)
+		else if (strcmp(key, "ALTNOCHECK") == 0) {
+			set_r->alt_dir_nocheck = TRUE;
+			continue;
+		} else if (strcmp(key, "LAYOUT") == 0)
 			dest = &set_r->layout;
 		else if (strcmp(key, "SUBSCRIPTIONS") == 0)
 			dest = &set_r->subscription_fname;
@@ -1384,6 +1389,39 @@
 	return type;
 }
 
+int mailbox_list_dirent_is_alias_symlink(struct mailbox_list *list,
+					 const char *dir_path,
+					 const struct dirent *d)
+{
+	struct stat st;
+	int ret;
+
+	if (mailbox_list_get_file_type(d) == MAILBOX_LIST_FILE_TYPE_SYMLINK)
+		return 1;
+
+	T_BEGIN {
+		const char *path, *linkpath;
+
+		path = t_strconcat(dir_path, "/", d->d_name, NULL);
+		if (lstat(path, &st) < 0) {
+			mailbox_list_set_critical(list,
+						  "lstat(%s) failed: %m", path);
+			ret = -1;
+		} else if (!S_ISLNK(st.st_mode)) {
+			ret = 0;
+		} else if (t_readlink(path, &linkpath) < 0) {
+			i_error("readlink(%s) failed: %m", path);
+			ret = -1;
+		} else {
+			/* it's an alias only if it points to the same
+			   directory */
+			ret = strchr(linkpath, '/') == NULL ? 1 : 0;
+		}
+	} T_END;
+	return ret;
+}
+
+
 static bool
 mailbox_list_try_get_home_path(struct mailbox_list *list, const char **name)
 {
--- a/src/lib-storage/mailbox-list.h	Wed Sep 26 17:17:08 2012 +0300
+++ b/src/lib-storage/mailbox-list.h	Wed Sep 26 18:01:01 2012 +0300
@@ -105,6 +105,8 @@
 	char escape_char;
 	/* Use UTF-8 mailbox names on filesystem instead of mUTF-7 */
 	bool utf8;
+	/* Don't check/create the alt-dir symlink. */
+	bool alt_dir_nocheck;
 };
 
 struct mailbox_permissions {
--- a/src/lib/hash.c	Wed Sep 26 17:17:08 2012 +0300
+++ b/src/lib/hash.c	Wed Sep 26 18:01:01 2012 +0300
@@ -8,7 +8,7 @@
 
 #include <ctype.h>
 
-#define HASH_TABLE_MIN_SIZE 131
+#define HASH_TABLE_MIN_SIZE 67
 
 #undef hash_table_create
 #undef hash_table_create_direct
--- a/src/lmtp/client.c	Wed Sep 26 17:17:08 2012 +0300
+++ b/src/lmtp/client.c	Wed Sep 26 18:01:01 2012 +0300
@@ -298,6 +298,9 @@
 {
 	struct mail_recipient *rcpt;
 
+	if (client->proxy != NULL)
+		lmtp_proxy_deinit(&client->proxy);
+
 	if (array_is_created(&client->state.rcpt_to)) {
 		array_foreach_modifiable(&client->state.rcpt_to, rcpt)
 			mail_storage_service_user_free(&rcpt->service_user);
--- a/src/lmtp/commands.c	Wed Sep 26 17:17:08 2012 +0300
+++ b/src/lmtp/commands.c	Wed Sep 26 18:01:01 2012 +0300
@@ -463,6 +463,42 @@
 	*address = str_c(username);
 }
 
+static int
+lmtp_rcpt_to_is_over_quota(struct client *client,
+			   const struct mail_recipient *rcpt)
+{
+	struct mail_user *user;
+	struct mail_namespace *ns;
+	struct mailbox *box;
+	struct mailbox_status status;
+	const char *errstr;
+	enum mail_error error;
+	int ret;
+
+	if (!client->lmtp_set->lmtp_rcpt_check_quota)
+		return 0;
+
+	ret = mail_storage_service_next(storage_service,
+					rcpt->service_user, &user);
+	if (ret < 0)
+		return -1;
+
+	ns = mail_namespace_find_inbox(user->namespaces);
+	box = mailbox_alloc(ns->list, "INBOX", 0);
+	ret = mailbox_get_status(box, STATUS_CHECK_OVER_QUOTA, &status);
+	if (ret < 0) {
+		errstr = mailbox_get_last_error(box, &error);
+		if (error == MAIL_ERROR_NOSPACE) {
+			client_send_line(client, "552 5.2.2 <%s> %s",
+					 rcpt->address, errstr);
+			ret = 1;
+		}
+	}
+	mailbox_free(&box);
+	mail_user_unref(&user);
+	return ret;
+}
+
 int cmd_rcpt(struct client *client, const char *args)
 {
 	struct mail_recipient rcpt;
@@ -498,16 +534,6 @@
 			return 0;
 	}
 
-	if (client->proxy != NULL) {
-		/* NOTE: if this restriction is ever removed, we'll also need
-		   to send different message bodies to local and proxy
-		   (with and without Return-Path: header) */
-		client_send_line(client, "451 4.3.0 <%s> "
-			"Can't handle mixed proxy/non-proxy destinations",
-			address);
-		return 0;
-	}
-
 	memset(&input, 0, sizeof(input));
 	input.module = input.service = "lmtp";
 	input.username = username;
@@ -531,14 +557,29 @@
 				 address, username);
 		return 0;
 	}
+	if (client->proxy != NULL) {
+		/* NOTE: if this restriction is ever removed, we'll also need
+		   to send different message bodies to local and proxy
+		   (with and without Return-Path: header) */
+		client_send_line(client, "451 4.3.0 <%s> "
+			"Can't handle mixed proxy/non-proxy destinations",
+			address);
+		return 0;
+	}
 
 	lmtp_address_translate(client, &address);
 
 	rcpt.address = p_strdup(client->state_pool, address);
 	rcpt.detail = p_strdup(client->state_pool, detail);
-	array_append(&client->state.rcpt_to, &rcpt, 1);
-
-	client_send_line(client, "250 2.1.5 OK");
+	if ((ret = lmtp_rcpt_to_is_over_quota(client, &rcpt)) < 0) {
+		client_send_line(client, ERRSTR_TEMP_MAILBOX_FAIL,
+				 rcpt.address);
+		return 0;
+	}
+	if (ret == 0) {
+		array_append(&client->state.rcpt_to, &rcpt, 1);
+		client_send_line(client, "250 2.1.5 OK");
+	}
 	return 0;
 }
 
--- a/src/lmtp/lmtp-settings.c	Wed Sep 26 17:17:08 2012 +0300
+++ b/src/lmtp/lmtp-settings.c	Wed Sep 26 18:01:01 2012 +0300
@@ -59,6 +59,7 @@
 static const struct setting_define lmtp_setting_defines[] = {
 	DEF(SET_BOOL, lmtp_proxy),
 	DEF(SET_BOOL, lmtp_save_to_detail_mailbox),
+	DEF(SET_BOOL, lmtp_rcpt_check_quota),
 	DEF(SET_STR, lmtp_address_translate),
 	DEF(SET_STR_VARS, login_greeting),
 	DEF(SET_STR, login_trusted_networks),
@@ -69,6 +70,7 @@
 static const struct lmtp_settings lmtp_default_settings = {
 	.lmtp_proxy = FALSE,
 	.lmtp_save_to_detail_mailbox = FALSE,
+	.lmtp_rcpt_check_quota = FALSE,
 	.lmtp_address_translate = "",
 	.login_greeting = PACKAGE_NAME" ready.",
 	.login_trusted_networks = ""
--- a/src/lmtp/lmtp-settings.h	Wed Sep 26 17:17:08 2012 +0300
+++ b/src/lmtp/lmtp-settings.h	Wed Sep 26 18:01:01 2012 +0300
@@ -7,6 +7,7 @@
 struct lmtp_settings {
 	bool lmtp_proxy;
 	bool lmtp_save_to_detail_mailbox;
+	bool lmtp_rcpt_check_quota;
 	const char *lmtp_address_translate;
 	const char *login_greeting;
 	const char *login_trusted_networks;
--- a/src/plugins/Makefile.am	Wed Sep 26 17:17:08 2012 +0300
+++ b/src/plugins/Makefile.am	Wed Sep 26 18:01:01 2012 +0300
@@ -21,6 +21,7 @@
 	listescape \
 	notify \
 	mail-log \
+	mailbox-alias \
 	quota \
 	imap-quota \
 	pop3-migration \
--- a/src/plugins/acl/acl-lookup-dict.c	Wed Sep 26 17:17:08 2012 +0300
+++ b/src/plugins/acl/acl-lookup-dict.c	Wed Sep 26 18:01:01 2012 +0300
@@ -91,6 +91,13 @@
 	}
 }
 
+static bool
+acl_rights_is_same_user(const struct acl_rights *right, struct mail_user *user)
+{
+	return right->id_type == ACL_ID_USER &&
+		strcmp(right->identifier, user->username) == 0;
+}
+
 static int acl_lookup_dict_rebuild_add_backend(struct mail_namespace *ns,
 					       ARRAY_TYPE(const_string) *ids)
 {
@@ -114,7 +121,10 @@
 
 		iter = acl_object_list_init(aclobj);
 		while ((ret = acl_object_list_next(iter, &rights)) > 0) {
-			if (acl_rights_has_nonowner_lookup_changes(&rights)) {
+			/* avoid pointless user -> user entries,
+			   which some clients do */
+			if (acl_rights_has_nonowner_lookup_changes(&rights) &&
+			    !acl_rights_is_same_user(&rights, ns->owner)) {
 				str_truncate(id, 0);
 				acl_lookup_dict_write_rights_id(id, &rights);
 				str_append_c(id, '/');
--- a/src/plugins/fts-lucene/lucene-wrapper.cc	Wed Sep 26 17:17:08 2012 +0300
+++ b/src/plugins/fts-lucene/lucene-wrapper.cc	Wed Sep 26 18:01:01 2012 +0300
@@ -110,7 +110,7 @@
 	index->path = i_strdup(path);
 	index->list = list;
 	index->normalizer = !set->normalize ? NULL :
-		list->ns->user->default_normalizer;
+		mailbox_list_get_namespace(list)->user->default_normalizer;
 	if (set != NULL)
 		index->set = *set;
 	else {
--- a/src/plugins/fts-solr/fts-backend-solr.c	Wed Sep 26 17:17:08 2012 +0300
+++ b/src/plugins/fts-solr/fts-backend-solr.c	Wed Sep 26 18:01:01 2012 +0300
@@ -570,6 +570,7 @@
 	/* FIXME: proper rescan needed. for now we'll just reset the
 	   last-uids */
 	iter = mailbox_list_iter_init(backend->ns->list, "*",
+				      MAILBOX_LIST_ITER_SKIP_ALIASES |
 				      MAILBOX_LIST_ITER_NO_AUTO_BOXES);
 	while ((info = mailbox_list_iter_next(iter)) != NULL) {
 		if ((info->flags &
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/plugins/mailbox-alias/Makefile.am	Wed Sep 26 18:01:01 2012 +0300
@@ -0,0 +1,18 @@
+AM_CPPFLAGS = \
+	-I$(top_srcdir)/src/lib \
+	-I$(top_srcdir)/src/lib-mail \
+	-I$(top_srcdir)/src/lib-imap \
+	-I$(top_srcdir)/src/lib-index \
+	-I$(top_srcdir)/src/lib-storage
+
+NOPLUGIN_LDFLAGS =
+lib20_mailbox_alias_plugin_la_LDFLAGS = -module -avoid-version
+
+module_LTLIBRARIES = \
+	lib20_mailbox_alias_plugin.la
+
+lib20_mailbox_alias_plugin_la_SOURCES = \
+	mailbox-alias-plugin.c
+
+noinst_HEADERS = \
+	mailbox-alias-plugin.h
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/plugins/mailbox-alias/mailbox-alias-plugin.c	Wed Sep 26 18:01:01 2012 +0300
@@ -0,0 +1,334 @@
+/* Copyright (c) 2012 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "str.h"
+#include "mail-storage-hooks.h"
+#include "mail-storage-private.h"
+#include "mailbox-list-private.h"
+#include "mailbox-alias-plugin.h"
+
+#define MAILBOX_ALIAS_USER_CONTEXT(obj) \
+	MODULE_CONTEXT(obj, mailbox_alias_user_module)
+#define MAILBOX_ALIAS_CONTEXT(obj) \
+	MODULE_CONTEXT(obj, mailbox_alias_storage_module)
+#define MAILBOX_ALIAS_LIST_CONTEXT(obj) \
+	MODULE_CONTEXT(obj, mailbox_alias_mailbox_list_module)
+
+struct mailbox_alias {
+	const char *old_vname, *new_vname;
+};
+
+struct mailbox_alias_user {
+	union mail_user_module_context module_ctx;
+
+	ARRAY(struct mailbox_alias) aliases;
+};
+
+struct mailbox_alias_mailbox_list {
+	union mailbox_list_module_context module_ctx;
+};
+
+struct mailbox_alias_mailbox {
+	union mailbox_module_context module_ctx;
+};
+
+enum mailbox_symlink_existence {
+	MAILBOX_SYMLINK_EXISTENCE_NONEXISTENT,
+	MAILBOX_SYMLINK_EXISTENCE_SYMLINK,
+	MAILBOX_SYMLINK_EXISTENCE_NOT_SYMLINK
+};
+
+static MODULE_CONTEXT_DEFINE_INIT(mailbox_alias_user_module,
+				  &mail_user_module_register);
+static MODULE_CONTEXT_DEFINE_INIT(mailbox_alias_storage_module,
+				  &mail_storage_module_register);
+static MODULE_CONTEXT_DEFINE_INIT(mailbox_alias_mailbox_list_module,
+				  &mailbox_list_module_register);
+
+const char *mailbox_alias_plugin_version = DOVECOT_VERSION;
+
+static const char *
+mailbox_alias_find_new(struct mail_user *user, const char *new_vname)
+{
+	struct mailbox_alias_user *auser = MAILBOX_ALIAS_USER_CONTEXT(user);
+	const struct mailbox_alias *alias;
+
+	array_foreach(&auser->aliases, alias) {
+		if (strcmp(alias->new_vname, new_vname) == 0)
+			return alias->old_vname;
+	}
+	return NULL;
+}
+
+static int mailbox_symlink_exists(struct mailbox_list *list, const char *vname,
+				  enum mailbox_symlink_existence *existence_r)
+{
+	struct mailbox_alias_mailbox_list *alist =
+		MAILBOX_ALIAS_LIST_CONTEXT(list);
+	struct stat st;
+	const char *symlink_name, *symlink_path;
+
+	symlink_name = alist->module_ctx.super.get_storage_name(list, vname);
+	symlink_path = mailbox_list_get_path(list, symlink_name,
+					     MAILBOX_LIST_PATH_TYPE_DIR);
+	if (lstat(symlink_path, &st) < 0) {
+		if (errno == ENOENT) {
+			*existence_r = MAILBOX_SYMLINK_EXISTENCE_NONEXISTENT;
+			return 0;
+		}
+		mailbox_list_set_critical(list,
+					  "lstat(%s) failed: %m", symlink_path);
+		return -1;
+	}
+	if (S_ISLNK(st.st_mode))
+		*existence_r = MAILBOX_SYMLINK_EXISTENCE_SYMLINK;
+	else
+		*existence_r = MAILBOX_SYMLINK_EXISTENCE_NOT_SYMLINK;
+	return 0;
+}
+
+static int mailbox_is_alias_symlink(struct mailbox *box)
+{
+	enum mailbox_symlink_existence existence;
+
+	if (mailbox_alias_find_new(box->storage->user, box->vname) == NULL)
+		return 0;
+	if (mailbox_symlink_exists(box->list, box->vname, &existence) < 0) {
+		mail_storage_copy_list_error(box->storage, box->list);
+		return -1;
+	}
+	return existence == MAILBOX_SYMLINK_EXISTENCE_SYMLINK ? 1 : 0;
+}
+
+static int
+mailbox_has_aliases(struct mailbox_list *list, const char *old_vname)
+{
+	struct mailbox_alias_user *auser =
+		MAILBOX_ALIAS_USER_CONTEXT(list->ns->user);
+	const struct mailbox_alias *alias;
+	enum mailbox_symlink_existence existence;
+	int ret = 0;
+
+	array_foreach(&auser->aliases, alias) {
+		if (strcmp(alias->old_vname, old_vname) == 0) {
+			if (mailbox_symlink_exists(list, alias->new_vname,
+						   &existence) < 0)
+				ret = -1;
+			if (existence == MAILBOX_SYMLINK_EXISTENCE_SYMLINK)
+				return 1;
+		}
+	}
+	return ret;
+}
+
+static int
+mailbox_alias_create_symlink(struct mailbox *box,
+			     const char *old_name, const char *new_name)
+{
+	const char *old_path, *new_path, *fname;
+
+	old_path = mailbox_list_get_path(box->list, old_name,
+					 MAILBOX_LIST_PATH_TYPE_DIR);
+	new_path = mailbox_list_get_path(box->list, new_name,
+					 MAILBOX_LIST_PATH_TYPE_DIR);
+	fname = strrchr(old_path, '/');
+	i_assert(fname != NULL);
+	fname++;
+	i_assert(strncmp(new_path, old_path, fname-old_path) == 0);
+
+	if (symlink(fname, new_path) < 0) {
+		if (errno == EEXIST) {
+			mail_storage_set_error(box->storage, MAIL_ERROR_EXISTS,
+					       "Mailbox already exists");
+			return -1;
+		}
+		mail_storage_set_critical(box->storage,
+			"symlink(%s, %s) failed: %m", fname, new_path);
+		return -1;
+	}
+	return 0;
+}
+
+static const char *
+mailbox_alias_get_storage_name(struct mailbox_list *list, const char *vname)
+{
+	struct mailbox_alias_mailbox_list *alist =
+		MAILBOX_ALIAS_LIST_CONTEXT(list);
+	const char *old_vname;
+	enum mailbox_symlink_existence existence;
+
+	/* access the old mailbox so that e.g. full text search won't
+	   index the mailbox twice. this also means that deletion must be
+	   careful to delete the symlink, box->name. */
+	old_vname = mailbox_alias_find_new(list->ns->user, vname);
+	if (old_vname != NULL &&
+	    mailbox_symlink_exists(list, vname, &existence) == 0 &&
+	    existence != MAILBOX_SYMLINK_EXISTENCE_NOT_SYMLINK)
+		vname = old_vname;
+
+	return alist->module_ctx.super.get_storage_name(list, vname);
+}
+
+static int
+mailbox_alias_create(struct mailbox *box, const struct mailbox_update *update,
+		     bool directory)
+{
+	struct mailbox_alias_mailbox *abox = MAILBOX_ALIAS_CONTEXT(box);
+	struct mailbox_alias_mailbox_list *alist =
+		MAILBOX_ALIAS_LIST_CONTEXT(box->list);
+	const char *symlink_name;
+	int ret;
+
+	ret = abox->module_ctx.super.create_box(box, update, directory);
+	if (mailbox_alias_find_new(box->storage->user, box->vname) == NULL)
+		return ret;
+	if (ret < 0 && mailbox_get_last_mail_error(box) != MAIL_ERROR_EXISTS)
+		return ret;
+
+	/* all the code so far has actually only created the original
+	   mailbox. now we'll create the symlink if it's missing. */
+	symlink_name = alist->module_ctx.super.
+		get_storage_name(box->list, box->vname);
+	return mailbox_alias_create_symlink(box, box->name, symlink_name);
+}
+
+static int mailbox_alias_delete(struct mailbox *box)
+{
+	struct mailbox_alias_mailbox *abox = MAILBOX_ALIAS_CONTEXT(box);
+	struct mailbox_alias_mailbox_list *alist =
+		MAILBOX_ALIAS_LIST_CONTEXT(box->list);
+	const char *symlink_name;
+	int ret;
+
+	ret = mailbox_has_aliases(box->list, box->vname);
+	if (ret < 0)
+		return -1;
+	if (ret > 0) {
+		mail_storage_set_error(box->storage, MAIL_ERROR_NOTPOSSIBLE,
+			"Can't delete mailbox while it has aliases");
+		return -1;
+	}
+
+	if (mailbox_is_alias_symlink(box)) {
+		/* we're deleting an alias mailbox. we'll need to handle this
+		   explicitly since box->name points to the original mailbox */
+		symlink_name = alist->module_ctx.super.
+			get_storage_name(box->list, box->vname);
+		if (mailbox_list_delete_symlink(box->list, symlink_name) < 0) {
+			mail_storage_copy_list_error(box->storage, box->list);
+			return -1;
+		}
+		return 0;
+	}
+
+	return abox->module_ctx.super.delete_box(box);
+}
+
+static int mailbox_alias_rename(struct mailbox *src, struct mailbox *dest)
+{
+	struct mailbox_alias_mailbox *abox = MAILBOX_ALIAS_CONTEXT(src);
+	int ret;
+
+	if (mailbox_is_alias_symlink(src)) {
+		mail_storage_set_error(src->storage, MAIL_ERROR_NOTPOSSIBLE,
+				       "Can't rename alias mailboxes");
+		return -1;
+	}
+	if (mailbox_is_alias_symlink(dest)) {
+		mail_storage_set_error(src->storage, MAIL_ERROR_NOTPOSSIBLE,
+				       "Can't rename to mailbox alias");
+		return -1;
+	}
+	ret = mailbox_has_aliases(src->list, src->vname);
+	if (ret < 0)
+		return -1;
+	if (ret > 0) {
+		mail_storage_set_error(src->storage, MAIL_ERROR_NOTPOSSIBLE,
+			"Can't rename mailbox while it has aliases");
+		return -1;
+	}
+
+	return abox->module_ctx.super.rename_box(src, dest);
+}
+
+static void mailbox_alias_mail_user_created(struct mail_user *user)
+{
+	struct mail_user_vfuncs *v = user->vlast;
+	struct mailbox_alias_user *auser;
+	struct mailbox_alias *alias;
+	string_t *oldkey, *newkey;
+	const char *old_vname, *new_vname;
+	unsigned int i;
+
+	auser = p_new(user->pool, struct mailbox_alias_user, 1);
+	auser->module_ctx.super = *v;
+	user->vlast = &auser->module_ctx.super;
+
+	p_array_init(&auser->aliases, user->pool, 8);
+
+	oldkey = t_str_new(32);
+	newkey = t_str_new(32);
+	str_append(oldkey, "mailbox_alias_old");
+	str_append(newkey, "mailbox_alias_new");
+	for (i = 2;; i++) {
+		old_vname = mail_user_plugin_getenv(user, str_c(oldkey));
+		new_vname = mail_user_plugin_getenv(user, str_c(newkey));
+		if (old_vname == NULL || new_vname == NULL)
+			break;
+
+		alias = array_append_space(&auser->aliases);
+		alias->old_vname = old_vname;
+		alias->new_vname = new_vname;
+
+		str_truncate(oldkey, 0);
+		str_truncate(newkey, 0);
+		str_printfa(oldkey, "mailbox_alias_old%u", i);
+		str_printfa(newkey, "mailbox_alias_new%u", i);
+	}
+
+	MODULE_CONTEXT_SET(user, mailbox_alias_user_module, auser);
+}
+
+static void mailbox_alias_mailbox_list_created(struct mailbox_list *list)
+{
+	struct mailbox_list_vfuncs *v = list->vlast;
+	struct mailbox_alias_mailbox_list *alist;
+
+	alist = p_new(list->pool, struct mailbox_alias_mailbox_list, 1);
+	alist->module_ctx.super = *v;
+	list->vlast = &alist->module_ctx.super;
+
+	v->get_storage_name = mailbox_alias_get_storage_name;
+	MODULE_CONTEXT_SET(list, mailbox_alias_mailbox_list_module, alist);
+}
+
+static void mailbox_alias_mailbox_allocated(struct mailbox *box)
+{
+	struct mailbox_vfuncs *v = box->vlast;
+	struct mailbox_alias_mailbox *abox;
+
+	abox = p_new(box->pool, struct mailbox_alias_mailbox, 1);
+	abox->module_ctx.super = *v;
+	box->vlast = &abox->module_ctx.super;
+
+	v->create_box = mailbox_alias_create;
+	v->delete_box = mailbox_alias_delete;
+	v->rename_box = mailbox_alias_rename;
+	MODULE_CONTEXT_SET(box, mailbox_alias_storage_module, abox);
+}
+
+static struct mail_storage_hooks mailbox_alias_mail_storage_hooks = {
+	.mail_user_created = mailbox_alias_mail_user_created,
+	.mailbox_list_created = mailbox_alias_mailbox_list_created,
+	.mailbox_allocated = mailbox_alias_mailbox_allocated
+};
+
+void mailbox_alias_plugin_init(struct module *module)
+{
+	mail_storage_hooks_add(module, &mailbox_alias_mail_storage_hooks);
+}
+
+void mailbox_alias_plugin_deinit(void)
+{
+	mail_storage_hooks_remove(&mailbox_alias_mail_storage_hooks);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/plugins/mailbox-alias/mailbox-alias-plugin.h	Wed Sep 26 18:01:01 2012 +0300
@@ -0,0 +1,7 @@
+#ifndef MAILBOX_ALIAS_PLUGIN_H
+#define MAILBOX_ALIAS_PLUGIN_H
+
+void mailbox_alias_plugin_init(struct module *module);
+void mailbox_alias_plugin_deinit(void);
+
+#endif
--- a/src/plugins/quota/quota-count.c	Wed Sep 26 17:17:08 2012 +0300
+++ b/src/plugins/quota/quota-count.c	Wed Sep 26 18:01:01 2012 +0300
@@ -72,6 +72,7 @@
 	int ret = 0;
 
 	ctx = mailbox_list_iter_init(ns->list, "*",
+				     MAILBOX_LIST_ITER_SKIP_ALIASES |
 				     MAILBOX_LIST_ITER_RETURN_NO_FLAGS);
 	while ((info = mailbox_list_iter_next(ctx)) != NULL) {
 		if ((info->flags & (MAILBOX_NONEXISTENT |
--- a/src/plugins/quota/quota-maildir.c	Wed Sep 26 17:17:08 2012 +0300
+++ b/src/plugins/quota/quota-maildir.c	Wed Sep 26 18:01:01 2012 +0300
@@ -131,6 +131,7 @@
 	ctx->path = str_new(default_pool, 512);
 	ctx->list = list;
 	ctx->iter = mailbox_list_iter_init(list, "*",
+					   MAILBOX_LIST_ITER_SKIP_ALIASES |
 					   MAILBOX_LIST_ITER_RETURN_NO_FLAGS);
 	return ctx;
 }
--- a/src/plugins/quota/quota-private.h	Wed Sep 26 17:17:08 2012 +0300
+++ b/src/plugins/quota/quota-private.h	Wed Sep 26 18:01:01 2012 +0300
@@ -33,7 +33,7 @@
 
 	int64_t bytes_limit, count_limit;
 	/* relative to default_rule */
-	unsigned int bytes_percent, count_percent;
+	int bytes_percent, count_percent;
 
 	/* Don't include this mailbox in quota */
 	unsigned int ignore:1;
--- a/src/plugins/quota/quota-storage.c	Wed Sep 26 17:17:08 2012 +0300
+++ b/src/plugins/quota/quota-storage.c	Wed Sep 26 18:01:01 2012 +0300
@@ -66,6 +66,36 @@
 	qmail->super.expunge(_mail);
 }
 
+static int
+quota_get_status(struct mailbox *box, enum mailbox_status_items items,
+		 struct mailbox_status *status_r)
+{
+	struct quota_mailbox *qbox = QUOTA_CONTEXT(box);
+	struct quota_transaction_context *qt;
+	bool too_large;
+	int ret = 0;
+
+	if ((items & STATUS_CHECK_OVER_QUOTA) != 0) {
+		qt = quota_transaction_begin(box);
+		if ((ret = quota_test_alloc(qt, 1, &too_large)) == 0) {
+			mail_storage_set_error(box->storage, MAIL_ERROR_NOSPACE,
+					       qt->quota->set->quota_exceeded_msg);
+			ret = -1;
+		}
+		quota_transaction_rollback(&qt);
+
+		if ((items & ~STATUS_CHECK_OVER_QUOTA) == 0) {
+			/* don't bother calling parent, it may unnecessarily
+			   try to open the mailbox */
+			return ret;
+		}
+	}
+
+	if (qbox->module_ctx.super.get_status(box, items, status_r) < 0)
+		ret = -1;
+	return ret < 0 ? -1 : 0;
+}
+
 static struct mailbox_transaction_context *
 quota_mailbox_transaction_begin(struct mailbox *box,
 				enum mailbox_transaction_flags flags)
@@ -390,6 +420,7 @@
 	qbox->module_ctx.super = *v;
 	box->vlast = &qbox->module_ctx.super;
 
+	v->get_status = quota_get_status;
 	v->transaction_begin = quota_mailbox_transaction_begin;
 	v->transaction_commit = quota_mailbox_transaction_commit;
 	v->transaction_rollback = quota_mailbox_transaction_rollback;
--- a/src/plugins/quota/quota.c	Wed Sep 26 17:17:08 2012 +0300
+++ b/src/plugins/quota/quota.c	Wed Sep 26 18:01:01 2012 +0300
@@ -361,7 +361,7 @@
 {
 	int64_t percentage = *limit;
 
-	if (percentage <= 0 || percentage >= -1U) {
+	if (percentage <= -100 || percentage >= -1U) {
 		*error_r = p_strdup_printf(root_set->set->pool,
 			"Invalid rule percentage: %lld", (long long)percentage);
 		return -1;
@@ -385,9 +385,9 @@
 quota_rule_recalculate_relative_rules(struct quota_rule *rule,
 				      int64_t bytes_limit, int64_t count_limit)
 {
-	if (rule->bytes_percent > 0)
+	if (rule->bytes_percent != 0)
 		rule->bytes_limit = bytes_limit * rule->bytes_percent / 100;
-	if (rule->count_percent > 0)
+	if (rule->count_percent != 0)
 		rule->count_limit = count_limit * rule->count_percent / 100;
 }