changeset 14840:6a0954d0ce09

Merged changes from v2.1 tree.
author Timo Sirainen <tss@iki.fi>
date Fri, 10 Aug 2012 05:24:07 +0300
parents 53139f2f2284 (current diff) 9d0873cefa08 (diff)
children 19b6a8c025e4
files configure.in src/auth/auth-cache.h src/auth/auth-master-connection.c src/auth/auth-request.h src/auth/auth.c src/auth/db-ldap.c src/auth/db-passwd-file.c src/auth/main.c src/auth/mech-gssapi.c src/auth/mech-winbind.c src/config/config-connection.c src/config/doveconf.c src/config/old-set-parser.c src/doveadm/client-connection.c src/doveadm/doveadm-mail-mailbox-status.c src/doveadm/main.c src/imap-login/client.c src/imap/cmd-list.c src/lib-auth/auth-master.c src/lib-auth/auth-master.h src/lib-dict/dict-memcached.c src/lib-dict/dict-redis.c src/lib-imap-client/imapc-client.c src/lib-imap-client/imapc-connection.c src/lib-imap-client/imapc-connection.h src/lib-master/mountpoint-list.c src/lib-settings/settings-parser.c src/lib-ssl-iostream/iostream-openssl.c src/lib-storage/index/dbox-common/dbox-attachment.c src/lib-storage/index/imapc/imapc-save.c src/lib-storage/index/index-attachment.c src/lib-storage/index/index-mail.c src/lib-storage/index/index-storage.c src/lib-storage/index/pop3c/pop3c-client.c src/lib-storage/index/shared/shared-list.c src/lib-storage/list/mailbox-list-delete.c src/lib-storage/list/mailbox-list-fs-iter.c src/lib-storage/mail-storage.c src/lib/Makefile.am src/lib/connection.c src/lib/connection.h src/lmtp/commands.c src/lmtp/lmtp-proxy.c src/lmtp/lmtp-settings.c src/lmtp/lmtp-settings.h src/login-common/client-common.c src/login-common/client-common.h src/login-common/ssl-proxy-openssl.c src/master/main.c src/plugins/fts-lucene/lucene-wrapper.cc src/plugins/fts-solr/fts-backend-solr.c src/plugins/fts-solr/solr-connection.c src/plugins/fts/fts-api.c src/plugins/fts/fts-search.c src/plugins/imap-acl/imap-acl-plugin.c src/plugins/imap-quota/imap-quota-plugin.c src/plugins/quota/quota-storage.c src/plugins/quota/quota.c src/pop3-login/client-authenticate.c src/pop3/pop3-commands.c src/util/script.c
diffstat 103 files changed, 2882 insertions(+), 272 deletions(-) [+]
line wrap: on
line diff
--- a/.hgsigs	Fri Aug 10 04:56:56 2012 +0300
+++ b/.hgsigs	Fri Aug 10 05:24:07 2012 +0300
@@ -47,3 +47,5 @@
 469cee314d9c54d2d7101ec9e38579fdc9610eaf 0 iEYEABECAAYFAk+VWqkACgkQyUhSUUBVislnXACfVjPqMmPUvYtXQXwqff0h7N76mZUAn02lPeUCyuyr1TF9e1hGM/sKgmko
 7c249e2a82a9cd33ae15ead6443c3499e16da623 0 iEYEABECAAYFAk+nX2sACgkQyUhSUUBVisn7uwCbBD3boxBOGEJ8OYsIJ57n5Cr09FAAoIvhxL6EHRB15AMOw4sPaALJ3/bB
 c92fb8b928f69ca01681a2c2976304b7e4bc3afc 0 iEYEABECAAYFAk/FIeIACgkQyUhSUUBVisk4IgCfUiXVXntqzPjJcALYRpqw4Zc7a/0An3HKWwgb6PBCbmvxBfTezNkqjqVK
+7e5f36fd989d27a2fb48250adbab8fa54ddb6083 0 iEYEABECAAYFAk/yVakACgkQyUhSUUBVismekwCfSEVQjd6fwdChjd53LSt03b4UWKoAoIxd/IjLatTISlHm44iiQwzRKByo
+bc86680293d256d5a8009690caeb73ab2e34e359 0 iEYEABECAAYFAlAZaTUACgkQyUhSUUBVisnTAACfU1pB34RrXEyLnpnL4Ee/oeNBYcoAnRWxTqx870Efjwf+eBPzafO0C/NU
--- a/.hgtags	Fri Aug 10 04:56:56 2012 +0300
+++ b/.hgtags	Fri Aug 10 05:24:07 2012 +0300
@@ -84,3 +84,5 @@
 469cee314d9c54d2d7101ec9e38579fdc9610eaf 2.1.5
 7c249e2a82a9cd33ae15ead6443c3499e16da623 2.1.6
 c92fb8b928f69ca01681a2c2976304b7e4bc3afc 2.1.7
+7e5f36fd989d27a2fb48250adbab8fa54ddb6083 2.1.8
+bc86680293d256d5a8009690caeb73ab2e34e359 2.1.9
--- a/NEWS	Fri Aug 10 04:56:56 2012 +0300
+++ b/NEWS	Fri Aug 10 05:24:07 2012 +0300
@@ -1,3 +1,42 @@
+v2.1.9 2012-08-01  Timo Sirainen <tss@iki.fi>
+
+	* mail-log plugin: Log mailbox names with UTF-8 everywhere 
+	  (instead of mUTF-7 in some places and UTF-8 in other places)
+	* director: Changed director_username_hash setting's default from %u
+	  to %Lu (= lowercase usernames). This doesn't break any existing
+	  installations, but might fix some of them.
+
+	+ doveadm: Added "auth cache flush [<username>]" command.
+	+ Implemented dict passdb/userdb
+	+ Implemented Redis and memcached dict backends, which can be used as
+	  auth backends. Redis can also be used as dict-quota backend.
+	+ Added plugin { quota_ignore_save_errors=yes } setting to allow saving
+	  a mail when quota lookup fails with temporary failure.
+	- Full text search indexing might have failed for some messages,
+	  always causing indexer-worker process to run out of memory.
+	- fts-lucene: Fixed handling SEARCH HEADER FROM/TO/SUBJECT/CC/BCC when
+	  the header wasn't lowercased.
+	- fts-squat: Fixed crash when searching a virtual mailbox.
+	- pop3: Fixed assert crash when doing UIDL on empty mailbox on some
+	  setups. 
+	- auth: GSSAPI RFC compliancy and error handling fixes.
+	- Various fixes related to handling shared namespaces
+
+v2.1.8 2012-07-03  Timo Sirainen <tss@iki.fi>
+
+	+ pop3c: Added pop3c_master_user setting.
+	- imap: Mailbox names were accidentally sent as UTF-8 instead of mUTF-7
+	  in previous v2.1.x releases for STATUS, MYRIGHTS and GETQUOTAROOT
+	  commands.
+	- lmtp proxy: Don't timeout connections too early when mail has a lot
+	  of RCPT TOs.
+	- director: Don't crash if the director is working alone.
+	- shared mailboxes: Avoid doing "@domain" userdb lookups.
+	- doveadm: Fixed crash with proxying some commands.
+	- fts-squat: Fixed handling multiple SEARCH parameters.
+	- imapc: Fixed a crash when message had more than 8 keywords.
+	- imapc: Don't crash on APPEND/COPY if server doesn't support UIDPLUS.
+
 v2.1.7 2012-05-29  Timo Sirainen <tss@iki.fi>
 
 	* LDAP: Compatibility fix for v2.0: ldap: If attributes contain
--- a/configure.in	Fri Aug 10 04:56:56 2012 +0300
+++ b/configure.in	Fri Aug 10 05:24:07 2012 +0300
@@ -2675,6 +2675,11 @@
       AC_CHECK_LIB(textcat, special_textcat_Init, [
 	have_lucene_textcat=yes
 	AC_DEFINE(HAVE_LUCENE_TEXTCAT,, Define if you want textcat support for CLucene)
+      ], [
+        AC_CHECK_LIB(exttextcat, special_textcat_Init, [
+          have_lucene_exttextcat=yes
+          AC_DEFINE(HAVE_LUCENE_EXTTEXTCAT,, Define if you want textcat (Debian version) support for CLucene)
+        ])
       ])
     ], [
       if test $want_stemmer = yes; then
@@ -2687,6 +2692,7 @@
 fi
 AM_CONDITIONAL(BUILD_LUCENE_STEMMER, test "$have_lucene_stemmer" = "yes")
 AM_CONDITIONAL(BUILD_LUCENE_TEXTCAT, test "$have_lucene_textcat" = "yes")
+AM_CONDITIONAL(BUILD_LUCENE_EXTTEXTCAT, test "$have_lucene_exttextcat" = "yes")
 
 if test $have_lucene = no; then
   not_fts="$not_fts lucene"
--- a/doc/Makefile.am	Fri Aug 10 04:56:56 2012 +0300
+++ b/doc/Makefile.am	Fri Aug 10 05:24:07 2012 +0300
@@ -7,7 +7,10 @@
 docfiles = \
 	documentation.txt \
 	securecoding.txt \
-	thread-refs.txt
+	thread-refs.txt \
+	mkcert.sh \
+	dovecot-openssl.cnf \
+	solr-schema.xml
 
 if BUILD_DOCS
 doc_DATA = $(docfiles)
@@ -15,7 +18,4 @@
 
 EXTRA_DIST = \
 	dovecot-initd.sh \
-	mkcert.sh \
-	dovecot-openssl.cnf \
-	solr-schema.xml \
 	$(docfiles)
--- a/doc/example-config/conf.d/10-director.conf	Fri Aug 10 04:56:56 2012 +0300
+++ b/doc/example-config/conf.d/10-director.conf	Fri Aug 10 05:24:07 2012 +0300
@@ -25,6 +25,11 @@
 # If you enable this, you'll also need to add inet_listener for the port.
 #director_doveadm_port = 0
 
+# How the username is translated before being hashed. Useful values include
+# %Ln if user can log in with or without @domain, %Ld if mailboxes are shared
+# within domain.
+#director_username_hash = %Lu
+
 # To enable director service, uncomment the modes and assign a port.
 service director {
   unix_listener login/director {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/example-config/conf.d/auth-dict.conf.ext	Fri Aug 10 05:24:07 2012 +0300
@@ -0,0 +1,16 @@
+# Authentication via dict backend. Included from auth.conf.
+#
+# <doc/wiki/AuthDatabase.Dict.txt>
+
+passdb {
+  driver = dict
+
+  # Path for dict configuration file, see
+  # example-config/dovecot-dict-auth.conf.ext
+  args = /etc/dovecot/dovecot-dict-auth.conf.ext
+}
+
+userdb {
+  driver = dict
+  args = /etc/dovecot/dovecot-dict-auth.conf.ext
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/example-config/dovecot-dict-auth.conf.ext	Fri Aug 10 05:24:07 2012 +0300
@@ -0,0 +1,22 @@
+# Dictionary URI
+#uri = 
+
+# Key for passdb lookups
+password_key = dovecot/passdb/%u
+
+# Key for userdb lookups
+user_key = dovecot/userdb/%u
+
+# How to parse the value for key=value pairs. Currently we support only JSON
+# format with { "key": "value", ... } object.
+#value_format = json
+
+# Username iteration prefix. Keys under this are assumed to contain usernames.
+iterate_prefix = dovecot/userdb/
+
+# Should iteration be disabled for this userdb? If this userdb acts only as a
+# cache there's no reason to try to iterate the (partial & duplicate) users.
+#iterate_disable = no
+
+# Default password scheme
+default_pass_scheme = MD5
--- a/src/auth/Makefile.am	Fri Aug 10 04:56:56 2012 +0300
+++ b/src/auth/Makefile.am	Fri Aug 10 05:24:07 2012 +0300
@@ -25,6 +25,7 @@
 	-I$(top_srcdir)/src/lib \
 	-I$(top_srcdir)/src/lib-auth \
 	-I$(top_srcdir)/src/lib-test \
+	-I$(top_srcdir)/src/lib-dict \
 	-I$(top_srcdir)/src/lib-dns \
 	-I$(top_srcdir)/src/lib-sql \
 	-I$(top_srcdir)/src/lib-settings \
@@ -73,6 +74,7 @@
 	auth-worker-client.c \
 	auth-worker-server.c \
 	db-checkpassword.c \
+	db-dict.c \
 	db-sql.c \
 	db-passwd-file.c \
 	main.c \
@@ -96,6 +98,7 @@
 	passdb-bsdauth.c \
 	passdb-cache.c \
 	passdb-checkpassword.c \
+	passdb-dict.c \
 	passdb-passwd.c \
 	passdb-passwd-file.c \
 	passdb-pam.c \
@@ -108,6 +111,7 @@
 	userdb.c \
 	userdb-blocking.c \
 	userdb-checkpassword.c \
+	userdb-dict.c \
 	userdb-nss.c \
 	userdb-passwd.c \
 	userdb-passwd-file.c \
@@ -134,6 +138,7 @@
 	auth-stream.h \
 	auth-worker-client.h \
 	auth-worker-server.h \
+	db-dict.h \
 	db-ldap.h \
 	db-sql.h \
 	db-passwd-file.h \
--- a/src/auth/auth-cache.c	Fri Aug 10 04:56:56 2012 +0300
+++ b/src/auth/auth-cache.c	Fri Aug 10 05:24:07 2012 +0300
@@ -23,36 +23,72 @@
 	unsigned long long pos_size, neg_size;
 };
 
-static const struct var_expand_table *
-auth_request_var_expand_tab_find(const char *key, unsigned int size)
+static bool
+auth_request_var_expand_tab_find(const char *key, unsigned int size,
+				 unsigned int *idx_r)
 {
 	const struct var_expand_table *tab = auth_request_var_expand_static_tab;
 	unsigned int i;
 
 	for (i = 0; tab[i].key != '\0' || tab[i].long_key != NULL; i++) {
 		if (size == 1) {
-			if (key[0] == tab[i].key)
-				return &tab[i];
+			if (key[0] == tab[i].key) {
+				*idx_r = i;
+				return TRUE;
+			}
 		} else if (tab[i].long_key != NULL) {
 			if (strncmp(key, tab[i].long_key, size) == 0 &&
-			    tab[i].long_key[size] == '\0')
-				return &tab[i];
+			    tab[i].long_key[size] == '\0') {
+				*idx_r = i;
+				return TRUE;
+			}
 		}
 	}
-	return NULL;
+	return FALSE;
+}
+
+static void
+auth_cache_key_add_var(string_t *str, const char *data, unsigned int len)
+{
+	if (str_len(str) > 0)
+		str_append_c(str, '\t');
+	str_append_c(str, '%');
+	if (len == 1)
+		str_append_c(str, data[0]);
+	else {
+		str_append_c(str, '{');
+		str_append_n(str, data, len);
+		str_append_c(str, '}');
+	}
+}
+
+static void auth_cache_key_add_tab_idx(string_t *str, unsigned int i)
+{
+	const struct var_expand_table *tab =
+		&auth_request_var_expand_static_tab[i];
+
+	if (str_len(str) > 0)
+		str_append_c(str, '\t');
+	str_append_c(str, '%');
+	if (tab->key != '\0')
+		str_append_c(str, tab->key);
+	else {
+		str_append_c(str, '{');
+		str_append(str, tab->long_key);
+		str_append_c(str, '}');
+	}
 }
 
 char *auth_cache_parse_key(pool_t pool, const char *query)
 {
-	const struct var_expand_table *tab;
 	string_t *str;
-	bool key_seen[100];
-	unsigned int idx, size, tab_idx;
-	bool add_key;
+	bool key_seen[AUTH_REQUEST_VAR_TAB_COUNT];
+	const char *extra_vars;
+	unsigned int i, idx, size, tab_idx;
 
 	memset(key_seen, 0, sizeof(key_seen));
 
-	str = str_new(pool, 32);
+	str = t_str_new(32);
 	for (; *query != '\0'; ) {
 		if (*query != '%') {
 			query++;
@@ -66,34 +102,45 @@
 		}
 		query += idx;
 
-		tab = auth_request_var_expand_tab_find(query, size);
-		if (tab == NULL) {
+		if (!auth_request_var_expand_tab_find(query, size, &tab_idx)) {
 			/* just add the key. it would be nice to prevent
 			   duplicates here as well, but that's just too
 			   much trouble and probably very rare. */
-			add_key = TRUE;
+			auth_cache_key_add_var(str, query, size);
 		} else {
-			tab_idx = tab - auth_request_var_expand_static_tab;
 			i_assert(tab_idx < N_ELEMENTS(key_seen));
-			/* @UNSAFE */
-			add_key = !key_seen[tab_idx];
 			key_seen[tab_idx] = TRUE;
 		}
-		if (add_key) {
-			if (str_len(str) != 0)
-				str_append_c(str, '\t');
-			str_append_c(str, '%');
-			if (size == 1)
-				str_append_c(str, query[0]);
-			else {
-				str_append_c(str, '{');
-				str_append_n(str, query, size);
-				str_append_c(str, '}');
-			}
-		}
 		query += size;
 	}
-	return str_free_without_data(&str);
+
+	if (key_seen[AUTH_REQUEST_VAR_TAB_USERNAME_IDX] &&
+	    key_seen[AUTH_REQUEST_VAR_TAB_DOMAIN_IDX]) {
+		/* %n and %d both used -> replace with %u */
+		key_seen[AUTH_REQUEST_VAR_TAB_USER_IDX] = TRUE;
+		key_seen[AUTH_REQUEST_VAR_TAB_USERNAME_IDX] = FALSE;
+		key_seen[AUTH_REQUEST_VAR_TAB_DOMAIN_IDX] = FALSE;
+	}
+
+	/* we rely on these being at the beginning */
+	i_assert(AUTH_REQUEST_VAR_TAB_USER_IDX == 0);
+	i_assert(AUTH_REQUEST_VAR_TAB_USERNAME_IDX == 1);
+	i_assert(AUTH_REQUEST_VAR_TAB_DOMAIN_IDX == 2);
+
+	extra_vars = t_strdup(str_c(str));
+	str_truncate(str, 0);
+	for (i = 0; i < N_ELEMENTS(key_seen); i++) {
+		if (key_seen[i])
+			auth_cache_key_add_tab_idx(str, i);
+	}
+
+	if (*extra_vars != '\0') {
+		if (str_len(str) > 0)
+			str_append_c(str, '\t');
+		str_append(str, extra_vars);
+	}
+
+	return p_strdup(pool, str_c(str));
 }
 
 static void
@@ -142,8 +189,8 @@
 {
 	struct auth_cache *cache = context;
 
-	i_info("SIGHUP received, clearing cache");
-	auth_cache_clear(cache);
+	i_info("SIGHUP received, %u cache entries flushed",
+	       auth_cache_clear(cache));
 }
 
 static void sig_auth_cache_stats(const siginfo_t *si ATTR_UNUSED, void *context)
@@ -200,11 +247,69 @@
 	i_free(cache);
 }
 
-void auth_cache_clear(struct auth_cache *cache)
+unsigned int auth_cache_clear(struct auth_cache *cache)
 {
+	unsigned int ret = hash_table_count(cache->hash);
+
 	while (cache->tail != NULL)
 		auth_cache_node_destroy(cache, cache->tail);
 	hash_table_clear(cache->hash, FALSE);
+	return ret;
+}
+
+static bool auth_cache_node_is_user(struct auth_cache_node *node,
+				    const char *username)
+{
+	const char *data = node->data;
+	unsigned int username_len;
+
+	/* The cache nodes begin with "P"/"U", passdb/userdb ID, "/" and
+	   then usually followed by the username. It's too much trouble to
+	   keep track of all the cache keys, so we'll just match it as if it
+	   was the username. If e.g. '%n' is used in the cache key instead of
+	   '%u', it means that cache entries can be removed only when @domain
+	   isn't in the username parameter. */
+	if (*data != 'P' && *data != 'U')
+		return FALSE;
+	data++;
+
+	while (*data >= '0' && *data <= '9')
+		data++;
+	if (*data != '/')
+		return FALSE;
+	data++;
+
+	username_len = strlen(username);
+	return strncmp(data, username, username_len) == 0 &&
+		(data[username_len] == '\t' || data[username_len] == '\0');
+}
+
+static bool auth_cache_node_is_one_of_users(struct auth_cache_node *node,
+					    const char *const *usernames)
+{
+	unsigned int i;
+
+	for (i = 0; usernames[i] != NULL; i++) {
+		if (auth_cache_node_is_user(node, usernames[i]))
+			return TRUE;
+	}
+	return FALSE;
+}
+
+unsigned int auth_cache_clear_users(struct auth_cache *cache,
+				    const char *const *usernames)
+{
+	struct auth_cache_node *node, *next;
+	unsigned int ret = 0;
+
+	for (node = cache->tail; node != NULL; node = next) {
+		next = node->next;
+		if (auth_cache_node_is_one_of_users(node, usernames)) {
+			auth_cache_node_destroy(cache, node);
+			ret++;
+		}
+	}
+	return ret;
 }
 
 static const char *
@@ -216,12 +321,27 @@
 	return str_tabescape(string);
 }
 
+static const char *
+auth_request_expand_cache_key(const struct auth_request *request,
+			      const char *key)
+{
+	string_t *str;
+
+	/* Uniquely identify the request's passdb/userdb with the P/U prefix
+	   and by "%!", which expands to the passdb/userdb ID number. */
+	key = t_strconcat(request->userdb_lookup ? "U" : "P", "%!/", key, NULL);
+
+	str = t_str_new(256);
+	var_expand(str, key,
+		   auth_request_get_var_expand_table(request, auth_cache_escape));
+	return str_c(str);
+}
+
 const char *
 auth_cache_lookup(struct auth_cache *cache, const struct auth_request *request,
 		  const char *key, struct auth_cache_node **node_r,
 		  bool *expired_r, bool *neg_expired_r)
 {
-	string_t *str;
 	struct auth_cache_node *node;
 	const char *value;
 	unsigned int ttl_secs;
@@ -230,13 +350,8 @@
 	*expired_r = FALSE;
 	*neg_expired_r = FALSE;
 
-	/* %! is prepended automatically. it contains the passdb ID number. */
-	str = t_str_new(256);
-	var_expand(str, t_strconcat(request->userdb_lookup ? "U" : "P",
-				    "%!/", key, NULL),
-		   auth_request_get_var_expand_table(request, auth_cache_escape));
-
-	node = hash_table_lookup(cache->hash, str_c(str));
+	key = auth_request_expand_cache_key(request, key);
+	node = hash_table_lookup(cache->hash, key);
 	if (node == NULL) {
 		cache->miss_count++;
 		return NULL;
@@ -269,9 +384,8 @@
 void auth_cache_insert(struct auth_cache *cache, struct auth_request *request,
 		       const char *key, const char *value, bool last_success)
 {
-	string_t *str;
         struct auth_cache_node *node;
-	size_t data_size, alloc_size, value_len = strlen(value);
+	size_t data_size, alloc_size, key_len, value_len = strlen(value);
 	char *current_username;
 
 	if (*value == '\0' && cache->neg_ttl_secs == 0) {
@@ -286,15 +400,12 @@
 	    request->requested_login_user == NULL)
 		request->user = t_strdup_noconst(request->translated_username);
 
-	/* %! is prepended automatically. it contains the db ID number. */
-	str = t_str_new(256);
-	var_expand(str, t_strconcat(request->userdb_lookup ? "U" : "P",
-				    "%!/", key, NULL),
-		   auth_request_get_var_expand_table(request, auth_cache_escape));
+	key = auth_request_expand_cache_key(request, key);
+	key_len = strlen(key);
 
 	request->user = current_username;
 
-	data_size = str_len(str) + 1 + value_len + 1;
+	data_size = key_len + 1 + value_len + 1;
 	alloc_size = sizeof(struct auth_cache_node) -
 		sizeof(node->data) + data_size;
 
@@ -302,7 +413,7 @@
 	while (cache->size_left < alloc_size && cache->tail != NULL)
 		auth_cache_node_destroy(cache, cache->tail);
 
-	node = hash_table_lookup(cache->hash, str_c(str));
+	node = hash_table_lookup(cache->hash, key);
 	if (node != NULL) {
 		/* key is already in cache (probably expired), remove it */
 		auth_cache_node_destroy(cache, node);
@@ -313,8 +424,8 @@
 	node->created = time(NULL);
 	node->alloc_size = alloc_size;
 	node->last_success = last_success;
-	memcpy(node->data, str_data(str), str_len(str));
-	memcpy(node->data + str_len(str) + 1, value, value_len);
+	memcpy(node->data, key, key_len);
+	memcpy(node->data + key_len + 1, value, value_len);
 
 	auth_cache_node_link_head(cache, node);
 
@@ -331,17 +442,12 @@
 }
 
 void auth_cache_remove(struct auth_cache *cache,
-		       const struct auth_request *request,
-		       const char *key)
+		       const struct auth_request *request, const char *key)
 {
-	string_t *str;
 	struct auth_cache_node *node;
 
-	str = t_str_new(256);
-	var_expand(str, key,
-		   auth_request_get_var_expand_table(request, auth_cache_escape));
-
-	node = hash_table_lookup(cache->hash, str_c(str));
+	key = auth_request_expand_cache_key(request, key);
+	node = hash_table_lookup(cache->hash, key);
 	if (node == NULL)
 		return;
 
--- a/src/auth/auth-cache.h	Fri Aug 10 04:56:56 2012 +0300
+++ b/src/auth/auth-cache.h	Fri Aug 10 05:24:07 2012 +0300
@@ -28,8 +28,11 @@
 				  unsigned int neg_ttl_secs);
 void auth_cache_free(struct auth_cache **cache);
 
-/* Clear the cache. */
-void auth_cache_clear(struct auth_cache *cache);
+/* Clear the cache. Returns how many entries were removed. */
+unsigned int ATTR_NOWARN_UNUSED_RESULT
+auth_cache_clear(struct auth_cache *cache);
+unsigned int auth_cache_clear_users(struct auth_cache *cache,
+				    const char *const *usernames);
 
 /* Look key from cache. key should be the same string as returned by
    auth_cache_parse_key(). Returned node can't be used after any other
--- a/src/auth/auth-master-connection.c	Fri Aug 10 04:56:56 2012 +0300
+++ b/src/auth/auth-master-connection.c	Fri Aug 10 05:24:07 2012 +0300
@@ -18,6 +18,7 @@
 #include "userdb.h"
 #include "userdb-blocking.h"
 #include "master-interface.h"
+#include "passdb-cache.h"
 #include "auth-request-handler.h"
 #include "auth-client-connection.h"
 #include "auth-master-connection.h"
@@ -137,6 +138,30 @@
 }
 
 static int
+master_input_cache_flush(struct auth_master_connection *conn, const char *args)
+{
+	const char *const *list;
+	unsigned int count;
+
+	/* <id> [<user> [<user> [..]] */
+	list = t_strsplit_tab(args);
+	if (list[0] == NULL) {
+		i_error("BUG: doveadm sent broken CACHE-FLUSH");
+		return FALSE;
+	}
+
+	if (list[1] == NULL) {
+		/* flush the whole cache */
+		count = auth_cache_clear(passdb_cache);
+	} else {
+		count = auth_cache_clear_users(passdb_cache, list+1);
+	}
+	(void)o_stream_send_str(conn->output,
+		t_strdup_printf("OK\t%s\t%u\n", list[0], count));
+	return TRUE;
+}
+
+static int
 master_input_auth_request(struct auth_master_connection *conn, const char *args,
 			  const char *cmd, struct auth_request **request_r,
 			  const char **error_r)
@@ -566,6 +591,8 @@
 		i_assert(conn->userdb_restricted_uid == 0);
 		if (strncmp(line, "REQUEST\t", 8) == 0)
 			return master_input_request(conn, line + 8);
+		if (strncmp(line, "CACHE-FLUSH\t", 12) == 0)
+			return master_input_cache_flush(conn, line + 12);
 		if (strncmp(line, "CPID\t", 5) == 0) {
 			i_error("Authentication client trying to connect to "
 				"master socket");
--- a/src/auth/auth-request.h	Fri Aug 10 04:56:56 2012 +0300
+++ b/src/auth/auth-request.h	Fri Aug 10 05:24:07 2012 +0300
@@ -126,6 +126,10 @@
 typedef void auth_request_proxy_cb_t(bool success, struct auth_request *);
 
 extern unsigned int auth_request_state_count[AUTH_REQUEST_STATE_MAX];
+#define AUTH_REQUEST_VAR_TAB_USER_IDX 0
+#define AUTH_REQUEST_VAR_TAB_USERNAME_IDX 1
+#define AUTH_REQUEST_VAR_TAB_DOMAIN_IDX 2
+#define AUTH_REQUEST_VAR_TAB_COUNT 19
 extern const struct var_expand_table auth_request_var_expand_static_tab[];
 
 struct auth_request *
--- a/src/auth/auth.c	Fri Aug 10 04:56:56 2012 +0300
+++ b/src/auth/auth.c	Fri Aug 10 05:24:07 2012 +0300
@@ -295,6 +295,15 @@
 {
 	struct auth *const *auth;
 
+	/* sanity checks */
+	i_assert(auth_request_var_expand_static_tab[AUTH_REQUEST_VAR_TAB_USER_IDX].key == 'u');
+	i_assert(auth_request_var_expand_static_tab[AUTH_REQUEST_VAR_TAB_USERNAME_IDX].key == 'n');
+	i_assert(auth_request_var_expand_static_tab[AUTH_REQUEST_VAR_TAB_DOMAIN_IDX].key == 'd');
+	i_assert(auth_request_var_expand_static_tab[AUTH_REQUEST_VAR_TAB_COUNT].key == '\0' &&
+		 auth_request_var_expand_static_tab[AUTH_REQUEST_VAR_TAB_COUNT].long_key == NULL);
+	i_assert(auth_request_var_expand_static_tab[AUTH_REQUEST_VAR_TAB_COUNT-1].key != '\0' ||
+		 auth_request_var_expand_static_tab[AUTH_REQUEST_VAR_TAB_COUNT-1].long_key != NULL);
+
 	array_foreach(&auths, auth)
 		auth_init(*auth);
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/auth/db-dict.c	Fri Aug 10 05:24:07 2012 +0300
@@ -0,0 +1,177 @@
+/* Copyright (c) 2012 Dovecot authors, see the included COPYING file */
+
+#include "auth-common.h"
+
+#include "settings.h"
+#include "dict.h"
+#include "json-parser.h"
+#include "str.h"
+#include "auth-request.h"
+#include "auth-worker-client.h"
+#include "db-dict.h"
+
+#include <stddef.h>
+#include <stdlib.h>
+
+#define DEF_STR(name) DEF_STRUCT_STR(name, db_dict_settings)
+#define DEF_BOOL(name) DEF_STRUCT_BOOL(name, db_dict_settings)
+
+static struct setting_def setting_defs[] = {
+	DEF_STR(uri),
+	DEF_STR(password_key),
+	DEF_STR(user_key),
+ 	DEF_STR(iterate_prefix),
+ 	DEF_STR(value_format),
+ 	DEF_BOOL(iterate_disable),
+	DEF_STR(default_pass_scheme),
+
+	{ 0, NULL, 0 }
+};
+
+static struct db_dict_settings default_dict_settings = {
+	.uri = NULL,
+	.password_key = "",
+	.user_key = "",
+	.iterate_prefix = "",
+	.iterate_disable = FALSE,
+	.value_format = "json",
+	.default_pass_scheme = "MD5"
+};
+
+static struct dict_connection *connections = NULL;
+
+static struct dict_connection *dict_conn_find(const char *config_path)
+{
+	struct dict_connection *conn;
+
+	for (conn = connections; conn != NULL; conn = conn->next) {
+		if (strcmp(conn->config_path, config_path) == 0)
+			return conn;
+	}
+
+	return NULL;
+}
+
+static const char *parse_setting(const char *key, const char *value,
+				 struct dict_connection *conn)
+{
+	return parse_setting_from_defs(conn->pool, setting_defs,
+				       &conn->set, key, value);
+}
+
+struct dict_connection *db_dict_init(const char *config_path)
+{
+	struct dict_connection *conn;
+	pool_t pool;
+
+	conn = dict_conn_find(config_path);
+	if (conn != NULL) {
+		conn->refcount++;
+		return conn;
+	}
+
+	if (*config_path == '\0')
+		i_fatal("dict: Configuration file path not given");
+
+	pool = pool_alloconly_create("dict_connection", 1024);
+	conn = p_new(pool, struct dict_connection, 1);
+	conn->pool = pool;
+
+	conn->refcount = 1;
+
+	conn->config_path = p_strdup(pool, config_path);
+	conn->set = default_dict_settings;
+	if (!settings_read(config_path, NULL, parse_setting,
+			   null_settings_section_callback, conn))
+		exit(FATAL_DEFAULT);
+
+	if (conn->set.uri == NULL)
+		i_fatal("dict %s: Empty uri setting", config_path);
+	if (strcmp(conn->set.value_format, "json") != 0) {
+		i_fatal("dict %s: Unsupported value_format %s in ",
+			config_path, conn->set.value_format);
+	}
+	conn->dict = dict_init(conn->set.uri, DICT_DATA_TYPE_STRING, "",
+			       global_auth_settings->base_dir);
+
+	conn->next = connections;
+	connections = conn;
+	return conn;
+}
+
+void db_dict_unref(struct dict_connection **_conn)
+{
+	struct dict_connection *conn = *_conn;
+
+	*_conn = NULL;
+	if (--conn->refcount > 0)
+		return;
+
+	dict_deinit(&conn->dict);
+	pool_unref(&conn->pool);
+}
+
+struct db_dict_value_iter {
+	struct json_parser *parser;
+	string_t *key;
+	const char *error;
+};
+
+struct db_dict_value_iter *
+db_dict_value_iter_init(struct dict_connection *conn, const char *value)
+{
+	struct db_dict_value_iter *iter;
+
+	i_assert(strcmp(conn->set.value_format, "json") == 0);
+
+	/* hardcoded for now for JSON value. make it more modular when other
+	   value types are supported. */
+	iter = i_new(struct db_dict_value_iter, 1);
+	iter->key = str_new(default_pool, 64);
+	iter->parser = json_parser_init((const void *)value, strlen(value));
+	return iter;
+}
+
+bool db_dict_value_iter_next(struct db_dict_value_iter *iter,
+			     const char **key_r, const char **value_r)
+{
+	enum json_type type;
+	const char *value;
+
+	if (!json_parse_next(iter->parser, &type, &value))
+		return FALSE;
+	if (type != JSON_TYPE_OBJECT_KEY) {
+		iter->error = "Object expected";
+		return FALSE;
+	}
+	if (*value == '\0') {
+		iter->error = "Empty object key";
+		return FALSE;
+	}
+	str_truncate(iter->key, 0);
+	str_append(iter->key, value);
+
+	if (!json_parse_next(iter->parser, &type, &value)) {
+		iter->error = "Missing value";
+		return FALSE;
+	}
+	*key_r = str_c(iter->key);
+	*value_r = value;
+	return TRUE;
+}
+
+int db_dict_value_iter_deinit(struct db_dict_value_iter **_iter,
+			      const char **error_r)
+{
+	struct db_dict_value_iter *iter = *_iter;
+
+	*_iter = NULL;
+
+	*error_r = iter->error;
+	if (json_parser_deinit(&iter->parser, &iter->error) < 0 &&
+	    *error_r == NULL)
+		*error_r = iter->error;
+	str_free(&iter->key);
+	i_free(iter);
+	return *error_r != NULL ? -1 : 0;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/auth/db-dict.h	Fri Aug 10 05:24:07 2012 +0300
@@ -0,0 +1,37 @@
+#ifndef DB_DICT_H
+#define DB_DICT_H
+
+#include "sql-api.h"
+
+struct db_dict_settings {
+	const char *uri;
+	const char *password_key;
+	const char *user_key;
+	const char *iterate_prefix;
+	bool iterate_disable;
+	const char *value_format;
+	const char *default_pass_scheme;
+};
+
+struct dict_connection {
+	struct dict_connection *next;
+
+	pool_t pool;
+	int refcount;
+
+	char *config_path;
+	struct db_dict_settings set;
+	struct dict *dict;
+};
+
+struct dict_connection *db_dict_init(const char *config_path);
+void db_dict_unref(struct dict_connection **conn);
+
+struct db_dict_value_iter *
+db_dict_value_iter_init(struct dict_connection *conn, const char *value);
+bool db_dict_value_iter_next(struct db_dict_value_iter *iter,
+			     const char **key_r, const char **value_r);
+int db_dict_value_iter_deinit(struct db_dict_value_iter **iter,
+			      const char **error_r);
+
+#endif
--- a/src/auth/db-ldap.c	Fri Aug 10 04:56:56 2012 +0300
+++ b/src/auth/db-ldap.c	Fri Aug 10 05:24:07 2012 +0300
@@ -314,7 +314,8 @@
 	i_assert(request->msgid == -1);
 
 	request->msgid =
-		ldap_search(conn->ld, srequest->base, conn->set.ldap_scope,
+		ldap_search(conn->ld, *srequest->base == '\0' ? NULL :
+			    srequest->base, conn->set.ldap_scope,
 			    srequest->filter, srequest->attributes, 0);
 	if (request->msgid == -1) {
 		auth_request_log_error(request->auth_request, "ldap",
--- a/src/auth/db-passwd-file.c	Fri Aug 10 04:56:56 2012 +0300
+++ b/src/auth/db-passwd-file.c	Fri Aug 10 05:24:07 2012 +0300
@@ -7,6 +7,7 @@
 #include "userdb.h"
 #include "db-passwd-file.h"
 
+#include "array.h"
 #include "buffer.h"
 #include "istream.h"
 #include "hash.h"
@@ -91,7 +92,7 @@
 	}
 
 	if (*args == NULL) {
-		if (pw->db->userdb) {
+		if (pw->db->userdb_warn_missing) {
 			i_error("passwd-file %s: User %s is missing "
 				"userdb info", pw->path, username);
 		}
@@ -291,6 +292,15 @@
 	return NULL;
 }
 
+static void db_passwd_file_set_userdb(struct db_passwd_file *db)
+{
+	db->userdb = TRUE;
+	/* warn about missing userdb fields only when there aren't any other
+	   userdbs. */
+	db->userdb_warn_missing =
+		array_count(&global_auth_settings->userdbs) == 1;
+}
+
 struct db_passwd_file *
 db_passwd_file_init(const char *path, bool userdb, bool debug)
 {
@@ -301,13 +311,15 @@
 	db = db_passwd_file_find(path);
 	if (db != NULL) {
 		db->refcount++;
-		db->userdb = TRUE;
+		if (userdb)
+			db_passwd_file_set_userdb(db);
 		return db;
 	}
 
 	db = i_new(struct db_passwd_file, 1);
 	db->refcount = 1;
-	db->userdb = userdb;
+	if (userdb)
+		db_passwd_file_set_userdb(db);
 	db->debug = debug;
 
 	for (p = path; *p != '\0'; p++) {
--- a/src/auth/db-passwd-file.h	Fri Aug 10 04:56:56 2012 +0300
+++ b/src/auth/db-passwd-file.h	Fri Aug 10 05:24:07 2012 +0300
@@ -37,6 +37,7 @@
 
 	unsigned int vars:1;
 	unsigned int userdb:1;
+	unsigned int userdb_warn_missing:1;
 	unsigned int debug:1;
 };
 
--- a/src/auth/main.c	Fri Aug 10 04:56:56 2012 +0300
+++ b/src/auth/main.c	Fri Aug 10 05:24:07 2012 +0300
@@ -16,6 +16,7 @@
 #include "master-service.h"
 #include "master-service-settings.h"
 #include "master-interface.h"
+#include "dict.h"
 #include "password-scheme.h"
 #include "passdb-cache.h"
 #include "mech.h"
@@ -192,6 +193,7 @@
 		auth_penalty = auth_penalty_init(AUTH_PENALTY_ANVIL_PATH);
 	mech_init(global_auth_settings);
 	mech_reg = mech_register_init(global_auth_settings);
+	dict_drivers_register_builtin();
 	auths_preinit(global_auth_settings, auth_set_pool,
 		      mech_reg, services);
 
@@ -260,6 +262,7 @@
 	auth_request_handler_deinit();
 	/* there are no more auth requests */
 	auths_free();
+	dict_drivers_unregister_builtin();
 
 	auth_client_connections_destroy_all();
 	auth_master_connections_destroy_all();
--- a/src/auth/mech-gssapi.c	Fri Aug 10 04:56:56 2012 +0300
+++ b/src/auth/mech-gssapi.c	Fri Aug 10 05:24:07 2012 +0300
@@ -78,6 +78,9 @@
 static gss_OID_desc mech_gssapi_krb5_oid =
 	{ 9, "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02" };
 
+static int
+mech_gssapi_wrap(struct gssapi_auth_request *request, gss_buffer_desc inbuf);
+
 static void mech_gssapi_log_error(struct auth_request *request,
 				  OM_uint32 status_value, int status_type,
 				  const char *description)
@@ -214,6 +217,21 @@
 	return name;
 }
 
+static gss_name_t
+duplicate_name(struct auth_request *request, gss_name_t old)
+{
+	OM_uint32 major_status, minor_status;
+	gss_name_t new;
+
+	major_status = gss_duplicate_name(&minor_status, old, &new);
+	if (GSS_ERROR(major_status)) {
+		mech_gssapi_log_error(request, major_status, GSS_C_GSS_CODE,
+				      "gss_duplicate_name");
+		return GSS_C_NO_NAME;
+	}
+	return new;
+}
+
 static bool data_has_nuls(const void *data, unsigned int len)
 {
 	const unsigned char *c = data;
@@ -328,9 +346,15 @@
 	}
 
 	if (ret == 0) {
-		auth_request_handler_reply_continue(auth_request,
-						    output_token.value,
-						    output_token.length);
+		if (output_token.length > 0) {
+			auth_request_handler_reply_continue(auth_request,
+							    output_token.value,
+							    output_token.length);
+		} else {
+			/* If there is no output token, go straight to wrap,
+			   which is expecting an empty input token. */
+			ret = mech_gssapi_wrap(request, output_token);
+		}
 	}
 	(void)gss_release_buffer(&minor_status, &output_token);
 	return ret;
@@ -415,8 +439,8 @@
 	bool authorized = FALSE;
 
 	/* Parse out the principal's username */
-	if (!get_display_name(&request->auth_request, name, &name_type,
-			      &princ_display_name) < 0)
+	if (get_display_name(&request->auth_request, name, &name_type,
+			     &princ_display_name) < 0)
 		return FALSE;
 
 	if (!mech_gssapi_oid_cmp(name_type, GSS_KRB5_NT_PRINCIPAL_NAME) &&
@@ -574,22 +598,32 @@
 
 	/* outbuf[0] contains bitmask for selected security layer,
 	   outbuf[1..3] contains maximum output_message size */
-	if (outbuf.length <= 4) {
+	if (outbuf.length < 4) {
 		auth_request_log_error(auth_request, "gssapi",
 				       "Invalid response length");
 		return -1;
 	}
-	name = (unsigned char *)outbuf.value + 4;
-	name_len = outbuf.length - 4;
+
+	if (outbuf.length > 4) {
+		name = (unsigned char *)outbuf.value + 4;
+		name_len = outbuf.length - 4;
 
-	if (data_has_nuls(name, name_len)) {
-		auth_request_log_info(auth_request, "gssapi",
-				      "authz_name has NULs");
-		return -1;
+		if (data_has_nuls(name, name_len)) {
+			auth_request_log_info(auth_request, "gssapi",
+					      "authz_name has NULs");
+			return -1;
+		}
+
+		login_user = p_strndup(auth_request->pool, name, name_len);
+		request->authz_name = import_name(auth_request, name, name_len);
+	} else {
+		request->authz_name = duplicate_name(auth_request,
+						     request->authn_name);
+		if (get_display_name(auth_request, request->authz_name,
+				     NULL, &login_user) < 0)
+			return -1;
 	}
 
-	login_user = p_strndup(auth_request->pool, name, name_len);
-	request->authz_name = import_name(auth_request, name, name_len);
 	if (request->authz_name == GSS_C_NO_NAME) {
 		auth_request_log_info(auth_request, "gssapi", "no authz_name");
 		return -1;
--- a/src/auth/mech-winbind.c	Fri Aug 10 04:56:56 2012 +0300
+++ b/src/auth/mech-winbind.c	Fri Aug 10 05:24:07 2012 +0300
@@ -241,7 +241,7 @@
 	} else if (strcmp(token[0], "AF") == 0) {
 		const char *user, *p, *error;
 
-		user = gss_spnego ? token[2] : token[1];
+		user = t_strarray_join(gss_spnego ? token+2 : token+1, " ");
 		i_assert(user != NULL);
 
 		p = strchr(user, '\\');
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/auth/passdb-dict.c	Fri Aug 10 05:24:07 2012 +0300
@@ -0,0 +1,197 @@
+/* Copyright (c) 2012 Dovecot authors, see the included COPYING file */
+
+#include "auth-common.h"
+#include "passdb.h"
+
+#include "str.h"
+#include "var-expand.h"
+#include "dict.h"
+#include "password-scheme.h"
+#include "auth-cache.h"
+#include "db-dict.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+struct dict_passdb_module {
+	struct passdb_module module;
+
+	struct dict_connection *conn;
+};
+
+struct passdb_dict_request {
+	struct auth_request *auth_request;
+	union {
+		verify_plain_callback_t *verify_plain;
+                lookup_credentials_callback_t *lookup_credentials;
+	} callback;
+};
+
+static int
+dict_query_save_results(struct auth_request *auth_request,
+			struct dict_connection *conn, const char *result)
+{
+	struct db_dict_value_iter *iter;
+	const char *key, *value, *error;
+
+	iter = db_dict_value_iter_init(conn, result);
+	while (db_dict_value_iter_next(iter, &key, &value)) {
+		if (value != NULL) {
+			auth_request_set_field(auth_request, key, value,
+					       conn->set.default_pass_scheme);
+		}
+	}
+	if (db_dict_value_iter_deinit(&iter, &error) < 0) {
+		auth_request_log_error(auth_request, "dict",
+			"Value '%s' not in valid %s format: %s",
+			result, conn->set.value_format, error);
+		return -1;
+	}
+	return 0;
+}
+
+static enum passdb_result
+passdb_dict_lookup_key(struct auth_request *auth_request,
+		       struct dict_passdb_module *module, const char *key)
+{
+	const char *value;
+	int ret;
+
+	auth_request_log_debug(auth_request, "dict", "lookup %s", key);
+	ret = dict_lookup(module->conn->dict, pool_datastack_create(),
+			  key, &value);
+	if (ret < 0) {
+		auth_request_log_error(auth_request, "dict", "Lookup failed");
+		return PASSDB_RESULT_INTERNAL_FAILURE;
+	} else if (ret == 0) {
+		auth_request_log_info(auth_request, "dict", "unknown user");
+		return PASSDB_RESULT_USER_UNKNOWN;
+	} else {
+		auth_request_log_debug(auth_request, "dict",
+				       "result: %s", value);
+		if (dict_query_save_results(auth_request, module->conn, value) < 0)
+			return PASSDB_RESULT_INTERNAL_FAILURE;
+
+		if (auth_request->passdb_password == NULL &&
+		    !auth_request->no_password) {
+			auth_request_log_info(auth_request, "dict",
+				"No password returned (and no nopassword)");
+			return PASSDB_RESULT_PASSWORD_MISMATCH;
+		} else {
+			return PASSDB_RESULT_OK;
+		}
+	}
+}
+
+static void passdb_dict_lookup_pass(struct passdb_dict_request *dict_request)
+{
+	struct auth_request *auth_request = dict_request->auth_request;
+	struct passdb_module *_module = auth_request->passdb->passdb;
+	struct dict_passdb_module *module =
+		(struct dict_passdb_module *)_module;
+	string_t *key;
+	const char *password = NULL, *scheme = NULL;
+	enum passdb_result passdb_result;
+	int ret;
+
+	key = t_str_new(512);
+	str_append(key, DICT_PATH_SHARED);
+	var_expand(key, module->conn->set.password_key,
+		   auth_request_get_var_expand_table(auth_request, NULL));
+
+	if (*module->conn->set.password_key == '\0') {
+		auth_request_log_error(auth_request, "dict",
+				       "password_key not specified");
+		passdb_result = PASSDB_RESULT_INTERNAL_FAILURE;
+	} else {
+		passdb_result = passdb_dict_lookup_key(auth_request, module,
+						       str_c(key));
+	}
+
+	if (passdb_result == PASSDB_RESULT_OK) {
+		/* passdb_password may change on the way,
+		   so we'll need to strdup. */
+		password = t_strdup(auth_request->passdb_password);
+		scheme = password_get_scheme(&password);
+		/* auth_request_set_field() sets scheme */
+		i_assert(password == NULL || scheme != NULL);
+	}
+
+	if (auth_request->credentials_scheme != NULL) {
+		passdb_handle_credentials(passdb_result, password, scheme,
+			dict_request->callback.lookup_credentials,
+			auth_request);
+	} else {
+		if (password != NULL) {
+			ret = auth_request_password_verify(auth_request,
+					auth_request->mech_password,
+					password, scheme, "dict");
+			passdb_result = ret > 0 ? PASSDB_RESULT_OK :
+				PASSDB_RESULT_PASSWORD_MISMATCH;
+		}
+
+		dict_request->callback.verify_plain(passdb_result,
+						    auth_request);
+	}
+}
+
+static void dict_verify_plain(struct auth_request *request,
+			      const char *password ATTR_UNUSED,
+			      verify_plain_callback_t *callback)
+{
+	struct passdb_dict_request *dict_request;
+
+	dict_request = p_new(request->pool, struct passdb_dict_request, 1);
+	dict_request->auth_request = request;
+	dict_request->callback.verify_plain = callback;
+
+	passdb_dict_lookup_pass(dict_request);
+}
+
+static void dict_lookup_credentials(struct auth_request *request,
+				    lookup_credentials_callback_t *callback)
+{
+	struct passdb_dict_request *dict_request;
+
+	dict_request = p_new(request->pool, struct passdb_dict_request, 1);
+	dict_request->auth_request = request;
+	dict_request->callback.lookup_credentials = callback;
+
+        passdb_dict_lookup_pass(dict_request);
+}
+
+static struct passdb_module *
+passdb_dict_preinit(pool_t pool, const char *args)
+{
+	struct dict_passdb_module *module;
+	struct dict_connection *conn;
+
+	module = p_new(pool, struct dict_passdb_module, 1);
+	module->conn = conn = db_dict_init(args);
+
+	module->module.blocking = TRUE;
+	module->module.cache_key =
+		auth_cache_parse_key(pool, conn->set.password_key);
+	module->module.default_pass_scheme = conn->set.default_pass_scheme;
+	return &module->module;
+}
+
+static void passdb_dict_deinit(struct passdb_module *_module)
+{
+	struct dict_passdb_module *module =
+		(struct dict_passdb_module *)_module;
+
+	db_dict_unref(&module->conn);
+}
+
+struct passdb_module_interface passdb_dict = {
+	"dict",
+
+	passdb_dict_preinit,
+	NULL,
+	passdb_dict_deinit,
+       
+	dict_verify_plain,
+	dict_lookup_credentials,
+	NULL
+};
--- a/src/auth/passdb.c	Fri Aug 10 04:56:56 2012 +0300
+++ b/src/auth/passdb.c	Fri Aug 10 05:24:07 2012 +0300
@@ -278,6 +278,7 @@
 
 extern struct passdb_module_interface passdb_passwd;
 extern struct passdb_module_interface passdb_bsdauth;
+extern struct passdb_module_interface passdb_dict;
 extern struct passdb_module_interface passdb_shadow;
 extern struct passdb_module_interface passdb_passwd_file;
 extern struct passdb_module_interface passdb_pam;
@@ -294,6 +295,7 @@
 	i_array_init(&passdb_modules, 16);
 	passdb_register_module(&passdb_passwd);
 	passdb_register_module(&passdb_bsdauth);
+	passdb_register_module(&passdb_dict);
 	passdb_register_module(&passdb_passwd_file);
 	passdb_register_module(&passdb_pam);
 	passdb_register_module(&passdb_checkpassword);
--- a/src/auth/test-auth-cache.c	Fri Aug 10 04:56:56 2012 +0300
+++ b/src/auth/test-auth-cache.c	Fri Aug 10 05:24:07 2012 +0300
@@ -6,6 +6,11 @@
 #include "test-common.h"
 
 const struct var_expand_table auth_request_var_expand_static_tab[] = {
+	/* these 3 must be in this order */
+	{ 'u', NULL, "user" },
+	{ 'n', NULL, "username" },
+	{ 'd', NULL, "domain" },
+
 	{ 'a', NULL, NULL },
 	{ '\0', NULL, "longb" },
 	{ 'c', NULL, "longc" },
@@ -24,14 +29,21 @@
 	struct {
 		const char *in, *out;
 	} tests[] = {
+		{ "%n@%d", "%u" },
+		{ "%{username}@%{domain}", "%u" },
+		{ "%n%d%u", "%u" },
+		{ "%n", "%n" },
+		{ "%d", "%d" },
+		{ "%a%b%u", "%u\t%a\t%b" },
+
 		{ "foo%5.5Mabar", "%a" },
 		{ "foo%5.5M{longb}bar", "%{longb}" },
 		{ "foo%5.5Mcbar", "%c" },
-		{ "foo%5.5M{longc}bar", "%{longc}" },
+		{ "foo%5.5M{longc}bar", "%c" },
 		{ "%a%b", "%a\t%b" },
 		{ "%a%{longb}%a", "%a\t%{longb}" },
-		{ "%{longc}%c", "%{longc}" },
-		{ "%c%a%{longc}%c", "%c\t%a" },
+		{ "%{longc}%c", "%c" },
+		{ "%c%a%{longc}%c", "%a\t%c" },
 		{ "%a%{env:foo}%{env:foo}%a", "%a\t%{env:foo}\t%{env:foo}" }
 	};
 	const char *cache_key;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/auth/userdb-dict.c	Fri Aug 10 05:24:07 2012 +0300
@@ -0,0 +1,207 @@
+/* Copyright (c) 2012 Dovecot authors, see the included COPYING file */
+
+#include "auth-common.h"
+#include "userdb.h"
+
+#include "ioloop.h"
+#include "array.h"
+#include "str.h"
+#include "var-expand.h"
+#include "auth-cache.h"
+#include "db-dict.h"
+
+#include <dict.h>
+#include <stdlib.h>
+
+struct dict_userdb_module {
+	struct userdb_module module;
+
+	struct dict_connection *conn;
+};
+
+struct dict_userdb_iterate_context {
+	struct userdb_iterate_context ctx;
+
+	userdb_callback_t *userdb_callback;
+	const char *key_prefix;
+	unsigned int key_prefix_len;
+	struct dict_iterate_context *iter;
+};
+
+static int
+dict_query_save_results(struct auth_request *auth_request,
+			struct dict_connection *conn, const char *result)
+{
+	struct db_dict_value_iter *iter;
+	const char *key, *value, *error;
+
+	auth_request_init_userdb_reply(auth_request);
+
+	iter = db_dict_value_iter_init(conn, result);
+	while (db_dict_value_iter_next(iter, &key, &value)) {
+		if (value != NULL)
+			auth_request_set_userdb_field(auth_request, key, value);
+	}
+	if (db_dict_value_iter_deinit(&iter, &error) < 0) {
+		auth_request_log_error(auth_request, "dict",
+			"Value '%s' not in valid %s format: %s",
+			result, conn->set.value_format, error);
+		return -1;
+	}
+	return 0;
+}
+
+static void userdb_dict_lookup(struct auth_request *auth_request,
+			       userdb_callback_t *callback)
+{
+	struct userdb_module *_module = auth_request->userdb->userdb;
+	struct dict_userdb_module *module =
+		(struct dict_userdb_module *)_module;
+	string_t *key;
+	enum userdb_result userdb_result;
+	const char *value;
+	int ret;
+
+	if (*module->conn->set.user_key == '\0') {
+		auth_request_log_error(auth_request, "dict",
+				       "user_key not specified");
+		callback(USERDB_RESULT_INTERNAL_FAILURE, auth_request);
+		return;
+	}
+
+	key = t_str_new(512);
+	str_append(key, DICT_PATH_SHARED);
+	var_expand(key, module->conn->set.user_key,
+		   auth_request_get_var_expand_table(auth_request, NULL));
+
+	auth_request_log_debug(auth_request, "dict", "lookup %s", str_c(key));
+	ret = dict_lookup(module->conn->dict, pool_datastack_create(),
+			  str_c(key), &value);
+	if (ret < 0) {
+		auth_request_log_error(auth_request, "dict", "Lookup failed");
+		userdb_result = USERDB_RESULT_INTERNAL_FAILURE;
+	} else if (ret == 0) {
+		auth_request_log_info(auth_request, "dict", "unknown user");
+		userdb_result = USERDB_RESULT_USER_UNKNOWN;
+	} else {
+		auth_request_log_debug(auth_request, "dict",
+				       "result: %s", value);
+		if (dict_query_save_results(auth_request, module->conn, value) < 0)
+			userdb_result = USERDB_RESULT_INTERNAL_FAILURE;
+		else
+			userdb_result = USERDB_RESULT_OK;
+ 	}
+	callback(userdb_result, auth_request);
+}
+
+static struct userdb_iterate_context *
+userdb_dict_iterate_init(struct auth_request *auth_request,
+			 userdb_iter_callback_t *callback, void *context)
+{
+	struct userdb_module *_module = auth_request->userdb->userdb;
+	struct dict_userdb_module *module =
+		(struct dict_userdb_module *)_module;
+	struct dict_userdb_iterate_context *ctx;
+        const struct var_expand_table *vars;
+	string_t *path;
+
+	ctx = i_new(struct dict_userdb_iterate_context, 1);
+	ctx->ctx.auth_request = auth_request;
+	ctx->ctx.callback = callback;
+	ctx->ctx.context = context;
+	auth_request_ref(auth_request);
+
+	if (*module->conn->set.iterate_prefix == '\0') {
+		if (!module->conn->set.iterate_disable) {
+			auth_request_log_error(auth_request, "dict",
+					       "iterate: iterate_prefix not set");
+			ctx->ctx.failed = TRUE;
+		}
+		return &ctx->ctx;
+	}
+
+	path = t_str_new(128);
+	str_append(path, DICT_PATH_SHARED);
+	vars = auth_request_get_var_expand_table(auth_request, NULL);
+	var_expand(path, module->conn->set.iterate_prefix, vars);
+	ctx->key_prefix = p_strdup(auth_request->pool, str_c(path));
+	ctx->key_prefix_len = strlen(ctx->key_prefix);
+
+	ctx->iter = dict_iterate_init(module->conn->dict, ctx->key_prefix, 0);
+	auth_request_log_debug(auth_request, "dict", "iterate: prefix=%s",
+			       ctx->key_prefix);
+	return &ctx->ctx;
+}
+
+static const char *
+userdb_dict_get_user(struct dict_userdb_iterate_context *ctx, const char *key)
+{
+	i_assert(strncmp(key, ctx->key_prefix, ctx->key_prefix_len) == 0);
+
+	return key + ctx->key_prefix_len;
+}
+
+static void userdb_dict_iterate_next(struct userdb_iterate_context *_ctx)
+{
+	struct dict_userdb_iterate_context *ctx =
+		(struct dict_userdb_iterate_context *)_ctx;
+	const char *key, *value;
+
+	if (ctx->iter != NULL && dict_iterate(ctx->iter, &key, &value))
+		_ctx->callback(userdb_dict_get_user(ctx, key), _ctx->context);
+	else
+		_ctx->callback(NULL, _ctx->context);
+}
+
+static int userdb_dict_iterate_deinit(struct userdb_iterate_context *_ctx)
+{
+	struct dict_userdb_iterate_context *ctx =
+		(struct dict_userdb_iterate_context *)_ctx;
+	int ret = _ctx->failed ? -1 : 0;
+
+	if (ctx->iter != NULL) {
+		if (dict_iterate_deinit(&ctx->iter) < 0)
+			ret = -1;
+	}
+	auth_request_unref(&ctx->ctx.auth_request);
+	i_free(ctx);
+	return ret;
+}
+
+static struct userdb_module *
+userdb_dict_preinit(pool_t pool, const char *args)
+{
+	struct dict_userdb_module *module;
+	struct dict_connection *conn;
+
+	module = p_new(pool, struct dict_userdb_module, 1);
+	module->conn = conn = db_dict_init(args);
+
+	module->module.blocking = TRUE;
+	module->module.cache_key =
+		auth_cache_parse_key(pool, conn->set.user_key);
+	return &module->module;
+}
+
+static void userdb_dict_deinit(struct userdb_module *_module)
+{
+	struct dict_userdb_module *module =
+		(struct dict_userdb_module *)_module;
+
+	db_dict_unref(&module->conn);
+}
+
+struct userdb_module_interface userdb_dict =
+{
+	"dict",
+
+	userdb_dict_preinit,
+	NULL,
+	userdb_dict_deinit,
+
+	userdb_dict_lookup,
+
+	userdb_dict_iterate_init,
+	userdb_dict_iterate_next,
+	userdb_dict_iterate_deinit
+};
--- a/src/auth/userdb.c	Fri Aug 10 04:56:56 2012 +0300
+++ b/src/auth/userdb.c	Fri Aug 10 05:24:07 2012 +0300
@@ -230,6 +230,7 @@
 extern struct userdb_module_interface userdb_sql;
 extern struct userdb_module_interface userdb_nss;
 extern struct userdb_module_interface userdb_checkpassword;
+extern struct userdb_module_interface userdb_dict;
 
 void userdbs_init(void)
 {
@@ -244,6 +245,7 @@
 	userdb_register_module(&userdb_sql);
 	userdb_register_module(&userdb_nss);
 	userdb_register_module(&userdb_checkpassword);
+	userdb_register_module(&userdb_dict);
 }
 
 void userdbs_deinit(void)
--- a/src/config/config-connection.c	Fri Aug 10 04:56:56 2012 +0300
+++ b/src/config/config-connection.c	Fri Aug 10 05:24:07 2012 +0300
@@ -100,7 +100,7 @@
 		path = master_service_get_config_path(master_service);
 		if (config_parse_file(path, TRUE, "", &error) <= 0) {
 			o_stream_nsend_str(conn->output,
-				t_strconcat("ERROR ", error, "\n", NULL));
+				t_strconcat("\nERROR ", error, "\n", NULL));
 			config_connection_destroy(conn);
 			return -1;
 		}
--- a/src/config/doveconf.c	Fri Aug 10 04:56:56 2012 +0300
+++ b/src/config/doveconf.c	Fri Aug 10 05:24:07 2012 +0300
@@ -452,7 +452,7 @@
 	unsigned int len;
 	bool dump_section = FALSE;
 
-	ctx = config_dump_human_init("", scope, TRUE);
+	ctx = config_dump_human_init("", scope, FALSE);
 	config_export_by_filter(ctx->export_ctx, filter);
 	if (config_export_finish(&ctx->export_ctx) < 0)
 		return -1;
@@ -694,6 +694,11 @@
 		ret2 = config_export_finish(&ctx);
 	} else if (setting_name_filters != NULL) {
 		ret2 = 0;
+		/* ignore settings-check failures in configuration. this allows
+		   using doveconf to lookup settings for things like install or
+		   uninstall scripts where the configuration might
+		   (temporarily) not be fully usable */
+		ret = 0;
 		for (i = 0; setting_name_filters[i] != NULL; i++) {
 			if (config_dump_one(&filter, hide_key, scope,
 					    setting_name_filters[i]) < 0)
--- a/src/config/old-set-parser.c	Fri Aug 10 04:56:56 2012 +0300
+++ b/src/config/old-set-parser.c	Fri Aug 10 05:24:07 2012 +0300
@@ -307,7 +307,7 @@
 			return TRUE;
 		}
 		p = strrchr(value, ':');
-		if (p != NULL) {
+		if (p != NULL && listen_has_port(value)) {
 			obsolete(ctx, "%s=..:port has been replaced by service { inet_listener { port } }", key);
 			value = t_strdup_until(value, p++);
 			if (config_filter_match(&old_section->filter, &imap_filter)) {
--- a/src/director/director-settings.c	Fri Aug 10 04:56:56 2012 +0300
+++ b/src/director/director-settings.c	Fri Aug 10 05:24:07 2012 +0300
@@ -80,7 +80,7 @@
 
 	.director_servers = "",
 	.director_mail_servers = "",
-	.director_username_hash = "%u",
+	.director_username_hash = "%Lu",
 	.director_user_expire = 60*15,
 	.director_doveadm_port = 0
 };
--- a/src/doveadm/client-connection.c	Fri Aug 10 04:56:56 2012 +0300
+++ b/src/doveadm/client-connection.c	Fri Aug 10 05:24:07 2012 +0300
@@ -21,6 +21,8 @@
 
 #define MAX_INBUF_SIZE 1024
 
+static void client_connection_input(struct client_connection *conn);
+
 struct client_connection {
 	pool_t pool;
 
@@ -208,6 +210,10 @@
 		return FALSE;
 	}
 
+	/* make sure client_connection_input() isn't called by the ioloop that
+	   is going to be run by doveadm_mail_cmd_server_run() */
+	io_remove(&conn->io);
+
 	o_stream_cork(conn->output);
 	ctx = doveadm_mail_cmd_server_parse(cmd_name, conn->set, &input, argc, args);
 	if (ctx == NULL)
@@ -220,6 +226,8 @@
 	net_set_nonblock(conn->fd, FALSE);
 	(void)o_stream_flush(conn->output);
 	net_set_nonblock(conn->fd, TRUE);
+
+	conn->io = io_add(conn->fd, IO_READ, client_connection_input, conn);
 	return TRUE;
 }
 
--- a/src/doveadm/doveadm-auth.c	Fri Aug 10 04:56:56 2012 +0300
+++ b/src/doveadm/doveadm-auth.c	Fri Aug 10 05:24:07 2012 +0300
@@ -27,6 +27,8 @@
 	bool success;
 };
 
+static void auth_cmd_help(doveadm_command_t *cmd);
+
 static int
 cmd_user_input(const char *auth_socket_path, const struct authtest_input *input,
 	       const char *show_field)
@@ -214,12 +216,52 @@
 	auth_master_deinit(&conn);
 }
 
+static void cmd_auth_cache_flush(int argc, char *argv[])
+{
+	const char *auth_socket_path = NULL;
+	struct auth_master_connection *conn;
+	unsigned int count;
+	int c;
+
+	while ((c = getopt(argc, argv, "a:")) > 0) {
+		switch (c) {
+		case 'a':
+			auth_socket_path = optarg;
+			break;
+		default:
+			auth_cmd_help(cmd_auth_cache_flush);
+		}
+	}
+	argv += optind;
+
+	if (auth_socket_path == NULL) {
+		auth_socket_path = t_strconcat(doveadm_settings->base_dir,
+					       "/auth-master", NULL);
+	}
+
+	conn = auth_master_init(auth_socket_path, 0);
+	if (auth_master_cache_flush(conn, (void *)argv, &count) < 0) {
+		i_error("Cache flush failed");
+		doveadm_exit_code = EX_TEMPFAIL;
+	} else {
+		printf("%u cache entries flushed\n", count);
+	}
+	auth_master_deinit(&conn);
+}
+
 static void cmd_auth(int argc, char *argv[])
 {
 	const char *auth_socket_path = NULL;
 	struct authtest_input input;
 	int c;
 
+	if (null_strcmp(argv[1], "cache") == 0 &&
+	    null_strcmp(argv[2], "flush") == 0) {
+		/* kludgy: handle "doveadm auth cache" command instead */
+		cmd_auth_cache_flush(argc-2, argv+2);
+		return;
+	}
+
 	memset(&input, 0, sizeof(input));
 	input.info.service = "doveadm";
 
@@ -232,12 +274,12 @@
 			auth_user_info_parse(&input.info, optarg);
 			break;
 		default:
-			help(&doveadm_cmd_auth);
+			auth_cmd_help(cmd_auth);
 		}
 	}
 
 	if (optind == argc)
-		help(&doveadm_cmd_auth);
+		auth_cmd_help(cmd_auth);
 
 	input.username = argv[optind++];
 	input.password = argv[optind] != NULL ? argv[optind++] :
@@ -331,12 +373,12 @@
 			auth_user_info_parse(&input.info, optarg);
 			break;
 		default:
-			help(&doveadm_cmd_user);
+			auth_cmd_help(cmd_user);
 		}
 	}
 
 	if (optind == argc)
-		help(&doveadm_cmd_user);
+		auth_cmd_help(cmd_user);
 
 	have_wildcards = FALSE;
 	for (i = optind; argv[i] != NULL; i++) {
@@ -380,12 +422,30 @@
 		mail_storage_service_deinit(&storage_service);
 }
 
-struct doveadm_cmd doveadm_cmd_auth = {
-	cmd_auth, "auth",
-	"[-a <auth socket path>] [-x <auth info>] <user> [<password>]"
+struct doveadm_cmd doveadm_cmd_auth[] = {
+	{ cmd_auth, "auth",
+	  "[-a <auth socket path>] [-x <auth info>] <user> [<password>]" },
+	{ cmd_auth_cache_flush, "auth cache flush",
+	  "[-a <master socket path>] [<user> [...]]" },
+	{ cmd_user, "user",
+	  "[-a <userdb socket path>] [-x <auth info>] [-f field] [-m] <user mask> [...]" }
 };
 
-struct doveadm_cmd doveadm_cmd_user = {
-	cmd_user, "user",
-	"[-a <userdb socket path>] [-x <auth info>] [-f field] [-m] <user mask> [...]"
-};
+static void auth_cmd_help(doveadm_command_t *cmd)
+{
+	unsigned int i;
+
+	for (i = 0; i < N_ELEMENTS(doveadm_cmd_auth); i++) {
+		if (doveadm_cmd_auth[i].cmd == cmd)
+			help(&doveadm_cmd_auth[i]);
+	}
+	i_unreached();
+}
+
+void doveadm_register_auth_commands(void)
+{
+	unsigned int i;
+
+	for (i = 0; i < N_ELEMENTS(doveadm_cmd_auth); i++)
+		doveadm_register_cmd(&doveadm_cmd_auth[i]);
+}
--- a/src/doveadm/doveadm-mail-mailbox-status.c	Fri Aug 10 04:56:56 2012 +0300
+++ b/src/doveadm/doveadm-mail-mailbox-status.c	Fri Aug 10 05:24:07 2012 +0300
@@ -154,6 +154,7 @@
 	int ret = 0;
 
 	memset(&ctx->total_status, 0, sizeof(ctx->total_status));
+	memset(&ctx->total_metadata, 0, sizeof(ctx->total_metadata));
 
 	iter = doveadm_mailbox_list_iter_init(_ctx, user, ctx->search_args,
 					      iter_flags);
--- a/src/doveadm/doveadm.c	Fri Aug 10 04:56:56 2012 +0300
+++ b/src/doveadm/doveadm.c	Fri Aug 10 05:24:07 2012 +0300
@@ -271,8 +271,6 @@
 	&doveadm_cmd_config,
 	&doveadm_cmd_stop,
 	&doveadm_cmd_reload,
-	&doveadm_cmd_auth,
-	&doveadm_cmd_user,
 	&doveadm_cmd_dump,
 	&doveadm_cmd_pw,
 	&doveadm_cmd_who,
@@ -342,6 +340,7 @@
 		quick_init = TRUE;
 	} else {
 		quick_init = FALSE;
+		doveadm_register_auth_commands();
 		doveadm_register_director_commands();
 		doveadm_register_instance_commands();
 		doveadm_register_mount_commands();
--- a/src/doveadm/doveadm.h	Fri Aug 10 04:56:56 2012 +0300
+++ b/src/doveadm/doveadm.h	Fri Aug 10 05:24:07 2012 +0300
@@ -22,8 +22,6 @@
 
 extern struct doveadm_cmd doveadm_cmd_stop;
 extern struct doveadm_cmd doveadm_cmd_reload;
-extern struct doveadm_cmd doveadm_cmd_auth;
-extern struct doveadm_cmd doveadm_cmd_user;
 extern struct doveadm_cmd doveadm_cmd_dump;
 extern struct doveadm_cmd doveadm_cmd_pw;
 extern struct doveadm_cmd doveadm_cmd_who;
@@ -41,6 +39,7 @@
 void help(const struct doveadm_cmd *cmd) ATTR_NORETURN;
 void doveadm_master_send_signal(int signo);
 
+void doveadm_register_auth_commands(void);
 void doveadm_register_director_commands(void);
 void doveadm_register_proxy_commands(void);
 void doveadm_register_log_commands(void);
--- a/src/doveadm/main.c	Fri Aug 10 04:56:56 2012 +0300
+++ b/src/doveadm/main.c	Fri Aug 10 05:24:07 2012 +0300
@@ -68,9 +68,12 @@
 		&doveadm_setting_parser_info,
 		NULL
 	};
+	enum master_service_flags service_flags =
+		MASTER_SERVICE_FLAG_KEEP_CONFIG_OPEN;
 	const char *error;
 
-	master_service = master_service_init("doveadm", 0, &argc, &argv, "");
+	master_service = master_service_init("doveadm", service_flags,
+					     &argc, &argv, "");
 	if (master_getopt(master_service) > 0)
 		return FATAL_DEFAULT;
 
--- a/src/imap-login/client.c	Fri Aug 10 04:56:56 2012 +0300
+++ b/src/imap-login/client.c	Fri Aug 10 05:24:07 2012 +0300
@@ -62,7 +62,7 @@
 		str_append(cap_str, imap_client->set->imap_capability + 1);
 	}
 
-	if (ssl_initialized && !client->tls)
+	if (client_is_tls_enabled(client) && !client->tls)
 		str_append(cap_str, " STARTTLS");
 	if (client->set->disable_plaintext_auth && !client->secured)
 		str_append(cap_str, " LOGINDISABLED");
--- a/src/imap/cmd-delete.c	Fri Aug 10 04:56:56 2012 +0300
+++ b/src/imap/cmd-delete.c	Fri Aug 10 05:24:07 2012 +0300
@@ -10,6 +10,7 @@
 	struct mailbox *box;
 	const char *name, *errstr;
 	enum mail_error error;
+	bool disconnect = FALSE;
 
 	/* <mailbox> */
 	if (!client_read_string_args(cmd, 1, &name))
@@ -31,6 +32,7 @@
 		/* deleting selected mailbox. close it first */
 		client_search_updates_free(client);
 		mailbox_free(&client->mailbox);
+		disconnect = TRUE;
 	}
 
 	if (mailbox_delete(box) == 0)
@@ -45,5 +47,10 @@
 		}
 	}
 	mailbox_free(&box);
+
+	if (disconnect) {
+		client_disconnect_with_error(cmd->client,
+			"Selected mailbox was deleted, have to disconnect.");
+	}
 	return TRUE;
 }
--- a/src/imap/cmd-list.c	Fri Aug 10 04:56:56 2012 +0300
+++ b/src/imap/cmd-list.c	Fri Aug 10 05:24:07 2012 +0300
@@ -236,17 +236,22 @@
 	enum imap_match_result match;
 	const char *ns_prefix, *p;
 	bool inboxcase;
+	unsigned int skip_len;
+
+	skip_len = strlen(ctx->ref);
+	if (strncmp(ctx->ns->prefix, ctx->ref, skip_len) != 0)
+		skip_len = 0;
 
 	inboxcase = strncasecmp(ctx->ns->prefix, "INBOX", 5) == 0 &&
 		ctx->ns->prefix[5] == mail_namespace_get_sep(ctx->ns);
 	glob = imap_match_init_multiple(pool_datastack_create(),
 					ctx->patterns, inboxcase,
 					mail_namespace_get_sep(ctx->ns));
-	ns_prefix = ctx->ns->prefix;
+	ns_prefix = ctx->ns->prefix + skip_len;
 	match = imap_match(glob, ns_prefix);
 	if (match == IMAP_MATCH_YES) {
-		return !ctx->cur_ns_skip_trailing_sep ? ns_prefix :
-			t_strndup(ns_prefix, strlen(ns_prefix)-1);
+		return !ctx->cur_ns_skip_trailing_sep ? ctx->ns->prefix :
+			t_strndup(ctx->ns->prefix, strlen(ctx->ns->prefix)-1);
 	}
 
 	while ((match & IMAP_MATCH_PARENT) != 0) {
@@ -256,7 +261,8 @@
 		match = imap_match(glob, ns_prefix);
 	}
 	i_assert(match == IMAP_MATCH_YES);
-	return ns_prefix;
+	return t_strconcat(t_strndup(ctx->ns->prefix, skip_len),
+			   ns_prefix, NULL);
 }
 
 static void list_reply_append_ns_sep_param(string_t *str, char sep)
@@ -364,8 +370,9 @@
 	client_send_line(ctx->cmd->client, str_c(str));
 }
 
-static void list_send_status(struct cmd_list_context *ctx, const char *name,
-			     enum mailbox_info_flags flags)
+static void
+list_send_status(struct cmd_list_context *ctx, const char *name,
+		 const char *mutf7_name, enum mailbox_info_flags flags)
 {
 	struct imap_status_result result;
 	struct mail_namespace *ns;
@@ -391,7 +398,8 @@
 		return;
 	}
 
-	imap_status_send(ctx->cmd->client, name, &ctx->status_items, &result);
+	imap_status_send(ctx->cmd->client, mutf7_name,
+			 &ctx->status_items, &result);
 }
 
 static bool list_has_empty_prefix_ns(struct mail_user *user)
@@ -481,7 +489,7 @@
 
 		ret = client_send_line_next(ctx->cmd->client, str_c(str));
 		if (ctx->used_status) T_BEGIN {
-			list_send_status(ctx, name, flags);
+			list_send_status(ctx, name, str_c(mutf7_name), flags);
 		} T_END;
 		if (ret == 0) {
 			/* buffer is full, continue later */
@@ -508,18 +516,14 @@
 skip_namespace_prefix(const char **prefix, const char **pattern,
 		      bool inbox_check, char sep)
 {
-	size_t pattern_len, prefix_len;
+	size_t pattern_len, prefix_len, min_len;
 	bool match;
 
 	prefix_len = strlen(*prefix);
 	pattern_len = strlen(*pattern);
+	min_len = I_MIN(prefix_len, pattern_len);
 
-	if (pattern_len < prefix_len) {
-		/* eg. namespace prefix = "INBOX.", pattern = "INBOX" */
-		return;
-	}
-
-	match = strncmp(*prefix, *pattern, prefix_len) == 0;
+	match = strncmp(*prefix, *pattern, min_len) == 0;
 	if (!match && inbox_check) {
 		/* try INBOX check. */
 		match = prefix_len >= 5 &&
@@ -531,8 +535,8 @@
 	}
 
 	if (match) {
-		*prefix += prefix_len;
-		*pattern += prefix_len;
+		*prefix += min_len;
+		*pattern += min_len;
 	}
 }
 
--- a/src/imap/cmd-status.c	Fri Aug 10 04:56:56 2012 +0300
+++ b/src/imap/cmd-status.c	Fri Aug 10 05:24:07 2012 +0300
@@ -13,7 +13,7 @@
 	struct imap_status_items items;
 	struct imap_status_result result;
 	struct mail_namespace *ns;
-	const char *mailbox, *error;
+	const char *mailbox, *orig_mailbox, *error;
 	bool selected_mailbox;
 
 	/* <mailbox> <status items> */
@@ -30,6 +30,7 @@
 	if (imap_status_parse_items(cmd, list_args, &items) < 0)
 		return TRUE;
 
+	orig_mailbox = mailbox;
 	ns = client_find_namespace(cmd, &mailbox);
 	if (ns == NULL)
 		return TRUE;
@@ -42,7 +43,7 @@
 		return TRUE;
 	}
 
-	imap_status_send(client, mailbox, &items, &result);
+	imap_status_send(client, orig_mailbox, &items, &result);
 	if (!selected_mailbox)
 		client_send_tagline(cmd, "OK Status completed.");
 	else {
--- a/src/imap/imap-status.c	Fri Aug 10 04:56:56 2012 +0300
+++ b/src/imap/imap-status.c	Fri Aug 10 05:24:07 2012 +0300
@@ -96,7 +96,7 @@
 	return ret;
 }
 
-void imap_status_send(struct client *client, const char *mailbox,
+void imap_status_send(struct client *client, const char *mailbox_mutf7,
 		      const struct imap_status_items *items,
 		      const struct imap_status_result *result)
 {
@@ -106,7 +106,7 @@
 
 	str = t_str_new(128);
 	str_append(str, "* STATUS ");
-        imap_quote_append_string(str, mailbox, FALSE);
+        imap_quote_append_string(str, mailbox_mutf7, FALSE);
 	str_append(str, " (");
 
 	prefix_len = str_len(str);
--- a/src/imap/imap-status.h	Fri Aug 10 04:56:56 2012 +0300
+++ b/src/imap/imap-status.h	Fri Aug 10 05:24:07 2012 +0300
@@ -18,7 +18,7 @@
 		    struct mail_namespace *ns,
 		    const char *mailbox, const struct imap_status_items *items,
 		    struct imap_status_result *result_r, const char **error_r);
-void imap_status_send(struct client *client, const char *mailbox,
+void imap_status_send(struct client *client, const char *mailbox_mutf7,
 		      const struct imap_status_items *items,
 		      const struct imap_status_result *result);
 
--- a/src/lib-auth/auth-master.c	Fri Aug 10 04:56:56 2012 +0300
+++ b/src/lib-auth/auth-master.c	Fri Aug 10 05:24:07 2012 +0300
@@ -9,6 +9,7 @@
 #include "istream.h"
 #include "ostream.h"
 #include "str.h"
+#include "strescape.h"
 #include "master-interface.h"
 #include "auth-master.h"
 
@@ -565,6 +566,48 @@
 }
 
 static bool
+auth_cache_flush_reply_callback(const char *cmd, const char *const *args,
+				void *context)
+{
+	unsigned int *countp = context;
+
+	if (strcmp(cmd, "OK") != 0)
+		*countp = -1U;
+	else if (args[0] == NULL || str_to_uint(args[0], countp) < 0)
+		*countp = -1U;
+
+	io_loop_stop(current_ioloop);
+	return TRUE;
+}
+
+int auth_master_cache_flush(struct auth_master_connection *conn,
+			    const char *const *users, unsigned int *count_r)
+{
+	string_t *str;
+
+	*count_r = -1U;
+
+	conn->reply_callback = auth_cache_flush_reply_callback;
+	conn->reply_context = count_r;
+
+	str = t_str_new(128);
+	str_printfa(str, "CACHE-FLUSH\t%u", auth_master_next_request_id(conn));
+	if (users != NULL) {
+		for (; *users != NULL; users++) {
+			str_append_c(str, '\t');
+			str_tabescape_write(str, *users);
+		}
+	}
+	str_append_c(str, '\n');
+
+	conn->prefix = "auth cache flush";
+	(void)auth_master_run_cmd(conn, str_c(str));
+	conn->prefix = DEFAULT_USERDB_LOOKUP_PREFIX;
+
+	return *count_r == -1U ? -1 : 0;
+}
+
+static bool
 auth_user_list_reply_callback(const char *cmd, const char *const *args,
 			      void *context)
 {
--- a/src/lib-auth/auth-master.h	Fri Aug 10 04:56:56 2012 +0300
+++ b/src/lib-auth/auth-master.h	Fri Aug 10 05:24:07 2012 +0300
@@ -39,6 +39,10 @@
 int auth_master_pass_lookup(struct auth_master_connection *conn,
 			    const char *user, const struct auth_user_info *info,
 			    pool_t pool, const char *const **fields_r);
+/* Flush authentication cache for everyone (users=NULL) or only for specified
+   users. Returns number of users flushed from cache. */
+int auth_master_cache_flush(struct auth_master_connection *conn,
+			    const char *const *users, unsigned int *count_r);
 
 /* Parse userdb extra fields into auth_user_reply structure. */
 void auth_user_fields_parse(const char *const *fields, pool_t pool,
--- a/src/lib-charset/charset-iconv.c	Fri Aug 10 04:56:56 2012 +0300
+++ b/src/lib-charset/charset-iconv.c	Fri Aug 10 05:24:07 2012 +0300
@@ -120,9 +120,8 @@
 charset_to_utf8(struct charset_translation *t,
 		const unsigned char *src, size_t *src_size, buffer_t *dest)
 {
-	bool dtcase = (t->flags & CHARSET_FLAG_DECOMP_TITLECASE) != 0;
 	enum charset_result result;
-	size_t pos, used, size, prev_pos = 0, prev_used = 0;
+	size_t pos, size;
 	size_t prev_invalid_pos = (size_t)-1;
 	bool ret;
 
@@ -141,16 +140,6 @@
 				prev_invalid_pos = dest->used;
 			}
 			pos++;
-		} else if (!dtcase) {
-			/* force buffer to grow */
-			used = dest->used;
-			size = buffer_get_size(dest) - used + 1;
-			(void)buffer_append_space_unsafe(dest, size);
-			buffer_set_used_size(dest, used);
-		} else {
-			i_assert(dest->used != prev_used || pos != prev_pos);
-			prev_pos = pos;
-			prev_used = dest->used;
 		}
 	}
 
--- a/src/lib-dict/Makefile.am	Fri Aug 10 04:56:56 2012 +0300
+++ b/src/lib-dict/Makefile.am	Fri Aug 10 05:24:07 2012 +0300
@@ -13,7 +13,9 @@
 base_sources = \
 	dict.c \
 	dict-client.c \
-	dict-file.c
+	dict-file.c \
+	dict-memcached.c \
+	dict-redis.c
 
 libdict_la_SOURCES = \
 	$(base_sources)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-dict/dict-memcached.c	Fri Aug 10 05:24:07 2012 +0300
@@ -0,0 +1,376 @@
+/* Copyright (c) 2012 Dovecot authors, see the included COPYING memcached */
+
+#include "lib.h"
+#include "array.h"
+#include "str.h"
+#include "istream.h"
+#include "ostream.h"
+#include "connection.h"
+#include "dict-private.h"
+
+#define MEMCACHED_DEFAULT_PORT 11211
+#define MEMCACHED_DEFAULT_LOOKUP_TIMEOUT_MSECS (1000*30)
+
+/* we need only very limited memcached functionality, so just define the binary
+   protocol ourself instead requiring protocol_binary.h */
+#define MEMCACHED_REQUEST_HDR_MAGIC 0x80
+#define MEMCACHED_REPLY_HDR_MAGIC 0x81
+
+#define MEMCACHED_REQUEST_HDR_LENGTH 24
+#define MEMCACHED_REPLY_HDR_LENGTH 24
+
+#define MEMCACHED_CMD_GET 0x00
+
+#define MEMCACHED_DATA_TYPE_RAW 0x00
+
+enum memcached_response {
+	MEMCACHED_RESPONSE_OK		= 0x0000,
+	MEMCACHED_RESPONSE_NOTFOUND	= 0x0001,
+	MEMCACHED_RESPONSE_INTERNALERROR= 0x0084,
+	MEMCACHED_RESPONSE_BUSY		= 0x0085,
+	MEMCACHED_RESPONSE_TEMPFAILURE	= 0x0086
+};
+
+struct memcached_connection {
+	struct connection conn;
+	struct memcached_dict *dict;
+
+	buffer_t *cmd;
+	struct {
+		const unsigned char *value;
+		unsigned int value_len;
+		enum memcached_response status;
+		bool reply_received;
+	} reply;
+};
+
+struct memcached_dict {
+	struct dict dict;
+	struct ip_addr ip;
+	char *key_prefix;
+	unsigned int port;
+	unsigned int timeout_msecs;
+
+	struct ioloop *ioloop;
+	struct memcached_connection conn;
+
+	bool connected;
+};
+
+static struct connection_list *memcached_connections;
+
+static void memcached_conn_destroy(struct connection *_conn)
+{
+	struct memcached_connection *conn = (struct memcached_connection *)_conn;
+
+	conn->dict->connected = FALSE;
+	connection_disconnect(_conn);
+
+	if (conn->dict->ioloop != NULL)
+		io_loop_stop(conn->dict->ioloop);
+}
+
+static int memcached_input_get(struct memcached_connection *conn)
+{
+	const unsigned char *data;
+	size_t size;
+	uint32_t body_len, value_pos;
+	uint16_t key_len, key_pos, status;
+	uint8_t extras_len, data_type;
+
+	data = i_stream_get_data(conn->conn.input, &size);
+	if (size < MEMCACHED_REPLY_HDR_LENGTH)
+		return 0;
+
+	if (data[0] != MEMCACHED_REPLY_HDR_MAGIC) {
+		i_error("memcached: Invalid reply magic: %u != %u",
+			data[0], MEMCACHED_REPLY_HDR_MAGIC);
+		return -1;
+	}
+	memcpy(&body_len, data+8, 4); body_len = ntohl(body_len);
+	body_len += MEMCACHED_REPLY_HDR_LENGTH;
+	if (size < body_len) {
+		/* we haven't read the whole response yet */
+		return 0;
+	}
+
+	memcpy(&key_len, data+2, 2); key_len = ntohs(key_len);
+	extras_len = data[4];
+	data_type = data[5];
+	memcpy(&status, data+6, 2); status = ntohs(status);
+	if (data_type != MEMCACHED_DATA_TYPE_RAW) {
+		i_error("memcached: Unsupported data type: %u != %u",
+			data[0], MEMCACHED_DATA_TYPE_RAW);
+		return -1;
+	}
+
+	key_pos = MEMCACHED_REPLY_HDR_LENGTH + extras_len;
+	value_pos = key_pos + key_len;
+	if (value_pos > body_len) {
+		i_error("memcached: Invalid key/extras lengths");
+		return -1;
+	}
+	conn->reply.value = data + value_pos;
+	conn->reply.value_len = body_len - value_pos;
+	conn->reply.status = status;
+
+	i_stream_skip(conn->conn.input, body_len);
+	conn->reply.reply_received = TRUE;
+
+	if (conn->dict->ioloop != NULL)
+		io_loop_stop(conn->dict->ioloop);
+	return 1;
+}
+
+static void memcached_conn_input(struct connection *_conn)
+{
+	struct memcached_connection *conn = (struct memcached_connection *)_conn;
+
+	switch (i_stream_read(_conn->input)) {
+	case 0:
+		return;
+	case -1:
+		memcached_conn_destroy(_conn);
+		return;
+	default:
+		break;
+	}
+
+	if (memcached_input_get(conn) < 0)
+		memcached_conn_destroy(_conn);
+}
+
+static void memcached_conn_connected(struct connection *_conn)
+{
+	struct memcached_connection *conn = (struct memcached_connection *)_conn;
+
+	if ((errno = net_geterror(_conn->fd_in)) != 0) {
+		i_error("memcached: connect(%s, %u) failed: %m",
+			net_ip2addr(&conn->dict->ip), conn->dict->port);
+	} else {
+		conn->dict->connected = TRUE;
+	}
+	if (conn->dict->ioloop != NULL)
+		io_loop_stop(conn->dict->ioloop);
+}
+
+static const struct connection_settings memcached_conn_set = {
+	.input_max_size = (size_t)-1,
+	.output_max_size = (size_t)-1,
+	.client = TRUE
+};
+
+static const struct connection_vfuncs memcached_conn_vfuncs = {
+	.destroy = memcached_conn_destroy,
+	.input = memcached_conn_input,
+	.connected = memcached_conn_connected
+};
+
+static struct dict *
+memcached_dict_init(struct dict *driver, const char *uri,
+		    enum dict_data_type value_type ATTR_UNUSED,
+		    const char *username ATTR_UNUSED,
+		    const char *base_dir ATTR_UNUSED)
+{
+	struct memcached_dict *dict;
+	const char *const *args;
+
+	if (memcached_connections == NULL) {
+		memcached_connections =
+			connection_list_init(&memcached_conn_set,
+					     &memcached_conn_vfuncs);
+	}
+
+	dict = i_new(struct memcached_dict, 1);
+	if (net_addr2ip("127.0.0.1", &dict->ip) < 0)
+		i_unreached();
+	dict->port = MEMCACHED_DEFAULT_PORT;
+	dict->timeout_msecs = MEMCACHED_DEFAULT_LOOKUP_TIMEOUT_MSECS;
+	dict->key_prefix = i_strdup("");
+
+	args = t_strsplit(uri, ":");
+	for (; *args != NULL; args++) {
+		if (strncmp(*args, "host=", 5) == 0) {
+			if (net_addr2ip(*args+5, &dict->ip) < 0)
+				i_error("Invalid IP: %s", *args+5);
+		} else if (strncmp(*args, "port=", 5) == 0) {
+			if (str_to_uint(*args+5, &dict->port) < 0)
+				i_error("Invalid port: %s", *args+5);
+		} else if (strncmp(*args, "prefix=", 7) == 0) {
+			i_free(dict->key_prefix);
+			dict->key_prefix = i_strdup(*args + 7);
+		} else if (strncmp(*args, "timeout_msecs=", 14) == 0) {
+			if (str_to_uint(*args+14, &dict->timeout_msecs) < 0)
+				i_error("Invalid timeout_msecs: %s", *args+14);
+		} else {
+			i_error("Unknown parameter: %s", *args);
+		}
+	}
+	connection_init_client_ip(memcached_connections, &dict->conn.conn,
+				  &dict->ip, dict->port);
+
+	dict->dict = *driver;
+	dict->conn.cmd = buffer_create_dynamic(default_pool, 256);
+	dict->conn.dict = dict;
+	return &dict->dict;
+}
+
+static void memcached_dict_deinit(struct dict *_dict)
+{
+	struct memcached_dict *dict = (struct memcached_dict *)_dict;
+
+	connection_deinit(&dict->conn.conn);
+	buffer_free(&dict->conn.cmd);
+	i_free(dict->key_prefix);
+	i_free(dict);
+
+	if (memcached_connections->connections == NULL)
+		connection_list_deinit(&memcached_connections);
+}
+
+static void memcached_dict_lookup_timeout(struct memcached_dict *dict)
+{
+	i_error("memcached: Lookup timed out in %u.%03u secs",
+		dict->timeout_msecs/1000, dict->timeout_msecs%1000);
+	io_loop_stop(dict->ioloop);
+}
+
+static void memcached_add_header(buffer_t *buf, unsigned int key_len)
+{
+	uint32_t body_len = htonl(key_len);
+
+	i_assert(key_len <= 0xffff);
+
+	buffer_append_c(buf, MEMCACHED_REQUEST_HDR_MAGIC);
+	buffer_append_c(buf, MEMCACHED_CMD_GET);
+	buffer_append_c(buf, (key_len >> 8) & 0xff);
+	buffer_append_c(buf, key_len & 0xff);
+	buffer_append_c(buf, 0); /* extras length */
+	buffer_append_c(buf, MEMCACHED_DATA_TYPE_RAW);
+	buffer_append_zero(buf, 2); /* vbucket id - we probably don't care? */
+	buffer_append(buf, &body_len, sizeof(body_len));
+	buffer_append_zero(buf, 4+8); /* opaque + cas */
+	i_assert(buf->used == MEMCACHED_REQUEST_HDR_LENGTH);
+}
+
+static int
+memcached_dict_lookup_real(struct memcached_dict *dict, pool_t pool,
+			   const char *key, const char **value_r)
+{
+	struct ioloop *prev_ioloop = current_ioloop;
+	struct timeout *to;
+	unsigned int key_len;
+
+	if (strncmp(key, DICT_PATH_SHARED, strlen(DICT_PATH_SHARED)) == 0)
+		key += strlen(DICT_PATH_SHARED);
+	else {
+		i_error("memcached: Only shared keys supported currently");
+		return -1;
+	}
+	if (*dict->key_prefix != '\0')
+		key = t_strconcat(dict->key_prefix, key, NULL);
+	key_len = strlen(key);
+	if (key_len > 0xffff) {
+		i_error("memcached: Key is too long (%u bytes): %s",
+			key_len, key);
+		return -1;
+	}
+
+	i_assert(dict->ioloop == NULL);
+
+	dict->ioloop = io_loop_create();
+	connection_switch_ioloop(&dict->conn.conn);
+
+	if (dict->conn.conn.fd_in == -1 &&
+	    connection_client_connect(&dict->conn.conn) < 0) {
+		i_error("memcached: Couldn't connect to %s:%u",
+			net_ip2addr(&dict->ip), dict->port);
+	} else {
+		to = timeout_add(dict->timeout_msecs,
+				 memcached_dict_lookup_timeout, dict);
+		if (!dict->connected) {
+			/* wait for connection */
+			io_loop_run(dict->ioloop);
+		}
+
+		if (dict->connected) {
+			buffer_set_used_size(dict->conn.cmd, 0);
+			memcached_add_header(dict->conn.cmd, key_len);
+			buffer_append(dict->conn.cmd, key, key_len);
+
+			o_stream_nsend(dict->conn.conn.output,
+				       dict->conn.cmd->data,
+				       dict->conn.cmd->used);
+
+			memset(&dict->conn.reply, 0, sizeof(dict->conn.reply));
+			io_loop_run(dict->ioloop);
+		}
+		timeout_remove(&to);
+	}
+
+	current_ioloop = prev_ioloop;
+	connection_switch_ioloop(&dict->conn.conn);
+	current_ioloop = dict->ioloop;
+	io_loop_destroy(&dict->ioloop);
+
+	if (!dict->conn.reply.reply_received) {
+		/* we failed in some way. make sure we disconnect since the
+		   connection state isn't known anymore */
+		memcached_conn_destroy(&dict->conn.conn);
+		return -1;
+	}
+	switch (dict->conn.reply.status) {
+	case MEMCACHED_RESPONSE_OK:
+		*value_r = p_strndup(pool, dict->conn.reply.value,
+				     dict->conn.reply.value_len);
+		return 1;
+	case MEMCACHED_RESPONSE_NOTFOUND:
+		return 0;
+	case MEMCACHED_RESPONSE_INTERNALERROR:
+		i_error("memcached: Lookup(%s) failed: Internal error", key);
+		return -1;
+	case MEMCACHED_RESPONSE_BUSY:
+		i_error("memcached: Lookup(%s) failed: Busy", key);
+		return -1;
+	case MEMCACHED_RESPONSE_TEMPFAILURE:
+		i_error("memcached: Lookup(%s) failed: Temporary failure", key);
+		return -1;
+	}
+
+	i_error("memcached: Lookup(%s) failed: Error code=%u",
+		key, dict->conn.reply.status);
+	return -1;
+}
+
+static int memcached_dict_lookup(struct dict *_dict, pool_t pool,
+				 const char *key, const char **value_r)
+{
+	struct memcached_dict *dict = (struct memcached_dict *)_dict;
+	int ret;
+
+	if (pool->datastack_pool)
+		ret = memcached_dict_lookup_real(dict, pool, key, value_r);
+	else T_BEGIN {
+		ret = memcached_dict_lookup_real(dict, pool, key, value_r);
+	} T_END;
+	return ret;
+}
+
+struct dict dict_driver_memcached = {
+	.name = "memcached",
+	{
+		memcached_dict_init,
+		memcached_dict_deinit,
+		NULL,
+		memcached_dict_lookup,
+		NULL,
+		NULL,
+		NULL,
+		NULL,
+		NULL,
+		NULL,
+		NULL,
+		NULL,
+		NULL
+	}
+};
--- a/src/lib-dict/dict-private.h	Fri Aug 10 04:56:56 2012 +0300
+++ b/src/lib-dict/dict-private.h	Fri Aug 10 05:24:07 2012 +0300
@@ -51,7 +51,9 @@
 	unsigned int changed:1;
 };
 
+extern struct dict dict_driver_client;
 extern struct dict dict_driver_file;
-extern struct dict dict_driver_client;
+extern struct dict dict_driver_memcached;
+extern struct dict dict_driver_redis;
 
 #endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-dict/dict-redis.c	Fri Aug 10 05:24:07 2012 +0300
@@ -0,0 +1,666 @@
+/* Copyright (c) 2008-2012 Dovecot authors, see the included COPYING redis */
+
+#include "lib.h"
+#include "array.h"
+#include "str.h"
+#include "istream.h"
+#include "ostream.h"
+#include "connection.h"
+#include "dict-private.h"
+
+#define REDIS_DEFAULT_PORT 6379
+#define REDIS_DEFAULT_LOOKUP_TIMEOUT_MSECS (1000*30)
+#define DICT_USERNAME_SEPARATOR '/'
+
+enum redis_input_state {
+	/* expecting $-1 / $<size> followed by GET reply */
+	REDIS_INPUT_STATE_GET,
+	/* expecting +QUEUED */
+	REDIS_INPUT_STATE_MULTI,
+	/* expecting +OK reply for DISCARD */
+	REDIS_INPUT_STATE_DISCARD,
+	/* expecting *<nreplies> */
+	REDIS_INPUT_STATE_EXEC,
+	/* expecting EXEC reply */
+	REDIS_INPUT_STATE_EXEC_REPLY
+};
+
+struct redis_connection {
+	struct connection conn;
+	struct redis_dict *dict;
+
+	string_t *last_reply;
+	unsigned int bytes_left;
+	bool value_not_found;
+	bool value_received;
+};
+
+struct redis_dict_reply {
+	unsigned int reply_count;
+	dict_transaction_commit_callback_t *callback;
+	void *context;
+};
+
+struct redis_dict {
+	struct dict dict;
+	struct ip_addr ip;
+	char *username, *key_prefix;
+	unsigned int port;
+	unsigned int timeout_msecs;
+
+	struct ioloop *ioloop;
+	struct redis_connection conn;
+
+	ARRAY_DEFINE(input_states, enum redis_input_state);
+	ARRAY_DEFINE(replies, struct redis_dict_reply);
+
+	bool connected;
+	bool transaction_open;
+};
+
+struct redis_dict_transaction_context {
+	struct dict_transaction_context ctx;
+	unsigned int cmd_count;
+	bool failed;
+};
+
+static struct connection_list *redis_connections;
+
+static void
+redis_input_state_add(struct redis_dict *dict, enum redis_input_state state)
+{
+	array_append(&dict->input_states, &state, 1);
+}
+
+static void redis_input_state_remove(struct redis_dict *dict)
+{
+	array_delete(&dict->input_states, 0, 1);
+}
+
+static void redis_conn_destroy(struct connection *_conn)
+{
+	struct redis_connection *conn = (struct redis_connection *)_conn;
+	const struct redis_dict_reply *reply;
+
+	conn->dict->connected = FALSE;
+	connection_disconnect(_conn);
+
+	array_foreach(&conn->dict->replies, reply) {
+		if (reply->callback != NULL)
+			reply->callback(-1, reply->context);
+	}
+	array_clear(&conn->dict->replies);
+	array_clear(&conn->dict->input_states);
+
+	if (conn->dict->ioloop != NULL)
+		io_loop_stop(conn->dict->ioloop);
+}
+
+static void redis_wait(struct redis_dict *dict)
+{
+	struct ioloop *prev_ioloop = current_ioloop;
+
+	i_assert(dict->ioloop == NULL);
+
+	dict->ioloop = io_loop_create();
+	connection_switch_ioloop(&dict->conn.conn);
+
+	do {
+		io_loop_run(dict->ioloop);
+	} while (array_count(&dict->input_states) > 0);
+
+	current_ioloop = prev_ioloop;
+	connection_switch_ioloop(&dict->conn.conn);
+	current_ioloop = dict->ioloop;
+	io_loop_destroy(&dict->ioloop);
+}
+
+static void redis_input_get(struct redis_connection *conn)
+{
+	const unsigned char *data;
+	size_t size;
+	const char *line;
+
+	if (conn->bytes_left == 0) {
+		/* read the size first */
+		line = i_stream_next_line(conn->conn.input);
+		if (line == NULL)
+			return;
+		if (strcmp(line, "$-1") == 0) {
+			conn->value_received = TRUE;
+			conn->value_not_found = TRUE;
+			if (conn->dict->ioloop != NULL)
+				io_loop_stop(conn->dict->ioloop);
+			redis_input_state_remove(conn->dict);
+			return;
+		}
+		if (line[0] != '$' || str_to_uint(line+1, &conn->bytes_left) < 0) {
+			i_error("redis: Unexpected input (wanted $size): %s",
+				line);
+			redis_conn_destroy(&conn->conn);
+			return;
+		}
+		conn->bytes_left += 2; /* include trailing CRLF */
+	}
+
+	data = i_stream_get_data(conn->conn.input, &size);
+	if (size > conn->bytes_left)
+		size = conn->bytes_left;
+	str_append_n(conn->last_reply, data, size);
+
+	conn->bytes_left -= size;
+	i_stream_skip(conn->conn.input, size);
+
+	if (conn->bytes_left == 0) {
+		/* reply fully read - drop trailing CRLF */
+		conn->value_received = TRUE;
+		str_truncate(conn->last_reply, str_len(conn->last_reply)-2);
+
+		if (conn->dict->ioloop != NULL)
+			io_loop_stop(conn->dict->ioloop);
+		redis_input_state_remove(conn->dict);
+	}
+}
+
+static int redis_conn_input_more(struct redis_connection *conn)
+{
+	struct redis_dict *dict = conn->dict;
+	struct redis_dict_reply *reply;
+	const enum redis_input_state *states;
+	enum redis_input_state state;
+	unsigned int count, num_replies;
+	const char *line;
+
+	states = array_get(&dict->input_states, &count);
+	if (count == 0) {
+		line = i_stream_next_line(conn->conn.input);
+		if (line == NULL) line = "";
+		i_error("redis: Unexpected input (expected nothing): %s",
+			line);
+		return -1;
+	}
+	state = states[0];
+	if (state == REDIS_INPUT_STATE_GET) {
+		redis_input_get(conn);
+		return 1;
+	}
+
+	line = i_stream_next_line(conn->conn.input);
+	if (line == NULL)
+		return 0;
+
+	redis_input_state_remove(dict);
+	switch (state) {
+	case REDIS_INPUT_STATE_GET:
+		i_unreached();
+	case REDIS_INPUT_STATE_MULTI:
+	case REDIS_INPUT_STATE_DISCARD:
+		if (line[0] != '+')
+			break;
+		return 1;
+	case REDIS_INPUT_STATE_EXEC:
+		if (line[0] != '*' || str_to_uint(line+1, &num_replies) < 0)
+			break;
+
+		reply = array_idx_modifiable(&dict->replies, 0);
+		i_assert(reply->reply_count > 0);
+		if (reply->reply_count != num_replies) {
+			i_error("redis: EXEC expected %u replies, not %u",
+				reply->reply_count, num_replies);
+			return -1;
+		}
+		return 1;
+	case REDIS_INPUT_STATE_EXEC_REPLY:
+		if (*line != '+' && *line != ':')
+			break;
+		/* success, just ignore the actual reply */
+		reply = array_idx_modifiable(&dict->replies, 0);
+		i_assert(reply->reply_count > 0);
+		if (--reply->reply_count == 0) {
+			if (reply->callback != NULL)
+				reply->callback(1, reply->context);
+			array_delete(&dict->replies, 0, 1);
+			/* if we're running in a dict-ioloop, we're handling a
+			   synchronous commit and need to stop now */
+			if (array_count(&dict->replies) == 0 &&
+			    conn->dict->ioloop != NULL)
+				io_loop_stop(conn->dict->ioloop);
+		}
+		return 1;
+	}
+	i_error("redis: Unexpected input (state=%d): %s", state, line);
+	return -1;
+}
+
+static void redis_conn_input(struct connection *_conn)
+{
+	struct redis_connection *conn = (struct redis_connection *)_conn;
+	int ret;
+
+	switch (i_stream_read(_conn->input)) {
+	case 0:
+		return;
+	case -1:
+		redis_conn_destroy(_conn);
+		return;
+	default:
+		break;
+	}
+
+	while ((ret = redis_conn_input_more(conn)) > 0) {
+		if (i_stream_get_data_size(_conn->input) == 0)
+			break;
+	}
+	if (ret < 0)
+		redis_conn_destroy(_conn);
+}
+
+static void redis_conn_connected(struct connection *_conn)
+{
+	struct redis_connection *conn = (struct redis_connection *)_conn;
+
+	if ((errno = net_geterror(_conn->fd_in)) != 0) {
+		i_error("redis: connect(%s, %u) failed: %m",
+			net_ip2addr(&conn->dict->ip), conn->dict->port);
+	} else {
+		conn->dict->connected = TRUE;
+	}
+	if (conn->dict->ioloop != NULL)
+		io_loop_stop(conn->dict->ioloop);
+}
+
+static const struct connection_settings redis_conn_set = {
+	.input_max_size = (size_t)-1,
+	.output_max_size = (size_t)-1,
+	.client = TRUE
+};
+
+static const struct connection_vfuncs redis_conn_vfuncs = {
+	.destroy = redis_conn_destroy,
+	.input = redis_conn_input,
+	.connected = redis_conn_connected
+};
+
+static const char *redis_escape_username(const char *username)
+{
+	const char *p;
+	string_t *str = t_str_new(64);
+
+	for (p = username; *p != '\0'; p++) {
+		switch (*p) {
+		case DICT_USERNAME_SEPARATOR:
+			str_append(str, "\\-");
+			break;
+		case '\\':
+			str_append(str, "\\\\");
+			break;
+		default:
+			str_append_c(str, *p);
+		}
+	}
+	return str_c(str);
+}
+
+static struct dict *
+redis_dict_init(struct dict *driver, const char *uri,
+		enum dict_data_type value_type ATTR_UNUSED,
+		const char *username,
+		const char *base_dir ATTR_UNUSED)
+{
+	struct redis_dict *dict;
+	const char *const *args;
+
+	if (redis_connections == NULL) {
+		redis_connections =
+			connection_list_init(&redis_conn_set,
+					     &redis_conn_vfuncs);
+	}
+
+	dict = i_new(struct redis_dict, 1);
+	if (net_addr2ip("127.0.0.1", &dict->ip) < 0)
+		i_unreached();
+	dict->port = REDIS_DEFAULT_PORT;
+	dict->timeout_msecs = REDIS_DEFAULT_LOOKUP_TIMEOUT_MSECS;
+	if (strchr(username, DICT_USERNAME_SEPARATOR) == NULL)
+		dict->username = i_strdup(username);
+	else {
+		/* escape the username */
+		dict->username = i_strdup(redis_escape_username(username));
+	}
+	dict->key_prefix = i_strdup("");
+	i_array_init(&dict->input_states, 4);
+	i_array_init(&dict->replies, 4);
+
+	args = t_strsplit(uri, ":");
+	for (; *args != NULL; args++) {
+		if (strncmp(*args, "host=", 5) == 0) {
+			if (net_addr2ip(*args+5, &dict->ip) < 0)
+				i_error("Invalid IP: %s", *args+5);
+		} else if (strncmp(*args, "port=", 5) == 0) {
+			if (str_to_uint(*args+5, &dict->port) < 0)
+				i_error("Invalid port: %s", *args+5);
+		} else if (strncmp(*args, "prefix=", 7) == 0) {
+			i_free(dict->key_prefix);
+			dict->key_prefix = i_strdup(*args + 7);
+		} else if (strncmp(*args, "timeout_msecs=", 14) == 0) {
+			if (str_to_uint(*args+14, &dict->timeout_msecs) < 0)
+				i_error("Invalid timeout_msecs: %s", *args+14);
+		} else {
+			i_error("Unknown parameter: %s", *args);
+		}
+	}
+	connection_init_client_ip(redis_connections, &dict->conn.conn,
+				  &dict->ip, dict->port);
+
+	dict->dict = *driver;
+	dict->conn.last_reply = str_new(default_pool, 256);
+	dict->conn.dict = dict;
+	return &dict->dict;
+}
+
+static void redis_dict_deinit(struct dict *_dict)
+{
+	struct redis_dict *dict = (struct redis_dict *)_dict;
+
+	if (array_count(&dict->input_states) > 0) {
+		i_assert(dict->connected);
+		redis_wait(dict);
+	}
+	connection_deinit(&dict->conn.conn);
+	str_free(&dict->conn.last_reply);
+	array_free(&dict->replies);
+	array_free(&dict->input_states);
+	i_free(dict->key_prefix);
+	i_free(dict->username);
+	i_free(dict);
+
+	if (redis_connections->connections == NULL)
+		connection_list_deinit(&redis_connections);
+}
+
+static void redis_dict_lookup_timeout(struct redis_dict *dict)
+{
+	i_error("redis: Lookup timed out in %u.%03u secs",
+		dict->timeout_msecs/1000, dict->timeout_msecs%1000);
+	io_loop_stop(dict->ioloop);
+}
+
+static const char *
+redis_dict_get_full_key(struct redis_dict *dict, const char *key)
+{
+	if (strncmp(key, DICT_PATH_SHARED, strlen(DICT_PATH_SHARED)) == 0)
+		key += strlen(DICT_PATH_SHARED);
+	else if (strncmp(key, DICT_PATH_PRIVATE, strlen(DICT_PATH_PRIVATE)) == 0) {
+		key = t_strdup_printf("%s%c%s", dict->username,
+				      DICT_USERNAME_SEPARATOR,
+				      key + strlen(DICT_PATH_PRIVATE));
+	} else {
+		i_unreached();
+	}
+	if (*dict->key_prefix != '\0')
+		key = t_strconcat(dict->key_prefix, key, NULL);
+	return key;
+}
+
+static int
+redis_dict_lookup_real(struct redis_dict *dict, pool_t pool,
+		       const char *key, const char **value_r)
+{
+	struct timeout *to;
+	const char *cmd;
+	struct ioloop *prev_ioloop = current_ioloop;
+
+	key = redis_dict_get_full_key(dict, key);
+
+	dict->conn.value_received = FALSE;
+	dict->conn.value_not_found = FALSE;
+
+	i_assert(dict->ioloop == NULL);
+
+	dict->ioloop = io_loop_create();
+	connection_switch_ioloop(&dict->conn.conn);
+
+	if (dict->conn.conn.fd_in == -1 &&
+	    connection_client_connect(&dict->conn.conn) < 0) {
+		i_error("redis: Couldn't connect to %s:%u",
+			net_ip2addr(&dict->ip), dict->port);
+	} else {
+		to = timeout_add(dict->timeout_msecs,
+				 redis_dict_lookup_timeout, dict);
+		if (!dict->connected) {
+			/* wait for connection */
+			io_loop_run(dict->ioloop);
+		}
+
+		if (dict->connected) {
+			cmd = t_strdup_printf("*2\r\n$3\r\nGET\r\n$%d\r\n%s\r\n",
+					      (int)strlen(key), key);
+			o_stream_nsend_str(dict->conn.conn.output, cmd);
+
+			str_truncate(dict->conn.last_reply, 0);
+			redis_input_state_add(dict, REDIS_INPUT_STATE_GET);
+			io_loop_run(dict->ioloop);
+		}
+		timeout_remove(&to);
+	}
+
+	current_ioloop = prev_ioloop;
+	connection_switch_ioloop(&dict->conn.conn);
+	current_ioloop = dict->ioloop;
+	io_loop_destroy(&dict->ioloop);
+
+	if (!dict->conn.value_received) {
+		/* we failed in some way. make sure we disconnect since the
+		   connection state isn't known anymore */
+		redis_conn_destroy(&dict->conn.conn);
+		return -1;
+	}
+	if (dict->conn.value_not_found)
+		return 0;
+
+	*value_r = p_strdup(pool, str_c(dict->conn.last_reply));
+	return 1;
+}
+
+static int redis_dict_lookup(struct dict *_dict, pool_t pool,
+			     const char *key, const char **value_r)
+{
+	struct redis_dict *dict = (struct redis_dict *)_dict;
+	int ret;
+
+	i_assert(!dict->transaction_open);
+
+	if (pool->datastack_pool)
+		ret = redis_dict_lookup_real(dict, pool, key, value_r);
+	else T_BEGIN {
+		ret = redis_dict_lookup_real(dict, pool, key, value_r);
+	} T_END;
+	return ret;
+}
+
+static struct dict_transaction_context *
+redis_transaction_init(struct dict *_dict)
+{
+	struct redis_dict *dict = (struct redis_dict *)_dict;
+	struct redis_dict_transaction_context *ctx;
+
+	i_assert(!dict->transaction_open);
+	dict->transaction_open = TRUE;
+
+	ctx = i_new(struct redis_dict_transaction_context, 1);
+	ctx->ctx.dict = _dict;
+
+	if (dict->conn.conn.fd_in == -1 &&
+	    connection_client_connect(&dict->conn.conn) < 0) {
+		i_error("redis: Couldn't connect to %s:%u",
+			net_ip2addr(&dict->ip), dict->port);
+	} else if (!dict->connected) {
+		/* wait for connection */
+		redis_wait(dict);
+	}
+	return &ctx->ctx;
+}
+
+static int
+redis_transaction_commit(struct dict_transaction_context *_ctx, bool async,
+			 dict_transaction_commit_callback_t *callback,
+			 void *context)
+{
+	struct redis_dict_transaction_context *ctx =
+		(struct redis_dict_transaction_context *)_ctx;
+	struct redis_dict *dict = (struct redis_dict *)_ctx->dict;
+	struct redis_dict_reply *reply;
+	unsigned int i;
+	int ret = 1;
+
+	i_assert(dict->transaction_open);
+	dict->transaction_open = FALSE;
+
+	if (ctx->failed) {
+		/* make sure we're disconnected */
+		redis_conn_destroy(&dict->conn.conn);
+		ret = -1;
+	} else if (_ctx->changed) {
+		i_assert(ctx->cmd_count > 0);
+
+		o_stream_nsend_str(dict->conn.conn.output,
+				   "*1\r\n$4\r\nEXEC\r\n");
+		reply = array_append_space(&dict->replies);
+		reply->callback = callback;
+		reply->context = context;
+		reply->reply_count = ctx->cmd_count;
+		redis_input_state_add(dict, REDIS_INPUT_STATE_EXEC);
+		for (i = 0; i < ctx->cmd_count; i++)
+			redis_input_state_add(dict, REDIS_INPUT_STATE_EXEC_REPLY);
+		if (async)
+			return 1;
+		redis_wait(dict);
+	}
+	if (callback != NULL)
+		callback(ret, context);
+	i_free(ctx);
+	return ret;
+}
+
+static void redis_transaction_rollback(struct dict_transaction_context *_ctx)
+{
+	struct redis_dict_transaction_context *ctx =
+		(struct redis_dict_transaction_context *)_ctx;
+	struct redis_dict *dict = (struct redis_dict *)_ctx->dict;
+	struct redis_dict_reply *reply;
+
+	i_assert(dict->transaction_open);
+	dict->transaction_open = FALSE;
+
+	if (ctx->failed) {
+		/* make sure we're disconnected */
+		redis_conn_destroy(&dict->conn.conn);
+	} else if (_ctx->changed) {
+		o_stream_nsend_str(dict->conn.conn.output,
+				   "*1\r\n$7\r\nDISCARD\r\n");
+		reply = array_append_space(&dict->replies);
+		reply->reply_count = 1;
+		redis_input_state_add(dict, REDIS_INPUT_STATE_DISCARD);
+	}
+	i_free(ctx);
+}
+
+static int redis_check_transaction(struct redis_dict_transaction_context *ctx)
+{
+	struct redis_dict *dict = (struct redis_dict *)ctx->ctx.dict;
+
+	if (ctx->failed)
+		return -1;
+	if (ctx->ctx.changed)
+		return 0;
+
+	redis_input_state_add(dict, REDIS_INPUT_STATE_MULTI);
+	if (o_stream_send_str(dict->conn.conn.output,
+			      "*1\r\n$5\r\nMULTI\r\n") < 0) {
+		ctx->failed = TRUE;
+		return -1;
+	}
+	return 0;
+}
+
+static void redis_set(struct dict_transaction_context *_ctx,
+		      const char *key, const char *value)
+{
+	struct redis_dict_transaction_context *ctx =
+		(struct redis_dict_transaction_context *)_ctx;
+	struct redis_dict *dict = (struct redis_dict *)_ctx->dict;
+	const char *cmd;
+
+	if (redis_check_transaction(ctx) < 0)
+		return;
+
+	key = redis_dict_get_full_key(dict, key);
+	cmd = t_strdup_printf("*3\r\n$3\r\nSET\r\n$%u\r\n%s\r\n$%u\r\n%s\r\n",
+			      (unsigned int)strlen(key), key,
+			      (unsigned int)strlen(value), value);
+	if (o_stream_send_str(dict->conn.conn.output, cmd) < 0)
+		ctx->failed = TRUE;
+	redis_input_state_add(dict, REDIS_INPUT_STATE_MULTI);
+	ctx->cmd_count++;
+}
+
+static void redis_unset(struct dict_transaction_context *_ctx,
+			const char *key)
+{
+	struct redis_dict_transaction_context *ctx =
+		(struct redis_dict_transaction_context *)_ctx;
+	struct redis_dict *dict = (struct redis_dict *)_ctx->dict;
+	const char *cmd;
+
+	if (redis_check_transaction(ctx) < 0)
+		return;
+
+	key = redis_dict_get_full_key(dict, key);
+	cmd = t_strdup_printf("*2\r\n$3\r\nDEL\r\n$%u\r\n%s\r\n",
+			      (unsigned int)strlen(key), key);
+	if (o_stream_send_str(dict->conn.conn.output, cmd) < 0)
+		ctx->failed = TRUE;
+	redis_input_state_add(dict, REDIS_INPUT_STATE_MULTI);
+	ctx->cmd_count++;
+}
+
+static void redis_atomic_inc(struct dict_transaction_context *_ctx,
+			     const char *key, long long diff)
+{
+	struct redis_dict_transaction_context *ctx =
+		(struct redis_dict_transaction_context *)_ctx;
+	struct redis_dict *dict = (struct redis_dict *)_ctx->dict;
+	const char *cmd, *diffstr;
+
+	if (redis_check_transaction(ctx) < 0)
+		return;
+
+	key = redis_dict_get_full_key(dict, key);
+	diffstr = t_strdup_printf("%lld", diff);
+	cmd = t_strdup_printf("*3\r\n$6\r\nINCRBY\r\n$%u\r\n%s\r\n$%u\r\n%s\r\n",
+			      (unsigned int)strlen(key), key,
+			      (unsigned int)strlen(diffstr), diffstr);
+	if (o_stream_send_str(dict->conn.conn.output, cmd) < 0)
+		ctx->failed = TRUE;
+	redis_input_state_add(dict, REDIS_INPUT_STATE_MULTI);
+	ctx->cmd_count++;
+}
+
+struct dict dict_driver_redis = {
+	.name = "redis",
+	{
+		redis_dict_init,
+		redis_dict_deinit,
+		NULL,
+		redis_dict_lookup,
+		NULL,
+		NULL,
+		NULL,
+		redis_transaction_init,
+		redis_transaction_commit,
+		redis_transaction_rollback,
+		redis_set,
+		redis_unset,
+		redis_atomic_inc
+	}
+};
--- a/src/lib-dict/dict.c	Fri Aug 10 04:56:56 2012 +0300
+++ b/src/lib-dict/dict.c	Fri Aug 10 05:24:07 2012 +0300
@@ -7,6 +7,7 @@
 #include "dict-private.h"
 
 static ARRAY_DEFINE(dict_drivers, struct dict *);
+static struct dict_iterate_context dict_iter_unsupported;
 
 static struct dict *dict_driver_lookup(const char *name)
 {
@@ -55,12 +56,16 @@
 {
 	dict_driver_register(&dict_driver_client);
 	dict_driver_register(&dict_driver_file);
+	dict_driver_register(&dict_driver_memcached);
+	dict_driver_register(&dict_driver_redis);
 }
 
 void dict_drivers_unregister_builtin(void)
 {
 	dict_driver_unregister(&dict_driver_client);
 	dict_driver_unregister(&dict_driver_file);
+	dict_driver_unregister(&dict_driver_memcached);
+	dict_driver_unregister(&dict_driver_redis);
 }
 
 struct dict *dict_init(const char *uri, enum dict_data_type value_type,
@@ -134,13 +139,19 @@
 	i_assert(paths[0] != NULL);
 	for (i = 0; paths[i] != NULL; i++)
 		i_assert(dict_key_prefix_is_valid(paths[i]));
+	if (dict->v.iterate_init == NULL) {
+		/* not supported by backend */
+		i_error("%s: dict iteration not supported", dict->name);
+		return &dict_iter_unsupported;
+	}
 	return dict->v.iterate_init(dict, paths, flags);
 }
 
 bool dict_iterate(struct dict_iterate_context *ctx,
 		  const char **key_r, const char **value_r)
 {
-	return ctx->dict->v.iterate(ctx, key_r, value_r);
+	return ctx == &dict_iter_unsupported ? FALSE :
+		ctx->dict->v.iterate(ctx, key_r, value_r);
 }
 
 int dict_iterate_deinit(struct dict_iterate_context **_ctx)
@@ -148,7 +159,8 @@
 	struct dict_iterate_context *ctx = *_ctx;
 
 	*_ctx = NULL;
-	return ctx->dict->v.iterate_deinit(ctx);
+	return ctx == &dict_iter_unsupported ? -1 :
+		ctx->dict->v.iterate_deinit(ctx);
 }
 
 struct dict_transaction_context *dict_transaction_begin(struct dict *dict)
--- a/src/lib-dict/test-dict.c	Fri Aug 10 04:56:56 2012 +0300
+++ b/src/lib-dict/test-dict.c	Fri Aug 10 05:24:07 2012 +0300
@@ -6,6 +6,8 @@
 
 struct dict dict_driver_client;
 struct dict dict_driver_file;
+struct dict dict_driver_memcached;
+struct dict dict_driver_redis;
 
 static void test_dict_escape(void)
 {
--- a/src/lib-imap-client/imapc-client.c	Fri Aug 10 04:56:56 2012 +0300
+++ b/src/lib-imap-client/imapc-client.c	Fri Aug 10 05:24:07 2012 +0300
@@ -270,7 +270,7 @@
 		/* reopen the mailbox */
 		box->reopen_callback(box->reopen_context);
 	} else {
-		imapc_connection_abort_commands(box->conn, TRUE, FALSE);
+		imapc_connection_abort_commands(box->conn, NULL, FALSE);
 	}
 }
 
--- a/src/lib-imap-client/imapc-connection.c	Fri Aug 10 04:56:56 2012 +0300
+++ b/src/lib-imap-client/imapc-connection.c	Fri Aug 10 05:24:07 2012 +0300
@@ -213,6 +213,7 @@
 static void
 imapc_connection_abort_commands_array(ARRAY_TYPE(imapc_command) *cmd_array,
 				      ARRAY_TYPE(imapc_command) *dest_array,
+				      struct imapc_client_mailbox *only_box,
 				      bool keep_retriable)
 {
 	struct imapc_command *const *cmdp, *cmd;
@@ -222,8 +223,10 @@
 		cmdp = array_idx(cmd_array, i);
 		cmd = *cmdp;
 
-		if (keep_retriable &&
-		    (cmd->flags & IMAPC_COMMAND_FLAG_RETRIABLE) != 0) {
+		if (cmd->box != only_box && only_box != NULL)
+			i++;
+		else if (keep_retriable &&
+			 (cmd->flags & IMAPC_COMMAND_FLAG_RETRIABLE) != 0) {
 			cmd->send_pos = 0;
 			cmd->wait_for_literal = 0;
 			i++;
@@ -235,22 +238,20 @@
 }
 
 void imapc_connection_abort_commands(struct imapc_connection *conn,
-				     bool disconnected, bool keep_retriable)
+				     struct imapc_client_mailbox *only_box,
+				     bool keep_retriable)
 {
 	struct imapc_command *const *cmdp, *cmd;
 	ARRAY_TYPE(imapc_command) tmp_array;
 	struct imapc_command_reply reply;
 
 	t_array_init(&tmp_array, 8);
-	if (disconnected) {
-		imapc_connection_abort_commands_array(&conn->cmd_wait_list,
-						      &tmp_array,
-						      keep_retriable);
-	}
-	imapc_connection_abort_commands_array(&conn->cmd_send_queue,
-					      &tmp_array, keep_retriable);
+	imapc_connection_abort_commands_array(&conn->cmd_wait_list, &tmp_array,
+					      only_box, keep_retriable);
+	imapc_connection_abort_commands_array(&conn->cmd_send_queue, &tmp_array,
+					      only_box, keep_retriable);
 
-	if (array_count(&conn->cmd_wait_list) > 0 && disconnected) {
+	if (array_count(&conn->cmd_wait_list) > 0 && only_box == NULL) {
 		/* need to move all the waiting commands to send queue */
 		array_append_array(&conn->cmd_wait_list,
 				   &conn->cmd_send_queue);
@@ -371,13 +372,13 @@
 	conn->fd = -1;
 
 	imapc_connection_set_state(conn, IMAPC_CONNECTION_STATE_DISCONNECTED);
-	imapc_connection_abort_commands(conn, TRUE, reconnecting);
+	imapc_connection_abort_commands(conn, NULL, reconnecting);
 }
 
 static void imapc_connection_set_disconnected(struct imapc_connection *conn)
 {
 	imapc_connection_set_state(conn, IMAPC_CONNECTION_STATE_DISCONNECTED);
-	imapc_connection_abort_commands(conn, TRUE, FALSE);
+	imapc_connection_abort_commands(conn, NULL, FALSE);
 }
 
 static void imapc_connection_reconnect(struct imapc_connection *conn)
@@ -1857,7 +1858,7 @@
 		conn->selecting_box = NULL;
 	}
 	imapc_connection_send_idle_done(conn);
-	imapc_connection_abort_commands(conn, FALSE, FALSE);
+	imapc_connection_abort_commands(conn, box, FALSE);
 }
 
 struct imapc_client_mailbox *
--- a/src/lib-imap-client/imapc-connection.h	Fri Aug 10 04:56:56 2012 +0300
+++ b/src/lib-imap-client/imapc-connection.h	Fri Aug 10 05:24:07 2012 +0300
@@ -26,7 +26,8 @@
 			      void *login_context) ATTR_NULL(2, 3);
 void imapc_connection_disconnect(struct imapc_connection *conn);
 void imapc_connection_abort_commands(struct imapc_connection *conn,
-				     bool disconnected, bool keep_retriable);
+				     struct imapc_client_mailbox *only_box,
+				     bool keep_retriable) ATTR_NULL(2);
 void imapc_connection_ioloop_changed(struct imapc_connection *conn);
 void imapc_connection_input_pending(struct imapc_connection *conn);
 
--- a/src/lib-mail/mail-user-hash.c	Fri Aug 10 04:56:56 2012 +0300
+++ b/src/lib-mail/mail-user-hash.c	Fri Aug 10 05:24:07 2012 +0300
@@ -21,6 +21,12 @@
 	if (strcmp(format, "%u") == 0) {
 		/* fast path */
 		md5_get_digest(username, strlen(username), md5);
+	} else if (strcmp(format, "%Lu") == 0) {
+		/* almost as fast path */
+		T_BEGIN {
+			md5_get_digest(t_str_lcase(username),
+				       strlen(username), md5);
+		} T_END;
 	} else T_BEGIN {
 		string_t *str = t_str_new(128);
 
--- a/src/lib-master/master-service-settings-cache.c	Fri Aug 10 04:56:56 2012 +0300
+++ b/src/lib-master/master-service-settings-cache.c	Fri Aug 10 05:24:07 2012 +0300
@@ -145,6 +145,8 @@
 	}
 
 	if (entry != NULL) {
+		DLLIST2_REMOVE(&cache->oldest, &cache->newest, entry);
+		DLLIST2_APPEND(&cache->oldest, &cache->newest, entry);
 		*parser_r = entry->parser;
 		return TRUE;
 	}
@@ -166,10 +168,11 @@
 	settings_parser_deinit(&entry->parser);
 }
 
-static void cache_add(struct master_service_settings_cache *cache,
-		      const struct master_service_settings_input *input,
-		      const struct master_service_settings_output *output,
-		      struct setting_parser_context *parser)
+static struct setting_parser_context *
+cache_add(struct master_service_settings_cache *cache,
+	  const struct master_service_settings_input *input,
+	  const struct master_service_settings_output *output,
+	  struct setting_parser_context *parser)
 {
 	struct settings_entry *entry;
 	pool_t pool;
@@ -185,41 +188,39 @@
 	}
 	if (cache->service_uses_remote) {
 		/* for now we don't try to handle caching remote IPs */
-		return;
+		return parser;
 	}
 
 	if (input->local_name == NULL && input->local_ip.family == 0)
-		return;
+		return parser;
 
 	if (!output->used_local) {
 		/* use global settings, but add local_ip/host to hash tables
 		   so we'll find them */
 		pool = pool_alloconly_create("settings global entry", 256);
-		entry = p_new(pool, struct settings_entry, 1);
 	} else if (cache->cache_malloc_size >= cache->max_cache_size) {
 		/* free the oldest and reuse its pool */
-		entry = cache->oldest;
-		pool = entry->pool;
-		setting_entry_detach(cache, entry);
-		p_clear(pool);
+		pool = cache->oldest->pool;
+		setting_entry_detach(cache, cache->oldest);
+		p_clear(pool); /* note: frees also entry */
 	} else {
 		pool_size = cache->approx_entry_pool_size != 0 ?
 			cache->approx_entry_pool_size :
 			CACHE_INITIAL_ENTRY_POOL_SIZE;
 		pool = pool_alloconly_create("settings entry", pool_size);
-		entry = p_new(pool, struct settings_entry, 1);
 	}
+	entry = p_new(pool, struct settings_entry, 1);
 	entry->pool = pool;
 	entry_local_name = p_strdup(pool, input->local_name);
 	entry->local_name = entry_local_name;
 	entry->local_ip = input->local_ip;
 	if (!output->used_local) {
 		entry->parser = cache->global_parser;
-		DLLIST2_PREPEND(&cache->oldest_global, &cache->newest_global,
-				entry);
+		DLLIST2_APPEND(&cache->oldest_global, &cache->newest_global,
+			       entry);
 	} else {
 		entry->parser = settings_parser_dup(parser, entry->pool);
-		DLLIST2_PREPEND(&cache->oldest, &cache->newest, entry);
+		DLLIST2_APPEND(&cache->oldest, &cache->newest, entry);
 
 		pool_size = pool_alloconly_get_total_used_size(pool);
 		if (pool_size > cache->approx_entry_pool_size) {
@@ -249,6 +250,7 @@
 		hash_table_insert(cache->local_ip_hash,
 				  &entry->local_ip, entry);
 	}
+	return entry->parser;
 }
 
 int master_service_settings_cache_read(struct master_service_settings_cache *cache,
@@ -294,7 +296,7 @@
 		return -1;
 	}
 
-	cache_add(cache, &new_input, &output, cache->service->set_parser);
-	*parser_r = cache->service->set_parser;
+	*parser_r = cache_add(cache, &new_input, &output,
+			      cache->service->set_parser);
 	return 0;
 }
--- a/src/lib-master/mountpoint-list.c	Fri Aug 10 04:56:56 2012 +0300
+++ b/src/lib-master/mountpoint-list.c	Fri Aug 10 05:24:07 2012 +0300
@@ -55,6 +55,8 @@
 	"/media",
 	"/sys",
 	"/proc",
+	"/var/run",
+	"/run",
 	NULL
 };
 
--- a/src/lib-settings/settings-parser.c	Fri Aug 10 04:56:56 2012 +0300
+++ b/src/lib-settings/settings-parser.c	Fri Aug 10 05:24:07 2012 +0300
@@ -946,6 +946,8 @@
 
 	switch (ret) {
 	case -1:
+		if (ctx->error != NULL)
+			break;
 		if (input->stream_errno != 0) {
 			ctx->error = p_strdup_printf(ctx->parser_pool,
 						     "read() failed: %m");
--- a/src/lib-sql/sql-db-cache.c	Fri Aug 10 04:56:56 2012 +0300
+++ b/src/lib-sql/sql-db-cache.c	Fri Aug 10 05:24:07 2012 +0300
@@ -75,6 +75,7 @@
 	db = cache->unused_tail;
 	ctx = SQL_DB_CACHE_CONTEXT(db);
 	sql_db_cache_unlink(ctx);
+	hash_table_remove(cache->dbs, ctx->key);
 
 	i_free(ctx->key);
 	ctx->orig_deinit(db);
--- a/src/lib-ssl-iostream/iostream-openssl.c	Fri Aug 10 04:56:56 2012 +0300
+++ b/src/lib-ssl-iostream/iostream-openssl.c	Fri Aug 10 05:24:07 2012 +0300
@@ -24,9 +24,9 @@
 		i_warning("%s: SSL failed: where=0x%x: %s",
 			  ssl_io->source, where, SSL_state_string_long(ssl));
 	} else {
-		i_warning("%s: SSL: where=0x%x, ret=%d: %s",
-			  ssl_io->source, where, ret,
-			  SSL_state_string_long(ssl));
+		i_debug("%s: SSL: where=0x%x, ret=%d: %s",
+			ssl_io->source, where, ret,
+			SSL_state_string_long(ssl));
 	}
 }
 
--- a/src/lib-storage/index/imapc/imapc-save.c	Fri Aug 10 04:56:56 2012 +0300
+++ b/src/lib-storage/index/imapc/imapc-save.c	Fri Aug 10 05:24:07 2012 +0300
@@ -165,7 +165,8 @@
 	uint32_t uid = 0;
 
 	if (reply->state == IMAPC_COMMAND_STATE_OK) {
-		if (strcasecmp(reply->resp_text_key, "APPENDUID") == 0)
+		if (reply->resp_text_key != NULL &&
+		    strcasecmp(reply->resp_text_key, "APPENDUID") == 0)
 			imapc_save_appenduid(ctx->ctx, reply, &uid);
 		imapc_save_add_to_index(ctx->ctx, uid);
 		ctx->ret = 0;
@@ -353,7 +354,8 @@
 	uint32_t uid = 0;
 
 	if (reply->state == IMAPC_COMMAND_STATE_OK) {
-		if (strcasecmp(reply->resp_text_key, "COPYUID") == 0)
+		if (reply->resp_text_key != NULL &&
+		    strcasecmp(reply->resp_text_key, "COPYUID") == 0)
 			imapc_save_copyuid(ctx->ctx, reply, &uid);
 		imapc_save_add_to_index(ctx->ctx, uid);
 		ctx->ret = 0;
--- a/src/lib-storage/index/index-mail.c	Fri Aug 10 04:56:56 2012 +0300
+++ b/src/lib-storage/index/index-mail.c	Fri Aug 10 05:24:07 2012 +0300
@@ -758,7 +758,8 @@
 	uint32_t t;
 
 	dates[0] = mail->data.received_date;
-	dates[1] = mail->data.save_date;
+	dates[1] = mail->mail.mail.saving ? ioloop_time :
+		mail->data.save_date;
 
 	for (i = 0; i < N_ELEMENTS(date_fields); i++) {
 		if (dates[i] != (time_t)-1 &&
--- a/src/lib-storage/index/index-storage.c	Fri Aug 10 04:56:56 2012 +0300
+++ b/src/lib-storage/index/index-storage.c	Fri Aug 10 05:24:07 2012 +0300
@@ -629,6 +629,7 @@
 	struct mailbox_transaction_context *dest_trans = ctx->transaction;
 	const struct mail_cache_field *dest_field;
 	unsigned int src_field_idx, dest_field_idx;
+	uint32_t t;
 
 	src_field_idx = mail_cache_register_lookup(src_mail->box->cache, name);
 	i_assert(src_field_idx != -1U);
@@ -647,8 +648,16 @@
 	}
 
 	buffer_set_used_size(buf, 0);
-	if (mail_cache_lookup_field(src_mail->transaction->cache_view, buf,
-				    src_mail->seq, src_field_idx) > 0) {
+	if (strcmp(name, "date.save") == 0) {
+		/* save date must update when mail is copied */
+		t = ioloop_time;
+		buffer_append(buf, &t, sizeof(t));
+	} else {
+		if (mail_cache_lookup_field(src_mail->transaction->cache_view, buf,
+					    src_mail->seq, src_field_idx) <= 0)
+			buffer_set_used_size(buf, 0);
+	}
+	if (buf->used > 0) {
 		mail_cache_add(dest_trans->cache_trans, dest_seq,
 			       dest_field_idx, buf->data, buf->used);
 	}
--- a/src/lib-storage/index/pop3c/pop3c-client.c	Fri Aug 10 04:56:56 2012 +0300
+++ b/src/lib-storage/index/pop3c/pop3c-client.c	Fri Aug 10 05:24:07 2012 +0300
@@ -85,7 +85,7 @@
 	client->set.debug = set->debug;
 	client->set.host = p_strdup(pool, set->host);
 	client->set.port = set->port;
-	client->set.master_user = p_strdup(pool, set->master_user);
+	client->set.master_user = p_strdup_empty(pool, set->master_user);
 	client->set.username = p_strdup(pool, set->username);
 	client->set.password = p_strdup(pool, set->password);
 	client->set.dns_client_socket_path =
@@ -352,8 +352,13 @@
 		client->state = POP3C_CLIENT_STATE_CAPA;
 		break;
 	case POP3C_CLIENT_STATE_CAPA:
-		if (strncasecmp(line, "-ERR", 4) == 0 ||
-		    strcmp(line, ".") == 0) {
+		if (strncasecmp(line, "-ERR", 4) == 0) {
+			/* CAPA command not supported. some commands still
+			   support UIDL though. */
+			client->capabilities |= POP3C_CAPABILITY_UIDL;
+			pop3c_client_login_finished(client);
+			break;
+		} else if (strcmp(line, ".") == 0) {
 			pop3c_client_login_finished(client);
 			break;
 		}
--- a/src/lib-storage/index/pop3c/pop3c-mail.c	Fri Aug 10 04:56:56 2012 +0300
+++ b/src/lib-storage/index/pop3c/pop3c-mail.c	Fri Aug 10 05:24:07 2012 +0300
@@ -110,10 +110,12 @@
 
 	if (mail->data.stream == NULL) {
 		capa = pop3c_client_get_capabilities(mbox->client);
-		if (get_body || (capa & POP3C_CAPABILITY_TOP) == 0)
+		if (get_body || (capa & POP3C_CAPABILITY_TOP) == 0) {
 			cmd = t_strdup_printf("RETR %u\r\n", _mail->seq);
-		else
+			get_body = TRUE;
+		} else {
 			cmd = t_strdup_printf("TOP %u 0\r\n", _mail->seq);
+		}
 		if (pop3c_client_cmd_stream(mbox->client, cmd,
 					    &input, &error) < 0) {
 			mail_storage_set_error(mbox->box.storage,
@@ -130,7 +132,8 @@
 			}
 		}
 		i_stream_set_name(mail->data.stream, t_strcut(cmd, '\r'));
-		pop3c_mail_cache_size(mail);
+		if (get_body)
+			pop3c_mail_cache_size(mail);
 	}
 	return index_mail_init_stream(mail, hdr_size, body_size, stream_r);
 }
--- a/src/lib-storage/index/pop3c/pop3c-settings.c	Fri Aug 10 04:56:56 2012 +0300
+++ b/src/lib-storage/index/pop3c/pop3c-settings.c	Fri Aug 10 05:24:07 2012 +0300
@@ -18,6 +18,7 @@
 	DEF(SET_UINT, pop3c_port),
 
 	DEF(SET_STR_VARS, pop3c_user),
+	DEF(SET_STR_VARS, pop3c_master_user),
 	DEF(SET_STR, pop3c_password),
 
 	DEF(SET_ENUM, pop3c_ssl),
@@ -35,6 +36,7 @@
 	.pop3c_port = 110,
 
 	.pop3c_user = "%u",
+	.pop3c_master_user = "",
 	.pop3c_password = "",
 
 	.pop3c_ssl = "no:pop3s:starttls",
--- a/src/lib-storage/index/pop3c/pop3c-settings.h	Fri Aug 10 04:56:56 2012 +0300
+++ b/src/lib-storage/index/pop3c/pop3c-settings.h	Fri Aug 10 05:24:07 2012 +0300
@@ -6,6 +6,7 @@
 	unsigned int pop3c_port;
 
 	const char *pop3c_user;
+	const char *pop3c_master_user;
 	const char *pop3c_password;
 
 	const char *pop3c_ssl;
--- a/src/lib-storage/index/pop3c/pop3c-storage.c	Fri Aug 10 04:56:56 2012 +0300
+++ b/src/lib-storage/index/pop3c/pop3c-storage.c	Fri Aug 10 05:24:07 2012 +0300
@@ -60,6 +60,7 @@
 	client_set.host = set->pop3c_host;
 	client_set.port = set->pop3c_port;
 	client_set.username = set->pop3c_user;
+	client_set.master_user = set->pop3c_master_user;
 	client_set.password = set->pop3c_password;
 	client_set.dns_client_socket_path =
 		t_strconcat(user->set->base_dir, "/",
--- a/src/lib-storage/index/shared/shared-list.c	Fri Aug 10 04:56:56 2012 +0300
+++ b/src/lib-storage/index/shared/shared-list.c	Fri Aug 10 05:24:07 2012 +0300
@@ -136,7 +136,8 @@
 	else
 		ns_ref = NULL;
 
-	if (ns_ref != NULL && shared_storage_get_namespace(&ns, &ns_ref) == 0)
+	if (ns_ref != NULL && *ns_ref != '\0' &&
+	    shared_storage_get_namespace(&ns, &ns_ref) == 0)
 		return mailbox_list_join_refpattern(ns->list, ref, pattern);
 
 	/* fallback to default behavior */
--- a/src/lib-storage/list/mailbox-list-delete.c	Fri Aug 10 04:56:56 2012 +0300
+++ b/src/lib-storage/list/mailbox-list-delete.c	Fri Aug 10 05:24:07 2012 +0300
@@ -340,11 +340,12 @@
 	if (errno == ENOENT) {
 		mailbox_list_set_error(list, MAIL_ERROR_NOTFOUND,
 			T_MAIL_ERR_MAILBOX_NOT_FOUND(name));
-	} else if (errno == EISDIR) {
+	} else if (errno == EISDIR ||
+		   errno == EPERM) { /* Solaris */
 		mailbox_list_set_error(list, MAIL_ERROR_NOTPOSSIBLE,
 				       "Mailbox isn't a symlink");
 	} else {
-		mailbox_list_set_critical(list, "stat(%s) failed: %m", path);
+		mailbox_list_set_critical(list, "unlink(%s) failed: %m", path);
 	}
 	return -1;
 }
--- a/src/lib-storage/list/mailbox-list-fs-iter.c	Fri Aug 10 04:56:56 2012 +0300
+++ b/src/lib-storage/list/mailbox-list-fs-iter.c	Fri Aug 10 05:24:07 2012 +0300
@@ -404,10 +404,18 @@
 			   we just want to see its contents (not the
 			   INBOX's children). */
 			root = "";
-		} else if (*prefix_vname == '\0') {
-			/* we need to handle "" explicitly here, because getting
-			   storage name with mail_shared_explicit_inbox=no
-			   would return root=INBOX. */
+		} else if ((ns->flags & NAMESPACE_FLAG_INBOX_ANY) != 0 &&
+			   ns->type == NAMESPACE_SHARED &&
+			   !ctx->ctx.list->mail_set->mail_shared_explicit_inbox &&
+			   (prefix_vname[0] == '\0' ||
+			    (strncmp(ns->prefix, prefix_vname, ns->prefix_len-1) == 0 &&
+			     prefix_vname[ns->prefix_len-1] == '\0'))) {
+			/* we need to handle ns prefix explicitly here, because
+			   getting storage name with
+			   mail_shared_explicit_inbox=no would return
+			   root=INBOX. (e.g. LIST "" shared/user/box has to
+			   return the box when it doesn't exist but
+			   shared/user/box/child exists) */
 			root = "";
 		} else {
 			root = mailbox_list_get_storage_name(ctx->ctx.list,
--- a/src/lib-storage/mail-namespace.c	Fri Aug 10 04:56:56 2012 +0300
+++ b/src/lib-storage/mail-namespace.c	Fri Aug 10 05:24:07 2012 +0300
@@ -249,15 +249,15 @@
 		return FALSE;
 	}
 	if (list_sep == '\0') {
-		*error_r = "no list=yes namespaces";
+		*error_r = "list=yes namespace missing";
 		return FALSE;
 	}
 	if (!visible_namespaces) {
-		*error_r = "no hidden=no namespaces";
+		*error_r = "hidden=no namespace missing";
 		return FALSE;
 	}
 	if (subscriptions_count == 0) {
-		*error_r = "no subscriptions=yes namespaces";
+		*error_r = "subscriptions=yes namespace missing";
 		return FALSE;
 	}
 	return TRUE;
--- a/src/lib-storage/mail-storage.c	Fri Aug 10 04:56:56 2012 +0300
+++ b/src/lib-storage/mail-storage.c	Fri Aug 10 05:24:07 2012 +0300
@@ -818,17 +818,18 @@
 	return 0;
 }
 
-static void mailbox_autocreate(struct mailbox *box)
+static int mailbox_autocreate(struct mailbox *box)
 {
 	const char *errstr;
 	enum mail_error error;
 
 	if (mailbox_create(box, NULL, FALSE) < 0) {
 		errstr = mailbox_get_last_error(box, &error);
-		if (error != MAIL_ERROR_NOTFOUND && !box->inbox_user) {
+		if (error != MAIL_ERROR_NOTFOUND) {
 			mail_storage_set_critical(box->storage,
 				"Failed to autocreate mailbox %s: %s",
 				box->vname, errstr);
+			return -1;
 		}
 	} else if (box->set != NULL &&
 		   strcmp(box->set->autocreate,
@@ -837,15 +838,18 @@
 			mail_storage_set_critical(box->storage,
 				"Failed to autosubscribe to mailbox %s: %s",
 				box->vname, mailbox_get_last_error(box, NULL));
+			return -1;
 		}
 	}
+	return 0;
 }
 
 static int mailbox_autocreate_and_reopen(struct mailbox *box)
 {
 	int ret;
 
-	mailbox_autocreate(box);
+	if (mailbox_autocreate(box) < 0)
+		return -1;
 	mailbox_close(box);
 
 	ret = box->v.open(box);
--- a/src/lib-storage/mail-user.c	Fri Aug 10 04:56:56 2012 +0300
+++ b/src/lib-storage/mail-user.c	Fri Aug 10 05:24:07 2012 +0300
@@ -201,7 +201,9 @@
 	};
 	struct var_expand_table *tab;
 
-	if (user->var_expand_table != NULL)
+	/* use a cached table, unless home directory has been set afterwards */
+	if (user->var_expand_table != NULL &&
+	    user->var_expand_table[4].value == user->_home)
 		return user->var_expand_table;
 
 	tab = p_malloc(user->pool, sizeof(static_tab));
--- a/src/lib/Makefile.am	Fri Aug 10 04:56:56 2012 +0300
+++ b/src/lib/Makefile.am	Fri Aug 10 05:24:07 2012 +0300
@@ -77,6 +77,7 @@
 	ioloop-select.c \
 	ioloop-epoll.c \
 	ioloop-kqueue.c \
+	json-parser.c \
 	lib.c \
 	lib-signals.c \
 	md4.c \
@@ -190,6 +191,7 @@
 	ioloop-iolist.h \
 	ioloop-private.h \
 	ioloop-notify-fd.h \
+	json-parser.h \
 	lib.h \
 	lib-signals.h \
 	llist.h \
--- a/src/lib/file-set-size.c	Fri Aug 10 04:56:56 2012 +0300
+++ b/src/lib/file-set-size.c	Fri Aug 10 05:24:07 2012 +0300
@@ -100,7 +100,7 @@
 	fs.fst_bytesalloc = 0;
 	if (fcntl(fd, F_PREALLOCATE, &fs) < 0)
 		return -1;
-	return 0;
+	return fs.fst_bytesalloc > 0 ? 1 : 0;
 #else
 	return 0;
 #endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/json-parser.c	Fri Aug 10 05:24:07 2012 +0300
@@ -0,0 +1,272 @@
+/* Copyright (c) 2012 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "str.h"
+#include "hex-dec.h"
+#include "unichar.h"
+#include "json-parser.h"
+
+enum json_state {
+	JSON_STATE_ROOT = 0,
+	JSON_STATE_OBJECT_KEY,
+	JSON_STATE_OBJECT_COLON,
+	JSON_STATE_OBJECT_VALUE,
+	JSON_STATE_OBJECT_VALUE_NEXT,
+	JSON_STATE_DONE
+};
+
+struct json_parser {
+	const unsigned char *data, *end;
+	const char *error;
+	string_t *value;
+
+	enum json_state state;
+};
+
+struct json_parser *
+json_parser_init(const unsigned char *data, unsigned int len)
+{
+	struct json_parser *parser;
+
+	parser = i_new(struct json_parser, 1);
+	parser->data = data;
+	parser->end = data + len;
+	parser->value = str_new(default_pool, 128);
+	return parser;
+}
+
+int json_parser_deinit(struct json_parser **_parser, const char **error_r)
+{
+	struct json_parser *parser = *_parser;
+
+	*_parser = NULL;
+
+	if (parser->error == NULL && parser->data == parser->end &&
+	    parser->state != JSON_STATE_ROOT &&
+	    parser->state != JSON_STATE_DONE)
+		parser->error = "Missing '}'";
+
+	*error_r = parser->error;
+	str_free(&parser->value);
+	i_free(parser);
+	return *error_r != NULL ? -1 : 0;
+}
+
+static bool json_parse_whitespace(struct json_parser *parser)
+{
+	for (; parser->data != parser->end; parser->data++) {
+		switch (*parser->data) {
+		case ' ':
+		case '\t':
+		case '\r':
+		case '\n':
+			break;
+		default:
+			return TRUE;
+		}
+	}
+	return FALSE;
+}
+
+static int json_parse_string(struct json_parser *parser, const char **value_r)
+{
+	const unsigned char *p;
+
+	if (*parser->data != '"')
+		return -1;
+
+	str_truncate(parser->value, 0);
+	for (p = parser->data + 1; p < parser->end; p++) {
+		if (*p == '"') {
+			parser->data = p + 1;
+			*value_r = str_c(parser->value);
+			return 0;
+		}
+		if (*p != '\\')
+			str_append_c(parser->value, *p);
+		else {
+			switch (*++p) {
+			case '"':
+			case '\\':
+			case '/':
+				str_append_c(parser->value, *p);
+				break;
+			case 'b':
+				str_append_c(parser->value, '\b');
+				break;
+			case 'f':
+				str_append_c(parser->value, '\f');
+				break;
+			case 'n':
+				str_append_c(parser->value, '\n');
+				break;
+			case 'r':
+				str_append_c(parser->value, '\r');
+				break;
+			case 't':
+				str_append_c(parser->value, '\t');
+				break;
+			case 'u':
+				if (parser->end - p < 4)
+					return -1;
+				uni_ucs4_to_utf8_c(hex2dec(p, 4),
+						   parser->value);
+				p += 3;
+				break;
+			default:
+				return -1;
+			}
+		}
+	}
+	return -1;
+}
+
+static int
+json_parse_digits(struct json_parser *parser, const unsigned char **_p)
+{
+	const unsigned char *p = *_p;
+
+	if (p >= parser->end || *p < '0' || *p > '9')
+		return -1;
+
+	for (; p < parser->end && *p >= '0' && *p <= '9'; p++)
+		str_append_c(parser->value, *p++);
+	*_p = p;
+	return 0;
+}
+
+static int json_parse_int(struct json_parser *parser, const unsigned char **_p)
+{
+	const unsigned char *p = *_p;
+
+	if (*p == '-') {
+		str_append_c(parser->value, *p++);
+		if (p == parser->end)
+			return -1;
+	}
+	if (*p == '0')
+		str_append_c(parser->value, *p++);
+	else {
+		if (json_parse_digits(parser, &p) < 0)
+			return -1;
+	}
+	*_p = p;
+	return 0;
+}
+
+static int json_parse_number(struct json_parser *parser, const char **value_r)
+{
+	const unsigned char *p = parser->data;
+
+	str_truncate(parser->value, 0);
+	if (json_parse_int(parser, &p) < 0)
+		return -1;
+	if (p < parser->end && *p == '.') {
+		/* frac */
+		str_append_c(parser->value, *p++);
+		if (json_parse_digits(parser, &p) < 0)
+			return -1;
+	}
+	if (p < parser->end && (*p == 'e' || *p == 'E')) {
+		/* exp */
+		str_append_c(parser->value, *p++);
+		if (p == parser->end)
+			return -1;
+		if (*p == '+' || *p == '-')
+			str_append_c(parser->value, *p++);
+		if (json_parse_digits(parser, &p) < 0)
+			return -1;
+	}
+	*value_r = str_c(parser->value);
+	return 0;
+}
+
+static int json_parse_atom(struct json_parser *parser, const char *atom)
+{
+	unsigned int len = strlen(atom);
+
+	if (parser->end - parser->data < len)
+		return -1;
+	if (memcmp(parser->data, atom, len) != 0)
+		return -1;
+	parser->data += len;
+	return 0;
+}
+
+bool json_parse_next(struct json_parser *parser, enum json_type *type_r,
+		     const char **value_r)
+{
+	*value_r = NULL;
+
+	if (!json_parse_whitespace(parser) || parser->error != NULL)
+		return FALSE;
+
+	switch (parser->state) {
+	case JSON_STATE_ROOT:
+		if (*parser->data == '{') {
+			parser->data++;
+			parser->state = JSON_STATE_OBJECT_KEY;
+			return json_parse_next(parser, type_r, value_r);
+		}
+		/* fall through */
+	case JSON_STATE_OBJECT_VALUE:
+		if (json_parse_string(parser, value_r) == 0)
+			*type_r = JSON_TYPE_STRING;
+		else if (json_parse_number(parser, value_r) == 0)
+			*type_r = JSON_TYPE_NUMBER;
+		else if (json_parse_atom(parser, "true") == 0) {
+			*type_r = JSON_TYPE_TRUE;
+			*value_r = "true";
+		} else if (json_parse_atom(parser, "false") == 0) {
+			*type_r = JSON_TYPE_FALSE;
+			*value_r = "false";
+		} else if (json_parse_atom(parser, "null") == 0) {
+			*type_r = JSON_TYPE_NULL;
+			*value_r = NULL;
+		} else if (*parser->data == '[') {
+			parser->error = "Arrays not supported";
+			return FALSE;
+		} else if (*parser->data == '{') {
+			parser->error = "Nested objects not supported";
+			return FALSE;
+		} else {
+			parser->error = "Invalid data as value";
+			return FALSE;
+		}
+		parser->state = parser->state == JSON_STATE_ROOT ?
+			JSON_STATE_DONE :
+			JSON_STATE_OBJECT_VALUE_NEXT;
+		break;
+	case JSON_STATE_OBJECT_KEY:
+		*type_r = JSON_TYPE_OBJECT_KEY;
+		if (json_parse_string(parser, value_r) < 0) {
+			parser->error = "Expected string as object key";
+			return FALSE;
+		}
+		parser->state = JSON_STATE_OBJECT_COLON;
+		break;
+	case JSON_STATE_OBJECT_COLON:
+		if (*parser->data != ':') {
+			parser->error = "Expected ':' after key";
+			return FALSE;
+		}
+		parser->data++;
+		parser->state = JSON_STATE_OBJECT_VALUE;
+		return json_parse_next(parser, type_r, value_r);
+	case JSON_STATE_OBJECT_VALUE_NEXT:
+		if (*parser->data == ',')
+			parser->state = JSON_STATE_OBJECT_KEY;
+		else if (*parser->data == '}')
+			parser->state = JSON_STATE_DONE;
+		else {
+			parser->error = "Expected ',' or '}' after object value";
+			return FALSE;
+		}
+		parser->data++;
+		return json_parse_next(parser, type_r, value_r);
+	case JSON_STATE_DONE:
+		parser->error = "Unexpected data at the end";
+		return FALSE;
+	}
+	return TRUE;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/json-parser.h	Fri Aug 10 05:24:07 2012 +0300
@@ -0,0 +1,22 @@
+#ifndef JSON_PARSER_H
+#define JSON_PARSER_H
+
+enum json_type {
+	JSON_TYPE_OBJECT_KEY,
+	JSON_TYPE_STRING,
+	JSON_TYPE_NUMBER,
+	JSON_TYPE_TRUE,
+	JSON_TYPE_FALSE,
+	JSON_TYPE_NULL
+};
+
+/* A really simple JSON parser, which for now only needs to be able to parse
+   a single { key: value, .. } object. */
+struct json_parser *
+json_parser_init(const unsigned char *data, unsigned int len);
+int json_parser_deinit(struct json_parser **parser, const char **error_r);
+
+bool json_parse_next(struct json_parser *parser, enum json_type *type_r,
+		     const char **value_r);
+
+#endif
--- a/src/lmtp/commands.c	Fri Aug 10 04:56:56 2012 +0300
+++ b/src/lmtp/commands.c	Fri Aug 10 05:24:07 2012 +0300
@@ -398,6 +398,69 @@
 	}
 }
 
+static void lmtp_address_translate(struct client *client, const char **address)
+{
+	const char *transpos = client->lmtp_set->lmtp_address_translate;
+	const char *p, *nextstr, *addrpos = *address;
+	unsigned int len;
+	string_t *username, *domain, *dest = NULL;
+
+	if (*transpos == '\0')
+		return;
+
+	username = t_str_new(64);
+	domain = t_str_new(64);
+
+	/* check that string matches up to the first '%' */
+	p = strchr(transpos, '%');
+	if (p == NULL)
+		len = strlen(transpos);
+	else
+		len = p-transpos;
+	if (strncmp(transpos, addrpos, len) != 0)
+		return;
+	transpos += len;
+	addrpos += len;
+
+	while (*transpos != '\0') {
+		switch (transpos[1]) {
+		case 'n':
+		case 'u':
+			dest = username;
+			break;
+		case 'd':
+			dest = domain;
+			break;
+		default:
+			return;
+		}
+		transpos += 2;
+
+		/* find where the next string starts */
+		if (*transpos == '\0') {
+			str_append(dest, addrpos);
+			break;
+		}
+		p = strchr(transpos, '%');
+		if (p == NULL)
+			nextstr = transpos;
+		else
+			nextstr = t_strdup_until(transpos, p);
+		p = strstr(addrpos, nextstr);
+		if (p == NULL)
+			return;
+		str_append_n(dest, addrpos, p-addrpos);
+
+		len = strlen(nextstr);
+		transpos += len;
+		addrpos = p + len;
+	}
+	str_append_c(username, '@');
+	if (domain != NULL)
+		str_append_str(username, domain);
+	*address = str_c(username);
+}
+
 int cmd_rcpt(struct client *client, const char *args)
 {
 	struct mail_recipient rcpt;
@@ -467,6 +530,8 @@
 		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);
@@ -789,6 +854,9 @@
 	struct istream *input;
 	bool ret = TRUE;
 
+	/* stop handling client input until saving/proxying is finished */
+	if (client->to_idle != NULL)
+		timeout_remove(&client->to_idle);
 	io_remove(&client->io);
 	i_stream_destroy(&client->dot_input);
 
--- a/src/lmtp/lmtp-proxy.c	Fri Aug 10 04:56:56 2012 +0300
+++ b/src/lmtp/lmtp-proxy.c	Fri Aug 10 05:24:07 2012 +0300
@@ -236,6 +236,10 @@
 	i_assert(!rcpt->rcpt_to_failed);
 	i_assert(rcpt->reply != NULL);
 
+	/* reset timeout in case there are a lot of RCPT TOs */
+	if (conn->to != NULL)
+		timeout_reset(conn->to);
+
 	rcpt->reply = p_strdup(conn->proxy->pool, reply);
 	rcpt->data_reply_received = TRUE;
 
--- a/src/lmtp/lmtp-settings.c	Fri Aug 10 04:56:56 2012 +0300
+++ b/src/lmtp/lmtp-settings.c	Fri Aug 10 05:24:07 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_STR, lmtp_address_translate),
 	DEF(SET_STR_VARS, login_greeting),
 	DEF(SET_STR, login_trusted_networks),
 
@@ -68,6 +69,7 @@
 static const struct lmtp_settings lmtp_default_settings = {
 	.lmtp_proxy = FALSE,
 	.lmtp_save_to_detail_mailbox = FALSE,
+	.lmtp_address_translate = "",
 	.login_greeting = PACKAGE_NAME" ready.",
 	.login_trusted_networks = ""
 };
--- a/src/lmtp/lmtp-settings.h	Fri Aug 10 04:56:56 2012 +0300
+++ b/src/lmtp/lmtp-settings.h	Fri Aug 10 05:24:07 2012 +0300
@@ -7,6 +7,7 @@
 struct lmtp_settings {
 	bool lmtp_proxy;
 	bool lmtp_save_to_detail_mailbox;
+	const char *lmtp_address_translate;
 	const char *login_greeting;
 	const char *login_trusted_networks;
 };
--- a/src/login-common/client-common.c	Fri Aug 10 04:56:56 2012 +0300
+++ b/src/login-common/client-common.c	Fri Aug 10 05:24:07 2012 +0300
@@ -15,6 +15,7 @@
 #include "var-expand.h"
 #include "master-interface.h"
 #include "master-service.h"
+#include "master-service-ssl-settings.h"
 #include "master-auth.h"
 #include "auth-client.h"
 #include "login-proxy.h"
@@ -379,7 +380,7 @@
 		return;
 	}
 
-	if (!ssl_initialized) {
+	if (!client_is_tls_enabled(client)) {
 		client->v.notify_starttls(client, FALSE, "TLS support isn't enabled.");
 		return;
 	}
@@ -599,6 +600,11 @@
 	} T_END;
 }
 
+bool client_is_tls_enabled(struct client *client)
+{
+	return ssl_initialized && strcmp(client->ssl_set->ssl, "no") != 0;
+}
+
 const char *client_get_extra_disconnect_reason(struct client *client)
 {
 	unsigned int auth_secs = client->auth_first_started == 0 ? 0 :
--- a/src/login-common/client-common.h	Fri Aug 10 04:56:56 2012 +0300
+++ b/src/login-common/client-common.h	Fri Aug 10 05:24:07 2012 +0300
@@ -188,6 +188,7 @@
 
 void client_auth_respond(struct client *client, const char *response);
 void client_auth_abort(struct client *client);
+bool client_is_tls_enabled(struct client *client);
 void client_auth_fail(struct client *client, const char *text);
 const char *client_get_session_id(struct client *client);
 
--- a/src/login-common/ssl-proxy-openssl.c	Fri Aug 10 04:56:56 2012 +0300
+++ b/src/login-common/ssl-proxy-openssl.c	Fri Aug 10 05:24:07 2012 +0300
@@ -856,9 +856,9 @@
 			  where, SSL_state_string_long(ssl),
 			  net_ip2addr(&proxy->ip));
 	} else {
-		i_warning("SSL: where=0x%x, ret=%d: %s [%s]",
-			  where, ret, SSL_state_string_long(ssl),
-			  net_ip2addr(&proxy->ip));
+		i_debug("SSL: where=0x%x, ret=%d: %s [%s]",
+			where, ret, SSL_state_string_long(ssl),
+			net_ip2addr(&proxy->ip));
 	}
 }
 
@@ -1036,6 +1036,17 @@
 	}
 }
 
+static const char *ssl_key_load_error(void)
+{
+	unsigned long err = ERR_peek_error();
+
+	if (ERR_GET_LIB(err) == ERR_LIB_X509 &&
+	    ERR_GET_REASON(err) == X509_R_KEY_VALUES_MISMATCH)
+		return "Key is for a different cert than ssl_cert";
+	else
+		return ssl_last_error();
+}
+
 static EVP_PKEY * ATTR_NULL(2)
 ssl_proxy_load_key(const char *key, const char *password)
 {
@@ -1050,23 +1061,14 @@
 	dup_password = t_strdup_noconst(password);
 	pkey = PEM_read_bio_PrivateKey(bio, NULL, pem_password_callback,
 				       dup_password);
-	if (pkey == NULL)
-		i_fatal("Couldn't parse private ssl_key");
+	if (pkey == NULL) {
+		i_fatal("Couldn't parse private ssl_key: %s",
+			ssl_key_load_error());
+	}
 	BIO_free(bio);
 	return pkey;
 }
 
-static const char *ssl_key_load_error(void)
-{
-	unsigned long err = ERR_peek_error();
-
-	if (ERR_GET_LIB(err) == ERR_LIB_X509 &&
-	    ERR_GET_REASON(err) == X509_R_KEY_VALUES_MISMATCH)
-		return "Key is for a different cert than ssl_cert";
-	else
-		return ssl_last_error();
-}
-
 static void
 ssl_proxy_ctx_use_key(SSL_CTX *ctx,
 		      const struct master_service_ssl_settings *set)
--- a/src/master/main.c	Fri Aug 10 04:56:56 2012 +0300
+++ b/src/master/main.c	Fri Aug 10 05:24:07 2012 +0300
@@ -299,8 +299,7 @@
 	while ((rec = mountpoint_list_iter_next(iter)) != NULL) {
 		if (MOUNTPOINT_WRONGLY_NOT_MOUNTED(rec)) {
 			i_warning("%s is no longer mounted. "
-				  "If this is intentional, "
-				  "remove it with doveadm mount",
+				  "See http://wiki2.dovecot.org/Mountpoints",
 				  rec->mount_path);
 		}
 	}
--- a/src/plugins/fts-lucene/Makefile.am	Fri Aug 10 04:56:56 2012 +0300
+++ b/src/plugins/fts-lucene/Makefile.am	Fri Aug 10 05:24:07 2012 +0300
@@ -21,6 +21,10 @@
 endif
 if BUILD_LUCENE_TEXTCAT
 TEXTCAT_LIBS = -ltextcat
+else
+if BUILD_LUCENE_EXTTEXTCAT
+TEXTCAT_LIBS = -lexttextcat
+endif
 endif
 
 lib21_fts_lucene_plugin_la_LIBADD = \
--- a/src/plugins/fts-lucene/lucene-wrapper.cc	Fri Aug 10 04:56:56 2012 +0300
+++ b/src/plugins/fts-lucene/lucene-wrapper.cc	Fri Aug 10 05:24:07 2012 +0300
@@ -18,6 +18,10 @@
 #include <sys/stat.h>
 #ifdef HAVE_LUCENE_TEXTCAT
 #  include <libtextcat/textcat.h>
+#else
+#ifdef HAVE_LUCENE_EXTTEXTCAT
+#  include <libexttextcat/textcat.h>
+#endif
 #endif
 };
 #include <CLucene.h>
@@ -1094,7 +1098,7 @@
 			return false;
 
 		q = lucene_get_query(index,
-				     t_lucene_utf8_to_tchar(index, arg->hdr_field_name, FALSE),
+				     t_lucene_utf8_to_tchar(index, t_str_lcase(arg->hdr_field_name), FALSE),
 				     arg);
 		break;
 	default:
@@ -1137,7 +1141,7 @@
 		if (*arg->value.str == '\0') {
 			/* checking potential existence of the header name */
 			q = lucene_get_query_str(index, _T("hdr"),
-						 arg->hdr_field_name, FALSE);
+				t_str_lcase(arg->hdr_field_name), FALSE);
 			break;
 		}
 
--- a/src/plugins/fts-solr/fts-backend-solr.c	Fri Aug 10 04:56:56 2012 +0300
+++ b/src/plugins/fts-solr/fts-backend-solr.c	Fri Aug 10 05:24:07 2012 +0300
@@ -38,6 +38,7 @@
 	struct solr_connection_post *post;
 	uint32_t prev_uid;
 	string_t *cmd, *cur_value, *cur_value2;
+	string_t *cmd_expunge;
 	ARRAY_DEFINE(fields, struct solr_fts_field);
 
 	uint32_t last_indexed_uid;
@@ -240,7 +241,6 @@
 
 	ctx = i_new(struct solr_fts_backend_update_context, 1);
 	ctx->ctx.backend = _backend;
-	ctx->cmd = str_new(default_pool, SOLR_CMDBUF_SIZE);
 	i_array_init(&ctx->fields, 16);
 	return &ctx->ctx;
 }
@@ -326,6 +326,15 @@
 	return solr_connection_post_end(ctx->post);
 }
 
+static void
+fts_backend_solr_expunge_flush(struct solr_fts_backend_update_context *ctx)
+{
+	str_append(ctx->cmd_expunge, "</delete>");
+	(void)solr_connection_post(solr_conn, str_c(ctx->cmd_expunge));
+	str_truncate(ctx->cmd_expunge, 0);
+	str_append(ctx->cmd_expunge, "<delete>");
+}
+
 static int
 fts_backend_solr_update_deinit(struct fts_backend_update_context *_ctx)
 {
@@ -341,6 +350,8 @@
 	if (ctx->documents_added || ctx->expunges) {
 		/* commit and wait until the documents we just indexed are
 		   visible to the following search */
+		if (ctx->expunges)
+			fts_backend_solr_expunge_flush(ctx);
 		str = t_strdup_printf("<commit waitFlush=\"false\" "
 				      "waitSearcher=\"%s\"/>",
 				      ctx->documents_added ? "true" : "false");
@@ -348,7 +359,10 @@
 			ret = -1;
 	}
 
-	str_free(&ctx->cmd);
+	if (ctx->cmd != NULL)
+		str_free(&ctx->cmd);
+	if (ctx->cmd_expunge != NULL)
+		str_free(&ctx->cmd_expunge);
 	array_foreach_modifiable(&ctx->fields, field) {
 		str_free(&field->value);
 		i_free(field->key);
@@ -404,18 +418,18 @@
 		   highly unlikely to be indexed at this time. */
 		return;
 	}
-	ctx->expunges = TRUE;
-
-	T_BEGIN {
-		string_t *cmd;
+	if (!ctx->expunges) {
+		ctx->expunges = TRUE;
+		ctx->cmd_expunge = str_new(default_pool, 1024);
+		str_append(ctx->cmd_expunge, "<delete>");
+	}
 
-		cmd = t_str_new(256);
-		str_append(cmd, "<delete><id>");
-		xml_encode_id(ctx, cmd, uid);
-		str_append(cmd, "</id></delete>");
+	if (str_len(ctx->cmd_expunge) >= SOLR_CMDBUF_FLUSH_SIZE)
+		fts_backend_solr_expunge_flush(ctx);
 
-		(void)solr_connection_post(solr_conn, str_c(cmd));
-	} T_END;
+	str_append(ctx->cmd_expunge, "<id>");
+	xml_encode_id(ctx, ctx->cmd_expunge, uid);
+	str_append(ctx->cmd_expunge, "</id>");
 }
 
 static void
@@ -425,6 +439,7 @@
 	if (ctx->post == NULL) {
 		i_assert(ctx->prev_uid == 0);
 
+		ctx->cmd = str_new(default_pool, SOLR_CMDBUF_SIZE);
 		ctx->post = solr_connection_post_begin(solr_conn);
 		str_append(ctx->cmd, "<add>");
 	} else {
--- a/src/plugins/fts-solr/solr-connection.c	Fri Aug 10 04:56:56 2012 +0300
+++ b/src/plugins/fts-solr/solr-connection.c	Fri Aug 10 05:24:07 2012 +0300
@@ -88,7 +88,7 @@
 			  const void *data, size_t size, bool done)
 {
 	enum XML_Error err;
-	int line;
+	int line, col;
 
 	if (conn->xml_failed)
 		return -1;
@@ -99,8 +99,10 @@
 	err = XML_GetErrorCode(conn->xml_parser);
 	if (err != XML_ERROR_FINISHED) {
 		line = XML_GetCurrentLineNumber(conn->xml_parser);
-		i_error("fts_solr: Invalid XML input at line %d: %s",
-			line, XML_ErrorString(err));
+		col = XML_GetCurrentColumnNumber(conn->xml_parser);
+		i_error("fts_solr: Invalid XML input at %d:%d: %s "
+			"(near: %.*s)", line, col, XML_ErrorString(err),
+			(int)I_MIN(size, 128), (const char *)data);
 		conn->xml_failed = TRUE;
 		return -1;
 	}
--- a/src/plugins/fts/fts-api.c	Fri Aug 10 04:56:56 2012 +0300
+++ b/src/plugins/fts/fts-api.c	Fri Aug 10 05:24:07 2012 +0300
@@ -318,9 +318,30 @@
 			     struct mail_search_arg *args, bool and_args,
 			     struct fts_multi_result *result)
 {
+	unsigned int i;
+
 	i_assert(boxes[0] != NULL);
 
-	return backend->v.lookup_multi(backend, boxes, args, and_args, result);
+	if (backend->v.lookup_multi != NULL) {
+		if (backend->v.lookup_multi(backend, boxes, args,
+					    and_args, result) < 0)
+			return -1;
+		if (result->box_results == NULL) {
+			result->box_results = p_new(result->pool,
+						    struct fts_result, 1);
+		}
+		return 0;
+	}
+
+	for (i = 0; boxes[i] != NULL; i++) ;
+	result->box_results = p_new(result->pool, struct fts_result, i+1);
+
+	for (i = 0; boxes[i] != NULL; i++) {
+		if (backend->v.lookup(backend, boxes[i], args,
+				      and_args, &result->box_results[i]) < 0)
+			return -1;
+	}
+	return 0;
 }
 
 void fts_backend_lookup_done(struct fts_backend *backend)
--- a/src/plugins/fts/fts-search.c	Fri Aug 10 04:56:56 2012 +0300
+++ b/src/plugins/fts/fts-search.c	Fri Aug 10 05:24:07 2012 +0300
@@ -199,13 +199,15 @@
 				   struct mail_search_arg *args,
 				   bool and_args)
 {
-	if (!fctx->virtual_mailbox) {
-		if (fts_search_lookup_level_single(fctx, args, and_args) < 0)
-			return -1;
-	} else T_BEGIN {
-		if (fts_search_lookup_level_multi(fctx, args, and_args) < 0)
-			return -1;
+	int ret;
+
+	T_BEGIN {
+		ret = !fctx->virtual_mailbox ?
+			fts_search_lookup_level_single(fctx, args, and_args) :
+			fts_search_lookup_level_multi(fctx, args, and_args);
 	} T_END;
+	if (ret < 0)
+		return -1;
 
 	for (; args != NULL; args = args->next) {
 		if (args->type != SEARCH_OR && args->type != SEARCH_SUB)
--- a/src/plugins/imap-acl/imap-acl-plugin.c	Fri Aug 10 04:56:56 2012 +0300
+++ b/src/plugins/imap-acl/imap-acl-plugin.c	Fri Aug 10 05:24:07 2012 +0300
@@ -309,12 +309,13 @@
 {
 	struct mail_namespace *ns;
 	struct mailbox *box;
-	const char *mailbox;
+	const char *mailbox, *orig_mailbox;
 	const char *const *rights;
 	string_t *str;
 
 	if (!client_read_string_args(cmd, 1, &mailbox))
 		return FALSE;
+	orig_mailbox = mailbox;
 
 	if (ACL_USER_CONTEXT(cmd->client->user) == NULL) {
 		client_send_command_error(cmd, "ACLs disabled.");
@@ -346,7 +347,7 @@
 
 	str = t_str_new(128);
 	str_append(str, "* MYRIGHTS ");
-	imap_quote_append_string(str, mailbox, FALSE);
+	imap_quote_append_string(str, orig_mailbox, FALSE);
 	str_append_c(str,' ');
 	imap_acl_write_rights_list(str, rights);
 
--- a/src/plugins/imap-quota/imap-quota-plugin.c	Fri Aug 10 04:56:56 2012 +0300
+++ b/src/plugins/imap-quota/imap-quota-plugin.c	Fri Aug 10 05:24:07 2012 +0300
@@ -74,12 +74,13 @@
 	struct mailbox *box;
 	struct quota_root_iter *iter;
         struct quota_root *root;
-	const char *mailbox, *name;
+	const char *mailbox, *orig_mailbox, *name;
 	string_t *quotaroot_reply, *quota_reply;
 
 	/* <mailbox> */
 	if (!client_read_string_args(cmd, 1, &mailbox))
 		return FALSE;
+	orig_mailbox = mailbox;
 
 	ns = client_find_namespace(cmd, &mailbox);
 	if (ns == NULL)
@@ -101,7 +102,7 @@
 	quotaroot_reply = t_str_new(128);
 	quota_reply = t_str_new(256);
 	str_append(quotaroot_reply, "* QUOTAROOT ");
-	imap_quote_append_string(quotaroot_reply, mailbox, FALSE);
+	imap_quote_append_string(quotaroot_reply, orig_mailbox, FALSE);
 
 	iter = quota_root_iter_init(box);
 	while ((root = quota_root_iter_next(iter)) != NULL) {
--- a/src/plugins/mail-log/mail-log-plugin.c	Fri Aug 10 04:56:56 2012 +0300
+++ b/src/plugins/mail-log/mail-log-plugin.c	Fri Aug 10 05:24:07 2012 +0300
@@ -363,7 +363,7 @@
 	const char *desc;
 
 	desc = t_strdup_printf("copy from %s",
-			       str_sanitize(mailbox_get_name(src->box),
+			       str_sanitize(mailbox_get_vname(src->box),
 					    MAILBOX_NAME_LOG_LEN));
 	mail_log_append_mail_message(ctx, dst,
 				     MAIL_LOG_EVENT_COPY, desc);
@@ -466,7 +466,7 @@
 		return;
 
 	i_info("Mailbox created: %s",
-	       str_sanitize(box->name, MAILBOX_NAME_LOG_LEN));
+	       str_sanitize(mailbox_get_vname(box), MAILBOX_NAME_LOG_LEN));
 }
 
 static void
@@ -478,7 +478,7 @@
 		return;
 
 	i_info("Mailbox deleted: %s",
-	       str_sanitize(box->name, MAILBOX_NAME_LOG_LEN));
+	       str_sanitize(mailbox_get_vname(box), MAILBOX_NAME_LOG_LEN));
 }
 
 static void
@@ -491,8 +491,8 @@
 		return;
 
 	i_info("Mailbox renamed: %s -> %s",
-	       str_sanitize(src->name, MAILBOX_NAME_LOG_LEN),
-	       str_sanitize(dest->name, MAILBOX_NAME_LOG_LEN));
+	       str_sanitize(mailbox_get_vname(src), MAILBOX_NAME_LOG_LEN),
+	       str_sanitize(mailbox_get_vname(dest), MAILBOX_NAME_LOG_LEN));
 }
 
 static const struct notify_vfuncs mail_log_vfuncs = {
--- a/src/plugins/pop3-migration/pop3-migration-plugin.c	Fri Aug 10 04:56:56 2012 +0300
+++ b/src/plugins/pop3-migration/pop3-migration-plugin.c	Fri Aug 10 05:24:07 2012 +0300
@@ -472,8 +472,13 @@
 
 	if (pop3_mailbox_open(box->storage) < 0)
 		return -1;
+	/* the POP3 server isn't connected to yet. handle all IMAP traffic
+	   first before connecting, so POP3 server won't disconnect us due to
+	   idling. */
+	if (imap_map_read(box) < 0)
+		return -1;
 
-	if (pop3_map_read(box->storage) < 0 || imap_map_read(box) < 0)
+	if (pop3_map_read(box->storage) < 0)
 		return -1;
 
 	if (!pop3_uidl_assign_by_size(box)) {
--- a/src/plugins/quota/quota-private.h	Fri Aug 10 04:56:56 2012 +0300
+++ b/src/plugins/quota/quota-private.h	Fri Aug 10 05:24:07 2012 +0300
@@ -25,6 +25,7 @@
 			  uoff_t size, bool *too_large_r);
 
 	const char *quota_exceeded_msg;
+	unsigned int ignore_save_errors:1;
 	unsigned int debug:1;
 };
 
@@ -101,7 +102,10 @@
 	/* this quota root applies only to this namespace. it may also be
 	   a public namespace without an owner. */
 	struct mail_namespace *ns;
-	/* this is set in quota init(), because namespaces aren't known yet */
+	/* this is set in quota init(), because namespaces aren't known yet.
+	   when accessing shared users the ns_prefix may be non-NULL but
+	   ns=NULL, so when checking if quota root applies only to a specific
+	   namespace use the ns_prefix!=NULL check. */
 	const char *ns_prefix;
 
 	/* initially the same as set->default_rule.*_limit, but some backends
--- a/src/plugins/quota/quota-storage.c	Fri Aug 10 04:56:56 2012 +0300
+++ b/src/plugins/quota/quota-storage.c	Fri Aug 10 05:24:07 2012 +0300
@@ -147,7 +147,7 @@
 	} else {
 		mail_storage_set_critical(t->box->storage,
 					  "Internal quota calculation error");
-		return -1;
+		return qt->quota->set->ignore_save_errors ? 0 : -1;
 	}
 }
 
@@ -213,7 +213,8 @@
 		} else if (ret < 0) {
 			mail_storage_set_critical(t->box->storage,
 				"Internal quota calculation error");
-			return -1;
+			if (!qt->quota->set->ignore_save_errors)
+				return -1;
 		}
 	}
 
--- a/src/plugins/quota/quota.c	Fri Aug 10 04:56:56 2012 +0300
+++ b/src/plugins/quota/quota.c	Fri Aug 10 05:24:07 2012 +0300
@@ -195,6 +195,8 @@
 	quota_set->debug = user->mail_debug;
 	quota_set->quota_exceeded_msg =
 		mail_user_plugin_getenv(user, "quota_exceeded_message");
+	quota_set->ignore_save_errors =
+		mail_user_plugin_getenv(user, "quota_ignore_save_errors") != NULL;
 	if (quota_set->quota_exceeded_msg == NULL)
 		quota_set->quota_exceeded_msg = DEFAULT_QUOTA_EXCEEDED_MSG;
 
@@ -770,7 +772,7 @@
 	    (storage->class_flags & MAIL_STORAGE_CLASS_FLAG_NOQUOTA) != 0)
 		return FALSE;
 
-	if (root->ns != NULL) {
+	if (root->ns_prefix != NULL) {
 		if (root->ns != ns)
 			return FALSE;
 	} else {
--- a/src/pop3-login/client-authenticate.c	Fri Aug 10 04:56:56 2012 +0300
+++ b/src/pop3-login/client-authenticate.c	Fri Aug 10 05:24:07 2012 +0300
@@ -31,7 +31,7 @@
 	str_append(str, "+OK\r\n");
 	str_append(str, capability_string);
 
-	if (ssl_initialized && !client->common.tls)
+	if (client_is_tls_enabled(&client->common) && !client->common.tls)
 		str_append(str, "STLS\r\n");
 	if (!client->common.set->disable_plaintext_auth ||
 	    client->common.secured)
--- a/src/pop3/pop3-commands.c	Fri Aug 10 04:56:56 2012 +0300
+++ b/src/pop3/pop3-commands.c	Fri Aug 10 05:24:07 2012 +0300
@@ -756,7 +756,7 @@
 				      str_hash, (hash_cmp_callback_t *)strcmp);
 	client->uidl_pool = pool_alloconly_create("message uidls", 1024);
 	client->message_uidls = p_new(client->uidl_pool, const char *,
-				      client->messages_count);
+				      client->messages_count+1);
 
 	str = t_str_new(128); msgnum = 0;
 	while (mailbox_search_next(search_ctx, &mail)) {
--- a/src/util/script.c	Fri Aug 10 04:56:56 2012 +0300
+++ b/src/util/script.c	Fri Aug 10 05:24:07 2012 +0300
@@ -133,8 +133,8 @@
 	}
 	alarm(0);
 
-	/* drop the last LF */
-	buffer_set_used_size(input, scanpos-1);
+	/* drop the last two LFs */
+	buffer_set_used_size(input, scanpos-2);
 
 	args = t_strsplit(str_c(input), "\n");
 	script_verify_version(*args); args++;
@@ -148,7 +148,7 @@
 			exec_child(conn, args + 1);
 			i_unreached();
 		}
-		if (*args == '\0')
+		if (**args == '\0')
 			i_fatal("empty options");
 		args++;
 	}