changeset 7798:8e206e25a142 HEAD

Merged latest v1.1 changes.
author Timo Sirainen <tss@iki.fi>
date Mon, 09 Jun 2008 05:11:18 +0300
parents 86e84fb00776 (current diff) d8d1fdbe29bf (diff)
children 4aa830b4f8bc
files README TODO configure.in dovecot-example.conf src/imap/client.c src/imap/client.h src/imap/cmd-append.c src/imap/cmd-search.c src/imap/cmd-sort.c src/imap/cmd-store.c src/imap/cmd-thread.c src/imap/commands.h src/imap/imap-search.c src/imap/imap-sync.c src/lib-index/mail-index-map.c src/lib-index/mail-index-private.h src/lib-index/mail-index-sync-ext.c src/lib-index/mail-index-sync-update.c src/lib-index/mail-index-transaction.c src/lib-index/mail-index.c src/lib-index/mail-index.h src/lib-mail/message-parser.c src/lib-storage/index/Makefile.am src/lib-storage/index/cydir/cydir-storage.c src/lib-storage/index/dbox/dbox-mail.c src/lib-storage/index/dbox/dbox-storage.c src/lib-storage/index/index-mail.c src/lib-storage/index/index-mail.h src/lib-storage/index/index-search.c src/lib-storage/index/index-storage.h src/lib-storage/index/index-sync.c src/lib-storage/index/maildir/maildir-mail.c src/lib-storage/index/maildir/maildir-storage.c src/lib-storage/index/mbox/mbox-mail.c src/lib-storage/index/mbox/mbox-storage.c src/lib-storage/index/raw/raw-mail.c src/lib-storage/index/raw/raw-storage.c src/lib-storage/mail-storage.c src/lib-storage/mail-storage.h src/plugins/convert/convert-storage.c src/plugins/expire/expire-tool.c src/plugins/fts-squat/fts-backend-squat.c src/plugins/fts/fts-storage.c src/plugins/lazy-expunge/lazy-expunge-plugin.c src/plugins/mbox-snarf/mbox-snarf-plugin.c src/plugins/quota/quota-count.c src/plugins/quota/quota-storage.c src/pop3/client.c src/pop3/commands.c src/util/idxview.c
diffstat 182 files changed, 3890 insertions(+), 1667 deletions(-) [+]
line wrap: on
line diff
--- a/.hgtags	Sat May 17 17:50:54 2008 +0300
+++ b/.hgtags	Mon Jun 09 05:11:18 2008 +0300
@@ -20,3 +20,8 @@
 958e377fc1dcdc88be2f8cfaba68fc5ad4c15dc6 1.1.rc1
 dfd811aa0418000e2b60d9cafb67817cdc2dbdb9 1.1.rc2
 c73d6224a96b3c07740dd9880cfe1b066e6238ff 1.1.rc3
+4607141a6bdc4226acbbece913d9780e5c3c818f 1.1.rc4
+3b09af6458e590f5b05fba89847a80441b6c0d5f 1.1.rc5
+79857a116d281846441c866ae2a9f8f2e4d7b0cf 1.1.rc6
+290bd8b0c2d76f6fccd3add14bb3007c86ce34ff 1.1.rc7
+58c7f5c31db1801d9c7495354e6911da898aaa65 1.1.rc8
--- a/NEWS	Sat May 17 17:50:54 2008 +0300
+++ b/NEWS	Mon Jun 09 05:11:18 2008 +0300
@@ -1,3 +1,84 @@
+v1.1.rc8 2008-06-03  Timo Sirainen <tss@iki.fi>
+
+	+ deliver: Added -p parameter to provide path to delivered mail.
+	  This allows maildir to save identical mails to multiple recipients
+	  using hard links.
+	- rc6/rc7 broke POP3 with non-Maildir formats
+	- mbox: Saving a message without a body or the end-of-headers line
+	  could have caused an assert-crash later.
+	- Several dbox fixes
+
+v1.1.rc7 2008-05-30  Timo Sirainen <tss@iki.fi>
+
+	- Fixed compiling problems with non-Linux OSes
+
+v1.1.rc6 2008-05-30  Timo Sirainen <tss@iki.fi>
+
+	* Index file format changed a bit. If an older Dovecot v1.1 reads
+	  index files updated by rc6+, they may give "Invalid header record
+	  size" or "ext reset: invalid record size" warnings. v1.0 won't give
+	  these errors.
+	* IMAP: LIST .. RETURN (X-STATUS) command return now LIST entries
+	  before STATUS entries.
+	* zlib plugin: Uncompress if the message begins with zlib header
+	  instead of looking at the 'Z' flag. This fixes copying with hard
+	  links. Based on a patch by Richard Platel.
+
+	+ IMAP: SORT index handling code was half-rewritten to fix several bugs
+	  when multiple sessions were sorting at the same time. The new code is
+	  hopefully also faster.
+	+ Maildir: If POP3 UIDL extra field is found from dovecot-uidlist,
+	  it's used instead of the default UIDL format (or X-UIDL: header).
+	  This allows easily preserving UIDLs when migrating from other POP3
+	  servers. Patch by Nicholas Von Hollen @ Mailtrust.
+	+ Maildir: ,W=<vsize> is now always added to maildir filenames
+	+ deliver: Avoid reading dovecot-uidlist's contents if possible.
+	+ Added %T modifier = Trim whitespace from end of string
+	- IMAP: Fixed some bugs in LIST-EXTENDED implementation.
+	- IMAP: If client tries to change the selected mailbox state while
+	  another command is still running, wait until the command is finished.
+	  This fixes some crashes and other unwanted behavior.
+	- allow_nets userdb setting was broken with big endian CPUs
+
+v1.1.rc5 2008-05-05  Timo Sirainen <tss@iki.fi>
+
+	+ Support cross-realm Kerberos 5 authentication. Based on patch by
+	  Zachary Kotlarek.
+	+ Added dict_db_config setting to point to a Berkeley DB config file.
+	+ If mail_chroot ends with "/.", remove chroot prefix from home
+	  directory.
+	- Fixed several bugs and memory leaks in ACL plugin. LIST and LSUB
+	  may have listed mailboxes where user had no 'l' access. STORE could
+	  have been used to update any flags without appropriate access.
+	- mbox: Valid-looking From_-lines in message bodies caused the message
+	  to be split to two messages (broken since v1.0).
+	- Plugin initialization hooks were called in wrong order, possibly
+	  causing problems when multiple plugins were used at the same time.
+	- Expire plugin was broken
+	- LIST-EXTENDED options were ignored.
+	- LDAP: Static attribute names weren't working correctly
+	- deliver: mail_uid and mail_gid settings weren't used.
+	- pop3 + maildir++ quota: maildirsize file wasn't created if it 
+	  didn't exist already.
+	- dnotify: Waiting for dotlock to be deleted used 100% CPU
+
+v1.1.rc4 2008-04-01  Timo Sirainen <tss@iki.fi>
+
+	* Fixed two buffer overflows in str_find_init(). It was used by
+	  SEARCH code when searching for headers or message body. Added code
+	  to catch these kind of overflows when compiling with --enable-debug.
+	  Found by Diego Liziero.
+
+	+ LDAP: Added debug_level and ldaprc_path settings (OpenLDAP-only)
+	+ Squat: Added fts_squat = partial=n full=m settings. See the wiki.
+	- dbox metadata updating fixes.
+	- quota: backend=n didn't work
+	- SEARCH RECENT may have returned non-recent messages if index files
+	  were created by v1.0.
+	- If mailbox was opened as read-only with EXAMINE, STOREs were
+	  permanently saved.
+	- LDAP: Templates were somewhat broken (by richs at whidbey.net)
+
 v1.1.rc3 2008-03-09  Timo Sirainen <tss@iki.fi>
 
 	* Fixed a security hole in blocking passdbs (MySQL always. PAM, passwd
--- a/TODO	Sat May 17 17:50:54 2008 +0300
+++ b/TODO	Mon Jun 09 05:11:18 2008 +0300
@@ -1,20 +1,27 @@
- - dbox:
-   - "metadata changed unexpectedly" with alt paths - why?
-    - check that metadata is always correct and whitespace contains only
-      whitespace
-    - "File unexpectedly lost" doesn't get fixed by itself
-    - doesn't call fsync
- - do something about From_-lines splitting mails with mboxes
+ - recent assert. both with mbox and maildir.
+ - sieve-cmu.c crash: i_assert(buf->used - 1 == part->body_size.physical_size);
+ - convert plugin: Create a r/w lock for a file. It's read-locked if
+   conversion isn't wanted and released when process dies. If conversion is
+   wanted and write-lock succeeds, conversion is done, if write-lock doesn't
+   succeed it fallbacks to using the old storage. When process is exiting it
+   again tries to write-lock and do the conversion. Add a parameter that
+   specifies if conversion should be done.
  - lucene: handle replacement chars?
  - squat:
    - wrong indexid
    - fts_build_init() assertion failed: (last_uid < last_uid_locked)
-   - nfs support (cache flushes, how can write fail with ESTALE?)
    - is locking done right? it reads header without file being locked?
    - split after ~8 bytes?
    - expunges are delayed until more mails are added
  - test replacement chars (SEARCH / SORT / Squat)
 
+ - dbox:
+    - "File unexpectedly lost" doesn't get fixed by itself
+    - Fix support for multi-message files
+    - Delete dovecot-keywords and dovecot-uidlist after all maildir files
+      have been converted to native dbox
+ - DEBUG: buffer overflow checking code probably doesn't handle a successful
+   t_try_realloc() or pool_alloconly_realloc() properly
  - cache: compress when we can drop temporary fields.
  - POP3 UIDL caching
  - ACL: "foo/bar" in public namespace -> LIST "" % doesn't show "foo"
@@ -22,6 +29,12 @@
    - auth_gssapi_hostname = %Xl
    - proxying would also want DNS lookups, but not reverse..
  - ldap domain lookups which set the base for user lookup
+ - ldap: same attribute can't be used for multiple values.
+ - ldap: multiple attributes can't be merged to a single value.
+
+ - Per-user options:
+   - Deny deleting non-empty mailboxes
+   - Disable IDLE "still here" notifications
 
  - maildir+pop3/deliver fast updates:
    - with locking enabled, pop3 could just keep the one and same sync lock and
--- a/configure.in	Sat May 17 17:50:54 2008 +0300
+++ b/configure.in	Mon Jun 09 05:11:18 2008 +0300
@@ -437,7 +437,7 @@
 have_ioloop=no
 
 if test "$ioloop" = "best" || test "$ioloop" = "epoll"; then
-  AC_CACHE_CHECK([whether we can use epoll],epoll_works,[
+  AC_CACHE_CHECK([whether we can use epoll],i_cv_epoll_works,[
     AC_TRY_RUN([
       #include <sys/epoll.h>
   
@@ -446,12 +446,12 @@
 	return epoll_create(5) < 1;
       }
     ], [
-      epoll_works=yes
+      i_cv_epoll_works=yes
     ], [
-      epoll_works=no
+      i_cv_epoll_works=no
     ])
   ])
-  if test $epoll_works = yes; then
+  if test $i_cv_epoll_works = yes; then
     AC_DEFINE(IOLOOP_EPOLL,, Implement I/O loop with Linux 2.6 epoll())
     have_ioloop=yes
     ioloop=epoll
@@ -489,7 +489,7 @@
 
 if test "$notify" = "" || test "$notify" = "inotify" ; then
   dnl * inotify?
-  AC_CACHE_CHECK([whether we can use inotify],inotify_works,[
+  AC_CACHE_CHECK([whether we can use inotify],i_cv_inotify_works,[
     AC_TRY_RUN([
       #define _GNU_SOURCE
       #include <sys/ioctl.h>
@@ -523,12 +523,12 @@
 	return 0;
       }
     ], [
-      inotify_works=yes
+      i_cv_inotify_works=yes
     ], [
-      inotify_works=no
+      i_cv_inotify_works=no
     ])
   ])
-  if test $inotify_works = yes; then
+  if test $i_cv_inotify_works = yes; then
     have_notify=inotify
     notify=inotify
     AC_DEFINE(IOLOOP_NOTIFY_INOTIFY,, Use Linux inotify)
@@ -809,7 +809,7 @@
 dnl * make sure size_t isn't signed. we'd probably work fine with it, but
 dnl * it's more likely vulnerable to buffer overflows. Anyway, C99 specifies
 dnl * that it's unsigned and only some old systems define it as signed.
-AC_CACHE_CHECK([whether size_t is signed],signed_size_t,[
+AC_CACHE_CHECK([whether size_t is signed],i_cv_signed_size_t,[
   AC_RUN_IFELSE([AC_LANG_SOURCE([[
     #include <sys/types.h>
     int main() {
@@ -817,7 +817,7 @@
       exit((size_t)(int)-1 <= 0 ? 0 : 1);
     }
   ]])],[
-    signed_size_t=yes
+    i_cv_signed_size_t=yes
 
     echo
     echo "Your system's size_t is a signed integer, Dovecot isn't designed to"
@@ -830,7 +830,7 @@
     fi
     echo "..ignoring as requested.."
   ],[
-    signed_size_t=no
+    i_cv_signed_size_t=no
   ],[])
 ])
 dnl Note: we check size_t rather than ssize_t here, because on OSX 10.2
@@ -924,7 +924,7 @@
 AC_MSG_RESULT($i_cv_field_tm_gmtoff)
 
 dnl * how large time_t values does gmtime() accept?
-AC_CACHE_CHECK([how large time_t values gmtime() accepts],gmtime_max_time_t,[
+AC_CACHE_CHECK([how large time_t values gmtime() accepts],i_cv_gmtime_max_time_t,[
   AC_RUN_IFELSE([AC_LANG_SOURCE([[
     #include <stdio.h>
     #include <time.h>
@@ -955,16 +955,16 @@
       return 0;
     }
   ]])],[
-    gmtime_max_time_t=`cat conftest.temp`
+    i_cv_gmtime_max_time_t=`cat conftest.temp`
     rm -f conftest.temp
   ], [
     printf "check failed, assuming "
-    gmtime_max_time_t=31
+    i_cv_gmtime_max_time_t=31
   ],[])
 ])
-AC_DEFINE_UNQUOTED(TIME_T_MAX_BITS, $gmtime_max_time_t, max. time_t bits gmtime() can handle)
+AC_DEFINE_UNQUOTED(TIME_T_MAX_BITS, $i_cv_gmtime_max_time_t, max. time_t bits gmtime() can handle)
 
-AC_CACHE_CHECK([whether time_t is signed],signed_time_t,[
+AC_CACHE_CHECK([whether time_t is signed],i_cv_signed_time_t,[
   AC_RUN_IFELSE([AC_LANG_SOURCE([[
     #include <sys/types.h>
     int main() {
@@ -972,12 +972,12 @@
       exit((time_t)(int)-1 <= 0 ? 0 : 1);
     }
   ]])],[
-    signed_time_t=yes
+    i_cv_signed_time_t=yes
   ], [
-    signed_time_t=no
+    i_cv_signed_time_t=no
   ])
 ])
-if test $signed_time_t = yes; then
+if test $i_cv_signed_time_t = yes; then
   AC_DEFINE(TIME_T_SIGNED,, Define if your time_t is signed)
 fi
 
@@ -1051,7 +1051,7 @@
 ])
 
 dnl * If mmap() plays nicely with write()
-AC_CACHE_CHECK([whether shared mmaps get updated by write()s],mmap_plays_with_write,[
+AC_CACHE_CHECK([whether shared mmaps get updated by write()s],i_cv_mmap_plays_with_write,[
   AC_TRY_RUN([
     #include <stdio.h>
     #include <sys/types.h>
@@ -1083,17 +1083,17 @@
       return strcmp(mem, "3") == 0 ? 0 : 1;
     }
   ], [
-    mmap_plays_with_write=yes
+    i_cv_mmap_plays_with_write=yes
   ], [
-    mmap_plays_with_write=no
+    i_cv_mmap_plays_with_write=no
   ])
 ])
-if test $mmap_plays_with_write = no; then
+if test $i_cv_mmap_plays_with_write = no; then
   AC_DEFINE(MMAP_CONFLICTS_WRITE,, [Define if shared mmaps don't get updated by write()s])
 fi
 
 dnl * see if fd passing works
-AC_CACHE_CHECK([whether fd passing works],fd_passing,[
+AC_CACHE_CHECK([whether fd passing works],i_cv_fd_passing,[
   for i in 1 2; do
     old_cflags="$CFLAGS"
     CFLAGS="$CFLAGS -I$srcdir/src/lib $srcdir/src/lib/fdpass.c"
@@ -1143,20 +1143,20 @@
     ], [
       CFLAGS=$old_cflags
       if test $i = 2; then
-	fd_passing=buggy_cmsg_macros
+	i_cv_fd_passing=buggy_cmsg_macros
       else
-        fd_passing=yes
+        i_cv_fd_passing=yes
       fi
       break
     ], [
       dnl no, try with BUGGY_CMSG_MACROS
       CFLAGS=$old_cflags
-      fd_passing=no
+      i_cv_fd_passing=no
     ])
   done
 ]);
 
-if test $fd_passing = buggy_cmsg_macros; then
+if test $i_cv_fd_passing = buggy_cmsg_macros; then
   AC_DEFINE(BUGGY_CMSG_MACROS,, Define if you have buggy CMSG macros)
 fi
 
@@ -1218,7 +1218,7 @@
   AC_MSG_RESULT(no)
 ])
 
-AC_MSG_CHECKING([if struct stat has tv_nsec fields])
+AC_MSG_CHECKING([if struct stat has st_?tim timespec fields])
 AC_TRY_COMPILE([
   #include <sys/types.h>
   #include <sys/stat.h>
@@ -1229,7 +1229,24 @@
 
   return 0;
 ], [
-  AC_DEFINE(HAVE_STAT_TV_NSEC,, Define if you have tv_nsec fields in struct stat)
+  AC_DEFINE(HAVE_STAT_XTIM,, Define if you have st_?tim timespec fields in struct stat)
+  AC_MSG_RESULT(yes)
+], [
+  AC_MSG_RESULT(no)
+])
+
+AC_MSG_CHECKING([if struct stat has st_?timespec fields])
+AC_TRY_COMPILE([
+  #include <sys/types.h>
+  #include <sys/stat.h>
+  #include <unistd.h>
+], [
+  struct stat st;
+  unsigned long x = st.st_mtimespec.tv_nsec;
+
+  return 0;
+], [
+  AC_DEFINE(HAVE_STAT_XTIMESPEC,, Define if you have st_?timespec fields in struct stat)
   AC_MSG_RESULT(yes)
 ], [
   AC_MSG_RESULT(no)
@@ -1335,7 +1352,7 @@
 dnl *** C99 vsnprintf()?
 dnl ***
 
-AC_CACHE_CHECK([for C99 vsnprintf()],c99_vsnprintf,[
+AC_CACHE_CHECK([for C99 vsnprintf()],i_cv_c99_vsnprintf,[
   AC_RUN_IFELSE([AC_LANG_SOURCE([[
   #include <stdio.h>
   #include <stdarg.h>
@@ -1352,11 +1369,11 @@
   int main() {
     return f("hello %s%d", "world", 1);
   }]])],
-  [c99_vsnprintf=yes],
-  [c99_vsnprintf=no],
+  [i_cv_c99_vsnprintf=yes],
+  [i_cv_c99_vsnprintf=no],
   [])
 ])
-if test $c99_vsnprintf = no; then
+if test $i_cv_c99_vsnprintf = no; then
   AC_ERROR([You don't appear to have C99 compatible vsnprintf() call])
 fi
 
@@ -1616,29 +1633,44 @@
 		# we have a kludgy check here to check that we have
 		# version >= v1.3. Although this doesn't work right with
 		# non-MIT kerberos versioning..
-		if `krb5-config --version|grep -v '1\.2' > /dev/null`; then
-			KRB5_LIBS=`krb5-config --libs gssapi`
-			KRB5_CFLAGS=`krb5-config --cflags gssapi`
+		if ! krb5-config --version gssapi 2>/dev/null > /dev/null; then
+		  # krb5-config doesn't support gssapi.
+		  KRB5_LIBS="`krb5-config --libs`"
+		  KRB5_CFLAGS=`krb5-config --cflags`
+		  AC_CHECK_LIB(gss, gss_acquire_cred, [
+		    # Solaris
+		    KRB5_LIBS="$KRB5_LIBS -lgss"
+		  ], [
+		    # failed
+		    KRB5_LIBS=
+		  ], $KRB5_LIBS)
+		elif krb5-config --version|grep '1\.2' > /dev/null; then
+		  if test $want_gssapi = yes; then
+		    AC_ERROR([Can't build with GSSAPI support: v1.2 library not supported])
+		  fi
+		else
+		  KRB5_LIBS=`krb5-config --libs gssapi`
+		  KRB5_CFLAGS=`krb5-config --cflags gssapi`
+		fi
+		if test "$KRB5_LIBS" != ""; then
 			AC_SUBST(KRB5_LIBS)
 			AC_SUBST(KRB5_CFLAGS)
 			
 			# Although krb5-config exists, all systems still don't
 			# have gssapi.h
 			old_CFLAGS=$CFLAGS
-			CFLAGS="$CFLAGS `krb5-config --cflags gssapi`"
+			CFLAGS="$CFLAGS $KRB5_CFLAGS"
 			AC_CHECK_HEADER([gssapi/gssapi.h], [
 				AC_DEFINE(HAVE_GSSAPI_GSSAPI_H,, GSSAPI headers in gssapi/gssapi.h)
 				have_gssapi=yes
 			])
-			AC_CHECK_HEADER([gssapi/gssapi_ext.h], [
-				AC_DEFINE(HAVE_GSSAPI_GSSAPI_EXT_H,, GSSAPI headers in gssapi/gssapi_ext.h)
-			])
 			AC_CHECK_HEADER([gssapi.h], [
 				AC_DEFINE(HAVE_GSSAPI_H,, GSSAPI headers in gssapi.h)
 				have_gssapi=yes
 			])
 			if test $have_gssapi = yes; then
 				AC_DEFINE(HAVE_GSSAPI,, Build with GSSAPI support)
+				AC_CHECK_HEADERS(gssapi/gssapi_ext.h gssapi_krb5.h gssapi/gssapi_krb5.h)
 				AC_CHECK_LIB(gss, __gss_userok, [
 					AC_DEFINE(HAVE___GSS_USEROK,,
 						Define if you have __gss_userok())
@@ -1647,7 +1679,7 @@
 
 				if test x$want_gssapi_plugin != xyes; then
 				  AUTH_LIBS="$AUTH_LIBS $KRB5_LIBS"
-				  AUTH_CFLAGS="$AUTH_CFLAGS `krb5-config --cflags gssapi`"
+				  AUTH_CFLAGS="$AUTH_CFLAGS $KRB5_CFLAGS"
 				  AC_DEFINE(BUILTIN_GSSAPI,, GSSAPI support is built in)
 				else
 				  have_gssapi_plugin=yes
@@ -1658,10 +1690,6 @@
 			  fi
 			fi
 			CFLAGS=$old_CFLAGS
-		else
-		  if test $want_gssapi = yes; then
-		    AC_ERROR([Can't build with GSSAPI support: v1.2 library not supported])
-		  fi
 		fi
 	else
 	  if test $want_gssapi = yes; then
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/dovecot-db.conf	Mon Jun 09 05:11:18 2008 +0300
@@ -0,0 +1,11 @@
+# Example DB_CONFIG for Berkeley DB. Typically dict_db_config setting is used
+# to point to this file.
+# http://www.oracle.com/technology/documentation/berkeley-db/db/ref/env/db_config.html
+
+# Maximum number of simultaneous transactions.
+set_tx_max 1000
+
+# http://www.oracle.com/technology/documentation/berkeley-db/db/ref/lock/max.html
+#set_lk_max_locks 1000
+#set_lk_max_lockers 1000
+#set_lk_max_objects 1000
--- a/dovecot-example.conf	Sat May 17 17:50:54 2008 +0300
+++ b/dovecot-example.conf	Mon Jun 09 05:11:18 2008 +0300
@@ -245,7 +245,7 @@
 
    # There can be only one INBOX, and this setting defines which namespace
    # has it.
-   #inbox = yes
+   #inbox = no
 
    # If namespace is hidden, it's not advertised to clients via NAMESPACE
    # extension. You'll most likely also want to set list=no. This is mostly
@@ -270,7 +270,7 @@
 #mail_gid =
 
 # Group to enable temporarily for privileged operations. Currently this is
-# used only for creating mbox dotlock files when creation fails for INBOX.
+# used only with INBOX when either its initial creation or dotlocking fails.
 # Typically this is set to "mail" to give access to /var/mail.
 #mail_privileged_group =
 
@@ -380,7 +380,8 @@
 # specific users in user database by giving /./ in user's home directory
 # (eg. /home/./user chroots into /home). Note that usually there is no real
 # need to do chrooting, Dovecot doesn't allow users to access files outside
-# their mail directory anyway. <doc/wiki/Chrooting.txt>
+# their mail directory anyway. If your home directories are prefixed with
+# the chroot directory, append "/." to mail_chroot. <doc/wiki/Chrooting.txt>
 #mail_chroot = 
 
 ##
@@ -1055,6 +1056,10 @@
   #quota = mysql:/etc/dovecot-dict-quota.conf 
 }
 
+# Path to Berkeley DB's configuration file. See doc/dovecot-db.conf for an
+# example.
+#dict_db_config = 
+
 ##
 ## Plugin settings
 ##
--- a/src/auth/auth-master-listener.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/auth/auth-master-listener.c	Mon Jun 09 05:11:18 2008 +0300
@@ -40,16 +40,16 @@
 }
 
 static void
-auth_master_listener_socket_free(struct auth_master_listener_socket *socket)
+auth_master_listener_socket_free(struct auth_master_listener_socket *s)
 {
-	if (socket->path != NULL) {
-		(void)unlink(socket->path);
-		i_free(socket->path);
+	if (s->path != NULL) {
+		(void)unlink(s->path);
+		i_free(s->path);
 	}
 
-	io_remove(&socket->io);
-	net_disconnect(socket->fd);
-	i_free(socket);
+	io_remove(&s->io);
+	net_disconnect(s->fd);
+	i_free(s);
 }
 
 void auth_master_listener_destroy(struct auth_master_listener *listener)
--- a/src/auth/auth-request.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/auth/auth-request.c	Mon Jun 09 05:11:18 2008 +0300
@@ -870,11 +870,9 @@
 
 static int is_ip_in_network(const char *network, const struct ip_addr *ip)
 {
-	const uint32_t *ip1, *ip2;
 	struct ip_addr src_ip, net_ip;
 	const char *p;
-	unsigned int max_bits, bits, pos, i;
-	uint32_t mask;
+	unsigned int max_bits, bits;
 
 	if (net_ipv6_mapped_ipv4_convert(ip, &src_ip) == 0)
 		ip = &src_ip;
@@ -895,44 +893,7 @@
 	if (net_addr2ip(network, &net_ip) < 0)
 		return -1;
 
-	if (IPADDR_IS_V4(ip) != IPADDR_IS_V4(&net_ip)) {
-		/* one is IPv6 and one is IPv4 */
-		return 0;
-	}
-	i_assert(IPADDR_IS_V6(ip) == IPADDR_IS_V6(&net_ip));
-
-	if (IPADDR_IS_V4(ip)) {
-		ip1 = &ip->u.ip4.s_addr;
-		ip2 = &net_ip.u.ip4.s_addr;
-	} else {
-#ifdef HAVE_IPV6
-		ip1 = (const void *)&ip->u.ip6;
-		ip2 = (const void *)&net_ip.u.ip6;
-#else
-		/* shouldn't get here */
-		return -1;
-#endif
-	}
-
-	/* check first the full 32bit ints */
-	for (pos = 0, i = 0; pos + 32 <= bits; pos += 32, i++) {
-		if (ip1[i] != ip2[i])
-			return 0;
-	}
-
-	/* check the last full bytes */
-	for (mask = 0xff; pos + 8 <= bits; pos += 8, mask <<= 8) {
-		if ((ip1[i] & mask) != (ip2[i] & mask))
-			return 0;
-	}
-
-	/* check the last bits, they're reversed in bytes */
-	bits -= pos;
-	for (mask = 0x80 << (pos % 32); bits > 0; bits--, mask >>= 1) {
-		if ((ip1[i] & mask) != (ip2[i] & mask))
-			return 0;
-	}
-	return 1;
+	return net_is_in_network(ip, &net_ip, bits);
 }
 
 static void auth_request_validate_networks(struct auth_request *request,
--- a/src/auth/auth-stream.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/auth/auth-stream.c	Mon Jun 09 05:11:18 2008 +0300
@@ -15,7 +15,7 @@
 	struct auth_stream_reply *reply;
 
 	reply = p_new(pool, struct auth_stream_reply, 1);
-	reply->str = str_new(pool, 256);
+	reply->str = str_new(pool, 128);
 	return reply;
 }
 
--- a/src/auth/db-ldap.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/auth/db-ldap.c	Mon Jun 09 05:11:18 2008 +0300
@@ -979,8 +979,15 @@
 	ctx->attr_map = attr_map;
 
 	static_data = hash_lookup(attr_map, "");
-	if (static_data != NULL)
-		ctx->static_attrs = t_strsplit(static_data, ",");
+	if (static_data != NULL) {
+		const struct var_expand_table *table;
+		string_t *str;
+
+		table = auth_request_get_var_expand_table(auth_request, NULL);
+		str = t_str_new(256);
+		var_expand(str, static_data, table);
+		ctx->static_attrs = t_strsplit(str_c(str), ",");
+	}
 
 	if (auth_request->auth->verbose_debug)
 		ctx->debug = t_str_new(256);
@@ -1009,6 +1016,7 @@
 db_ldap_result_change_attr(struct db_ldap_result_iterate_context *ctx)
 {
 	ctx->name = hash_lookup(ctx->attr_map, ctx->attr);
+	ctx->template = NULL;
 
 	if (ctx->debug != NULL) {
 		str_printfa(ctx->debug, " %s(%s)=", ctx->attr,
@@ -1095,6 +1103,9 @@
 			ctx->name = t_strdup_until(*ctx->static_attrs, p);
 			ctx->value = p + 1;
 		}
+		/* make _next_all() return correct values */
+		ctx->template = "";
+		ctx->val_1_arr[0] = ctx->value;
 		ctx->static_attrs++;
 		return TRUE;
 	}
--- a/src/auth/db-passwd-file.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/auth/db-passwd-file.c	Mon Jun 09 05:11:18 2008 +0300
@@ -178,7 +178,7 @@
 	pw->stamp = st.st_mtime;
 	pw->size = st.st_size;
 
-	pw->pool = pool_alloconly_create("passwd_file", 10240);;
+	pw->pool = pool_alloconly_create(MEMPOOL_GROWING"passwd_file", 10240);
 	pw->users = hash_create(default_pool, pw->pool, 100,
 				str_hash, (hash_cmp_callback_t *)strcmp);
 
@@ -385,15 +385,12 @@
 	struct passwd_file *pw;
 	struct passwd_user *pu;
 	const struct var_expand_table *table;
-	string_t *username;
+	string_t *username, *dest;
 	const char *path;
 
 	if (!db->vars)
 		pw = db->default_file;
 	else {
-		const struct var_expand_table *table;
-		string_t *dest;
-
 		table = auth_request_get_var_expand_table(request, path_fix);
 		dest = t_str_new(256);
 		var_expand(dest, db->path, table);
--- a/src/auth/mech-gssapi.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/auth/mech-gssapi.c	Mon Jun 09 05:11:18 2008 +0300
@@ -23,12 +23,25 @@
 
 #ifdef HAVE_GSSAPI
 
+#ifndef HAVE___GSS_USEROK
+#  define USE_KRB5_USEROK
+#  include <krb5.h>
+#endif
+
 #ifdef HAVE_GSSAPI_GSSAPI_H
 #  include <gssapi/gssapi.h>
 #elif defined (HAVE_GSSAPI_H)
 #  include <gssapi.h>
 #endif
 
+#ifdef HAVE_GSSAPI_GSSAPI_KRB5_H
+#  include <gssapi/gssapi_krb5.h>
+#elif defined (HAVE_GSSAPI_KRB5_H)
+#  include <gssapi_krb5.h>
+#else
+#  undef USE_KRB5_USEROK
+#endif
+
 #ifdef HAVE_GSSAPI_GSSAPI_EXT_H
 #  include <gssapi/gssapi_ext.h>
 #endif
@@ -156,6 +169,7 @@
 	return major_status;
 }
 
+#ifndef HAVE___GSS_USEROK
 static gss_name_t
 import_name(struct auth_request *request, void *str, size_t len)
 {
@@ -177,6 +191,7 @@
 
 	return name;
 }
+#endif
 
 static void gssapi_sec_context(struct gssapi_auth_request *request,
 			       gss_buffer_desc inbuf)
@@ -271,6 +286,61 @@
 	request->sasl_gssapi_state = GSS_STATE_UNWRAP;
 }
 
+#ifdef USE_KRB5_USEROK
+static bool gssapi_krb5_userok(struct gssapi_auth_request *request)
+{
+	krb5_context ctx;
+	krb5_principal princ;
+	krb5_error_code krb5_err;
+	OM_uint32 major_status, minor_status;
+	gss_buffer_desc princ_name;
+	gss_OID name_type;
+	const char *princ_display_name;
+	bool ret = FALSE;
+
+	/* Parse out the principal's username */
+	major_status = gss_display_name(&minor_status, request->authn_name,
+					&princ_name, &name_type);
+	if (major_status != GSS_S_COMPLETE) {
+		auth_request_log_gss_error(&request->auth_request, major_status,
+					   GSS_C_GSS_CODE,
+					   "gssapi_krb5_userok");
+		return FALSE;
+	}
+	if (name_type != GSS_KRB5_NT_PRINCIPAL_NAME) {
+		auth_request_log_error(&request->auth_request, "gssapi",
+				       "OID not kerberos principal name");
+		return FALSE;
+	}
+	princ_display_name = t_strndup(princ_name.value, princ_name.length);
+	gss_release_buffer(&minor_status, &princ_name);
+
+	/* Init a krb5 context and parse the principal username */
+	krb5_err = krb5_init_context(&ctx);
+	if (krb5_err != 0) {
+		auth_request_log_error(&request->auth_request, "gssapi",
+			"krb5_init_context() failed: %d", (int)krb5_err);
+		return FALSE;
+	}
+	krb5_err = krb5_parse_name(ctx, princ_display_name, &princ);
+	if (krb5_err != 0) {
+		/* writing the error string would be better, but we probably
+		   rarely get here and there doesn't seem to be a standard
+		   way of getting it */
+		auth_request_log_error(&request->auth_request, "gssapi",
+				       "krb5_parse_name() failed: %d",
+				       (int)krb5_err);
+	} else {
+		/* See if the principal is authorized to act as the
+		   specified user */
+		ret = krb5_kuserok(ctx, princ, request->auth_request.user);
+		krb5_free_principal(ctx, princ);
+	}
+	krb5_free_context(ctx);
+	return ret;
+}
+#endif
+
 static void gssapi_unwrap(struct gssapi_auth_request *request,
 			  gss_buffer_desc inbuf)
 {
@@ -334,22 +404,26 @@
 		auth_request_fail(&request->auth_request);
 		return;
 	}
+
+	request->auth_request.user =
+		p_strndup(request->auth_request.pool,
+			  (unsigned char *)outbuf.value + 4,
+			  outbuf.length - 4);
+
 	major_status = gss_compare_name(&minor_status,
 					request->authn_name,
 					request->authz_name,
 					&equal_authn_authz);
+#ifdef USE_KRB5_USEROK
+	if (equal_authn_authz == 0)
+		equal_authn_authz = gssapi_krb5_userok(request);
+#endif
 	if (equal_authn_authz == 0) {
 		auth_request_log_error(&request->auth_request, "gssapi",
 			"authn_name and authz_name differ: not supported");
 		auth_request_fail(&request->auth_request);
 		return;
 	}
-
-	request->auth_request.user =
-		p_strndup(request->auth_request.pool,
-			  (unsigned char *)outbuf.value + 4,
-			  outbuf.length - 4);
-
 #endif
 	auth_request_success(&request->auth_request, NULL, 0);
 }
--- a/src/auth/passdb.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/auth/passdb.c	Mon Jun 09 05:11:18 2008 +0300
@@ -78,9 +78,15 @@
 
 	if (!password_scheme_is_alias(input_scheme, wanted_scheme)) {
 		if (!password_scheme_is_alias(input_scheme, "PLAIN")) {
-			auth_request_log_info(auth_request, "password",
+			const char *error = t_strdup_printf(
 				"Requested %s scheme, but we have only %s",
 				wanted_scheme, input_scheme);
+			if (auth_request->auth->verbose_debug_passwords) {
+				error = t_strdup_printf("%s (input: %s)",
+							error, input);
+			}
+			auth_request_log_info(auth_request, "password",
+					      "%s", error);
 			return FALSE;
 		}
 
--- a/src/auth/userdb-nss.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/auth/userdb-nss.c	Mon Jun 09 05:11:18 2008 +0300
@@ -130,13 +130,13 @@
 static void userdb_nss_deinit(struct userdb_module *_module)
 {
 	struct nss_userdb_module *module = (struct nss_userdb_module *)_module;
-	void (*endpwent)(void);
+	void (*mod_endpwent)(void);
+	const char *symbol;
 
-	endpwent = module_get_symbol(&module->nss_module,
-				     t_strdup_printf("_nss_%s_endpwent",
-						     module->nss_module.name));
-	if (endpwent != NULL)
-		endpwent();
+	symbol = t_strdup_printf("_nss_%s_endpwent", module->nss_module.name);
+	mod_endpwent = module_get_symbol(&module->nss_module, symbol);
+	if (mod_endpwent != NULL)
+		mod_endpwent();
 }
 
 struct userdb_module_interface userdb_nss = {
--- a/src/deliver/auth-client.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/deliver/auth-client.c	Mon Jun 09 05:11:18 2008 +0300
@@ -95,8 +95,10 @@
 	const char *const *tmp, *extra_groups;
 	uid_t uid = 0;
 	gid_t gid = 0;
-	const char *chroot = getenv("MAIL_CHROOT");
+	const char *chroot_dir = getenv("MAIL_CHROOT");
+	const char *home_dir = NULL;
 	bool debug = getenv("DEBUG") != NULL;
+	unsigned int len;
 
 	for (tmp = t_strsplit(args, "\t"); *tmp != NULL; tmp++) {
 		if (debug)
@@ -110,10 +112,6 @@
 					conn->user);
 				return_value = EX_TEMPFAIL;
 			}
-			if (conn->euid != uid) {
-				env_put(t_strconcat("RESTRICT_SETUID=",
-						    dec2str(uid), NULL));
-			}
 		} else if (strncmp(*tmp, "gid=", 4) == 0) {
 			gid = strtoul(*tmp + 4, NULL, 10);
 
@@ -122,24 +120,19 @@
 					conn->user);
 				return_value = EX_TEMPFAIL;
 			}
-
-			if (conn->euid == 0 || getegid() != gid) {
-				env_put(t_strconcat("RESTRICT_SETGID=",
-						    *tmp + 4, NULL));
-			}
 		} else if (strncmp(*tmp, "chroot=", 7) == 0) {
-			chroot = *tmp + 7;
+			chroot_dir = *tmp + 7;
 		} else {
 			char *field = i_strdup(*tmp);
 
 			if (strncmp(field, "home=", 5) == 0)
-				env_put(t_strconcat("HOME=", field + 5, NULL));
+				home_dir = field + 5;
 
 			array_append(conn->extra_fields, &field, 1);
 		}
 	}
 
-	if (uid == 0 && getenv("MAIL_UID")) {
+	if (uid == 0 && getenv("MAIL_UID") != NULL) {
 		if (!parse_uid(getenv("MAIL_UID"), &uid) || uid == 0) {
 			i_error("mail_uid setting is invalid");
 			return_value = EX_TEMPFAIL;
@@ -151,7 +144,7 @@
 		return_value = EX_TEMPFAIL;
 		return;
 	}
-	if (gid == 0 && getenv("MAIL_GID")) {
+	if (gid == 0 && getenv("MAIL_GID") != NULL) {
 		if (!parse_gid(getenv("MAIL_GID"), &gid) || gid == 0) {
 			i_error("mail_gid setting is invalid");
 			return_value = EX_TEMPFAIL;
@@ -164,8 +157,23 @@
 		return;
 	}
 
-	if (chroot != NULL)
-		env_put(t_strconcat("RESTRICT_CHROOT=", chroot, NULL));
+	if (conn->euid != uid)
+		env_put(t_strconcat("RESTRICT_SETUID=", dec2str(uid), NULL));
+	if (conn->euid == 0 || getegid() != gid)
+		env_put(t_strconcat("RESTRICT_SETGID=", dec2str(gid), NULL));
+
+	if (chroot_dir != NULL) {
+		len = strlen(chroot_dir);
+		if (len > 2 && strcmp(chroot_dir + len - 2, "/.") == 0 &&
+		    home_dir != NULL &&
+		    strncmp(home_dir, chroot_dir, len - 2) == 0) {
+			/* strip chroot dir from home dir */
+			home_dir += len - 2;
+		}
+		env_put(t_strconcat("RESTRICT_CHROOT=", chroot_dir, NULL));
+	}
+	if (home_dir != NULL)
+		env_put(t_strconcat("HOME=", home_dir, NULL));
 
 	extra_groups = getenv("MAIL_EXTRA_GROUPS");
 	if (extra_groups != NULL) {
--- a/src/deliver/deliver.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/deliver/deliver.c	Mon Jun 09 05:11:18 2008 +0300
@@ -633,7 +633,7 @@
 static void print_help(void)
 {
 	printf(
-"Usage: deliver [-c <config file>] [-a <address>] [-d <username>]\n"
+"Usage: deliver [-c <config file>] [-a <address>] [-d <username>] [-p <path>]\n"
 "               [-f <envelope sender>] [-m <mailbox>] [-n] [-e] [-k]\n");
 }
 
@@ -711,7 +711,7 @@
 	const char *config_path = DEFAULT_CONFIG_FILE;
 	const char *mailbox = "INBOX";
 	const char *auth_socket;
-	const char *home, *destaddr, *user, *value, *error;
+	const char *home, *destaddr, *user, *value, *errstr, *path;
 	ARRAY_TYPE(string) extra_fields;
 	struct mail_namespace *ns, *raw_ns;
 	struct mail_storage *storage;
@@ -743,7 +743,7 @@
         lib_signals_ignore(SIGXFSZ, TRUE);
 #endif
 
-	destaddr = user = NULL;
+	destaddr = user = path = NULL;
 	for (i = 1; i < argc; i++) {
 		if (strcmp(argv[i], "-a") == 0) {
 			/* destination address */
@@ -758,6 +758,12 @@
 				i_fatal_status(EX_USAGE, "Missing -d argument");
 			user = argv[i];
 			user_auth = TRUE;
+		} else if (strcmp(argv[i], "-p") == 0) {
+			/* input path */
+			i++;
+			if (i == argc)
+				i_fatal_status(EX_USAGE, "Missing -p argument");
+			path = argv[i];
 		} else if (strcmp(argv[i], "-e") == 0) {
 			stderr_rejection = TRUE;
 		} else if (strcmp(argv[i], "-c") == 0) {
@@ -938,11 +944,19 @@
 	raw_ns = mail_namespaces_init_empty(namespace_pool);
 	raw_ns->flags |= NAMESPACE_FLAG_INTERNAL;
 	if (mail_storage_create(raw_ns, "raw", "/tmp", user,
-				0, FILE_LOCK_METHOD_FCNTL, &error) < 0)
-		i_fatal("Couldn't create internal raw storage: %s", error);
-	input = create_raw_stream(0, &mtime);
-	box = mailbox_open(raw_ns->storage, "Dovecot Delivery Mail", input,
-			   MAILBOX_OPEN_NO_INDEX_FILES);
+				MAIL_STORAGE_FLAG_FULL_FS_ACCESS,
+				FILE_LOCK_METHOD_FCNTL, &errstr) < 0)
+		i_fatal("Couldn't create internal raw storage: %s", errstr);
+	if (path == NULL) {
+		input = create_raw_stream(0, &mtime);
+		box = mailbox_open(raw_ns->storage, "Dovecot Delivery Mail",
+				   input, MAILBOX_OPEN_NO_INDEX_FILES);
+		i_stream_unref(&input);
+	} else {
+		mtime = (time_t)-1;
+		box = mailbox_open(raw_ns->storage, path, NULL,
+				   MAILBOX_OPEN_NO_INDEX_FILES);
+	}
 	if (box == NULL)
 		i_fatal("Can't open delivery mail as raw");
 	if (mailbox_sync(box, 0, 0, NULL) < 0) {
@@ -978,20 +992,17 @@
 
 	if (ret < 0 && !tried_default_save) {
 		/* plugins didn't handle this. save into the default mailbox. */
-		i_stream_seek(input, 0);
 		ret = deliver_save(ns, &storage, mailbox, mail, 0, NULL);
 	}
 	if (ret < 0 && strcasecmp(mailbox, "INBOX") != 0) {
 		/* still didn't work. try once more to save it
 		   to INBOX. */
-		i_stream_seek(input, 0);
 		ret = deliver_save(ns, &storage, "INBOX", mail, 0, NULL);
 	}
 
 	if (ret < 0 ) {
 		const char *error_string;
 		enum mail_error error;
-		int ret;
 
 		if (storage == NULL) {
 			/* This shouldn't happen */
@@ -1000,6 +1011,13 @@
 		}
 
 		error_string = mail_storage_get_last_error(storage, &error);
+
+		if (stderr_rejection) {
+			/* write to stderr also for tempfails so that MTA
+			   can log the reason if it wants to. */
+			fprintf(stderr, "%s\n", error_string);
+		}
+
 		if (error != MAIL_ERROR_NOSPACE ||
 		    getenv("QUOTA_FULL_TEMPFAIL") != NULL) {
 			/* Saving to INBOX should always work unless
@@ -1008,20 +1026,17 @@
 			return EX_TEMPFAIL;
 		}
 
+		/* we'll have to reply with permanent failure */
 		deliver_log(mail, "rejected: %s",
 			    str_sanitize(error_string, 512));
 
-		/* we'll have to reply with permanent failure */
-		if (stderr_rejection) {
-			fprintf(stderr, "%s\n", error_string);
+		if (stderr_rejection)
 			return EX_NOPERM;
-		}
 		ret = mail_send_rejection(mail, user, error_string);
 		if (ret != 0)
 			return ret < 0 ? EX_TEMPFAIL : ret;
 		/* ok, rejection sent */
 	}
-	i_stream_unref(&input);
 	i_free(explicit_envelope_sender);
 
 	mail_free(&mail);
--- a/src/deliver/duplicate.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/deliver/duplicate.c	Mon Jun 09 05:11:18 2008 +0300
@@ -245,7 +245,7 @@
 }
 
 void duplicate_mark(const void *id, size_t id_size,
-                    const char *user, time_t time)
+                    const char *user, time_t timestamp)
 {
 	struct duplicate *d;
 	void *new_id;
@@ -260,7 +260,7 @@
 	d->id = new_id;
 	d->id_size = id_size;
 	d->user = p_strdup(duplicate_file->pool, user);
-	d->time = time;
+	d->time = timestamp;
 
 	duplicate_file->changed = TRUE;
 	hash_insert(duplicate_file->hash, d, d);
--- a/src/deliver/duplicate.h	Sat May 17 17:50:54 2008 +0300
+++ b/src/deliver/duplicate.h	Mon Jun 09 05:11:18 2008 +0300
@@ -5,7 +5,7 @@
 
 int duplicate_check(const void *id, size_t id_size, const char *user);
 void duplicate_mark(const void *id, size_t id_size,
-                    const char *user, time_t time);
+                    const char *user, time_t timestamp);
 
 void duplicate_flush(void);
 
--- a/src/imap-login/client-authenticate.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/imap-login/client-authenticate.c	Mon Jun 09 05:11:18 2008 +0300
@@ -156,7 +156,7 @@
 		}
 		client_send_tagline(client, str_c(reply));
 		if (!nologin) {
-			client_destroy(client, "Login with referral");
+			client_destroy_success(client, "Login with referral");
 			return TRUE;
 		}
 	} else if (nologin || proxy_self) {
@@ -209,7 +209,7 @@
 		}
 
 		client_send_tagline(client, "OK Logged in.");
-		client_destroy(client, "Login");
+		client_destroy_success(client, "Login");
 		break;
 	case SASL_SERVER_REPLY_AUTH_FAILED:
 	case SASL_SERVER_REPLY_CLIENT_ERROR:
@@ -234,7 +234,9 @@
 		else {
 			client_send_tagline(client,
 					    t_strconcat("NO ", data, NULL));
-			client_destroy(client, data);
+			/* authentication itself succeeded, we just hit some
+			   internal failure. */
+			client_destroy_success(client, data);
 		}
 		break;
 	case SASL_SERVER_REPLY_CONTINUE:
--- a/src/imap-login/client.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/imap-login/client.c	Mon Jun 09 05:11:18 2008 +0300
@@ -211,9 +211,7 @@
 		client_destroy(client, "Aborted login "
 			"(tried to use disabled plaintext authentication)");
 	} else {
-		client_destroy(client, t_strdup_printf(
-			"Aborted login (%u authentication attempts)",
-			client->common.auth_attempts));
+		client_destroy(client, "Aborted login");
 	}
 	return 1;
 }
@@ -284,8 +282,8 @@
 		if (fatal) {
 			client_send_line(client, t_strconcat("* BYE ",
 							     msg, NULL));
-			client_destroy(client, t_strconcat("Disconnected: ",
-							   msg, NULL));
+			client_destroy(client,
+				t_strconcat("Disconnected: ", msg, NULL));
 			return FALSE;
 		}
 
@@ -311,8 +309,8 @@
 		if (++client->bad_counter >= CLIENT_MAX_BAD_COMMANDS) {
 			client_send_line(client,
 				"* BYE Too many invalid IMAP commands.");
-			client_destroy(client, "Disconnected: "
-				       "Too many invalid commands");
+			client_destroy(client,
+				"Disconnected: Too many invalid commands");
 			return FALSE;
 		}  
 		client_send_tagline(client,
@@ -486,6 +484,10 @@
 		return;
 	client->destroyed = TRUE;
 
+	if (!client->login_success && reason != NULL) {
+		reason = t_strdup_printf("%s (auth failed, %u attempts)",
+					 reason, client->common.auth_attempts);
+	}
 	if (reason != NULL)
 		client_syslog(&client->common, reason);
 
@@ -543,6 +545,12 @@
 	main_unref();
 }
 
+void client_destroy_success(struct imap_client *client, const char *reason)
+{
+	client->login_success = TRUE;
+	client_destroy(client, reason);
+}
+
 void client_destroy_internal_failure(struct imap_client *client)
 {
 	client_send_line(client, "* BYE Internal login failure. "
--- a/src/imap-login/client.h	Sat May 17 17:50:54 2008 +0300
+++ b/src/imap-login/client.h	Mon Jun 09 05:11:18 2008 +0300
@@ -24,6 +24,7 @@
 
 	const char *cmd_tag, *cmd_name;
 
+	unsigned int login_success:1;
 	unsigned int cmd_finished:1;
 	unsigned int proxy_login_sent:1;
 	unsigned int skip_line:1;
@@ -33,6 +34,7 @@
 };
 
 void client_destroy(struct imap_client *client, const char *reason);
+void client_destroy_success(struct imap_client *client, const char *reason);
 void client_destroy_internal_failure(struct imap_client *client);
 
 void client_send_line(struct imap_client *client, const char *line);
--- a/src/imap-login/imap-proxy.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/imap-login/imap-proxy.c	Mon Jun 09 05:11:18 2008 +0300
@@ -46,9 +46,12 @@
 		return 0;
 	} else if (strncmp(line, "P OK ", 5) == 0) {
 		/* Login successful. Send this line to client. */
-		(void)o_stream_send_str(client->output, client->cmd_tag);
-		(void)o_stream_send_str(client->output, line + 1);
-		(void)o_stream_send(client->output, "\r\n", 2);
+		str = t_str_new(128);
+		str_append(str, client->cmd_tag);
+		str_append(str, line + 1);
+		str_append(str, "\r\n");
+		(void)o_stream_send(client->output,
+				    str_data(str), str_len(str));
 
 		msg = t_strdup_printf("proxy(%s): started proxying to %s:%u",
 				      client->common.virtual_user,
@@ -63,7 +66,7 @@
 		client->input = NULL;
 		client->output = NULL;
 		client->common.fd = -1;
-		client_destroy(client, msg);
+		client_destroy_success(client, msg);
 		return -1;
 	} else if (strncmp(line, "P ", 2) == 0) {
 		/* If the backend server isn't Dovecot, the error message may
@@ -117,7 +120,7 @@
 
 		/* failed for some reason, probably server disconnected */
 		client_send_line(client, "* BYE Temporary login failure.");
-		client_destroy(client, NULL);
+		client_destroy_success(client, NULL);
 		return;
 	}
 
@@ -132,7 +135,7 @@
 		return;
 	case -1:
 		/* disconnected */
-		client_destroy(client, "Proxy: Remote disconnected");
+		client_destroy_success(client, "Proxy: Remote disconnected");
 		return;
 	}
 
--- a/src/imap/client.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/imap/client.c	Mon Jun 09 05:11:18 2008 +0300
@@ -66,17 +66,36 @@
 	return client;
 }
 
-void client_command_cancel(struct client_command_context *cmd)
+void client_command_cancel(struct client_command_context **_cmd)
 {
+	struct client_command_context *cmd = *_cmd;
 	bool cmd_ret;
 
-	cmd->cancel = TRUE;
-	cmd_ret = cmd->func == NULL ? TRUE : cmd->func(cmd);
+	switch (cmd->state) {
+	case CLIENT_COMMAND_STATE_WAIT_INPUT:
+		/* a bit kludgy check: cancel command only if it has context
+		   set. currently only append command matches this check. all
+		   other commands haven't even started the processing yet. */
+		if (cmd->context == NULL)
+			break;
+		/* fall through */
+	case CLIENT_COMMAND_STATE_WAIT_OUTPUT:
+		cmd->cancel = TRUE;
+		break;
+	case CLIENT_COMMAND_STATE_WAIT_UNAMBIGUITY:
+	case CLIENT_COMMAND_STATE_WAIT_SYNC:
+		/* commands haven't started yet */
+		break;
+	case CLIENT_COMMAND_STATE_DONE:
+		i_unreached();
+	}
+
+	cmd_ret = !cmd->cancel || cmd->func == NULL ? TRUE : cmd->func(cmd);
 	if (!cmd_ret && cmd->state != CLIENT_COMMAND_STATE_DONE) {
 		if (cmd->client->output->closed)
 			i_panic("command didn't cancel itself: %s", cmd->name);
 	} else {
-		client_command_free(cmd);
+		client_command_free(_cmd);
 	}
 }
 
@@ -112,6 +131,7 @@
 
 void client_destroy(struct client *client, const char *reason)
 {
+	struct client_command_context *cmd;
 	i_assert(!client->destroyed);
 	client->destroyed = TRUE;
 
@@ -127,11 +147,17 @@
 
 	/* finish off all the queued commands. */
 	if (client->output_lock != NULL)
-		client_command_cancel(client->output_lock);
+		client_command_cancel(&client->output_lock);
+	while (client->command_queue != NULL) {
+		cmd = client->command_queue;
+		client_command_cancel(&cmd);
+	}
+	/* handle the input_lock command last. it might have been waiting on
+	   other queued commands (although we probably should just drop the
+	   command at that point since it hasn't started running. but this may
+	   change in future). */
 	if (client->input_lock != NULL)
-		client_command_cancel(client->input_lock);
-	while (client->command_queue != NULL)
-		client_command_cancel(client->command_queue);
+		client_command_cancel(&client->input_lock);
 
 	if (client->mailbox != NULL) {
 		client_search_updates_free(client);
@@ -354,7 +380,12 @@
 	enum command_flags flags;
 	bool broken_client = FALSE;
 
-	if ((cmd->cmd_flags & COMMAND_FLAG_USES_SEQS) != 0) {
+	if ((cmd->cmd_flags & COMMAND_FLAG_BREAKS_MAILBOX) ==
+	    COMMAND_FLAG_BREAKS_MAILBOX) {
+		/* there must be no other command running that uses the
+		   selected mailbox */
+		flags = COMMAND_FLAG_USES_MAILBOX;
+	} else if ((cmd->cmd_flags & COMMAND_FLAG_USES_SEQS) != 0) {
 		/* no existing command must be breaking sequences */
 		flags = COMMAND_FLAG_BREAKS_SEQS;
 		broken_client = TRUE;
@@ -409,10 +440,13 @@
 	return cmd;
 }
 
-void client_command_free(struct client_command_context *cmd)
+void client_command_free(struct client_command_context **_cmd)
 {
+	struct client_command_context *cmd = *_cmd;
 	struct client *client = cmd->client;
 
+	*_cmd = NULL;
+
 	/* reset input idle time because command output might have taken a
 	   long time and we don't want to disconnect client immediately then */
 	client->last_input = ioloop_time;
@@ -556,7 +590,7 @@
 		/* command is being executed - continue it */
 		if (cmd->func(cmd) || cmd->state == CLIENT_COMMAND_STATE_DONE) {
 			/* command execution was finished */
-			client_command_free(cmd);
+			client_command_free(&cmd);
 			client_add_missing_io(client);
 			return TRUE;
 		}
@@ -598,7 +632,7 @@
 		/* unknown command */
 		client_send_command_error(cmd, "Unknown command.");
 		cmd->param_error = TRUE;
-		client_command_free(cmd);
+		client_command_free(&cmd);
 		return TRUE;
 	} else {
 		i_assert(!client->disconnected);
@@ -709,7 +743,7 @@
 			client_command_new(client);
 		cmd->param_error = TRUE;
 		client_send_command_error(cmd, "Too long argument.");
-		client_command_free(cmd);
+		client_command_free(&cmd);
 	}
 	o_stream_uncork(output);
 	o_stream_unref(&output);
@@ -726,7 +760,7 @@
 		(void)client_handle_unfinished_cmd(cmd);
 	else {
 		/* command execution was finished */
-		client_command_free(cmd);
+		client_command_free(&cmd);
 	}
 }
 
--- a/src/imap/client.h	Sat May 17 17:50:54 2008 +0300
+++ b/src/imap/client.h	Mon Jun 09 05:11:18 2008 +0300
@@ -158,8 +158,8 @@
 void clients_init(void);
 void clients_deinit(void);
 
-void client_command_cancel(struct client_command_context *cmd);
-void client_command_free(struct client_command_context *cmd);
+void client_command_cancel(struct client_command_context **cmd);
+void client_command_free(struct client_command_context **cmd);
 
 bool client_handle_unfinished_cmd(struct client_command_context *cmd);
 void client_continue_pending_input(struct client **_client);
--- a/src/imap/cmd-append.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/imap/cmd-append.c	Mon Jun 09 05:11:18 2008 +0300
@@ -51,7 +51,7 @@
 		cmd_append_finish(cmd->context);
 		/* Reset command so that client_destroy() doesn't try to call
 		   cmd_append_continue_message() anymore. */
-		client_command_free(cmd);
+		client_command_free(&cmd);
 		client_destroy(client, "Disconnected in APPEND");
 		return;
 	case -2:
@@ -69,7 +69,7 @@
 
 		client_send_command_error(cmd, "Too long argument.");
 		cmd->param_error = TRUE;
-		client_command_free(cmd);
+		client_command_free(&cmd);
 		return;
 	}
 
@@ -79,7 +79,7 @@
 	if (!finished && cmd->state != CLIENT_COMMAND_STATE_DONE)
 		(void)client_handle_unfinished_cmd(cmd);
 	else
-		client_command_free(cmd);
+		client_command_free(&cmd);
 	(void)cmd_sync_delayed(client);
 	client_continue_pending_input(&client);
 }
--- a/src/imap/cmd-idle.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/imap/cmd-idle.c	Mon Jun 09 05:11:18 2008 +0300
@@ -54,7 +54,7 @@
 
 	o_stream_uncork(client->output);
 	if (free_cmd)
-		client_command_free(ctx->cmd);
+		client_command_free(&ctx->cmd);
 }
 
 static void idle_client_input(struct cmd_idle_context *ctx)
@@ -102,12 +102,13 @@
 		return;
 	}
 
-	/* Sending this keeps NATs/stateful firewalls alive, and it also
-	   updates client->last_output so we don't ever disconnect the
-	   client. Sending this output should kill dead connections and there
-	   are several clients that really want to IDLE forever (Outlook
-	   especially). */
+	/* Sending this keeps NATs/stateful firewalls alive. Sending this
+	   also catches dead connections. */
 	client_send_line(ctx->client, "* OK Still here");
+	/* Make sure idling connections don't get disconnected. There are
+	   several clients that really want to IDLE forever and there's not
+	   much harm in letting them do so. */
+	timeout_reset(ctx->client->to_idle);
 }
 
 static void idle_sync_now(struct mailbox *box, struct cmd_idle_context *ctx)
--- a/src/imap/cmd-list.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/imap/cmd-list.c	Mon Jun 09 05:11:18 2008 +0300
@@ -85,7 +85,7 @@
 		return;
 
 	if ((flags & MAILBOX_CHILD_SUBSCRIBED) != 0)
-		str_append(str, " (\"CHILDINFO\" (\"SUBSCRIBED\"))");
+		str_append(str, " (CHILDINFO (\"SUBSCRIBED\"))");
 }
 
 static bool
@@ -347,14 +347,14 @@
 		imap_quote_append_string(str, name, FALSE);
 		mailbox_childinfo2str(ctx, str, flags);
 
+		ret = client_send_line(ctx->cmd->client, str_c(str));
 		if (ctx->status_items != 0 &&
 		    (flags & (MAILBOX_NONEXISTENT | MAILBOX_NOSELECT)) == 0) {
 			T_BEGIN {
 				list_send_status(ctx, name);
 			} T_END;
 		}
-
-		if (client_send_line(ctx->cmd->client, str_c(str)) == 0) {
+		if (ret == 0) {
 			/* buffer is full, continue later */
 			return 0;
 		}
@@ -812,7 +812,7 @@
 		args += 2;
 	}
 
-	ctx->list_flags = MAILBOX_LIST_ITER_VIRTUAL_NAMES;
+	ctx->list_flags |= MAILBOX_LIST_ITER_VIRTUAL_NAMES;
 	if (lsub) {
 		/* LSUB - we don't care about flags */
 		ctx->list_flags |= MAILBOX_LIST_ITER_SELECT_SUBSCRIBED |
--- a/src/imap/cmd-search.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/imap/cmd-search.c	Mon Jun 09 05:11:18 2008 +0300
@@ -465,7 +465,7 @@
 	if (!finished)
 		(void)client_handle_unfinished_cmd(cmd);
 	else
-		client_command_free(cmd);
+		client_command_free(&cmd);
 	(void)cmd_sync_delayed(client);
 	client_continue_pending_input(&client);
 }
--- a/src/imap/cmd-store.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/imap/cmd-store.c	Mon Jun 09 05:11:18 2008 +0300
@@ -125,7 +125,7 @@
 	ARRAY_TYPE(seq_range) modified_set = ARRAY_INIT;
 	enum mailbox_transaction_flags flags = 0;
 	enum imap_sync_flags imap_sync_flags = 0;
-	const char *tagged_reply;
+	const char *reply, *tagged_reply;
 	string_t *str;
 	int ret;
 
@@ -149,10 +149,21 @@
 	if (!store_parse_args(&ctx, ++args))
 		return TRUE;
 
+	if (mailbox_is_readonly(client->mailbox)) {
+		if (ctx.max_modseq < (uint64_t)-1)
+			reply = "NO CONDSTORE failed: Mailbox is read-only.";
+		else
+			reply = "OK Store ignored with read-only mailbox.";
+		return cmd_sync(cmd, MAILBOX_SYNC_FLAG_FAST |
+				(cmd->uid ? 0 : MAILBOX_SYNC_FLAG_NO_EXPUNGES),
+				0, reply);
+	}
+
 	if (ctx.silent)
 		flags |= MAILBOX_TRANSACTION_FLAG_HIDE;
 	if (ctx.max_modseq < (uint64_t)-1)
 		flags |= MAILBOX_TRANSACTION_FLAG_REFRESH;
+
 	t = mailbox_transaction_begin(client->mailbox, flags);
 	search_ctx = mailbox_search_init(t, search_args, NULL);
 	mail_search_args_unref(&search_args);
--- a/src/imap/cmd-subscribe.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/imap/cmd-subscribe.c	Mon Jun 09 05:11:18 2008 +0300
@@ -28,8 +28,6 @@
 bool cmd_subscribe_full(struct client_command_context *cmd, bool subscribe)
 {
 	struct mail_namespace *ns;
-        struct mail_storage *storage;
-	struct mailbox_list *list;
 	const char *mailbox, *verify_name;
 
 	/* <mailbox> */
@@ -43,11 +41,10 @@
 		client_send_tagline(cmd, "NO Unknown namespace.");
 		return TRUE;
 	}
-	storage = ns->storage;
 
 	if ((client_workarounds & WORKAROUND_TB_EXTRA_MAILBOX_SEP) != 0 &&
 	    *mailbox != '\0' && mailbox[strlen(mailbox)-1] ==
-	    mail_storage_get_hierarchy_sep(storage)) {
+	    mail_storage_get_hierarchy_sep(ns->storage)) {
 		/* verify the validity without the trailing '/' */
 		verify_name = t_strndup(verify_name, strlen(verify_name)-1);
 	}
@@ -61,9 +58,8 @@
 			return TRUE;
 	}
 
-	list = mail_storage_get_list(storage);
-	if (mailbox_list_set_subscribed(list, mailbox, subscribe) < 0)
-		client_send_storage_error(cmd, storage);
+	if (mailbox_list_set_subscribed(ns->list, mailbox, subscribe) < 0)
+		client_send_list_error(cmd, ns->list);
 	else {
 		client_send_tagline(cmd, subscribe ?
 				    "OK Subscribe completed." :
--- a/src/imap/cmd-x-cancel.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/imap/cmd-x-cancel.c	Mon Jun 09 05:11:18 2008 +0300
@@ -16,7 +16,7 @@
 	for (; cancel_cmd != NULL; cancel_cmd = cancel_cmd->next) {
 		if (cancel_cmd->tag != NULL && cancel_cmd != cmd &&
 		    strcmp(cancel_cmd->tag, tag) == 0) {
-			client_command_cancel(cancel_cmd);
+			client_command_cancel(&cancel_cmd);
 			client_send_tagline(cmd, "OK Command cancelled.");
 			return TRUE;
 		}
--- a/src/imap/commands.h	Sat May 17 17:50:54 2008 +0300
+++ b/src/imap/commands.h	Mon Jun 09 05:11:18 2008 +0300
@@ -16,7 +16,11 @@
 	/* Command may reply with EXPUNGE, causing sequences to break */
 	COMMAND_FLAG_BREAKS_SEQS	= 0x02,
 	/* Command changes the mailbox */
-	COMMAND_FLAG_BREAKS_MAILBOX	= 0x04 | COMMAND_FLAG_BREAKS_SEQS
+	COMMAND_FLAG_BREAKS_MAILBOX	= 0x04 | COMMAND_FLAG_BREAKS_SEQS,
+
+	/* Command uses selected mailbox */
+	COMMAND_FLAG_USES_MAILBOX	= COMMAND_FLAG_BREAKS_MAILBOX |
+					  COMMAND_FLAG_USES_SEQS
 };
 
 struct command {
--- a/src/imap/imap-fetch-body.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/imap/imap-fetch-body.c	Mon Jun 09 05:11:18 2008 +0300
@@ -41,7 +41,7 @@
 	struct message_size pos;
 };
 
-static struct partial_cache partial = { 0, 0, 0, 0, { 0, 0, 0 } };
+static struct partial_cache last_partial = { 0, 0, 0, 0, { 0, 0, 0 } };
 
 static bool seek_partial(unsigned int select_counter, unsigned int uid,
 			 struct partial_cache *partial, struct istream *stream,
@@ -224,10 +224,10 @@
 
 	ctx->cur_offset += ret;
 	if (ctx->update_partial) {
-		partial.cr_skipped = ctx->skip_cr != 0;
-		partial.pos.physical_size =
-			ctx->cur_input->v_offset - partial.physical_start;
-		partial.pos.virtual_size += ret;
+		last_partial.cr_skipped = ctx->skip_cr != 0;
+		last_partial.pos.physical_size =
+			ctx->cur_input->v_offset - last_partial.physical_start;
+		last_partial.pos.virtual_size += ret;
 	}
 
 	return ctx->cur_offset == ctx->cur_size;
@@ -312,7 +312,7 @@
 	} else {
 		ctx->skip_cr =
 			seek_partial(ctx->select_counter, ctx->cur_mail->uid,
-				     &partial, ctx->cur_input, body->skip);
+				     &last_partial, ctx->cur_input, body->skip);
 	}
 
 	return fetch_stream(ctx, size);
--- a/src/imap/imap-search.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/imap/imap-search.c	Mon Jun 09 05:11:18 2008 +0300
@@ -42,6 +42,11 @@
 	struct mail_search_args *sargs;
 	const char *error;
 
+	if (args->type == IMAP_ARG_EOL) {
+		client_send_command_error(cmd, "Missing search parameters");
+		return -1;
+	}
+
 	if (mail_search_build_from_imap_args(args, charset,
 					     &sargs, &error) < 0) {
 		client_send_command_error(cmd, error);
--- a/src/imap/imap-sync.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/imap/imap-sync.c	Mon Jun 09 05:11:18 2008 +0300
@@ -168,6 +168,7 @@
 				STATUS_MESSAGES | STATUS_RECENT, &status) < 0 ||
 	    ctx->failed) {
 		mailbox_transaction_rollback(&ctx->t);
+		array_free(&ctx->tmp_keywords);
 		i_free(ctx);
 		return -1;
 	}
@@ -412,7 +413,7 @@
 
 static bool cmd_sync_continue(struct client_command_context *sync_cmd)
 {
-	struct client_command_context *cmd;
+	struct client_command_context *cmd, *prev;
 	struct client *client = sync_cmd->client;
 	struct imap_sync_context *ctx = sync_cmd->context;
 	int ret;
@@ -431,13 +432,19 @@
 	}
 	sync_cmd->context = NULL;
 
-	/* finish all commands that waited for this sync */
-	for (cmd = client->command_queue; cmd != NULL; cmd = cmd->next) {
+	/* Finish all commands that waited for this sync. Go through the queue
+	   backwards, so that tagged replies are sent in the same order as
+	   they were received. This fixes problems with clients that rely on
+	   this (Apple Mail 3.2) */
+	for (cmd = client->command_queue; cmd->next != NULL; cmd = cmd->next) ;
+	for (; cmd != NULL; cmd = prev) {
+		prev = cmd->prev;
+
 		if (cmd->state == CLIENT_COMMAND_STATE_WAIT_SYNC &&
 		    cmd != sync_cmd &&
 		    cmd->sync->counter+1 == client->sync_counter) {
 			if (cmd_finish_sync(cmd))
-				client_command_free(cmd);
+				client_command_free(&cmd);
 		}
 	}
 	return cmd_finish_sync(sync_cmd);
@@ -465,10 +472,9 @@
 			count++;
 		}
 	}
+	i_assert(noexpunges_count == 0 || noexpunges_count == count);
 	if (fast_count != count)
 		*flags_r &= ~MAILBOX_SYNC_FLAG_FAST;
-	if (noexpunges_count != count)
-		*flags_r &= ~MAILBOX_SYNC_FLAG_NO_EXPUNGES;
 
 	i_assert((*flags_r & (MAILBOX_SYNC_AUTO_STOP |
 			      MAILBOX_SYNC_FLAG_FIX_INCONSISTENT)) == 0);
@@ -509,7 +515,7 @@
 		return FALSE;
 	}
 
-	client_command_free(sync_cmd);
+	client_command_free(&sync_cmd);
 	(void)cmd_sync_delayed(client);
 	return TRUE;
 }
@@ -566,16 +572,20 @@
 
 static bool cmd_sync_drop_fast(struct client *client)
 {
-	struct client_command_context *cmd, *next;
+	struct client_command_context *cmd, *prev;
 	bool ret = FALSE;
 
-	for (cmd = client->command_queue; cmd != NULL; cmd = next) {
-		next = cmd->next;
+	if (client->command_queue == NULL)
+		return FALSE;
+
+	for (cmd = client->command_queue; cmd->next != NULL; cmd = cmd->next) ;
+	for (; cmd != NULL; cmd = prev) {
+		prev = cmd->next;
 
 		if (cmd->state == CLIENT_COMMAND_STATE_WAIT_SYNC &&
 		    (cmd->sync->flags & MAILBOX_SYNC_FLAG_FAST) != 0) {
 			if (cmd_finish_sync(cmd)) {
-				client_command_free(cmd);
+				client_command_free(&cmd);
 				ret = TRUE;
 			}
 		}
@@ -585,7 +595,7 @@
 
 bool cmd_sync_delayed(struct client *client)
 {
-	struct client_command_context *cmd;
+	struct client_command_context *cmd, *first_expunge, *first_nonexpunge;
 
 	if (client->output_lock != NULL) {
 		/* wait until we can send output to client */
@@ -599,13 +609,32 @@
 		return cmd_sync_drop_fast(client);
 	}
 
-	/* find a command that we can sync */
+	/* separate syncs that can send expunges from those that can't */
+	first_expunge = first_nonexpunge = NULL;
 	for (cmd = client->command_queue; cmd != NULL; cmd = cmd->next) {
-		if (cmd->state == CLIENT_COMMAND_STATE_WAIT_SYNC) {
-			if (cmd->sync->counter == client->sync_counter)
-				break;
+		if (cmd->sync != NULL &&
+		    cmd->sync->counter == client->sync_counter) {
+			if (cmd->sync->flags & MAILBOX_SYNC_FLAG_NO_EXPUNGES) {
+				if (first_nonexpunge == NULL)
+					first_nonexpunge = cmd;
+			} else {
+				if (first_expunge == NULL)
+					first_expunge = cmd;
+			}
 		}
 	}
+	if (first_expunge != NULL && first_nonexpunge != NULL) {
+		/* sync expunges after nonexpunges */
+		for (cmd = first_expunge; cmd != NULL; cmd = cmd->next) {
+			if (cmd->sync != NULL &&
+			    cmd->sync->counter == client->sync_counter &&
+			    (cmd->sync->flags &
+			     MAILBOX_SYNC_FLAG_NO_EXPUNGES) == 0)
+				cmd->sync->counter++;
+		}
+		first_expunge = NULL;
+	}
+	cmd = first_nonexpunge != NULL ? first_nonexpunge : first_expunge;
 
 	if (cmd == NULL)
 		return cmd_sync_drop_fast(client);
--- a/src/lib-dict/dict-db.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib-dict/dict-db.c	Mon Jun 09 05:11:18 2008 +0300
@@ -406,8 +406,9 @@
 	dict->pdb->del(dict->pdb, ctx->tid, &dkey, 0);
 }
 
-static void db_dict_atomic_inc(struct dict_transaction_context *_ctx,
-			       const char *key, long long diff)
+static void
+db_dict_atomic_inc(struct dict_transaction_context *_ctx ATTR_UNUSED,
+		   const char *key ATTR_UNUSED, long long diff ATTR_UNUSED)
 {
 	/* FIXME */
 }
--- a/src/lib-dict/dict.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib-dict/dict.c	Mon Jun 09 05:11:18 2008 +0300
@@ -57,6 +57,8 @@
 	struct dict *dict;
 	const char *p, *name;
 
+	i_assert(username != NULL);
+
 	p = strchr(uri, ':');
 	if (p == NULL) {
 		i_error("Dictionary URI is missing ':': %s", uri);
--- a/src/lib-imap/imap-date.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib-imap/imap-date.c	Mon Jun 09 05:11:18 2008 +0300
@@ -105,7 +105,7 @@
 	return FALSE;
 }
 
-bool imap_parse_date(const char *str, time_t *time)
+bool imap_parse_date(const char *str, time_t *timestamp_r)
 {
 	struct tm tm;
 
@@ -114,11 +114,12 @@
 		return FALSE;
 
 	tm.tm_isdst = -1;
-	(void)imap_mktime(&tm, time);
+	(void)imap_mktime(&tm, timestamp_r);
 	return TRUE;
 }
 
-bool imap_parse_datetime(const char *str, time_t *time, int *timezone_offset)
+bool imap_parse_datetime(const char *str, time_t *timestamp_r,
+			 int *timezone_offset_r)
 {
 	struct tm tm;
 
@@ -149,22 +150,22 @@
 	str += 3;
 
 	/* timezone */
-	*timezone_offset = parse_timezone(str);
+	*timezone_offset_r = parse_timezone(str);
 
 	tm.tm_isdst = -1;
-	if (imap_mktime(&tm, time))
-		*time -= *timezone_offset * 60;
+	if (imap_mktime(&tm, timestamp_r))
+		*timestamp_r -= *timezone_offset_r * 60;
 	return TRUE;
 }
 
-const char *imap_to_datetime(time_t time)
+const char *imap_to_datetime(time_t timestamp)
 {
 	char *buf;
 	struct tm *tm;
 	int timezone_offset, year;
 
-	tm = localtime(&time);
-	timezone_offset = utc_offset(tm, time);
+	tm = localtime(&timestamp);
+	timezone_offset = utc_offset(tm, timestamp);
 
 	/* @UNSAFE: but faster than t_strdup_printf() call.. */
 	buf = t_malloc(27);
--- a/src/lib-imap/imap-date.h	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib-imap/imap-date.h	Mon Jun 09 05:11:18 2008 +0300
@@ -8,10 +8,11 @@
    If date is too low or too high to fit to time_t, it's set to lowest/highest
    allowed value. This allows you to use the value directly for comparing
    timestamps. */
-bool imap_parse_date(const char *str, time_t *time);
-bool imap_parse_datetime(const char *str, time_t *time, int *timezone_offset);
+bool imap_parse_date(const char *str, time_t *timestamp_r);
+bool imap_parse_datetime(const char *str, time_t *timestamp_r,
+			 int *timezone_offset_r);
 
 /* Returns given UTC time in local timezone. */
-const char *imap_to_datetime(time_t time);
+const char *imap_to_datetime(time_t timestamp);
 
 #endif
--- a/src/lib-index/mail-cache-compress.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib-index/mail-cache-compress.c	Mon Jun 09 05:11:18 2008 +0300
@@ -199,28 +199,28 @@
 		used_fields_count = i;
 	} else {
 		for (i = used_fields_count = 0; i < orig_fields_count; i++) {
-			struct mail_cache_field_private *field =
+			struct mail_cache_field_private *priv =
 				&cache->fields[i];
 			enum mail_cache_decision_type dec =
-				field->field.decision;
+				priv->field.decision;
 
 			/* if the decision isn't forced and this field hasn't
 			   been accessed for a while, drop it */
 			if ((dec & MAIL_CACHE_DECISION_FORCED) == 0 &&
-			    (time_t)field->last_used < max_drop_time &&
-			    !field->adding) {
+			    (time_t)priv->last_used < max_drop_time &&
+			    !priv->adding) {
 				dec = MAIL_CACHE_DECISION_NO;
-				field->field.decision = dec;
+				priv->field.decision = dec;
 			}
 
 			/* drop all fields we don't want */
 			if ((dec & ~MAIL_CACHE_DECISION_FORCED) ==
-			    MAIL_CACHE_DECISION_NO && !field->adding) {
-				field->used = FALSE;
-				field->last_used = 0;
+			    MAIL_CACHE_DECISION_NO && !priv->adding) {
+				priv->used = FALSE;
+				priv->last_used = 0;
 			}
 
-			ctx.field_file_map[i] = !field->used ?
+			ctx.field_file_map[i] = !priv->used ?
 				(uint32_t)-1 : used_fields_count++;
 		}
 	}
@@ -414,7 +414,7 @@
 
 	/* once we're sure that the compression was successful,
 	   update the offsets */
-	mail_index_ext_reset(trans, cache->ext_id, file_seq);
+	mail_index_ext_reset(trans, cache->ext_id, file_seq, TRUE);
 	offsets = array_get(&ext_offsets, &count);
 	for (i = 0; i < count; i++) {
 		if (offsets[i] != 0) {
--- a/src/lib-index/mail-index-map.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib-index/mail-index-map.c	Mon Jun 09 05:11:18 2008 +0300
@@ -26,7 +26,7 @@
 		size = EXT_GLOBAL_ALLOC_SIZE +
 			initial_count * EXT_PER_ALLOC_SIZE;
 		map->extension_pool =
-			pool_alloconly_create("map extensions",
+			pool_alloconly_create(MEMPOOL_GROWING"map extensions",
 					      nearest_power(size));
 	} else {
 		p_clear(map->extension_pool);
@@ -332,11 +332,11 @@
 	for (i = 0; i < array_count(&map->keyword_idx_map); i++) {
 		const char *keyword = name + kw_rec[i].name_offset;
 		const unsigned int *old_idx;
-		unsigned int idx;
+		unsigned int kw_idx;
 
 		old_idx = array_idx(&map->keyword_idx_map, i);
-		if (!mail_index_keyword_lookup(index, keyword, &idx) ||
-		    idx != *old_idx) {
+		if (!mail_index_keyword_lookup(index, keyword, &kw_idx) ||
+		    kw_idx != *old_idx) {
 			mail_index_set_error(index, "Corrupted index file %s: "
 					     "Keywords changed unexpectedly",
 					     index->filepath);
@@ -348,7 +348,7 @@
 	i = array_count(&map->keyword_idx_map);
 	for (; i < kw_hdr->keywords_count; i++) {
 		const char *keyword = name + kw_rec[i].name_offset;
-		unsigned int idx;
+		unsigned int kw_idx;
 
 		if (*keyword == '\0') {
 			mail_index_set_error(index, "Corrupted index file %s: "
@@ -356,8 +356,8 @@
 				index->filepath);
 			return -1;
 		}
-		mail_index_keyword_lookup_or_create(index, keyword, &idx);
-		array_append(&map->keyword_idx_map, &idx, 1);
+		mail_index_keyword_lookup_or_create(index, keyword, &kw_idx);
+		array_append(&map->keyword_idx_map, &kw_idx, 1);
 	}
 	return 0;
 }
@@ -419,6 +419,17 @@
 	return TRUE;
 }
 
+static void mail_index_map_clear_recent_flags(struct mail_index_map *map)
+{
+	struct mail_index_record *rec;
+	unsigned int i;
+
+	for (i = 0; i < map->hdr.messages_count; i++) {
+		rec = MAIL_INDEX_MAP_IDX(map, i);
+		rec->flags &= ~MAIL_RECENT;
+	}
+}
+
 int mail_index_map_check_header(struct mail_index_map *map)
 {
 	struct mail_index *index = map->index;
@@ -446,13 +457,18 @@
 	if (hdr->seen_messages_count > hdr->messages_count ||
 	    hdr->deleted_messages_count > hdr->messages_count)
 		return 0;
-	if (hdr->minor_version == 0) {
+	switch (hdr->minor_version) {
+	case 0:
 		/* upgrade silently from v1.0 */
-		map->hdr.minor_version = MAIL_INDEX_MINOR_VERSION;
 		map->hdr.unused_old_recent_messages_count = 0;
 		if (hdr->first_recent_uid == 0)
 			map->hdr.first_recent_uid = 1;
 		index->need_recreate = TRUE;
+		/* fall through */
+	case 1:
+		/* pre-v1.1.rc6: make sure the \Recent flags are gone */
+		mail_index_map_clear_recent_flags(map);
+		map->hdr.minor_version = MAIL_INDEX_MINOR_VERSION;
 	}
 	if (hdr->first_recent_uid == 0 ||
 	    hdr->first_recent_uid > hdr->next_uid ||
@@ -793,13 +809,15 @@
 	return mail_index_map_clone(&tmp_map);
 }
 
+/* returns -1 = error, 0 = index files are unusable,
+   1 = index files are usable or at least repairable */
 static int mail_index_map_latest_file(struct mail_index *index)
 {
 	struct mail_index_map *old_map, *new_map;
 	struct stat st;
 	unsigned int lock_id;
 	uoff_t file_size;
-	bool use_mmap;
+	bool use_mmap, unusable = FALSE;
 	int ret, try;
 
 	ret = mail_index_reopen_if_changed(index);
@@ -809,7 +827,7 @@
 
 		/* the index file is lost/broken. let's hope that we can
 		   build it from the transaction log. */
-		return 0;
+		return 1;
 	}
 
 	/* the index file is still open, lock it */
@@ -843,6 +861,10 @@
 		ret = mail_index_read_map(new_map, file_size, &lock_id);
 		mail_index_unlock(index, &lock_id);
 	}
+	if (ret == 0) {
+		/* the index files are unusable */
+		unusable = TRUE;
+	}
 
 	for (try = 0; ret > 0; try++) {
 		/* make sure the header is ok before using this mapping */
@@ -853,8 +875,13 @@
 			else if (mail_index_map_parse_keywords(new_map) < 0)
 				ret = 0;
 		} T_END;
-		if (ret != 0 || try == 2)
+		if (ret != 0 || try == 2) {
+			if (ret < 0) {
+				unusable = TRUE;
+				ret = 0;
+			}
 			break;
+		}
 
 		/* fsck and try again */
 		old_map = index->map;
@@ -870,7 +897,7 @@
 	}
 	if (ret <= 0) {
 		mail_index_unmap(&new_map);
-		return ret;
+		return ret < 0 ? -1 : (unusable ? 0 : 1);
 	}
 	i_assert(new_map->rec_map->records != NULL);
 
@@ -909,18 +936,24 @@
 	}
 
 	if (ret == 0) {
-		/* try to open and read the latest index. if it fails for
-		   any reason, we'll fallback to updating the existing mapping
-		   from transaction logs (which we'll also do even if the
-		   reopening succeeds) */
-		(void)mail_index_map_latest_file(index);
-
-		/* if we're creating the index file, we don't have any
-		   logs yet */
-		if (index->log->head != NULL && index->indexid != 0) {
-			/* and update the map with the latest changes from
-			   transaction log */
-			ret = mail_index_sync_map(&index->map, type, TRUE);
+		/* try to open and read the latest index. if it fails, we'll
+		   fallback to updating the existing mapping from transaction
+		   logs (which we'll also do even if the reopening succeeds).
+		   if index files are unusable (e.g. major version change)
+		   don't even try to use the transaction log. */
+		if (mail_index_map_latest_file(index) == 0) {
+			/* make sure we don't try to open the file again */
+			if (unlink(index->filepath) < 0 && errno != ENOENT)
+				mail_index_set_syscall_error(index, "unlink()");
+		} else {
+			/* if we're creating the index file, we don't have any
+			   logs yet */
+			if (index->log->head != NULL && index->indexid != 0) {
+				/* and update the map with the latest changes
+				   from transaction log */
+				ret = mail_index_sync_map(&index->map, type,
+							  TRUE);
+			}
 		}
 	}
 
--- a/src/lib-index/mail-index-private.h	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib-index/mail-index-private.h	Mon Jun 09 05:11:18 2008 +0300
@@ -221,6 +221,7 @@
 	unsigned int syncing:1;
 	unsigned int need_recreate:1;
 	unsigned int modseqs_enabled:1;
+	unsigned int initial_create:1;
 };
 
 extern struct mail_index_module_register mail_index_module_register;
--- a/src/lib-index/mail-index-sync-ext.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib-index/mail-index-sync-ext.c	Mon Jun 09 05:11:18 2008 +0300
@@ -513,7 +513,8 @@
 			ctx->cur_ext_ignore = FALSE;
 		} else {
 			/* extension was reset and this transaction hadn't
-			   yet seen it. ignore this update. */
+			   yet seen it. ignore this update (except for
+			   resets). */
 			ctx->cur_ext_ignore = TRUE;
 		}
 
@@ -529,31 +530,13 @@
 	return 1;
 }
 
-int mail_index_sync_ext_reset(struct mail_index_sync_map_ctx *ctx,
-			      const struct mail_transaction_ext_reset *u)
+static void mail_index_sync_ext_clear(struct mail_index_view *view,
+				      struct mail_index_map *map,
+				      struct mail_index_ext *ext)
 {
-	struct mail_index_view *view = ctx->view;
-	struct mail_index_map *map = view->map;
-	struct mail_index_ext_header *ext_hdr;
-        struct mail_index_ext *ext;
 	struct mail_index_record *rec;
 	uint32_t i;
 
-	if (ctx->cur_ext_map_idx == (uint32_t)-1) {
-		mail_index_sync_set_corrupted(ctx,
-			"Extension reset without intro prefix");
-		return -1;
-	}
-	if (ctx->cur_ext_ignore)
-		return 1;
-
-	/* a new index file will be created, so the old data won't be
-	   accidentally used by other processes. */
-	map = mail_index_sync_get_atomic_map(ctx);
-
-	ext = array_idx_modifiable(&map->extensions, ctx->cur_ext_map_idx);
-	ext->reset_id = u->new_reset_id;
-
 	memset(buffer_get_space_unsafe(map->hdr_copy_buf, ext->hdr_offset,
 				       ext->hdr_size), 0, ext->hdr_size);
 	map->hdr_base = map->hdr_copy_buf->data;
@@ -565,10 +548,34 @@
 	}
 	map->rec_map->write_seq_first = 1;
 	map->rec_map->write_seq_last = view->map->rec_map->records_count;
+}
+
+int mail_index_sync_ext_reset(struct mail_index_sync_map_ctx *ctx,
+			      const struct mail_transaction_ext_reset *u)
+{
+	struct mail_index_map *map = ctx->view->map;
+	struct mail_index_ext_header *ext_hdr;
+        struct mail_index_ext *ext;
+
+	if (ctx->cur_ext_map_idx == (uint32_t)-1) {
+		mail_index_sync_set_corrupted(ctx,
+			"Extension reset without intro prefix");
+		return -1;
+	}
+	/* since we're resetting the extension, don't check cur_ext_ignore */
+
+	/* a new index file will be created, so the old data won't be
+	   accidentally used by other processes. */
+	map = mail_index_sync_get_atomic_map(ctx);
+
+	ext = array_idx_modifiable(&map->extensions, ctx->cur_ext_map_idx);
+	ext->reset_id = u->new_reset_id;
+
+	if (!u->preserve_data)
+		mail_index_sync_ext_clear(ctx->view, map, ext);
 
 	ext_hdr = get_ext_header(map, ext);
 	ext_hdr->reset_id = u->new_reset_id;
-
 	return 1;
 }
 
--- a/src/lib-index/mail-index-sync-update.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib-index/mail-index-sync-update.c	Mon Jun 09 05:11:18 2008 +0300
@@ -539,15 +539,17 @@
 		break;
 	}
 	case MAIL_TRANSACTION_EXT_RESET: {
-		const struct mail_transaction_ext_reset *rec = data;
+		struct mail_transaction_ext_reset rec;
 
-		if (hdr->size != sizeof(*rec)) {
+		/* old versions have only new_reset_id */
+		if (hdr->size < sizeof(uint32_t)) {
 			mail_index_sync_set_corrupted(ctx,
 				"ext reset: invalid record size");
 			ret = -1;
 			break;
 		}
-		ret = mail_index_sync_ext_reset(ctx, rec);
+		memcpy(&rec, data, I_MIN(hdr->size, sizeof(rec)));
+		ret = mail_index_sync_ext_reset(ctx, &rec);
 		break;
 	}
 	case MAIL_TRANSACTION_EXT_HDR_UPDATE: {
@@ -791,6 +793,9 @@
 		map->hdr_base = map->hdr_copy_buf->data;
 	}
 
+	mail_transaction_log_view_get_prev_pos(view->log_view,
+					       &prev_seq, &prev_offset);
+
 	mail_index_sync_map_init(&sync_map_ctx, view, type);
 	if (reset) {
 		/* Reset the entire index. Leave only indexid and
--- a/src/lib-index/mail-index-transaction-private.h	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib-index/mail-index-transaction-private.h	Mon Jun 09 05:11:18 2008 +0300
@@ -51,8 +51,9 @@
 		     struct mail_index_transaction_ext_hdr_update *);
 	ARRAY_DEFINE(ext_rec_updates, ARRAY_TYPE(seq_array));
 	ARRAY_DEFINE(ext_resizes, struct mail_transaction_ext_intro);
-	ARRAY_DEFINE(ext_resets, uint32_t);
+	ARRAY_DEFINE(ext_resets, struct mail_transaction_ext_reset);
 	ARRAY_DEFINE(ext_reset_ids, uint32_t);
+	ARRAY_DEFINE(ext_reset_atomic, uint32_t);
 
 	ARRAY_DEFINE(keyword_updates,
 		     struct mail_index_transaction_keyword_update);
--- a/src/lib-index/mail-index-transaction.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib-index/mail-index-transaction.c	Mon Jun 09 05:11:18 2008 +0300
@@ -75,6 +75,8 @@
 		array_free(&t->ext_resets);
 	if (array_is_created(&t->ext_reset_ids))
 		array_free(&t->ext_reset_ids);
+	if (array_is_created(&t->ext_reset_atomic))
+		array_free(&t->ext_reset_atomic);
 
 	t->first_new_seq = mail_index_view_get_messages_count(t->view)+1;
 	t->last_new_seq = 0;
@@ -1087,18 +1089,36 @@
 }
 
 void mail_index_ext_reset(struct mail_index_transaction *t, uint32_t ext_id,
-			  uint32_t reset_id)
+			  uint32_t reset_id, bool clear_data)
 {
+	struct mail_transaction_ext_reset reset;
+
 	i_assert(reset_id != 0);
 
+	memset(&reset, 0, sizeof(reset));
+	reset.new_reset_id = reset_id;
+	reset.preserve_data = !clear_data;
+
 	mail_index_ext_set_reset_id(t, ext_id, reset_id);
 
 	if (!array_is_created(&t->ext_resets))
 		i_array_init(&t->ext_resets, ext_id + 2);
-	array_idx_set(&t->ext_resets, ext_id, &reset_id);
+	array_idx_set(&t->ext_resets, ext_id, &reset);
 	t->log_ext_updates = TRUE;
 }
 
+void mail_index_ext_reset_inc(struct mail_index_transaction *t, uint32_t ext_id,
+			      uint32_t prev_reset_id, bool clear_data)
+{
+	uint32_t expected_reset_id = prev_reset_id + 1;
+
+	mail_index_ext_reset(t, ext_id, (uint32_t)-1, clear_data);
+
+	if (!array_is_created(&t->ext_reset_atomic))
+		i_array_init(&t->ext_reset_atomic, ext_id + 2);
+	array_idx_set(&t->ext_reset_atomic, ext_id, &expected_reset_id);
+}
+
 static bool
 mail_index_transaction_has_ext_changes(struct mail_index_transaction *t)
 {
@@ -1123,11 +1143,11 @@
 		}
 	}
 	if (array_is_created(&t->ext_resets)) {
-		const uint32_t *ids;
+		const struct mail_transaction_ext_reset *resets;
 
-		ids = array_get(&t->ext_resets, &count);
+		resets = array_get(&t->ext_resets, &count);
 		for (i = 0; i < count; i++) {
-			if (ids[i] != 0)
+			if (resets[i].new_reset_id != 0)
 				return TRUE;
 		}
 	}
--- a/src/lib-index/mail-index.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib-index/mail-index.c	Mon Jun 09 05:11:18 2008 +0300
@@ -343,19 +343,21 @@
 			/* Create a new indexid for us. If we're opening index
 			   into memory, index->map doesn't exist yet. */
 			index->indexid = ioloop_time;
+			index->initial_create = TRUE;
 			if (index->map != NULL)
 				index->map->hdr.indexid = index->indexid;
 		}
 
-		ret = mail_transaction_log_create(index->log);
+		ret = mail_transaction_log_create(index->log, FALSE);
+		index->initial_create = FALSE;
 		created = TRUE;
 	}
 	if (ret >= 0) {
-		ret = index->map != NULL ? 0 : mail_index_try_open(index);
+		ret = index->map != NULL ? 1 : mail_index_try_open(index);
 		if (ret == 0) {
-			/* doesn't exist / corrupted */
+			/* corrupted */
 			mail_transaction_log_close(index->log);
-			ret = mail_transaction_log_create(index->log);
+			ret = mail_transaction_log_create(index->log, TRUE);
 			if (ret == 0) {
 				if (index->map != NULL)
 					mail_index_unmap(&index->map);
--- a/src/lib-index/mail-index.h	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib-index/mail-index.h	Mon Jun 09 05:11:18 2008 +0300
@@ -6,7 +6,7 @@
 #include "seq-range-array.h"
 
 #define MAIL_INDEX_MAJOR_VERSION 7
-#define MAIL_INDEX_MINOR_VERSION 1
+#define MAIL_INDEX_MINOR_VERSION 2
 
 #define MAIL_INDEX_HEADER_MIN_SIZE 120
 
@@ -433,14 +433,21 @@
 			   uint32_t hdr_size, uint16_t record_size,
 			   uint16_t record_align);
 
-/* Reset extension records and header. Any updates for this extension which
-   were issued before the writer had seen this reset are discarded. reset_id is
-   used to figure this out, so it must be different every time. */
+/* Reset extension. Any updates for this extension which were issued before the
+   writer had seen this reset are discarded. reset_id is used to figure this
+   out, so it must be different every time. If clear_data=TRUE, records and
+   header is zeroed. */
 void mail_index_ext_reset(struct mail_index_transaction *t, uint32_t ext_id,
-			  uint32_t reset_id);
-/* Discard existing extension updates and write new updates using the given
-   reset_id. The difference to mail_index_ext_reset() is that this doesn't
-   clear any existing record or header data. */
+			  uint32_t reset_id, bool clear_data);
+/* Like mail_index_ext_reset(), but increase extension's reset_id atomically
+   when the transaction is being committed. If prev_reset_id doesn't match the
+   latest reset_id, the reset_id isn't increased and all extension changes are
+   ignored. */
+void mail_index_ext_reset_inc(struct mail_index_transaction *t, uint32_t ext_id,
+			      uint32_t prev_reset_id, bool clear_data);
+/* Discard existing extension updates in this transaction and write new updates
+   using the given reset_id. The difference to mail_index_ext_reset() is that
+   this doesn't clear any existing record or header data. */
 void mail_index_ext_set_reset_id(struct mail_index_transaction *t,
 				 uint32_t ext_id, uint32_t reset_id);
 /* Get the current reset_id for given extension. Returns TRUE if it exists. */
--- a/src/lib-index/mail-transaction-log-append.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib-index/mail-transaction-log-append.c	Mon Jun 09 05:11:18 2008 +0300
@@ -166,6 +166,58 @@
 	return buf;
 }
 
+static void
+ext_reset_update_atomic(struct mail_index_transaction *t,
+			uint32_t ext_id, uint32_t expected_reset_id)
+{
+	const struct mail_index_ext *map_ext;
+	struct mail_transaction_ext_reset *reset;
+	uint32_t idx, reset_id;
+
+	if (!mail_index_map_get_ext_idx(t->view->index->map, ext_id, &idx)) {
+		/* new extension */
+		reset_id = 1;
+	} else {
+		map_ext = array_idx(&t->view->index->map->extensions, idx);
+		reset_id = map_ext->reset_id + 1;
+	}
+	if (reset_id != expected_reset_id) {
+		/* ignore this extension update */
+		mail_index_ext_set_reset_id(t, ext_id, 0);
+		return;
+	}
+
+	if (reset_id == 0)
+		reset_id++;
+
+	array_idx_set(&t->ext_reset_ids, ext_id, &reset_id);
+
+	/* reseting existing data is optional */
+	if (array_is_created(&t->ext_resets)) {
+		reset = array_idx_modifiable(&t->ext_resets, ext_id);
+		if (reset->new_reset_id == (uint32_t)-1)
+			reset->new_reset_id = reset_id;
+	}
+}
+
+static void
+transaction_update_atomic_reset_ids(struct mail_index_transaction *t)
+{
+	const uint32_t *expected_reset_ids;
+	unsigned int ext_id, count;
+
+	if (!array_is_created(&t->ext_reset_atomic))
+		return;
+
+	expected_reset_ids = array_get(&t->ext_reset_atomic, &count);
+	for (ext_id = 0; ext_id < count; ext_id++) {
+		if (expected_reset_ids[ext_id] != 0) {
+			ext_reset_update_atomic(t, ext_id,
+						expected_reset_ids[ext_id]);
+		}
+	}
+}
+
 static void log_append_ext_intro(struct log_append_context *ctx,
 				 uint32_t ext_id, uint32_t reset_id)
 {
@@ -257,7 +309,8 @@
 	unsigned int update_count, resize_count, ext_count = 0;
 	unsigned int hdrs_count, reset_id_count, reset_count;
 	uint32_t ext_id, reset_id;
-	const uint32_t *reset_ids, *reset;
+	const struct mail_transaction_ext_reset *reset;
+	const uint32_t *reset_ids;
 	const ARRAY_TYPE(seq_array) *update;
 	buffer_t *buf;
 
@@ -304,15 +357,15 @@
 	}
 
 	memset(&ext_reset, 0, sizeof(ext_reset));
-
 	buf = buffer_create_data(pool_datastack_create(),
 				 &ext_reset, sizeof(ext_reset));
 	buffer_set_used_size(buf, sizeof(ext_reset));
 
 	for (ext_id = 0; ext_id < ext_count; ext_id++) {
-		ext_reset.new_reset_id =
-			ext_id < reset_count && reset[ext_id] != 0 ?
-			reset[ext_id] : 0;
+		if (ext_id < reset_count)
+			ext_reset = reset[ext_id];
+		else
+			ext_reset.new_reset_id = 0;
 		if ((ext_id < resize_count && resize[ext_id].name_size) ||
 		    (ext_id < update_count &&
 		     array_is_created(&update[ext_id])) ||
@@ -499,6 +552,18 @@
 			return -1;
 	}
 
+	if (array_is_created(&t->ext_reset_atomic)) {
+		if (mail_index_map(t->view->index,
+				   MAIL_INDEX_SYNC_HANDLER_HEAD) <= 0)
+			return -1;
+		transaction_update_atomic_reset_ids(t);
+
+		if (!TRANSACTION_HAS_CHANGES(t)) {
+			/* we aborted the ext changes, nothing else to do */
+			return 0;
+		}
+	}
+
 	file = log->head;
 
 	if (file->sync_offset < file->buffer_offset)
--- a/src/lib-index/mail-transaction-log-file.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib-index/mail-transaction-log-file.c	Mon Jun 09 05:11:18 2008 +0300
@@ -357,7 +357,8 @@
 		return 0;
 	}
 	if (file->hdr.indexid != file->log->index->indexid) {
-		if (file->log->index->indexid != 0) {
+		if (file->log->index->indexid != 0 &&
+		    !file->log->index->initial_create) {
 			/* index file was probably just rebuilt and we don't
 			   know about it yet */
 			mail_transaction_log_file_set_corrupted(file,
@@ -1065,8 +1066,9 @@
 		return 0;
 	}
 
-	if ((uoff_t)st.st_size == file->mmap_size) {
-		/* we already have the whole file mmaped */
+	if (file->buffer != NULL && file->buffer_offset <= start_offset &&
+	    (uoff_t)st.st_size == file->buffer_offset + file->buffer->used) {
+		/* we already have the whole file mapped */
 		if ((ret = mail_transaction_log_file_sync(file)) < 0)
 			return 0;
 		if (ret > 0)
--- a/src/lib-index/mail-transaction-log-view.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib-index/mail-transaction-log-view.c	Mon Jun 09 05:11:18 2008 +0300
@@ -149,6 +149,17 @@
 		return -1;
 	}
 
+	if (min_file_offset > 0 &&
+	    min_file_offset < view->log->files->hdr.hdr_size) {
+		/* log file offset is probably corrupted in the index file. */
+		mail_transaction_log_view_set_corrupted(view,
+			"file_seq=%u, min_file_offset (%"PRIuUOFF_T
+			") < hdr_size (%u)",
+			min_file_seq, min_file_offset,
+			view->tail->hdr.hdr_size);
+		return -1;
+	}
+
 	view->tail = view->head = file = NULL;
 	for (seq = min_file_seq; seq <= max_file_seq; seq++) {
 		if (file == NULL || file->hdr.file_seq != seq) {
--- a/src/lib-index/mail-transaction-log.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib-index/mail-transaction-log.c	Mon Jun 09 05:11:18 2008 +0300
@@ -99,7 +99,7 @@
 	return 1;
 }
 
-int mail_transaction_log_create(struct mail_transaction_log *log)
+int mail_transaction_log_create(struct mail_transaction_log *log, bool reset)
 {
 	struct mail_transaction_log_file *file;
 	const char *path;
@@ -125,7 +125,7 @@
 		mail_transaction_log_file_free(&log->open_file);
 	}
 
-	if (mail_transaction_log_file_create(file, FALSE) < 0) {
+	if (mail_transaction_log_file_create(file, reset) < 0) {
 		mail_transaction_log_file_free(&file);
 		return -1;
 	}
@@ -187,7 +187,7 @@
 	    log->head->hdr.indexid != log->index->indexid) {
 		if (--log->head->refcount == 0)
 			mail_transaction_log_file_free(&log->head);
-		(void)mail_transaction_log_create(log);
+		(void)mail_transaction_log_create(log, FALSE);
 	}
 }
 
@@ -291,7 +291,7 @@
 		   someone deleted it manually while the index was open. try to
 		   handle this nicely by creating a new log file. */
 		file = log->head;
-		if (mail_transaction_log_create(log) < 0)
+		if (mail_transaction_log_create(log, FALSE) < 0)
 			return -1;
 		i_assert(file->refcount > 0);
 		file->refcount--;
--- a/src/lib-index/mail-transaction-log.h	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib-index/mail-transaction-log.h	Mon Jun 09 05:11:18 2008 +0300
@@ -95,6 +95,8 @@
 
 struct mail_transaction_ext_reset {
 	uint32_t new_reset_id;
+	uint8_t preserve_data;
+	uint8_t unused_padding[3];
 };
 
 /* these are set for the last ext_intro */
@@ -120,7 +122,7 @@
    is corrupted, -1 if there was some I/O error. */
 int mail_transaction_log_open(struct mail_transaction_log *log);
 /* Create, or recreate, the transaction log. Returns 0 if ok, -1 if error. */
-int mail_transaction_log_create(struct mail_transaction_log *log);
+int mail_transaction_log_create(struct mail_transaction_log *log, bool reset);
 /* Close all the open transactions log files. */
 void mail_transaction_log_close(struct mail_transaction_log *log);
 
--- a/src/lib-mail/istream-header-filter.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib-mail/istream-header-filter.c	Mon Jun 09 05:11:18 2008 +0300
@@ -28,10 +28,12 @@
 	ARRAY_DEFINE(match_change_lines, unsigned int);
 
 	unsigned int header_read:1;
+	unsigned int seen_eoh:1;
 	unsigned int header_parsed:1;
 	unsigned int exclude:1;
 	unsigned int crlf:1;
 	unsigned int hide_body:1;
+	unsigned int add_missing_eoh:1;
 };
 
 header_filter_callback *null_header_filter_callback = NULL;
@@ -115,6 +117,14 @@
 		       cmp_uint) != NULL;
 }
 
+static void add_eol(struct header_filter_istream *mstream)
+{
+	if (mstream->crlf)
+		buffer_append(mstream->hdr_buf, "\r\n", 2);
+	else
+		buffer_append_c(mstream->hdr_buf, '\n');
+}
+
 static ssize_t read_header(struct header_filter_istream *mstream)
 {
 	struct message_header_line *hdr;
@@ -154,6 +164,7 @@
 		mstream->cur_line++;
 
 		if (hdr->eoh) {
+			mstream->seen_eoh = TRUE;
 			matched = TRUE;
 			if (!mstream->header_parsed &&
 			    mstream->callback != NULL) {
@@ -164,10 +175,7 @@
 			if (!matched)
 				continue;
 
-			if (mstream->crlf)
-				buffer_append(mstream->hdr_buf, "\r\n", 2);
-			else
-				buffer_append_c(mstream->hdr_buf, '\n');
+			add_eol(mstream);
 			continue;
 		}
 
@@ -207,13 +215,8 @@
 			}
 			buffer_append(mstream->hdr_buf,
 				      hdr->value, hdr->value_len);
-			if (!hdr->no_newline) {
-				if (mstream->crlf) {
-					buffer_append(mstream->hdr_buf,
-						      "\r\n", 2);
-				} else
-					buffer_append_c(mstream->hdr_buf, '\n');
-			}
+			if (!hdr->no_newline)
+				add_eol(mstream);
 
 			if (mstream->skip_count >= mstream->hdr_buf->used) {
 				/* we need more */
@@ -230,6 +233,11 @@
 		}
 	}
 
+	if (hdr_ret < 0 && !mstream->seen_eoh && mstream->add_missing_eoh) {
+		mstream->seen_eoh = TRUE;
+		add_eol(mstream);
+	}
+
 	/* don't copy eof here because we're only returning headers here.
 	   the body will be returned in separate read() call. */
 	mstream->istream.buffer = buffer_get_data(mstream->hdr_buf, &pos);
@@ -349,6 +357,7 @@
 		mstream->skip_count = v_offset;
 		mstream->cur_line = 0;
 		mstream->header_read = FALSE;
+		mstream->seen_eoh = FALSE;
 	} else {
 		/* body */
 		v_offset += mstream->header_size.physical_size -
@@ -398,7 +407,8 @@
 	i_assert((flags & (HEADER_FILTER_INCLUDE|HEADER_FILTER_EXCLUDE)) != 0);
 
 	mstream = i_new(struct header_filter_istream, 1);
-	mstream->pool = pool_alloconly_create("header filter stream", 4096);
+	mstream->pool = pool_alloconly_create(MEMPOOL_GROWING
+					      "header filter stream", 4096);
 	mstream->istream.max_buffer_size = input->real_stream->max_buffer_size;
 
 	mstream->headers = headers_count == 0 ? NULL :
@@ -413,6 +423,7 @@
 	mstream->exclude = (flags & HEADER_FILTER_EXCLUDE) != 0;
 	mstream->crlf = (flags & HEADER_FILTER_NO_CR) == 0;
 	mstream->hide_body = (flags & HEADER_FILTER_HIDE_BODY) != 0;
+	mstream->add_missing_eoh = (flags & HEADER_FILTER_ADD_MISSING_EOH) != 0;
 
 	mstream->istream.iostream.destroy = i_stream_header_filter_destroy;
 	mstream->istream.iostream.set_max_buffer_size =
--- a/src/lib-mail/istream-header-filter.h	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib-mail/istream-header-filter.h	Mon Jun 09 05:11:18 2008 +0300
@@ -3,14 +3,16 @@
 
 enum header_filter_flags {
 	/* Include only specified headers in output.*/
-	HEADER_FILTER_INCLUDE	= 0x01,
+	HEADER_FILTER_INCLUDE		= 0x01,
 	/* Exclude specified headers from output. */
-	HEADER_FILTER_EXCLUDE	= 0x02,
+	HEADER_FILTER_EXCLUDE		= 0x02,
 
 	/* Use LF linefeeds instead of CRLF. */
-	HEADER_FILTER_NO_CR	= 0x04,
+	HEADER_FILTER_NO_CR		= 0x04,
 	/* Return EOF at the beginning of message body. */
-	HEADER_FILTER_HIDE_BODY	= 0x08
+	HEADER_FILTER_HIDE_BODY		= 0x08,
+	/* If the empty "end of headers" line doesn't exist, add it. */
+	HEADER_FILTER_ADD_MISSING_EOH	= 0x10
 };
 
 struct message_header_line;
--- a/src/lib-mail/mbox-from.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib-mail/mbox-from.c	Mon Jun 09 05:11:18 2008 +0300
@@ -54,7 +54,7 @@
 {
 	const unsigned char *msg_start, *sender_end, *msg_end;
 	struct tm tm;
-	int esc, alt_stamp, timezone = 0, seen_timezone = FALSE;
+	int esc, alt_stamp, timezone_secs = 0, seen_timezone = FALSE;
 	time_t t;
 
 	*time_r = (time_t)-1;
@@ -198,9 +198,9 @@
 		   i_isdigit(msg[3]) && i_isdigit(msg[4]) && msg[5] == ' ') {
 		/* numeric timezone, use it */
                 seen_timezone = TRUE;
-		timezone = (msg[1]-'0') * 10*60*60 + (msg[2]-'0') * 60*60 +
+		timezone_secs = (msg[1]-'0') * 10*60*60 + (msg[2]-'0') * 60*60 +
 			(msg[3]-'0') * 10 + (msg[4]-'0');
-		if (msg[0] == '-') timezone = -timezone;
+		if (msg[0] == '-') timezone_secs = -timezone_secs;
 		msg += 6;
 	}
 
@@ -217,9 +217,9 @@
 	    i_isdigit(msg[2]) && i_isdigit(msg[3]) &&
 	    i_isdigit(msg[4]) && i_isdigit(msg[5])) {
 		seen_timezone = TRUE;
-		timezone = (msg[2]-'0') * 10*60*60 + (msg[3]-'0') * 60*60 +
+		timezone_secs = (msg[2]-'0') * 10*60*60 + (msg[3]-'0') * 60*60 +
 			(msg[4]-'0') * 10 + (msg[5]-'0');
-		if (msg[1] == '-') timezone = -timezone;
+		if (msg[1] == '-') timezone_secs = -timezone_secs;
 	}
 
 	if (seen_timezone) {
@@ -227,7 +227,7 @@
 		if (t == (time_t)-1)
 			return -1;
 
-		t -= timezone;
+		t -= timezone_secs;
 		*time_r = t;
 	} else {
 		/* assume local timezone */
@@ -238,7 +238,7 @@
 	return 0;
 }
 
-const char *mbox_from_create(const char *sender, time_t time)
+const char *mbox_from_create(const char *sender, time_t timestamp)
 {
 	string_t *str;
 	struct tm *tm;
@@ -251,7 +251,7 @@
 
 	/* we could use simply asctime(), but i18n etc. may break it.
 	   Example: "Thu Nov 29 22:33:52 2001" */
-	tm = localtime(&time);
+	tm = localtime(&timestamp);
 
 	/* week day */
 	str_append(str, weekdays[tm->tm_wday]);
--- a/src/lib-mail/mbox-from.h	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib-mail/mbox-from.h	Mon Jun 09 05:11:18 2008 +0300
@@ -7,6 +7,6 @@
 		    time_t *time_r, char **sender_r);
 /* Return a mbox-compatible From_-line using given sender and time.
    The returned string begins with "From ". */
-const char *mbox_from_create(const char *sender, time_t time);
+const char *mbox_from_create(const char *sender, time_t timestamp);
 
 #endif
--- a/src/lib-mail/message-date.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib-mail/message-date.c	Mon Jun 09 05:11:18 2008 +0300
@@ -105,8 +105,9 @@
 	return ret < 0 ? -1 : *value_len > 0;
 }
 
-static bool message_date_parser_tokens(struct message_date_parser_context *ctx,
-				       time_t *time, int *timezone_offset)
+static bool
+message_date_parser_tokens(struct message_date_parser_context *ctx,
+			   time_t *timestamp_r, int *timezone_offset_r)
 {
 	struct tm tm;
 	const unsigned char *value;
@@ -212,24 +213,24 @@
 		return FALSE;
 	if (ret == 0) {
 		/* missing timezone */
-		*timezone_offset = 0;
+		*timezone_offset_r = 0;
 	} else {
 		/* timezone */
-		*timezone_offset = parse_timezone(value, len);
+		*timezone_offset_r = parse_timezone(value, len);
 	}
 
 	tm.tm_isdst = -1;
-	*time = utc_mktime(&tm);
-	if (*time == (time_t)-1)
+	*timestamp_r = utc_mktime(&tm);
+	if (*timestamp_r == (time_t)-1)
 		return FALSE;
 
-	*time -= *timezone_offset * 60;
+	*timestamp_r -= *timezone_offset_r * 60;
 
 	return TRUE;
 }
 
 bool message_date_parse(const unsigned char *data, size_t size,
-		       time_t *time, int *timezone_offset)
+			time_t *timestamp_r, int *timezone_offset_r)
 {
 	bool success;
 
@@ -238,21 +239,21 @@
 
 		rfc822_parser_init(&ctx.parser, data, size, NULL);
 		ctx.str = t_str_new(128);
-		success = message_date_parser_tokens(&ctx, time,
-						     timezone_offset);
+		success = message_date_parser_tokens(&ctx, timestamp_r,
+						     timezone_offset_r);
 	} T_END;
 
 	return success;
 }
 
-const char *message_date_create(time_t time)
+const char *message_date_create(time_t timestamp)
 {
 	struct tm *tm;
 	int offset;
 	bool negative;
 
-	tm = localtime(&time);
-	offset = utc_offset(tm, time);
+	tm = localtime(&timestamp);
+	offset = utc_offset(tm, timestamp);
 	if (offset >= 0)
 		negative = FALSE;
 	else {
--- a/src/lib-mail/message-date.h	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib-mail/message-date.h	Mon Jun 09 05:11:18 2008 +0300
@@ -4,9 +4,9 @@
 /* Parses RFC2822 date/time string. timezone_offset is filled with the
    timezone's difference to UTC in minutes. */
 bool message_date_parse(const unsigned char *data, size_t size,
-			time_t *time, int *timezone_offset);
+			time_t *timestamp_r, int *timezone_offset_r);
 
 /* Create RFC2822 date/time string from given time in local timezone. */
-const char *message_date_create(time_t time);
+const char *message_date_create(time_t timestamp);
 
 #endif
--- a/src/lib-mail/message-parser.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib-mail/message-parser.c	Mon Jun 09 05:11:18 2008 +0300
@@ -617,7 +617,11 @@
 	uoff_t offset = ctx->part->physical_pos +
 		ctx->part->header_size.physical_size;
 
-	i_assert(offset >= ctx->input->v_offset);
+	if (offset < ctx->input->v_offset) {
+		/* header was actually larger than the cached size suggested */
+		ctx->broken = TRUE;
+		return -1;
+	}
 	i_stream_skip(ctx->input, offset - ctx->input->v_offset);
 
 	ctx->parse_next_block = preparsed_parse_body_more;
--- a/src/lib-sql/driver-mysql.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib-sql/driver-mysql.c	Mon Jun 09 05:11:18 2008 +0300
@@ -534,10 +534,11 @@
 }
 
 static const unsigned char *
-driver_mysql_result_get_field_value_binary(struct sql_result *_result,
-					   unsigned int idx, size_t *size_r)
+driver_mysql_result_get_field_value_binary(struct sql_result *_result ATTR_UNUSED,
+					   unsigned int idx ATTR_UNUSED,
+					   size_t *size_r ATTR_UNUSED)
 {
-	// FIXME
+	/* FIXME */
 	return NULL;
 }
 
--- a/src/lib-storage/index/Makefile.am	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib-storage/index/Makefile.am	Mon Jun 09 05:11:18 2008 +0300
@@ -16,6 +16,7 @@
 	index-mailbox-check.c \
 	index-search.c \
 	index-sort.c \
+	index-sort-string.c \
 	index-status.c \
 	index-storage.c \
 	index-sync.c \
@@ -29,6 +30,7 @@
 headers = \
 	index-mail.h \
 	index-sort.h \
+	index-sort-private.h \
 	index-storage.h \
 	index-sync-changes.h \
 	index-sync-private.h \
--- a/src/lib-storage/index/cydir/cydir-storage.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib-storage/index/cydir/cydir-storage.c	Mon Jun 09 05:11:18 2008 +0300
@@ -28,6 +28,7 @@
 cydir_list_delete_mailbox(struct mailbox_list *list, const char *name);
 static int cydir_list_iter_is_mailbox(struct mailbox_list_iterate_context *ctx,
 				      const char *dir, const char *fname,
+				      const char *mailbox_name,
 				      enum mailbox_list_file_type type,
 				      enum mailbox_info_flags *flags);
 
@@ -335,6 +336,7 @@
 static int cydir_list_iter_is_mailbox(struct mailbox_list_iterate_context *ctx
 				      			ATTR_UNUSED,
 				      const char *dir, const char *fname,
+				      const char *mailbox_name ATTR_UNUSED,
 				      enum mailbox_list_file_type type,
 				      enum mailbox_info_flags *flags)
 {
@@ -374,6 +376,9 @@
 			if (st.st_nlink > 2)
 				*flags |= MAILBOX_CHILDREN;
 		}
+	} else if (errno == ENOENT) {
+		/* doesn't exist - probably a non-existing subscribed mailbox */
+		*flags |= MAILBOX_NONEXISTENT;
 	} else {
 		/* non-selectable. probably either access denied, or symlink
 		   destination not found. don't bother logging errors. */
--- a/src/lib-storage/index/dbox/dbox-file-maildir.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib-storage/index/dbox/dbox-file-maildir.c	Mon Jun 09 05:11:18 2008 +0300
@@ -40,11 +40,13 @@
 {
 	struct stat st;
 	uoff_t size;
+	const char *value = NULL;
 
 	switch (key) {
 	case DBOX_METADATA_FLAGS:
 	case DBOX_METADATA_KEYWORDS:
-		return dbox_file_maildir_get_flags(file, key);
+		value = dbox_file_maildir_get_flags(file, key);
+		break;
 	case DBOX_METADATA_RECEIVED_TIME:
 	case DBOX_METADATA_SAVE_TIME:
 		if (file->fd != -1) {
@@ -61,17 +63,23 @@
 			}
 		}
 		if (key == DBOX_METADATA_RECEIVED_TIME)
-			return dec2str(st.st_mtime);
+			value = dec2str(st.st_mtime);
 		else
-			return dec2str(st.st_ctime);
+			value = dec2str(st.st_ctime);
+		break;
 	case DBOX_METADATA_VIRTUAL_SIZE:
-		maildir_filename_get_size(file->fname,
-					  MAILDIR_EXTRA_VIRTUAL_SIZE, &size);
-		return dec2str(size);
+		if (maildir_filename_get_size(file->fname,
+					      MAILDIR_EXTRA_VIRTUAL_SIZE,
+					      &size))
+			value = dec2str(size);
+		break;
+	case DBOX_METADATA_POP3_UIDL:
 	case DBOX_METADATA_EXPUNGED:
 	case DBOX_METADATA_EXT_REF:
 	case DBOX_METADATA_SPACE:
 		break;
 	}
-	return NULL;
+	if (value != NULL)
+		dbox_file_metadata_set(file, key, value);
+	return value;
 }
--- a/src/lib-storage/index/dbox/dbox-file.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib-storage/index/dbox/dbox-file.c	Mon Jun 09 05:11:18 2008 +0300
@@ -600,7 +600,10 @@
 		return 0;
 	}
 
-	*uid_r = hex2dec(hdr.uid_hex, sizeof(hdr.uid_hex));
+	/* Ignore the UID header with UID files */
+	*uid_r = (file->file_id & DBOX_FILE_ID_FLAG_UID) != 0 ?
+		(file->file_id & ~DBOX_FILE_ID_FLAG_UID) :
+		hex2dec(hdr.uid_hex, sizeof(hdr.uid_hex));
 	*physical_size_r = hex2dec(hdr.message_size_hex,
 				   sizeof(hdr.message_size_hex));
 	return 1;
@@ -623,7 +626,7 @@
 	if (offset == 0)
 		offset = file->file_header_size;
 
-	if (offset != file->cur_offset) {
+	if (offset != file->cur_offset || file->cur_uid == 0) {
 		file->cur_offset = offset;
 		i_stream_seek(file->input, offset);
 		ret = dbox_file_read_mail_header(file, &file->cur_uid,
@@ -757,8 +760,13 @@
 {
 	int ret;
 
-	if (file->nonappendable)
-		return 0;
+	if (file->append_count == 0) {
+		if (file->nonappendable)
+			return 0;
+	} else {
+		if (!dbox_file_can_append(file, mail_size))
+			return 0;
+	}
 
 	ret = dbox_file_get_append_stream_int(file, mail_size, stream_r);
 	if (ret == 0)
@@ -940,7 +948,7 @@
 	const char **changes, *data;
 	unsigned int i, count;
 
-	data = dbox_file_metadata_get(file, key);
+	data = file->maildir_file ? NULL : dbox_file_metadata_get(file, key);
 	if (data != NULL && strcmp(data, value) == 0) {
 		/* value didn't change */
 		return;
--- a/src/lib-storage/index/dbox/dbox-file.h	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib-storage/index/dbox/dbox-file.h	Mon Jun 09 05:11:18 2008 +0300
@@ -54,15 +54,17 @@
 	DBOX_METADATA_FLAGS		= 'F',
 	/* Space separated list of keywords */
 	DBOX_METADATA_KEYWORDS		= 'K',
-	/* Pointer to external message data. Format is:
-	   1*(<start offset> <byte count> <ref>) */
-	DBOX_METADATA_EXT_REF		= 'P',
+	/* POP3 UIDL overriding the default format */
+	DBOX_METADATA_POP3_UIDL		= 'P',
 	/* Received UNIX timestamp in hex */
 	DBOX_METADATA_RECEIVED_TIME	= 'R',
 	/* Saved UNIX timestamp in hex */
 	DBOX_METADATA_SAVE_TIME		= 'S',
 	/* Virtual message size in hex (line feeds counted as CRLF) */
 	DBOX_METADATA_VIRTUAL_SIZE	= 'V',
+	/* Pointer to external message data. Format is:
+	   1*(<start offset> <byte count> <ref>) */
+	DBOX_METADATA_EXT_REF		= 'X',
 
 	/* End of metadata block. The spaces can be used for writing more
 	   metadata. */
--- a/src/lib-storage/index/dbox/dbox-index.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib-storage/index/dbox/dbox-index.c	Mon Jun 09 05:11:18 2008 +0300
@@ -155,11 +155,11 @@
 				   struct dbox_index_file_header *hdr)
 {
 	if (index->uid_validity == 0) {
-		const struct mail_index_header *hdr;
+		const struct mail_index_header *idx_hdr;
 
-		hdr = mail_index_get_header(index->mbox->ibox.view);
-		index->uid_validity = hdr->uid_validity != 0 ?
-			hdr->uid_validity : (uint32_t)ioloop_time;
+		idx_hdr = mail_index_get_header(index->mbox->ibox.view);
+		index->uid_validity = idx_hdr->uid_validity != 0 ?
+			idx_hdr->uid_validity : (uint32_t)ioloop_time;
 	}
 
 	memset(hdr, ' ', sizeof(*hdr));
@@ -346,6 +346,7 @@
 		      off_t start, off_t len)
 {
 	struct flock fl;
+	const char *errstr;
 
 	fl.l_type = lock_type;
 	fl.l_whence = SEEK_SET;
@@ -355,9 +356,12 @@
 		if ((errno == EACCES || errno == EAGAIN || errno == EINTR) &&
 		    cmd == F_SETLK)
 			return 0;
+
+		errstr = errno != EACCES ? strerror(errno) :
+			"File is locked by another process (EACCES)";
 		mail_storage_set_critical(index->mbox->ibox.box.storage,
-			"fcntl(%s, %s) failed: %m", index->path,
-			lock_type == F_UNLCK ? "F_UNLCK" : "F_WRLCK");
+			"fcntl(%s, %s) failed: %s", index->path,
+			lock_type == F_UNLCK ? "F_UNLCK" : "F_WRLCK", errstr);
 		return -1;
 	}
 	return 1;
--- a/src/lib-storage/index/dbox/dbox-mail.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib-storage/index/dbox/dbox-mail.c	Mon Jun 09 05:11:18 2008 +0300
@@ -3,6 +3,7 @@
 #include "lib.h"
 #include "ioloop.h"
 #include "istream.h"
+#include "str.h"
 #include "index-mail.h"
 #include "dbox-storage.h"
 #include "dbox-file.h"
@@ -177,6 +178,47 @@
 }
 
 static int
+dbox_mail_get_special(struct mail *_mail, enum mail_fetch_field field,
+		      const char **value_r)
+{
+	struct dbox_mail *mail = (struct dbox_mail *)_mail;
+	struct index_mail *imail = &mail->imail;
+	const unsigned int pop3_uidl_cache_field =
+		imail->ibox->cache_fields[MAIL_CACHE_POP3_UIDL].idx;
+	struct dbox_file *file;
+	const char *value;
+	string_t *str;
+
+	switch (field) {
+	case MAIL_FETCH_UIDL_BACKEND:
+		/* keep the UIDL in cache file, otherwise POP3 would open all
+		   mail files and read the metadata */
+		str = str_new(imail->data_pool, 64);
+		if (mail_cache_lookup_field(imail->trans->cache_view, str,
+					    _mail->seq,
+					    pop3_uidl_cache_field) > 0) {
+			*value_r = str_c(str);
+			return 0;
+		}
+
+		if (dbox_mail_metadata_seek(mail, &file) < 0)
+			return -1;
+
+		value = dbox_file_metadata_get(file, DBOX_METADATA_POP3_UIDL);
+		if (value == NULL)
+			value = "";
+		index_mail_cache_add_idx(imail, pop3_uidl_cache_field,
+					 value, strlen(value)+1);
+		*value_r = value;
+		return 0;
+	default:
+		break;
+	}
+
+	return index_mail_get_special(_mail, field, value_r);
+}
+							
+static int
 dbox_mail_get_stream(struct mail *_mail, struct message_size *hdr_size,
 		     struct message_size *body_size, struct istream **stream_r)
 {
@@ -204,6 +246,9 @@
 			/* FIXME: broken file/offset */
 			if (ret > 0)
 				i_stream_unref(&input);
+			mail_storage_set_critical(_mail->box->storage,
+				"broken pointer to dbox file %s",
+				mail->open_file->current_path);
 			return -1;
 		}
 		data->physical_size = size;
@@ -234,7 +279,7 @@
 	index_mail_get_headers,
 	index_mail_get_header_stream,
 	dbox_mail_get_stream,
-	index_mail_get_special,
+	dbox_mail_get_special,
 	index_mail_update_flags,
 	index_mail_update_keywords,
 	index_mail_expunge,
--- a/src/lib-storage/index/dbox/dbox-save.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib-storage/index/dbox/dbox-save.c	Mon Jun 09 05:11:18 2008 +0300
@@ -2,6 +2,7 @@
 
 #include "lib.h"
 #include "array.h"
+#include "fdatasync-path.h"
 #include "hex-dec.h"
 #include "str.h"
 #include "istream.h"
@@ -218,6 +219,29 @@
 	o_stream_send(ctx->cur_output, "\n", 1);
 }
 
+static int dbox_save_mail_write_header(struct dbox_save_mail *mail)
+{
+	struct dbox_message_header dbox_msg_hdr;
+
+	i_assert(mail->file->msg_header_size == sizeof(dbox_msg_hdr));
+
+	mail->file->last_append_uid = mail->uid;
+	dbox_msg_header_fill(&dbox_msg_hdr, mail->uid, mail->message_size);
+
+	if (pwrite_full(mail->file->fd, &dbox_msg_hdr,
+			sizeof(dbox_msg_hdr), mail->append_offset) < 0) {
+		dbox_file_set_syscall_error(mail->file, "write");
+		return -1;
+	}
+	if (!mail->file->mbox->ibox.fsync_disable) {
+		if (fdatasync(mail->file->fd) < 0) {
+			dbox_file_set_syscall_error(mail->file, "fdatasync");
+			return -1;
+		}
+	}
+	return 0;
+}
+
 int dbox_save_finish(struct mail_save_context *_ctx)
 {
 	struct dbox_save_context *ctx = (struct dbox_save_context *)_ctx;
@@ -250,12 +274,6 @@
 	i_stream_unref(&ctx->input);
 
 	count = array_count(&ctx->mails);
-	if (count >= ctx->mbox->max_open_files) {
-		/* too many open files, close one of them */
-		save_mail = array_idx_modifiable(&ctx->mails, count -
-						 ctx->mbox->max_open_files);
-		dbox_file_close(save_mail->file);
-	}
 	save_mail = array_idx_modifiable(&ctx->mails, count - 1);
 	if (ctx->failed) {
 		dbox_file_cancel_append(save_mail->file,
@@ -267,6 +285,12 @@
 		dbox_file_finish_append(save_mail->file);
 		save_mail->message_size = offset - save_mail->append_offset -
 			save_mail->file->msg_header_size;
+
+		if (save_mail->file->append_count == 1 &&
+		    !dbox_file_can_append(save_mail->file, 0)) {
+			dbox_save_mail_write_header(save_mail);
+			dbox_file_close(save_mail->file);
+		}
 		return 0;
 	}
 }
@@ -279,23 +303,6 @@
 	(void)dbox_save_finish(_ctx);
 }
 
-static int dbox_save_mail_write_header(struct dbox_save_mail *mail)
-{
-	struct dbox_message_header dbox_msg_hdr;
-
-	i_assert(mail->file->msg_header_size == sizeof(dbox_msg_hdr));
-
-	mail->file->last_append_uid = mail->uid;
-	dbox_msg_header_fill(&dbox_msg_hdr, mail->uid, mail->message_size);
-
-	if (pwrite_full(mail->file->fd, &dbox_msg_hdr,
-			sizeof(dbox_msg_hdr), mail->append_offset) < 0) {
-		dbox_file_set_syscall_error(mail->file, "write");
-		return -1;
-	}
-	return 0;
-}
-
 static int
 dbox_save_file_write_append_offset(struct dbox_file *file, uoff_t append_offset)
 {
@@ -359,6 +366,14 @@
 	/* update headers */
 	qsort(mails, count, sizeof(*mails), dbox_save_mail_file_cmp);
 	for (i = 0; i < count; i++) {
+		mails[i].file->last_append_uid = mails[i].uid;
+		if (mails[i].file->append_count == 1 &&
+		    !dbox_file_can_append(mails[i].file, 0)) {
+			/* UID file - there's no need to write it to the
+			   header */
+			continue;
+		}
+
 		if (dbox_file_open_if_needed(mails[i].file) < 0 ||
 		    dbox_save_mail_write_header(&mails[i]) < 0) {
 			ret = -1;
@@ -444,6 +459,13 @@
 	ctx->ctx.transaction = NULL; /* transaction is already freed */
 
 	(void)dbox_sync_finish(&ctx->sync_ctx, TRUE);
+
+	if (!ctx->mbox->ibox.fsync_disable) {
+		if (fdatasync_path(ctx->mbox->path) < 0) {
+			i_error("fdatasync_path(%s) failed: %m",
+				ctx->mbox->path);
+		}
+	}
 	dbox_transaction_save_rollback(ctx);
 }
 
--- a/src/lib-storage/index/dbox/dbox-storage.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib-storage/index/dbox/dbox-storage.c	Mon Jun 09 05:11:18 2008 +0300
@@ -40,6 +40,7 @@
 			     const char *oldname, const char *newname);
 static int dbox_list_iter_is_mailbox(struct mailbox_list_iterate_context *ctx,
 				     const char *dir, const char *fname,
+				     const char *mailbox_name,
 				     enum mailbox_list_file_type type,
 				     enum mailbox_info_flags *flags);
 
@@ -306,9 +307,12 @@
 static int dbox_storage_mailbox_close(struct mailbox *box)
 {
 	struct dbox_mailbox *mbox = (struct dbox_mailbox *)box;
-	int ret;
+	int ret = 0;
 
-	ret = dbox_sync(mbox, TRUE);
+	if (box->opened) {
+		/* see if we want to flush dirty flags */
+		ret = dbox_sync(mbox, TRUE);
+	}
 
 	dbox_index_deinit(&mbox->dbox_index);
 	dbox_files_free(mbox);
@@ -318,13 +322,14 @@
 }
 
 static int dbox_mailbox_create(struct mail_storage *_storage,
-			       const char *name, bool directory ATTR_UNUSED)
+			       const char *name, bool directory)
 {
 	struct dbox_storage *storage = (struct dbox_storage *)_storage;
 	const char *path, *alt_path;
 	struct stat st;
 
 	path = mailbox_list_get_path(_storage->list, name,
+				     directory ? MAILBOX_LIST_PATH_TYPE_DIR :
 				     MAILBOX_LIST_PATH_TYPE_MAILBOX);
 	if (stat(path, &st) == 0) {
 		mail_storage_set_error(_storage, MAIL_ERROR_NOTPOSSIBLE,
@@ -336,7 +341,7 @@
 	   race conditions with RENAME/DELETE), but if something crashed and
 	   left it lying around we don't want to start overwriting files in
 	   it. */
-	alt_path = dbox_get_alt_path(storage, path);
+	alt_path = directory ? NULL : dbox_get_alt_path(storage, path);
 	if (alt_path != NULL && stat(alt_path, &st) == 0) {
 		mail_storage_set_error(_storage, MAIL_ERROR_NOTPOSSIBLE,
 				       "Mailbox already exists");
@@ -578,6 +583,7 @@
 static int dbox_list_iter_is_mailbox(struct mailbox_list_iterate_context *ctx
 				      			ATTR_UNUSED,
 				     const char *dir, const char *fname,
+				     const char *mailbox_name ATTR_UNUSED,
 				     enum mailbox_list_file_type type,
 				     enum mailbox_info_flags *flags)
 {
@@ -617,12 +623,15 @@
 			if (st.st_nlink > 2)
 				*flags |= MAILBOX_CHILDREN;
 		}
+	} else if (errno == ENOENT) {
+		/* doesn't exist - probably a non-existing subscribed mailbox */
+		*flags |= MAILBOX_NONEXISTENT;
 	} else {
 		/* non-selectable. probably either access denied, or symlink
 		   destination not found. don't bother logging errors. */
 		*flags |= MAILBOX_NOSELECT;
 	}
-	if ((*flags & MAILBOX_NOSELECT) == 0) {
+	if ((*flags & (MAILBOX_NOSELECT | MAILBOX_NONEXISTENT)) == 0) {
 		/* make sure it's a selectable mailbox */
 		maildir_path = t_strconcat(path, "/"DBOX_MAILDIR_NAME, NULL);
 		if (stat(maildir_path, &st) < 0 || !S_ISDIR(st.st_mode))
--- a/src/lib-storage/index/dbox/dbox-sync-file.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib-storage/index/dbox/dbox-sync-file.c	Mon Jun 09 05:11:18 2008 +0300
@@ -196,15 +196,22 @@
 dbox_sync_file_split(struct dbox_sync_context *ctx, struct dbox_file *in_file,
 		     uoff_t offset, uint32_t seq)
 {
+	static enum dbox_metadata_key maildir_metadata_keys[] = {
+		DBOX_METADATA_VIRTUAL_SIZE,
+		DBOX_METADATA_RECEIVED_TIME,
+		DBOX_METADATA_SAVE_TIME,
+		DBOX_METADATA_POP3_UIDL
+	};
 	struct dbox_index_append_context *append_ctx;
 	struct dbox_file *out_file;
 	struct istream *input;
 	struct ostream *output;
 	struct dbox_message_header dbox_msg_hdr;
 	struct dbox_mail_index_record rec;
-	const char *out_path;
+	const char *out_path, *value;
 	uint32_t uid;
 	uoff_t size, append_offset;
+	unsigned int i;
 	int ret;
 	bool expunged;
 
@@ -233,6 +240,16 @@
 		dbox_sync_update_metadata(ctx, out_file, NULL, seq);
 	} T_END;
 
+	/* set static metadata */
+	for (i = 0; i < N_ELEMENTS(maildir_metadata_keys); i++) {
+		value = dbox_file_metadata_get(in_file,
+					       maildir_metadata_keys[i]);
+		if (value != NULL) {
+			dbox_file_metadata_set(out_file,
+					       maildir_metadata_keys[i], value);
+		}
+	}
+
 	/* copy the message */
 	out_path = dbox_file_get_path(out_file);
 	o_stream_cork(output);
--- a/src/lib-storage/index/dbox/dbox-sync-rebuild.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib-storage/index/dbox/dbox-sync-rebuild.c	Mon Jun 09 05:11:18 2008 +0300
@@ -70,7 +70,7 @@
 		ctx->cache_used = TRUE;
 		ctx->cache_reset_id = reset_id;
 		mail_index_ext_reset(ctx->trans, ctx->cache_ext_id,
-				     ctx->cache_reset_id);
+				     ctx->cache_reset_id, TRUE);
 	}
 	if (ctx->cache_reset_id == reset_id) {
 		mail_index_update_ext(ctx->trans, new_seq,
@@ -225,8 +225,9 @@
 }
 
 static int
-dbox_sync_index_multi_file(struct dbox_sync_rebuild_context *ctx,
-			   const char *dir, const char *fname)
+dbox_sync_index_multi_file(struct dbox_sync_rebuild_context *ctx ATTR_UNUSED,
+			   const char *dir ATTR_UNUSED,
+			   const char *fname ATTR_UNUSED)
 {
 	/* FIXME */
 	return 0;
@@ -253,8 +254,12 @@
 	}
 
 	file = dbox_file_init_new_maildir(ctx->mbox, fname);
-	if ((ret = dbox_sync_index_file_next(ctx, file, &offset)) > 0)
+	if ((ret = dbox_sync_index_file_next(ctx, file, &offset)) > 0) {
 		dbox_index_append_file(ctx->append_ctx, file);
+		/* appending referenced the file, so make sure it gets closed
+		   so we don't have too many open files. */
+		dbox_file_close(file);
+	}
 	dbox_file_unref(&file);
 	return ret < 0 ? -1 : 0;
 }
@@ -413,6 +418,7 @@
 					MAIL_INDEX_TRANSACTION_FLAG_EXTERNAL);
 	i_array_init(&ctx.maildir_new_files, 8);
 	mail_index_reset(ctx.trans);
+	index_mailbox_reset_uidvalidity(&mbox->ibox);
 	mail_index_ext_lookup(mbox->ibox.index, "cache", &ctx.cache_ext_id);
 
 	if ((ret = dbox_sync_index_rebuild_ctx(&ctx)) < 0)
--- a/src/lib-storage/index/index-mail-headers.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib-storage/index/index-mail-headers.c	Mon Jun 09 05:11:18 2008 +0300
@@ -235,7 +235,8 @@
 		}
 	}
 
-	if ((mail->data.cache_fetch_fields & MAIL_FETCH_DATE) != 0) {
+	if ((mail->data.cache_fetch_fields & MAIL_FETCH_DATE) != 0 ||
+	    mail->data.save_sent_date) {
 		array_idx_set(&mail->header_match,
 			      get_header_field_idx(mail->ibox, "Date"),
 			      &mail->header_match_value);
--- a/src/lib-storage/index/index-mail.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib-storage/index/index-mail.c	Mon Jun 09 05:11:18 2008 +0300
@@ -32,6 +32,7 @@
 	{ "imap.body", 0, MAIL_CACHE_FIELD_STRING, 0, 0 },
 	{ "imap.bodystructure", 0, MAIL_CACHE_FIELD_STRING, 0, 0 },
 	{ "imap.envelope", 0, MAIL_CACHE_FIELD_STRING, 0, 0 },
+	{ "pop3.uidl", 0, MAIL_CACHE_FIELD_STRING, 0, 0 },
 	{ "mime.parts", 0, MAIL_CACHE_FIELD_VARIABLE_SIZE, 0, 0 }
 };
 
@@ -1005,6 +1006,7 @@
 		return 0;
 	case MAIL_FETCH_FROM_ENVELOPE:
 	case MAIL_FETCH_UIDL_FILE_NAME:
+	case MAIL_FETCH_UIDL_BACKEND:
 		*value_r = "";
 		return 0;
 	case MAIL_FETCH_HEADER_MD5:
--- a/src/lib-storage/index/index-mail.h	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib-storage/index/index-mail.h	Mon Jun 09 05:11:18 2008 +0300
@@ -18,6 +18,7 @@
 	MAIL_CACHE_IMAP_BODY,
 	MAIL_CACHE_IMAP_BODYSTRUCTURE,
 	MAIL_CACHE_IMAP_ENVELOPE,
+	MAIL_CACHE_POP3_UIDL,
 	MAIL_CACHE_MESSAGE_PARTS,
 
 	MAIL_INDEX_CACHE_FIELD_COUNT
--- a/src/lib-storage/index/index-search.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib-storage/index/index-search.c	Mon Jun 09 05:11:18 2008 +0300
@@ -143,7 +143,9 @@
 	case SEARCH_UIDSET:
 		return seq_range_exists(&arg->value.seqset, rec->uid);
 	case SEARCH_FLAGS:
-		flags = rec->flags;
+		/* recent flag shouldn't be set, but indexes from v1.0.x
+		   may contain it. */
+		flags = rec->flags & ~MAIL_RECENT;
 		if ((arg->value.flags & MAIL_RECENT) != 0 &&
 		    index_mailbox_is_recent(ctx->ibox, rec->uid))
 			flags |= MAIL_RECENT;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-storage/index/index-sort-private.h	Mon Jun 09 05:11:18 2008 +0300
@@ -0,0 +1,33 @@
+#ifndef INDEX_SORT_PRIVATE_H
+#define INDEX_SORT_PRIVATE_H
+
+#include "index-sort.h"
+
+struct mail_search_sort_program {
+	struct mailbox_transaction_context *t;
+	enum mail_sort_type sort_program[MAX_SORT_PROGRAM_SIZE];
+	struct mail *temp_mail;
+
+	void (*sort_list_add)(struct mail_search_sort_program *program,
+			      struct mail *mail);
+	void (*sort_list_finish)(struct mail_search_sort_program *program);
+	void *context;
+
+	ARRAY_TYPE(uint32_t) seqs;
+	unsigned int iter_idx;
+
+	unsigned int reverse:1;
+};
+
+int index_sort_header_get(struct mail *mail, uint32_t seq,
+			  enum mail_sort_type sort_type, string_t *dest);
+int index_sort_node_cmp_type(struct mail *mail,
+			     const enum mail_sort_type *sort_program,
+			     uint32_t seq1, uint32_t seq2);
+
+void index_sort_list_init_string(struct mail_search_sort_program *program);
+void index_sort_list_add_string(struct mail_search_sort_program *program,
+				struct mail *mail);
+void index_sort_list_finish_string(struct mail_search_sort_program *program);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-storage/index/index-sort-string.c	Mon Jun 09 05:11:18 2008 +0300
@@ -0,0 +1,800 @@
+/* Copyright (c) 2006-2008 Dovecot authors, see the included COPYING file */
+
+/* The idea is that we use 32bit integers for string sort IDs which specifiy
+   the sort order for primary sort condition. The whole 32bit integer space is
+   used and whenever adding a string, the available space is halved and the new
+   ID is added in the middle. For example if we add one mail the first time, it
+   gets ID 2^31. If we then add two mails which are sorted before the first
+   one, they get IDs 2^31/3 and 2^31/3*2. Once we run out of the available
+   space between IDs, more space is made by renumbering some IDs.
+*/
+#include "lib.h"
+#include "array.h"
+#include "str.h"
+#include "index-storage.h"
+#include "index-sort-private.h"
+
+#include <stdlib.h>
+
+struct mail_sort_node {
+	uint32_t seq:29;
+	uint32_t wanted:1;
+	uint32_t no_update:1;
+	uint32_t sort_id_changed:1;
+	uint32_t sort_id;
+};
+ARRAY_DEFINE_TYPE(mail_sort_node, struct mail_sort_node);
+
+struct sort_string_context {
+	struct mail_search_sort_program *program;
+
+	ARRAY_TYPE(mail_sort_node) zero_nodes, nonzero_nodes, sorted_nodes;
+	const char **sort_strings;
+	pool_t sort_string_pool;
+	unsigned int first_missing_sort_id_idx;
+
+	uint32_t ext_id, last_seq, highest_reset_id;
+	uint32_t lowest_nonexpunged_zero;
+
+	unsigned int regetting:1;
+	unsigned int have_all_wanted:1;
+	unsigned int no_writing:1;
+};
+
+static char expunged_msg;
+static struct sort_string_context *static_zero_cmp_context;
+static struct mail_search_sort_program *static_sort_node_cmp_context;
+
+static void index_sort_node_add(struct sort_string_context *ctx,
+				struct mail_sort_node *node);
+
+void index_sort_list_init_string(struct mail_search_sort_program *program)
+{
+	struct index_mailbox *ibox = (struct index_mailbox *)program->t->box;
+	struct sort_string_context *ctx;
+	const char *name;
+
+	switch (program->sort_program[0] & MAIL_SORT_MASK) {
+	case MAIL_SORT_CC:
+		name = "sort-c";
+		break;
+	case MAIL_SORT_FROM:
+		name = "sort-f";
+		break;
+	case MAIL_SORT_SUBJECT:
+		name = "sort-s";
+		break;
+	case MAIL_SORT_TO:
+		name = "sort-t";
+		break;
+	default:
+		i_unreached();
+	}
+
+	program->context = ctx = i_new(struct sort_string_context, 1);
+	ctx->program = program;
+	ctx->ext_id = mail_index_ext_register(ibox->index, name, 0,
+					      sizeof(uint32_t),
+					      sizeof(uint32_t));
+	i_array_init(&ctx->zero_nodes, 128);
+	i_array_init(&ctx->nonzero_nodes, 128);
+}
+
+static void index_sort_generate_seqs(struct sort_string_context *ctx)
+{
+	struct mail_sort_node *nodes, *nodes2;
+	unsigned int i, j, count, count2;
+	uint32_t seq;
+
+	nodes = array_get_modifiable(&ctx->nonzero_nodes, &count);
+	nodes2 = array_get_modifiable(&ctx->zero_nodes, &count2);
+
+	if (!array_is_created(&ctx->program->seqs))
+		i_array_init(&ctx->program->seqs, count + count2);
+	else
+		array_clear(&ctx->program->seqs);
+
+	for (i = j = 0;;) {
+		if (i < count && j < count2) {
+			if (nodes[i].seq < nodes2[j].seq)
+				seq = nodes[i++].seq;
+			else
+				seq = nodes2[j++].seq;
+		} else if (i < count) {
+			seq = nodes[i++].seq;
+		} else if (j < count2) {
+			seq = nodes2[j++].seq;
+		} else {
+			break;
+		}
+		array_append(&ctx->program->seqs, &seq, 1);
+	}
+}
+
+static void index_sort_reget_sort_ids(struct sort_string_context *ctx)
+{
+	struct mail_sort_node node;
+	const uint32_t *seqs;
+	unsigned int i, count;
+
+	i_assert(!ctx->regetting);
+	ctx->regetting = TRUE;
+
+	index_sort_generate_seqs(ctx);
+	array_clear(&ctx->zero_nodes);
+	array_clear(&ctx->nonzero_nodes);
+
+	memset(&node, 0, sizeof(node));
+	node.wanted = TRUE;
+	seqs = array_get(&ctx->program->seqs, &count);
+	for (i = 0; i < count; i++) {
+		node.seq = seqs[i];
+		index_sort_node_add(ctx, &node);
+	}
+	ctx->regetting = FALSE;
+}
+
+static void index_sort_node_add(struct sort_string_context *ctx,
+				struct mail_sort_node *node)
+{
+	struct index_transaction_context *t =
+		(struct index_transaction_context *)ctx->program->t;
+	struct mail_index_map *map;
+	const void *data;
+	uint32_t reset_id;
+	bool expunged;
+
+	mail_index_lookup_ext_full(t->trans_view, node->seq,
+				   ctx->ext_id, &map, &data, &expunged);
+	if (expunged) {
+		/* we don't want to update expunged messages' sort IDs */
+		node->no_update = TRUE;
+		/* we can't trust expunged messages' sort IDs. they might be
+		   valid, but it's also possible that sort IDs were updated
+		   and the expunged messages' sort IDs became invalid. we could
+		   use sort ID if we could know the extension's reset_id at the
+		   time of the expunge so we could compare it to
+		   highest_reset_id, but this isn't currently possible. */
+		node->sort_id = 0;
+	} else {
+		node->sort_id = data == NULL ? 0 : *(const uint32_t *)data;
+		if (node->sort_id == 0) {
+			if (ctx->lowest_nonexpunged_zero > node->seq ||
+			    ctx->lowest_nonexpunged_zero == 0)
+				ctx->lowest_nonexpunged_zero = node->seq;
+		} else {
+			i_assert(ctx->lowest_nonexpunged_zero == 0 ||
+				 ctx->lowest_nonexpunged_zero > node->seq);
+		}
+	}
+
+	if (node->sort_id != 0) {
+		/* if reset ID increases, lookup all existing messages' sort
+		   IDs again. if it decreases, ignore the sort ID. */
+		if (!mail_index_ext_get_reset_id(t->trans_view, map,
+						 ctx->ext_id, &reset_id))
+			reset_id = 0;
+		if (reset_id != ctx->highest_reset_id) {
+			if (reset_id < ctx->highest_reset_id) {
+				i_assert(expunged);
+				node->sort_id = 0;
+			} else if (ctx->have_all_wanted) {
+				/* a bit late to start changing the reset_id.
+				   the node lists aren't ordered by sequence
+				   anymore. */
+				node->sort_id = 0;
+				ctx->no_writing = TRUE;
+			} else {
+				ctx->highest_reset_id = reset_id;
+				index_sort_reget_sort_ids(ctx);
+			}
+		}
+	}
+
+	if (node->sort_id == 0)
+		array_append(&ctx->zero_nodes, node, 1);
+	else
+		array_append(&ctx->nonzero_nodes, node, 1);
+	if (ctx->last_seq < node->seq)
+		ctx->last_seq = node->seq;
+}
+
+void index_sort_list_add_string(struct mail_search_sort_program *program,
+				struct mail *mail)
+{
+	struct mail_sort_node node;
+
+	memset(&node, 0, sizeof(node));
+	node.seq = mail->seq;
+	node.wanted = TRUE;
+
+	index_sort_node_add(program->context, &node);
+}
+
+static int sort_node_zero_string_cmp(const void *p1, const void *p2)
+{
+	struct sort_string_context *ctx = static_zero_cmp_context;
+	const struct mail_sort_node *n1 = p1, *n2 = p2;
+	int ret;
+
+	ret = strcmp(ctx->sort_strings[n1->seq], ctx->sort_strings[n2->seq]);
+	if (ret != 0)
+		return ret;
+
+	return index_sort_node_cmp_type(ctx->program->temp_mail,
+					ctx->program->sort_program + 1,
+					n1->seq, n2->seq);
+}
+
+static void index_sort_zeroes(struct sort_string_context *ctx)
+{
+	struct mail *mail = ctx->program->temp_mail;
+	enum mail_sort_type sort_type = ctx->program->sort_program[0];
+	string_t *str;
+	pool_t pool;
+	struct mail_sort_node *nodes;
+	unsigned int i, count;
+
+	/* first get all the messages' sort strings. although this takes more
+	   memory, it makes error handling easier and probably also helps
+	   CPU caching. */
+	ctx->sort_strings = i_new(const char *, ctx->last_seq + 1);
+	ctx->sort_string_pool = pool =
+		pool_alloconly_create("sort strings", 1024*64);
+	str = t_str_new(512);
+	nodes = array_get_modifiable(&ctx->zero_nodes, &count);
+	for (i = 0; i < count; i++) {
+		i_assert(nodes[i].seq <= ctx->last_seq);
+
+		index_sort_header_get(mail, nodes[i].seq, sort_type, str);
+		ctx->sort_strings[nodes[i].seq] = str_len(str) == 0 ? "" :
+			p_strdup(pool, str_c(str));
+	}
+
+	/* we have all strings, sort nodes based on them */
+	static_zero_cmp_context = ctx;
+	qsort(nodes, count, sizeof(struct mail_sort_node),
+	      sort_node_zero_string_cmp);
+}
+
+static const char *
+index_sort_get_expunged_string(struct sort_string_context *ctx, uint32_t idx,
+			       string_t *str)
+{
+	struct mail *mail = ctx->program->temp_mail;
+	enum mail_sort_type sort_type = ctx->program->sort_program[0];
+	const struct mail_sort_node *nodes;
+	const char *result = NULL;
+	unsigned int i, count;
+	uint32_t sort_id;
+
+	/* Look forwards and backwards to see if there are
+	   identical sort_ids. If we do find them, try to get
+	   their sort string and use it to update the rest. */
+	nodes = array_get(&ctx->nonzero_nodes, &count);
+	sort_id = nodes[idx].sort_id;
+	/* If previous sort ID is identical and its sort string is set, we can
+	   trust it. If it's expunged, we already verified that there are no
+	   non-expunged messages. */
+	if (idx > 0 && nodes[idx-1].sort_id == sort_id &&
+	    ctx->sort_strings[nodes[idx].seq] != NULL)
+		return ctx->sort_strings[nodes[idx].seq];
+
+	/* Go forwards as long as there are identical sort IDs. If we find one
+	   that's not expunged, update string table for all messages with
+	   identical sort IDs. */
+	for (i = idx + 1; i < count; i++) {
+		if (nodes[i].sort_id != sort_id)
+			break;
+
+		if (ctx->sort_strings[nodes[i].seq] != NULL) {
+			/* usually we fill all identical sort_ids and this
+			   shouldn't happen, but we can get here if we skipped
+			   over messages when binary searching */
+			result = ctx->sort_strings[nodes[i].seq];
+			break;
+		}
+		if (index_sort_header_get(mail, nodes[i].seq,
+					  sort_type, str) >= 0) {
+			result = str_len(str) == 0 ? "" :
+				p_strdup(ctx->sort_string_pool, str_c(str));
+			break;
+		}
+	}
+	if (result == NULL) {
+		/* unknown */
+		return &expunged_msg;
+	}
+
+	/* fill all identical sort_ids with the same value */
+	for (i = idx; i > 0 && nodes[i-1].sort_id == sort_id; i--) ;
+	for (i = idx; i < count && nodes[i].sort_id == sort_id; i++)
+		ctx->sort_strings[nodes[i].seq] = result;
+	return result;
+}
+
+static const char *
+index_sort_get_string(struct sort_string_context *ctx,
+		      uint32_t idx, uint32_t seq)
+{
+	struct mail *mail = ctx->program->temp_mail;
+	int ret;
+
+	if (ctx->sort_strings[seq] == NULL) T_BEGIN {
+		string_t *str;
+
+		str = t_str_new(256);
+		ret = index_sort_header_get(mail, seq,
+					    ctx->program->sort_program[0], str);
+		if (str_len(str) > 0) {
+			ctx->sort_strings[seq] =
+				p_strdup(ctx->sort_string_pool, str_c(str));
+		} else if (ret >= 0) {
+			ctx->sort_strings[seq] = "";
+		} else {
+			ctx->sort_strings[seq] = 
+				index_sort_get_expunged_string(ctx, idx, str);
+		}
+	} T_END;
+
+	return ctx->sort_strings[seq];
+}
+
+static void
+index_sort_bsearch(struct sort_string_context *ctx, const char *key,
+		   unsigned int start_idx, unsigned int *idx_r,
+		   const char **prev_str_r)
+{
+	const struct mail_sort_node *nodes;
+	const char *str, *str2;
+	unsigned int idx, left_idx, right_idx, prev;
+	int ret;
+
+	nodes = array_get_modifiable(&ctx->nonzero_nodes, &right_idx);
+	idx = left_idx = start_idx;
+	while (left_idx < right_idx) {
+		idx = (left_idx + right_idx) / 2;
+		str = index_sort_get_string(ctx, idx, nodes[idx].seq);
+		if (str != &expunged_msg)
+			ret = strcmp(key, str);
+		else {
+			/* put expunged messages first */
+			ret = 1;
+			for (prev = idx; prev > 0; ) {
+				prev--;
+				str2 = index_sort_get_string(ctx, prev,
+							     nodes[prev].seq);
+				if (str2 != &expunged_msg) {
+					ret = strcmp(key, str2);
+					if (ret <= 0) {
+						idx = prev;
+						str = str2;
+					}
+					break;
+				}
+			}
+		}
+		if (ret > 0)
+			left_idx = idx+1;
+		else if (ret < 0)
+			right_idx = idx;
+		else {
+			*idx_r = idx + 1;
+			*prev_str_r = str;
+			return;
+		}
+	}
+
+	if (left_idx > idx)
+		idx++;
+
+	*idx_r = idx;
+	if (idx > start_idx) {
+		prev = idx;
+		do {
+			prev--;
+			str2 = index_sort_get_string(ctx, prev,
+						     nodes[prev].seq);
+		} while (str2 == &expunged_msg && prev > 0 &&
+			 nodes[prev-1].sort_id == nodes[prev].sort_id);
+		*prev_str_r = str2;
+	}
+}
+
+static void index_sort_merge(struct sort_string_context *ctx)
+{
+	struct mail_sort_node *znodes, *nznodes;
+	const char *zstr, *nzstr, *prev_str;
+	unsigned int zpos, nzpos, nz_next_pos, zcount, nzcount;
+	int ret;
+
+	/* both zero_nodes and nonzero_nodes are sorted. we'll now just have
+	   to merge them together. use sorted_nodes as the result array. */
+	i_array_init(&ctx->sorted_nodes, array_count(&ctx->nonzero_nodes) +
+		     array_count(&ctx->zero_nodes));
+
+	znodes = array_get_modifiable(&ctx->zero_nodes, &zcount);
+	nznodes = array_get_modifiable(&ctx->nonzero_nodes, &nzcount);
+
+	prev_str = NULL;
+	for (zpos = nzpos = 0; zpos < zcount && nzpos < nzcount; ) {
+		zstr = ctx->sort_strings[znodes[zpos].seq];
+		nzstr = index_sort_get_string(ctx, nzpos, nznodes[nzpos].seq);
+
+		if (nzstr != &expunged_msg)
+			ret = strcmp(zstr, nzstr);
+		else if (prev_str != NULL && strcmp(zstr, prev_str) == 0) {
+			/* identical to previous message, must keep them
+			   together */
+			ret = -1;
+		} else {
+			/* we can't be yet sure about the order, but future
+			   nznodes may reveal that the znode must be added
+			   later. if future nznodes don't reveal that, we have
+			   no idea about these nodes' order. so just always
+			   put the expunged message first. */
+			ret = 1;
+		}
+
+		if (ret <= 0) {
+			array_append(&ctx->sorted_nodes, &znodes[zpos], 1);
+			prev_str = zstr;
+			zpos++;
+		} else {
+			array_append(&ctx->sorted_nodes, &nznodes[nzpos], 1);
+			prev_str = nzstr;
+			nzpos++;
+
+			/* avoid looking up all existing messages' strings by
+			   binary searching the next zero-node position. don't
+			   bother if it looks like more work than linear
+			   scanning. */
+			if (zcount - zpos < (nzcount - nzpos)/2) {
+				index_sort_bsearch(ctx, zstr, nzpos,
+						   &nz_next_pos, &prev_str);
+				array_append(&ctx->sorted_nodes,
+					     &nznodes[nzpos],
+					     nz_next_pos - nzpos);
+				nzpos = nz_next_pos;
+			}
+		}
+	}
+	/* only one of zero_nodes and nonzero_nodes can be non-empty now */
+	for (; zpos < zcount; zpos++)
+		array_append(&ctx->sorted_nodes, &znodes[zpos], 1);
+	for (; nzpos < nzcount; nzpos++)
+		array_append(&ctx->sorted_nodes, &nznodes[nzpos], 1);
+
+	/* future index_sort_get_string() calls use ctx->nonzero_nodes, but we
+	   use only ctx->sorted_nodes. make them identical. */
+	array_free(&ctx->nonzero_nodes);
+	ctx->nonzero_nodes = ctx->sorted_nodes;
+}
+
+static int
+index_sort_add_ids_range(struct sort_string_context *ctx,
+			 unsigned int left_idx, unsigned int right_idx)
+{
+
+	struct mail_sort_node *nodes;
+	unsigned int i, count, rightmost_idx, skip;
+	const char *left_str = NULL, *right_str = NULL, *str;
+	uint32_t left_sort_id, right_sort_id, diff;
+	bool no_left_str = FALSE, no_right_str = FALSE;
+	int ret;
+
+	nodes = array_get_modifiable(&ctx->sorted_nodes, &count);
+	rightmost_idx = count - 1;
+
+	/* get the sort IDs from left and right */
+	left_sort_id = nodes[left_idx].sort_id;
+	right_sort_id = nodes[right_idx].sort_id;
+	/* check if all of them should have the same sort IDs. we don't want
+	   to hit the renumbering code in that situation. */
+	if (left_sort_id == right_sort_id && left_sort_id != 0) {
+		/* they should all have the same sort ID */
+		for (i = left_idx + 1; i < right_idx; i++) {
+			nodes[i].sort_id = left_sort_id;
+			nodes[i].sort_id_changed = TRUE;
+		}
+		return 0;
+	}
+
+	if (left_sort_id == 0) {
+		i_assert(left_idx == 0);
+		left_sort_id = 1;
+	}
+	if (right_sort_id == 0) {
+		i_assert(right_idx == rightmost_idx);
+		right_sort_id = (uint32_t)-1;
+	}
+	i_assert(left_sort_id <= right_sort_id);
+
+	diff = right_sort_id - left_sort_id;
+	while (diff / (right_idx-left_idx + 2) == 0) {
+		/* we most likely don't have enough space. we have to
+		   renumber some of the existing sort IDs. do this by
+		   widening the area we're giving sort IDs. */
+		if (left_idx > 0) {
+			left_sort_id = nodes[--left_idx].sort_id;
+			if (left_sort_id == 0) {
+				i_assert(left_idx == 0);
+				left_sort_id = 1;
+			}
+		}
+
+		while (right_idx < rightmost_idx) {
+			right_idx++;
+			if (nodes[right_idx].sort_id > right_sort_id)
+				break;
+		}
+		right_sort_id = nodes[right_idx].sort_id;
+		if (right_sort_id == 0) {
+			i_assert(right_idx == rightmost_idx);
+			right_sort_id = (uint32_t)-1;
+		}
+		i_assert(left_sort_id < right_sort_id);
+
+		if (diff == right_sort_id - left_sort_id) {
+			/* we did nothing, but there's still not enough space.
+			   have to renumber the leftmost/rightmost node(s) */
+			i_assert(left_idx == 0 && right_idx == rightmost_idx);
+			if (left_sort_id > 1) {
+				left_sort_id = 1;
+				no_left_str = TRUE;
+			} else {
+				i_assert(right_sort_id != (uint32_t)-1);
+				right_sort_id = (uint32_t)-1;
+				no_right_str = TRUE;
+			}
+		}
+		diff = right_sort_id - left_sort_id;
+	}
+
+	if (nodes[left_idx].sort_id != 0 && !no_left_str) {
+		left_str = index_sort_get_string(ctx, left_idx,
+						 nodes[left_idx].seq);
+		if (left_str == &expunged_msg) {
+			/* not equivalent with any message */
+			left_str = NULL;
+		}
+		left_idx++;
+	}
+	if (nodes[right_idx].sort_id != 0 && !no_right_str) {
+		right_str = index_sort_get_string(ctx, right_idx,
+						  nodes[right_idx].seq);
+		if (right_str == &expunged_msg) {
+			/* not equivalent with any message */
+			right_str = NULL;
+		}
+		right_idx--;
+	}
+	i_assert(left_idx <= right_idx);
+
+	/* give (new) sort IDs to all nodes in left_idx..right_idx range.
+	   divide the available space so that each message gets an equal sized
+	   share. some messages' sort strings may be equivalent, so give them
+	   the same sort IDs. */
+	for (i = left_idx; i <= right_idx; i++) {
+		str = index_sort_get_string(ctx, i, nodes[i].seq);
+		if (str == &expunged_msg) {
+			/* it doesn't really matter what we give to this
+			   message, since it's only temporary and we don't
+			   know its correct position anyway. so let's assume
+			   it's equivalent to previous message. */
+			nodes[i].sort_id = left_sort_id;
+			continue;
+		}
+
+		ret = left_str == NULL ? 1 : strcmp(str, left_str);
+		if (ret <= 0) {
+			if (ret < 0) {
+				/* broken sort_ids */
+				return -1;
+			}
+			nodes[i].sort_id = left_sort_id;
+		} else if (right_str != NULL && strcmp(str, right_str) == 0) {
+			/* the rest of the sort IDs should be the same */
+			nodes[i].sort_id = right_sort_id;
+			left_sort_id = right_sort_id;
+		} else {
+			/* divide the available space equally. leave the same
+			   sized space also between the first and the last
+			   messages */
+			skip = (right_sort_id - left_sort_id) /
+				(right_idx - i + 2);
+			i_assert(skip > 0);
+			left_sort_id += skip;
+			i_assert(left_sort_id < right_sort_id);
+
+			nodes[i].sort_id = left_sort_id;
+			left_str = str;
+		}
+		nodes[i].sort_id_changed = TRUE;
+	}
+	return right_str == NULL || strcmp(str, right_str) < 0 ? 0 : -1;
+}
+
+static int
+index_sort_add_sort_ids(struct sort_string_context *ctx)
+{
+	const struct mail_sort_node *nodes;
+	unsigned int i, left_idx, right_idx, count;
+
+	nodes = array_get(&ctx->sorted_nodes, &count);
+	for (i = 0; i < count; i++) {
+		if (nodes[i].sort_id != 0)
+			continue;
+
+		/* get the range for all sort_id=0 nodes. include the nodes
+		   left and right of the range as well */
+		for (right_idx = i + 1; right_idx < count; right_idx++) {
+			if (nodes[right_idx].sort_id != 0)
+				break;
+		}
+		if (right_idx == count)
+			right_idx--;
+		left_idx = i == 0 ? 0 : i - 1;
+		if (index_sort_add_ids_range(ctx, left_idx, right_idx) < 0)
+			return -1;
+	}
+	return 0;
+}
+
+static void index_sort_write_changed_sort_ids(struct sort_string_context *ctx)
+{
+	struct index_transaction_context *t =
+		(struct index_transaction_context *)ctx->program->t;
+	uint32_t ext_id = ctx->ext_id;
+	const struct mail_sort_node *nodes;
+	unsigned int i, count;
+
+	if (ctx->no_writing) {
+		/* our reset_id is already stale - don't even bother
+		   trying to write */
+		return;
+	}
+
+	mail_index_ext_reset_inc(t->trans, ext_id, ctx->highest_reset_id, FALSE);
+
+	/* add the missing sort IDs to index */
+	nodes = array_get_modifiable(&ctx->sorted_nodes, &count);
+	for (i = 0; i < count; i++) {
+		i_assert(nodes[i].sort_id != 0);
+		if (!nodes[i].sort_id_changed || nodes[i].no_update)
+			continue;
+
+		mail_index_update_ext(t->trans, nodes[i].seq, ext_id,
+				      &nodes[i].sort_id, NULL);
+	}
+}
+
+static int sort_node_cmp(const void *p1, const void *p2)
+{
+	struct mail_search_sort_program *program = static_sort_node_cmp_context;
+	const struct mail_sort_node *n1 = p1, *n2 = p2;
+
+	if (n1->sort_id < n2->sort_id)
+		return -1;
+	if (n1->sort_id > n2->sort_id)
+		return 1;
+
+	return index_sort_node_cmp_type(program->temp_mail,
+					program->sort_program + 1,
+					n1->seq, n2->seq);
+}
+
+static void index_sort_add_missing(struct sort_string_context *ctx)
+{
+	struct mail_sort_node node;
+	const uint32_t *seqs;
+	unsigned int i, count;
+	uint32_t seq, next_seq;
+
+	ctx->have_all_wanted = TRUE;
+
+	seqs = array_get(&ctx->program->seqs, &count);
+	for (i = 0, next_seq = 1; i < count; i++) {
+		if (seqs[i] == next_seq)
+			next_seq++;
+		else {
+			i_assert(next_seq < seqs[i]);
+			for (seq = next_seq; seq < seqs[i]; seq++) {
+				memset(&node, 0, sizeof(node));
+				node.seq = seq;
+				index_sort_node_add(ctx, &node);
+			}
+			next_seq = seqs[i] + 1;
+		}
+	}
+
+	if (ctx->lowest_nonexpunged_zero == 0) {
+		/* we're handling only expunged zeros. if it causes us to
+		   renumber some existing sort IDs, don't save them. */
+		ctx->no_writing = TRUE;
+	}
+}
+
+static void index_sort_list_reset_broken(struct sort_string_context *ctx)
+{
+	struct mailbox *box = ctx->program->t->box;
+	struct mail_sort_node *nodes;
+	unsigned int i, count;
+
+	mail_storage_set_critical(box->storage,
+				  "Sort IDs %u broken in mailbox %s, reseting",
+				  ctx->ext_id, box->name);
+
+	array_clear(&ctx->zero_nodes);
+	array_append_array(&ctx->zero_nodes,
+			   &ctx->nonzero_nodes);
+	array_clear(&ctx->nonzero_nodes);
+
+	nodes = array_get_modifiable(&ctx->zero_nodes, &count);
+	for (i = 0; i < count; i++)
+		nodes[i].sort_id = 0;
+}
+
+void index_sort_list_finish_string(struct mail_search_sort_program *program)
+{
+	struct sort_string_context *ctx = program->context;
+	struct mail_sort_node *nodes;
+	unsigned int i, count;
+	uint32_t seq;
+
+	nodes = array_get_modifiable(&ctx->nonzero_nodes, &count);
+
+	static_sort_node_cmp_context = program;
+	if (array_count(&ctx->zero_nodes) == 0) {
+		/* fast path: we have all sort IDs */
+		qsort(nodes, count, sizeof(struct mail_sort_node),
+		      sort_node_cmp);
+
+		i_array_init(&program->seqs, count);
+		for (i = 0; i < count; i++) {
+			seq = nodes[i].seq;
+			array_append(&program->seqs, &seq, 1);
+		}
+		array_free(&ctx->nonzero_nodes);
+	} else {
+		/* we have to add some sort IDs. we'll do this for all
+		   messages, so first remember what messages we wanted
+		   to know about. */
+		index_sort_generate_seqs(ctx);
+		/* add messages not in seqs list */
+		index_sort_add_missing(ctx);
+		/* sort all messages with sort IDs */
+		nodes = array_get_modifiable(&ctx->nonzero_nodes, &count);
+		qsort(nodes, count, sizeof(struct mail_sort_node),
+		      sort_node_cmp);
+		for (;;) {
+			/* sort all messages without sort IDs */
+			index_sort_zeroes(ctx);
+			/* merge zero and non-zero arrays into sorted_nodes */
+			index_sort_merge(ctx);
+			/* give sort IDs to messages missing them */
+			if (index_sort_add_sort_ids(ctx) == 0)
+				break;
+
+			/* broken, try again */
+			index_sort_list_reset_broken(ctx);
+		}
+		index_sort_write_changed_sort_ids(ctx);
+
+		nodes = array_get_modifiable(&ctx->sorted_nodes, &count);
+		array_clear(&program->seqs);
+		for (i = 0; i < count; i++) {
+			if (nodes[i].wanted) {
+				seq = nodes[i].seq;
+				array_append(&program->seqs, &seq, 1);
+			}
+		}
+		pool_unref(&ctx->sort_string_pool);
+		i_free(ctx->sort_strings);
+		array_free(&ctx->sorted_nodes);
+		/* NOTE: we already freed nonzero_nodes and made it point to
+		   sorted_nodes. */
+	}
+
+	array_free(&ctx->zero_nodes);
+}
--- a/src/lib-storage/index/index-sort.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib-storage/index/index-sort.c	Mon Jun 09 05:11:18 2008 +0300
@@ -1,56 +1,27 @@
 /* Copyright (c) 2006-2008 Dovecot authors, see the included COPYING file */
 
-/* The idea in here is that we use a 32bit integer (sort ID) which specifies
-   the sort order for primary sort condition. With fixed size fields (time,
-   size) we use the field itself as the sort ID. They can be looked up fast
-   enough from cache file, so we don't add them to index file.
-
-   Strings can't be used as sort IDs directly. The way they're currently
-   handled is that the whole 32bit integer space is used for them and whenever
-   adding a string, the available space is halved and the new ID is added in
-   the middle. For example if we add one mail the first time, it gets ID
-   2^31. If we then add two mails which are sorted before the first one, they
-   get IDs 2^31/3 and 2^31/3*2. Once we run out of the available space between
-   IDs, a large amount of the IDs are renumbered.
-*/
-
 #include "lib.h"
 #include "array.h"
-#include "bsearch-insert-pos.h"
 #include "str.h"
 #include "unichar.h"
 #include "message-address.h"
 #include "imap-base-subject.h"
 #include "index-storage.h"
-#include "index-sort.h"
+#include "index-sort-private.h"
 
 #include <stdlib.h>
 
-#define RENUMBER_SPACE 100
-
-struct mail_sort_node {
+struct mail_sort_node_date {
 	uint32_t seq;
-	uint32_t sort_id;
+	time_t date;
 };
-ARRAY_DEFINE_TYPE(mail_sort_node, struct mail_sort_node);
-
-struct mail_search_sort_program {
-	struct mailbox_transaction_context *t;
-	enum mail_sort_type sort_program[MAX_SORT_PROGRAM_SIZE];
-	struct mail *temp_mail;
+ARRAY_DEFINE_TYPE(mail_sort_node_date, struct mail_sort_node_date);
 
-	ARRAY_TYPE(mail_sort_node) nodes, all_nodes;
-	const struct mail_sort_node *nodes_ptr;
-	unsigned int nodes_count, iter_idx;
-
-	uint32_t ext_id;
-	unsigned int first_missing_sort_id_idx;
-	uint32_t (*get_sort_id)(struct mail *);
-
-	unsigned int reverse:1;
-	unsigned int sort_ids_added:1;
-	unsigned int missing_sort_ids:1;
+struct mail_sort_node_size {
+	uint32_t seq;
+	uoff_t size;
 };
+ARRAY_DEFINE_TYPE(mail_sort_node_size, struct mail_sort_node_size);
 
 struct sort_cmp_context {
 	struct mail_search_sort_program *program;
@@ -59,17 +30,154 @@
 
 static struct sort_cmp_context static_node_cmp_context;
 
-static uint32_t sort_get_arrival(struct mail *mail);
-static uint32_t sort_get_date(struct mail *mail);
-static uint32_t sort_get_size(struct mail *mail);
+static void
+index_sort_list_add_arrival(struct mail_search_sort_program *program,
+			    struct mail *mail)
+{
+	ARRAY_TYPE(mail_sort_node_date) *nodes = program->context;
+	struct mail_sort_node_date *node;
+
+	node = array_append_space(nodes);
+	node->seq = mail->seq;
+	if (mail_get_received_date(mail, &node->date) < 0)
+		node->date = 0;
+}
+
+static void
+index_sort_list_add_date(struct mail_search_sort_program *program,
+			 struct mail *mail)
+{
+	ARRAY_TYPE(mail_sort_node_date) *nodes = program->context;
+	struct mail_sort_node_date *node;
+
+	node = array_append_space(nodes);
+	node->seq = mail->seq;
+	if (mail_get_date(mail, &node->date, NULL) < 0)
+		node->date = 0;
+	else if (node->date == 0) {
+		if (mail_get_received_date(mail, &node->date) < 0)
+			node->date = 0;
+	}
+}
+
+static void
+index_sort_list_add_size(struct mail_search_sort_program *program,
+			 struct mail *mail)
+{
+	ARRAY_TYPE(mail_sort_node_size) *nodes = program->context;
+	struct mail_sort_node_size *node;
+
+	node = array_append_space(nodes);
+	node->seq = mail->seq;
+	if (mail_get_virtual_size(mail, &node->size) < 0)
+		node->size = 0;
+}
+
+void index_sort_list_add(struct mail_search_sort_program *program,
+			 struct mail *mail)
+{
+	i_assert(mail->transaction == program->t);
+
+	program->sort_list_add(program, mail);
+}
+
+static int sort_node_date_cmp(const void *p1, const void *p2)
+{
+	struct sort_cmp_context *ctx = &static_node_cmp_context;
+	const struct mail_sort_node_date *n1 = p1, *n2 = p2;
+
+	if (n1->date < n2->date)
+		return -1;
+	if (n2->date > n2->date)
+		return 1;
+
+	return index_sort_node_cmp_type(ctx->mail,
+					ctx->program->sort_program + 1,
+					n1->seq, n2->seq);
+}
+
+static void
+index_sort_list_finish_date(struct mail_search_sort_program *program)
+{
+	ARRAY_TYPE(mail_sort_node_date) *nodes = program->context;
+	struct mail_sort_node_date *date_nodes;
+	unsigned int count;
+
+	date_nodes = array_get_modifiable(nodes, &count);
+	qsort(date_nodes, count, sizeof(struct mail_sort_node_date),
+	      sort_node_date_cmp);
+	memcpy(&program->seqs, nodes, sizeof(program->seqs));
+	i_free(nodes);
+	program->context = NULL;
+}
+
+static int sort_node_size_cmp(const void *p1, const void *p2)
+{
+	struct sort_cmp_context *ctx = &static_node_cmp_context;
+	const struct mail_sort_node_size *n1 = p1, *n2 = p2;
+
+	if (n1->size < n2->size)
+		return -1;
+	if (n2->size > n2->size)
+		return 1;
+
+	return index_sort_node_cmp_type(ctx->mail,
+					ctx->program->sort_program + 1,
+					n1->seq, n2->seq);
+}
+
+static void
+index_sort_list_finish_size(struct mail_search_sort_program *program)
+{
+	ARRAY_TYPE(mail_sort_node_size) *nodes = program->context;
+	struct mail_sort_node_size *size_nodes;
+	unsigned int count;
+
+	size_nodes = array_get_modifiable(nodes, &count);
+	qsort(size_nodes, count, sizeof(struct mail_sort_node_size),
+	      sort_node_size_cmp);
+	memcpy(&program->seqs, nodes, sizeof(program->seqs));
+	i_free(nodes);
+	program->context = NULL;
+}
+
+void index_sort_list_finish(struct mail_search_sort_program *program)
+{
+	memset(&static_node_cmp_context, 0, sizeof(static_node_cmp_context));
+	static_node_cmp_context.program = program;
+	static_node_cmp_context.mail = program->temp_mail;
+
+	program->sort_list_finish(program);
+
+	if (program->reverse)
+		program->iter_idx = array_count(&program->seqs);
+}
+
+bool index_sort_list_next(struct mail_search_sort_program *program,
+			  struct mail *mail)
+{
+	const uint32_t *seqp;
+
+	if (!program->reverse) {
+		if (program->iter_idx == array_count(&program->seqs))
+			return FALSE;
+
+		seqp = array_idx(&program->seqs, program->iter_idx++);
+	} else {
+		if (program->iter_idx == 0)
+			return FALSE;
+
+		seqp = array_idx(&program->seqs, --program->iter_idx);
+	}
+	mail_set_seq(mail, *seqp);
+	return TRUE;
+}
 
 struct mail_search_sort_program *
 index_sort_program_init(struct mailbox_transaction_context *t,
 			const enum mail_sort_type *sort_program)
 {
-	struct index_mailbox *ibox = (struct index_mailbox *)t->box;
 	struct mail_search_sort_program *program;
-	const char *name = NULL;
 	unsigned int i;
 
 	if (sort_program == NULL || sort_program[0] == MAIL_SORT_END)
@@ -79,7 +187,6 @@
 	program = i_new(struct mail_search_sort_program, 1);
 	program->t = t;
 	program->temp_mail = mail_alloc(t, 0, NULL);
-	i_array_init(&program->nodes, 64);
 
 	/* primary reversion isn't stored to sort_program. we handle it by
 	   iterating backwards at the end. */
@@ -95,32 +202,42 @@
 
 	switch (program->sort_program[0] & MAIL_SORT_MASK) {
 	case MAIL_SORT_ARRIVAL:
-		program->get_sort_id = sort_get_arrival;
+	case MAIL_SORT_DATE: {
+		ARRAY_TYPE(mail_sort_node_date) *nodes;
+
+		nodes = i_malloc(sizeof(*nodes));
+		i_array_init(nodes, 128);
+
+		if ((program->sort_program[0] &
+		     MAIL_SORT_MASK) == MAIL_SORT_ARRIVAL)
+			program->sort_list_add = index_sort_list_add_arrival;
+		else
+			program->sort_list_add = index_sort_list_add_date;
+		program->sort_list_finish = index_sort_list_finish_date;
+		program->context = nodes;
 		break;
-	case MAIL_SORT_DATE:
-		program->get_sort_id = sort_get_date;
+	}
+	case MAIL_SORT_SIZE: {
+		ARRAY_TYPE(mail_sort_node_size) *nodes;
+
+		nodes = i_malloc(sizeof(*nodes));
+		i_array_init(nodes, 128);
+		program->sort_list_add = index_sort_list_add_size;
+		program->sort_list_finish = index_sort_list_finish_size;
+		program->context = nodes;
 		break;
-	case MAIL_SORT_SIZE:
-		program->get_sort_id = sort_get_size;
-		break;
+	}
 	case MAIL_SORT_CC:
-		name = "sort-c";
-		break;
 	case MAIL_SORT_FROM:
-		name = "sort-f";
-		break;
 	case MAIL_SORT_SUBJECT:
-		name = "sort-s";
-		break;
 	case MAIL_SORT_TO:
-		name = "sort-t";
+		program->sort_list_add = index_sort_list_add_string;
+		program->sort_list_finish = index_sort_list_finish_string;
+		index_sort_list_init_string(program);
 		break;
 	default:
 		i_unreached();
 	}
-	program->ext_id = name == NULL ? (uint32_t)-1 :
-		mail_index_ext_register(ibox->index, name, 0,
-					sizeof(uint32_t), sizeof(uint32_t));
 	return program;
 }
 
@@ -130,106 +247,70 @@
 
 	*_program = NULL;
 	mail_free(&program->temp_mail);
-	array_free(&program->nodes);
+	array_free(&program->seqs);
 	i_free(program);
 }
 
-static const char *get_first_mailbox(struct mail *mail, const char *header)
+static int
+get_first_mailbox(struct mail *mail, const char *header, const char **mailbox_r)
 {
 	struct message_address *addr;
 	const char *str;
+	int ret;
 
-	if (mail_get_first_header_utf8(mail, header, &str) <= 0)
-		return "";
+	if ((ret = mail_get_first_header_utf8(mail, header, &str)) <= 0) {
+		*mailbox_r = "";
+		return ret;
+	}
 
 	addr = message_address_parse(pool_datastack_create(),
 				     (const unsigned char *)str,
 				     strlen(str), 1, TRUE);
-	return addr != NULL ? addr->mailbox : "";
+	*mailbox_r = addr != NULL ? addr->mailbox : "";
+	return 0;
 }
 
-static void
-sort_header_get(string_t *dest, enum mail_sort_type sort_type,
-		struct mail *mail, uint32_t seq)
+int index_sort_header_get(struct mail *mail, uint32_t seq,
+			  enum mail_sort_type sort_type, string_t *dest)
 {
 	const char *str;
+	int ret;
 
 	mail_set_seq(mail, seq);
+	str_truncate(dest, 0);
+
 	switch (sort_type & MAIL_SORT_MASK) {
 	case MAIL_SORT_SUBJECT:
-		if (mail_get_first_header(mail, "Subject", &str) <= 0)
-			return;
+		if ((ret = mail_get_first_header(mail, "Subject", &str)) <= 0)
+			return ret;
 		str = imap_get_base_subject_cased(pool_datastack_create(),
 						  str, NULL);
 		str_append(dest, str);
-		return;
+		return 0;
 	case MAIL_SORT_CC:
-		str = get_first_mailbox(mail, "Cc");
+		ret = get_first_mailbox(mail, "Cc", &str);
 		break;
 	case MAIL_SORT_FROM:
-		str = get_first_mailbox(mail, "From");
+		ret = get_first_mailbox(mail, "From", &str);
 		break;
 	case MAIL_SORT_TO:
-		str = get_first_mailbox(mail, "To");
+		ret = get_first_mailbox(mail, "To", &str);
 		break;
 	default:
 		i_unreached();
 	}
 
 	(void)uni_utf8_to_decomposed_titlecase(str, (size_t)-1, dest);
-}
-
-static uint32_t sort_get_arrival(struct mail *mail)
-{
-	time_t t;
-
-	if (mail_get_received_date(mail, &t) < 0)
-		t = 0;
-
-	i_assert(t != (time_t)-1);
-	/* FIXME: truncation isn't good.. */
-	return t <= 0 ? 1 :
-		((uint64_t)t >= (uint32_t)-1 ? (uint32_t)-1 : (uint32_t)t + 1);
+	return ret;
 }
 
-static uint32_t sort_get_date(struct mail *mail)
-{
-	time_t t;
-
-	if (mail_get_date(mail, &t, NULL) < 0)
-		t = 0;
-	if (t == 0) {
-		if (mail_get_received_date(mail, &t) < 0)
-			return 1;
-	}
-	i_assert(t != (time_t)-1);
-	/* FIXME: truncation isn't good.. */
-	return t <= 0 ? 1 :
-		((uint64_t)t >= (uint32_t)-1 ? (uint32_t)-1 : (uint32_t)t + 1);
-}
-
-static uint32_t sort_get_size(struct mail *mail)
-{
-	uoff_t size;
-
-	if (mail_get_virtual_size(mail, &size) < 0)
-		return 1;
-
-	/* FIXME: elsewhere we support 64bit message sizes, but here
-	   we support only 32bit sizes.. It's a bit too much trouble
-	   to support 64bit here currently, so until such messages
-	   actually start showing up somewhere, 32bit is enough */
-	i_assert(size < (uint32_t)-1);
-	return size + 1;
-}
-
-static int sort_node_cmp_type(struct sort_cmp_context *ctx,
-			      const enum mail_sort_type *sort_program,
-			      const struct mail_sort_node *n1,
-			      const struct mail_sort_node *n2)
+int index_sort_node_cmp_type(struct mail *mail,
+			     const enum mail_sort_type *sort_program,
+			     uint32_t seq1, uint32_t seq2)
 {
 	enum mail_sort_type sort_type;
-	uint32_t time1, time2, size1, size2;
+	time_t time1, time2;
+	uoff_t size1, size2;
 	int ret = 0;
 
 	sort_type = *sort_program & MAIL_SORT_MASK;
@@ -243,353 +324,71 @@
 
 			str1 = t_str_new(256);
 			str2 = t_str_new(256);
-			sort_header_get(str1, sort_type, ctx->mail, n1->seq);
-			sort_header_get(str2, sort_type, ctx->mail, n2->seq);
+			index_sort_header_get(mail, seq1, sort_type, str1);
+			index_sort_header_get(mail, seq2, sort_type, str2);
 
 			ret = strcmp(str_c(str1), str_c(str2));
 		} T_END;
 		break;
 	case MAIL_SORT_ARRIVAL:
-		mail_set_seq(ctx->mail, n1->seq);
-		time1 = sort_get_arrival(ctx->mail);
+		mail_set_seq(mail, seq1);
+		if (mail_get_received_date(mail, &time1) < 0)
+			time1 = 0;
 
-		mail_set_seq(ctx->mail, n2->seq);
-		time2 = sort_get_arrival(ctx->mail);
+		mail_set_seq(mail, seq2);
+		if (mail_get_received_date(mail, &time2) < 0)
+			time1 = 0;
 
 		ret = time1 < time2 ? -1 :
 			(time1 > time2 ? 1 : 0);
 		break;
 	case MAIL_SORT_DATE:
-		mail_set_seq(ctx->mail, n1->seq);
-		time1 = sort_get_date(ctx->mail);
+		mail_set_seq(mail, seq1);
+		if (mail_get_date(mail, &time1, NULL) < 0)
+			time1 = 0;
+		else if (time1 == 0) {
+			if (mail_get_received_date(mail, &time1) < 0)
+				time1 = 0;
+		}
 
-		mail_set_seq(ctx->mail, n2->seq);
-		time2 = sort_get_date(ctx->mail);
+		mail_set_seq(mail, seq2);
+		if (mail_get_date(mail, &time2, NULL) < 0)
+			time2 = 0;
+		else if (time2 == 0) {
+			if (mail_get_received_date(mail, &time2) < 0)
+				time2 = 0;
+		}
 
 		ret = time1 < time2 ? -1 :
 			(time1 > time2 ? 1 : 0);
 		break;
 	case MAIL_SORT_SIZE:
-		mail_set_seq(ctx->mail, n1->seq);
-		size1 = sort_get_size(ctx->mail);
+		mail_set_seq(mail, seq1);
+		if (mail_get_virtual_size(mail, &size1) < 0)
+			size1 = 0;
 
-		mail_set_seq(ctx->mail, n2->seq);
-		size2 = sort_get_size(ctx->mail);
+		mail_set_seq(mail, seq2);
+		if (mail_get_virtual_size(mail, &size2) < 0)
+			size2 = 0;
 
 		ret = size1 < size2 ? -1 :
 			(size1 > size2 ? 1 : 0);
 		break;
 	case MAIL_SORT_END:
-		return n1->seq < n2->seq ? -1 :
-			(n1->seq > n2->seq ? 1 : 0);
+		return seq1 < seq2 ? -1 :
+			(seq1 > seq2 ? 1 : 0);
 	case MAIL_SORT_MASK:
 	case MAIL_SORT_FLAG_REVERSE:
 		i_unreached();
 	}
 
-	if (ret == 0)
-		return sort_node_cmp_type(ctx, sort_program+1, n1, n2);
+	if (ret == 0) {
+		return index_sort_node_cmp_type(mail, sort_program+1,
+						seq1, seq2);
+	}
 
 	/* primary reversion isn't in sort_program */
 	if ((*sort_program & MAIL_SORT_FLAG_REVERSE) != 0)
 		ret = ret < 0 ? 1 : -1;
 	return ret;
 }
-
-static int sort_node_cmp(const void *p1, const void *p2)
-{
-	struct sort_cmp_context *ctx = &static_node_cmp_context;
-	const struct mail_sort_node *n1 = p1, *n2 = p2;
-
-	if (n1->sort_id < n2->sort_id)
-		return -1;
-	if (n1->sort_id > n2->sort_id)
-		return 1;
-
-	return sort_node_cmp_type(ctx, ctx->program->sort_program + 1, n1, n2);
-}
-
-static int sort_node_cmp_nozero_sort_id(const void *p1, const void *p2)
-{
-	struct sort_cmp_context *ctx = &static_node_cmp_context;
-	const struct mail_sort_node *n1 = p1, *n2 = p2;
-	const enum mail_sort_type *sort_program;
-
-	/* Use sort IDs only if both have them */
-	if (n1->sort_id != 0 && n2->sort_id != 0) {
-		if (n1->sort_id < n2->sort_id)
-			return -1;
-		if (n1->sort_id > n2->sort_id)
-			return 1;
-		sort_program = ctx->program->sort_program + 1;
-	} else {
-		sort_program = ctx->program->sort_program;
-	}
-
-	return sort_node_cmp_type(ctx, sort_program, n1, n2);
-}
-
-static void
-index_sort_add_ids_range(struct mail_search_sort_program *program,
-			 struct mail *mail, unsigned int left_idx,
-			 unsigned int right_idx)
-{
-	struct mail_sort_node *nodes;
-	unsigned int i, count, rightmost_idx;
-	uint32_t left_sort_id, right_sort_id;
-	string_t *right_str, *left_str, *str;
-	unsigned int skip;
-	bool have_right_sort_id = FALSE;
-
-	nodes = array_get_modifiable(&program->all_nodes, &count);
-	rightmost_idx = count - 1;
-
-	/* get the sort IDs from left and right */
-	left_sort_id = left_idx == 0 ? 1 : nodes[left_idx].sort_id;
-	right_sort_id = right_idx == rightmost_idx ? (uint32_t)-1 :
-		nodes[right_idx].sort_id;
-	i_assert(left_sort_id != 0 && right_sort_id != 0);
-
-	while ((right_sort_id - left_sort_id) / (right_idx-left_idx + 2) == 0) {
-		/* we most likely don't have enough space. we have to
-		   renumber some of the existing sort IDs. do this by
-		   widening the area we're giving sort IDs. */
-		if (left_idx > 0) {
-			left_idx--;
-			left_sort_id = left_idx == 0 ? 1 :
-				nodes[left_idx].sort_id;
-			i_assert(left_sort_id != 0);
-		}
-
-		while (right_idx < rightmost_idx) {
-			if (nodes[++right_idx].sort_id != 0)
-				break;
-		}
-		right_sort_id = right_idx == rightmost_idx ? (uint32_t)-1 :
-			nodes[right_idx].sort_id;
-		i_assert(left_sort_id < right_sort_id);
-	}
-
-	left_str = t_str_new(256);
-	right_str = t_str_new(256);
-	if (nodes[left_idx].sort_id != 0) {
-		sort_header_get(left_str, program->sort_program[0], mail,
-				nodes[left_idx].seq);
-		left_idx++;
-	}
-	if (nodes[right_idx].sort_id != 0) {
-		have_right_sort_id = TRUE;
-		sort_header_get(right_str, program->sort_program[0], mail,
-				nodes[right_idx].seq);
-		right_idx--;
-	}
-
-	/* give (new) sort IDs to all nodes in left_idx..right_idx range.
-	   divide the available space so that each messagets an equal sized
-	   share. some messages' sort strings may be equivalent, so give them
-	   the same sort IDs. */
-	str = str_new(default_pool, 256);
-	for (i = left_idx; i <= right_idx; i++) {
-		T_BEGIN {
-			sort_header_get(str, program->sort_program[0], mail,
-					nodes[i].seq);
-		} T_END;
-
-		if (left_idx != 0 && str_equals(str, left_str))
-			nodes[i].sort_id = left_sort_id;
-		else if (have_right_sort_id && str_equals(str, right_str)) {
-			/* the rest of the sort IDs should be the same */
-			nodes[i].sort_id = right_sort_id;
-		} else {
-			/* divide the available space equally. leave the same
-			   sized space also between the first and the last
-			   messages */
-			skip = (right_sort_id - left_sort_id) /
-				(right_idx - i + 2);
-			i_assert(skip > 0);
-			left_sort_id += skip;
-			nodes[i].sort_id = left_sort_id;
-
-			str_truncate(left_str, 0);
-			str_append_str(left_str, str);
-		}
-	}
-	str_free(&str);
-}
-
-static void
-index_sort_add_ids(struct mail_search_sort_program *program, struct mail *mail)
-{
-	const struct mail_sort_node *nodes;
-	unsigned int i, left_idx = 0, right_idx, count;
-
-	nodes = array_get(&program->all_nodes, &count);
-	for (i = 0; i < count; i++) {
-		if (nodes[i].sort_id != 0)
-			continue;
-
-		/* get the range for all sort_id=0 nodes. include the nodes
-		   left and right of the range as well */
-		for (right_idx = i + 1; right_idx < count; right_idx++) {
-			if (nodes[right_idx].sort_id != 0)
-				break;
-		}
-		if (right_idx == count)
-			right_idx--;
-		left_idx = i == 0 ? 0 : i - 1;
-		T_BEGIN {
-			index_sort_add_ids_range(program, mail,
-						 left_idx, right_idx);
-		} T_END;
-	}
-}
-
-static void
-index_sort_add_string_sort_ids(struct mail_search_sort_program *program,
-			       uint32_t last_seq)
-{
-	ARRAY_TYPE(mail_sort_node) seq_nodes_arr;
-	struct mail_sort_node *nodes, node, *seq_nodes;
-	unsigned int i, count, count2;
-
-	/* insert missing nodes */
-	memset(&node, 0, sizeof(node));
-	node.seq = array_count(&program->all_nodes) + 1;
-	for (; node.seq <= last_seq; node.seq++)
-		array_append(&program->all_nodes, &node, 1);
-
-	/* sort everything. use sort_ids whenever possible */
-	nodes = array_get_modifiable(&program->all_nodes, &count);
-	i_assert(count == last_seq);
-	qsort(nodes, count, sizeof(struct mail_sort_node),
-	      sort_node_cmp_nozero_sort_id);
-
-	/* we can now build the sort_ids */
-	index_sort_add_ids(program, static_node_cmp_context.mail);
-
-	/* @UNSAFE: and finally get the range sorted back by sequence */
-	i_array_init(&seq_nodes_arr, count);
-	(void)array_idx_modifiable(&seq_nodes_arr, count-1);
-	seq_nodes = array_get_modifiable(&seq_nodes_arr, &count2);
-	i_assert(count2 == count);
-	for (i = 0; i < count; i++)
-		seq_nodes[nodes[i].seq-1] = nodes[i];
-
-	array_free(&program->all_nodes);
-	program->all_nodes = seq_nodes_arr;
-}
-
-static void index_sort_build(struct mail_search_sort_program *program)
-{
-	struct index_transaction_context *t =
-		(struct index_transaction_context *)program->t;
-	struct mail_sort_node node, *all_nodes, *nodes;
-	const void *data;
-	uint32_t last_seq;
-	unsigned int seq, i, count, count2;
-
-	/* add messages that have sort_ids. they're always at the beginning
-	   of the mailbox. */
-	memset(&node, 0, sizeof(node));
-	last_seq = mail_index_view_get_messages_count(t->ibox->view);
-	i_array_init(&program->all_nodes, last_seq);
-	for (seq = 1; seq <= last_seq; seq++) {
-		node.seq = seq;
-
-		mail_index_lookup_ext(t->ibox->view, seq, program->ext_id,
-				      &data, NULL);
-		node.sort_id = data == NULL ? 0 : *(const uint32_t *)data;
-		if (node.sort_id == 0) {
-			/* the rest don't have sort_ids either */
-			break;
-		}
-		array_append(&program->all_nodes, &node, 1);
-	}
-	i_assert(seq <= last_seq);
-	index_sort_add_string_sort_ids(program, last_seq);
-
-	/* add the missing sort IDs to index */
-	all_nodes = array_get_modifiable(&program->all_nodes, &count);
-	for (; seq <= count; seq++) {
-		i_assert(all_nodes[seq-1].sort_id != 0);
-		mail_index_update_ext(t->trans, seq, program->ext_id,
-				      &all_nodes[seq-1].sort_id, NULL);
-	}
-
-	/* set missing sort_ids to wanted nodes */
-	nodes = array_get_modifiable(&program->nodes, &count2);
-	for (i = program->first_missing_sort_id_idx; i < count2; i++)
-		nodes[i].sort_id = all_nodes[nodes[i].seq-1].sort_id;
-	array_free(&program->all_nodes);
-}
-
-void index_sort_list_add(struct mail_search_sort_program *program,
-			 struct mail *mail)
-{
-	struct index_transaction_context *t =
-		(struct index_transaction_context *)program->t;
-	const void *data;
-	struct mail_sort_node node;
-
-	i_assert(mail->transaction == program->t);
-
-	node.seq = mail->seq;
-	if (program->ext_id == (uint32_t)-1) {
-		/* no indexing for this field */
-		node.sort_id = program->get_sort_id(mail);
-		array_append(&program->nodes, &node, 1);
-		return;
-	}
-
-	mail_index_lookup_ext(t->trans_view, mail->seq,
-			      program->ext_id, &data, NULL);
-	node.sort_id = data == NULL ? 0 : *(const uint32_t *)data;
-	if (node.sort_id == 0 && !program->missing_sort_ids) {
-		program->missing_sort_ids = TRUE;
-		program->first_missing_sort_id_idx =
-			array_count(&program->nodes);
-	}
-	array_append(&program->nodes, &node, 1);
-}
-
-void index_sort_list_finish(struct mail_search_sort_program *program)
-{
-	struct mail_sort_node *nodes;
-
-	memset(&static_node_cmp_context, 0, sizeof(static_node_cmp_context));
-	static_node_cmp_context.program = program;
-	static_node_cmp_context.mail = program->temp_mail;
-
-	if (program->missing_sort_ids) {
-		i_assert(program->ext_id != (uint32_t)-1);
-		index_sort_build(program);
-	}
-
-	nodes = array_get_modifiable(&program->nodes, &program->nodes_count);
-	qsort(nodes, program->nodes_count, sizeof(struct mail_sort_node),
-	      sort_node_cmp);
-
-	program->nodes_ptr = nodes;
-	if (program->reverse)
-		program->iter_idx = program->nodes_count;
-}
-
-bool index_sort_list_next(struct mail_search_sort_program *program,
-			  struct mail *mail)
-{
-	const struct mail_sort_node *node;
-
-	if (!program->reverse) {
-		if (program->iter_idx == program->nodes_count)
-			return FALSE;
-
-		node = &program->nodes_ptr[program->iter_idx++];
-	} else {
-		if (program->iter_idx == 0)
-			return FALSE;
-
-		node = &program->nodes_ptr[--program->iter_idx];
-	}
-	mail_set_seq(mail, node->seq);
-	return TRUE;
-}
--- a/src/lib-storage/index/index-storage.h	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib-storage/index/index-storage.h	Mon Jun 09 05:11:18 2008 +0300
@@ -123,6 +123,7 @@
 				  uint32_t seq1, uint32_t seq2);
 bool index_mailbox_is_recent(struct index_mailbox *ibox, uint32_t uid);
 unsigned int index_mailbox_get_recent_count(struct index_mailbox *ibox);
+void index_mailbox_reset_uidvalidity(struct index_mailbox *ibox);
 
 void index_mailbox_check_add(struct index_mailbox *ibox,
 			     const char *path);
--- a/src/lib-storage/index/index-sync.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib-storage/index/index-sync.c	Mon Jun 09 05:11:18 2008 +0300
@@ -49,6 +49,15 @@
 		seq_range_exists(&ibox->recent_flags, uid);
 }
 
+void index_mailbox_reset_uidvalidity(struct index_mailbox *ibox)
+{
+	/* can't trust the currently cached recent flags anymore */
+	if (array_is_created(&ibox->recent_flags))
+		array_clear(&ibox->recent_flags);
+	ibox->recent_flags_count = 0;
+	ibox->recent_flags_prev_uid = 0;
+}
+
 unsigned int index_mailbox_get_recent_count(struct index_mailbox *ibox)
 {
 	const struct mail_index_header *hdr;
@@ -95,14 +104,14 @@
 
 static void index_view_sync_recs_get(struct index_mailbox_sync_context *ctx)
 {
-	struct mail_index_view_sync_rec sync;
+	struct mail_index_view_sync_rec sync_rec;
 	uint32_t seq1, seq2;
 
 	i_array_init(&ctx->flag_updates, 128);
 	if ((ctx->ibox->box.enabled_features & MAILBOX_FEATURE_CONDSTORE) != 0)
 		i_array_init(&ctx->modseq_updates, 32);
-	while (mail_index_view_sync_next(ctx->sync_ctx, &sync)) {
-		switch (sync.type) {
+	while (mail_index_view_sync_next(ctx->sync_ctx, &sync_rec)) {
+		switch (sync_rec.type) {
 		case MAIL_INDEX_SYNC_TYPE_APPEND:
 			/* not interested */
 			break;
@@ -114,11 +123,11 @@
 		case MAIL_INDEX_SYNC_TYPE_KEYWORD_REMOVE:
 		case MAIL_INDEX_SYNC_TYPE_KEYWORD_RESET:
 			if (!mail_index_lookup_seq_range(ctx->ibox->view,
-							 sync.uid1, sync.uid2,
+							 sync_rec.uid1, sync_rec.uid2,
 							 &seq1, &seq2))
 				break;
 
-			if (!sync.hidden) {
+			if (!sync_rec.hidden) {
 				seq_range_array_add_range(&ctx->flag_updates,
 							  seq1, seq2);
 			} else if (array_is_created(&ctx->modseq_updates)) {
@@ -190,14 +199,14 @@
 	return &ctx->ctx;
 }
 
-static int
+static bool
 index_mailbox_sync_next_expunge(struct index_mailbox_sync_context *ctx,
 				struct mailbox_sync_rec *sync_rec_r)
 {
 	const struct seq_range *range;
 
 	if (ctx->expunge_pos == 0)
-		return 0;
+		return FALSE;
 
 	/* expunges is a sorted array of sequences. it's easiest for
 	   us to print them from end to beginning. */
@@ -211,7 +220,7 @@
 	sync_rec_r->seq1 = range->seq1;
 	sync_rec_r->seq2 = range->seq2;
 	sync_rec_r->type = MAILBOX_SYNC_TYPE_EXPUNGE;
-	return 1;
+	return TRUE;
 }
 
 bool index_mailbox_sync_next(struct mailbox_sync_context *_ctx,
@@ -231,7 +240,7 @@
 		sync_rec_r->seq1 = range[ctx->flag_update_idx].seq1;
 		sync_rec_r->seq2 = range[ctx->flag_update_idx].seq2;
 		ctx->flag_update_idx++;
-		return 1;
+		return TRUE;
 	}
 	if (array_is_created(&ctx->modseq_updates)) {
 		range = array_get(&ctx->modseq_updates, &count);
@@ -240,7 +249,7 @@
 			sync_rec_r->seq1 = range[ctx->modseq_update_idx].seq1;
 			sync_rec_r->seq2 = range[ctx->modseq_update_idx].seq2;
 			ctx->modseq_update_idx++;
-			return 1;
+			return TRUE;
 		}
 	}
 
--- a/src/lib-storage/index/maildir/maildir-copy.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib-storage/index/maildir/maildir-copy.c	Mon Jun 09 05:11:18 2008 +0300
@@ -106,15 +106,24 @@
 {
 	struct maildir_mailbox *dest_mbox =
 		(struct maildir_mailbox *)t->ictx.ibox;
-	struct maildir_mailbox *src_mbox =
-		(struct maildir_mailbox *)mail->box;
+	struct maildir_mailbox *src_mbox;
 	struct maildir_save_context *ctx;
 	struct hardlink_ctx do_ctx;
-	const char *filename = NULL;
+	const char *path, *filename = NULL;
 	uint32_t seq;
 
 	i_assert((t->ictx.flags & MAILBOX_TRANSACTION_FLAG_EXTERNAL) != 0);
 
+	if (strcmp(mail->box->storage->name, MAILDIR_STORAGE_NAME) == 0)
+		src_mbox = (struct maildir_mailbox *)mail->box;
+	else if (strcmp(mail->box->storage->name, "raw") == 0) {
+		/* deliver uses raw format */
+		src_mbox = NULL;
+	} else {
+		/* Can't hard link files from the source storage */
+		return 0;
+	}
+
 	if (t->save_ctx == NULL)
 		t->save_ctx = maildir_save_transaction_init(t);
 	ctx = t->save_ctx;
@@ -127,7 +136,7 @@
 	memset(&do_ctx, 0, sizeof(do_ctx));
 	do_ctx.dest_path = str_new(default_pool, 512);
 
-	if (dest_mbox->storage->copy_preserve_filename) {
+	if (dest_mbox->storage->copy_preserve_filename && src_mbox != NULL) {
 		enum maildir_uidlist_rec_flag src_flags;
 		const char *src_fname;
 
@@ -175,15 +184,26 @@
 		}
 	} else
 #endif
-{
+	{
 		/* keywords, hardlink to tmp/ with basename and later when we
 		   have uidlist locked, move it to new/cur. */
 		str_printfa(do_ctx.dest_path, "%s/tmp/%s",
 			    dest_mbox->path, do_ctx.dest_fname);
 		do_ctx.base_end_pos = str_len(do_ctx.dest_path);
 	}
-	if (maildir_file_do(src_mbox, mail->uid, do_hardlink, &do_ctx) < 0)
-		return -1;
+	if (src_mbox != NULL) {
+		/* maildir */
+		if (maildir_file_do(src_mbox, mail->uid,
+				    do_hardlink, &do_ctx) < 0)
+			return -1;
+	} else {
+		/* raw / deliver */
+		if (mail_get_special(mail, MAIL_FETCH_UIDL_FILE_NAME,
+				     &path) < 0 || *path == '\0')
+			return 0;
+		if (do_hardlink(dest_mbox, path, &do_ctx) < 0)
+			return -1;
+	}
 
 	if (!do_ctx.success) {
 		/* couldn't copy with hardlinking, fallback to copying */
@@ -223,7 +243,6 @@
 	int ret;
 
 	if (mbox->storage->copy_with_hardlinks &&
-	    mail->box->storage == mbox->ibox.box.storage &&
 	    maildir_compatible_file_modes(&mbox->ibox.box, mail->box)) {
 		T_BEGIN {
 			ret = maildir_copy_hardlink(t, mail, flags,
--- a/src/lib-storage/index/maildir/maildir-mail.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib-storage/index/maildir/maildir-mail.c	Mon Jun 09 05:11:18 2008 +0300
@@ -6,6 +6,7 @@
 #include "maildir-storage.h"
 #include "maildir-filename.h"
 #include "maildir-uidlist.h"
+#include "maildir-sync.h"
 
 #include <stdlib.h>
 #include <fcntl.h>
@@ -143,13 +144,32 @@
 		       const char **fname_r)
 {
 	enum maildir_uidlist_rec_flag flags;
+	struct mail_index_view *view;
+	uint32_t seq;
+	bool exists;
 
 	*fname_r = maildir_uidlist_lookup(mbox->uidlist, mail->uid, &flags);
-	if (*fname_r == NULL) {
-		mail_set_expunged(mail);
-		return FALSE;
+	if (*fname_r != NULL)
+		return TRUE;
+
+	/* file exists in index file, but not in dovecot-uidlist anymore. */
+	mail_set_expunged(mail);
+
+	/* one reason this could happen is if we delayed opening
+	   dovecot-uidlist and we're trying to open a mail that got recently
+	   expunged. Let's test this theory first: */
+	(void)mail_index_refresh(mbox->ibox.index);
+	view = mail_index_view_open(mbox->ibox.index);
+	exists = mail_index_lookup_seq(view, mail->uid, &seq);
+	mail_index_view_close(&view);
+
+	if (exists) {
+		/* the message still exists in index. this means there's some
+		   kind of a desync, which doesn't get fixed if cur/ mtime is
+		   the same as in index. fix this by forcing a resync. */
+		(void)maildir_storage_sync_force(mbox, mail->uid);
 	}
-	return TRUE;
+	return FALSE;
 }
 
 static int maildir_get_pop3_state(struct index_mail *mail)
@@ -274,14 +294,16 @@
 		   do some extra checks here to catch potential cache bugs. */
 		if (vsize && mail->data.virtual_size != size) {
 			mail_cache_set_corrupted(mail->ibox->cache,
-				"Corrupted virtual size: "
+				"Corrupted virtual size for uid=%u: "
 				"%"PRIuUOFF_T" != %"PRIuUOFF_T,
+				mail->mail.mail.uid,
 				mail->data.virtual_size, size);
 			mail->data.virtual_size = size;
 		} else if (!vsize && mail->data.physical_size != size) {
 			mail_cache_set_corrupted(mail->ibox->cache,
-				"Corrupted physical size: "
+				"Corrupted physical size for uid=%u: "
 				"%"PRIuUOFF_T" != %"PRIuUOFF_T,
+				mail->mail.mail.uid,
 				mail->data.physical_size, size);
 			mail->data.physical_size = size;
 		}
@@ -390,7 +412,7 @@
 {
 	struct index_mail *mail = (struct index_mail *)_mail;
 	struct maildir_mailbox *mbox = (struct maildir_mailbox *)mail->ibox;
-	const char *path, *fname, *end;
+	const char *path, *fname, *end, *uidl;
 
 	if (field == MAIL_FETCH_UIDL_FILE_NAME) {
 		if (_mail->uid != 0) {
@@ -405,6 +427,11 @@
 		end = strchr(fname, MAILDIR_INFO_SEP);
 		*value_r = end == NULL ? fname : t_strdup_until(fname, end);
 		return 0;
+	} else if (field == MAIL_FETCH_UIDL_BACKEND) {
+		uidl = maildir_uidlist_lookup_ext(mbox->uidlist, _mail->uid,
+					MAILDIR_UIDLIST_REC_EXT_POP3_UIDL);
+		*value_r = uidl != NULL ? uidl : "";
+		return 0;
 	}
 
 	return index_mail_get_special(_mail, field, value_r);
@@ -432,6 +459,34 @@
 	return index_mail_init_stream(mail, hdr_size, body_size, stream_r);
 }
 
+static void maildir_mail_set_cache_corrupted(struct mail *_mail,
+					     enum mail_fetch_field field)
+{
+	struct index_mail *mail = (struct index_mail *)_mail;
+	struct maildir_mailbox *mbox = (struct maildir_mailbox *)mail->ibox;
+	enum maildir_uidlist_rec_flag flags;
+	const char *fname;
+	uoff_t size;
+
+	if (field == MAIL_FETCH_VIRTUAL_SIZE) {
+		/* make sure it gets removed from uidlist.
+		   if it's in file name, we can't really do more than log it. */
+		fname = maildir_uidlist_lookup(mbox->uidlist,
+					       _mail->uid, &flags);
+		if (maildir_filename_get_size(fname, MAILDIR_EXTRA_VIRTUAL_SIZE,
+					      &size)) {
+			i_error("Maildir filename has wrong W value: %s/%s",
+				mbox->path, fname);
+		} else if (maildir_uidlist_lookup_ext(mbox->uidlist, _mail->uid,
+				MAILDIR_UIDLIST_REC_EXT_VSIZE) != NULL) {
+			maildir_uidlist_set_ext(mbox->uidlist, _mail->uid,
+						MAILDIR_UIDLIST_REC_EXT_VSIZE,
+						NULL);
+		}
+	}
+	index_mail_set_cache_corrupted(_mail, field);
+}
+
 struct mail_vfuncs maildir_mail_vfuncs = {
 	index_mail_close,
 	index_mail_free,
@@ -456,6 +511,6 @@
 	index_mail_update_flags,
 	index_mail_update_keywords,
 	index_mail_expunge,
-	index_mail_set_cache_corrupted,
+	maildir_mail_set_cache_corrupted,
 	index_mail_get_index_mail
 };
--- a/src/lib-storage/index/maildir/maildir-save.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib-storage/index/maildir/maildir-save.c	Mon Jun 09 05:11:18 2008 +0300
@@ -221,11 +221,11 @@
 					   MAILDIR_EXTRA_FILE_SIZE, mf->size);
 	}
 
-	/*if (mf->vsize != (uoff_t)-1) {
+	if (mf->vsize != (uoff_t)-1) {
 		basename = t_strdup_printf("%s,%c=%"PRIuUOFF_T, basename,
 					   MAILDIR_EXTRA_VIRTUAL_SIZE,
 					   mf->vsize);
-	}*/
+	}
 
 	if (mf->keywords_count == 0) {
 		if ((mf->flags & MAIL_FLAGS_MASK) == MAIL_RECENT) {
@@ -593,81 +593,99 @@
 	return 0;
 }
 
+static int
+maildir_transaction_save_commit_pre_sync(struct maildir_save_context *ctx)
+{
+	struct maildir_transaction_context *t =
+		(struct maildir_transaction_context *)ctx->ctx.transaction;
+	struct maildir_mailbox *mbox = ctx->mbox;
+	uint32_t uid, first_uid, next_uid;
+	int ret;
+
+	/* we'll need to keep the lock past the sync deinit */
+	ret = maildir_uidlist_lock(mbox->uidlist);
+	i_assert(ret > 0);
+
+	if (maildir_sync_index_begin(mbox, NULL, &ctx->sync_ctx) < 0)
+		return -1;
+
+	if (maildir_sync_header_refresh(mbox) < 0)
+		return -1;
+	if (maildir_uidlist_refresh_fast_init(mbox->uidlist) < 0)
+		return 1;
+
+	ctx->keywords_sync_ctx =
+		maildir_sync_get_keywords_sync_ctx(ctx->sync_ctx);
+
+	/* now that uidlist is locked, make sure all the existing mails
+	   have been added to index. we don't really look into the
+	   maildir, just add all the new mails listed in
+	   dovecot-uidlist to index. */
+	if (maildir_sync_index(ctx->sync_ctx, TRUE) < 0)
+		return -1;
+
+	/* if messages were added to index, assign them UIDs */
+	first_uid = maildir_uidlist_get_next_uid(mbox->uidlist);
+	i_assert(first_uid != 0);
+	mail_index_append_assign_uids(ctx->trans, first_uid, &next_uid);
+	i_assert(next_uid = first_uid + ctx->files_count);
+
+	/* these mails are all recent in our session */
+	for (uid = first_uid; uid < next_uid; uid++)
+		index_mailbox_set_recent_uid(&mbox->ibox, uid);
+
+	if (!mbox->ibox.keep_recent) {
+		/* maildir_sync_index() dropped recent flags from
+		   existing messages. we'll still need to drop recent
+		   flags from these newly added messages. */
+		mail_index_update_header(ctx->trans,
+					 offsetof(struct mail_index_header,
+						  first_recent_uid),
+					 &next_uid, sizeof(next_uid), FALSE);
+	}
+
+	/* this will work even if index isn't updated */
+	*t->ictx.first_saved_uid = first_uid;
+	*t->ictx.last_saved_uid = next_uid - 1;
+	return 0;
+}
+
 int maildir_transaction_save_commit_pre(struct maildir_save_context *ctx)
 {
 	struct maildir_transaction_context *t =
 		(struct maildir_transaction_context *)ctx->ctx.transaction;
 	struct maildir_filename *mf;
-	uint32_t seq, uid, first_uid, next_uid;
+	uint32_t seq;
 	enum maildir_uidlist_rec_flag flags;
-	bool newdir, new_changed, cur_changed, sync_commit = FALSE;
+	enum maildir_uidlist_sync_flags sync_flags;
+	bool newdir, new_changed, cur_changed;
 	int ret;
 
 	i_assert(ctx->output == NULL);
 	i_assert(ctx->finished);
 
+	sync_flags = MAILDIR_UIDLIST_SYNC_PARTIAL |
+		MAILDIR_UIDLIST_SYNC_NOREFRESH;
+
 	/* if we want to assign UIDs or keywords, we require uidlist lock */
 	if ((t->ictx.flags & MAILBOX_TRANSACTION_FLAG_ASSIGN_UIDS) == 0 &&
 	    !ctx->have_keywords) {
 		/* assign the UIDs if we happen to get a lock */
-		ctx->locked = maildir_uidlist_try_lock(ctx->mbox->uidlist) > 0;
-	} else {
-		if (maildir_uidlist_lock(ctx->mbox->uidlist) <= 0) {
-			/* error or timeout - our transaction is broken */
-			maildir_transaction_save_rollback(ctx);
-			return -1;
-		}
-		ctx->locked = TRUE;
+		sync_flags |= MAILDIR_UIDLIST_SYNC_TRYLOCK;
+	}
+	ret = maildir_uidlist_sync_init(ctx->mbox->uidlist, sync_flags,
+					&ctx->uidlist_sync_ctx);
+	if (ret < 0) {
+		maildir_transaction_save_rollback(ctx);
+		return -1;
 	}
 
+	ctx->locked = ret > 0;
 	if (ctx->locked) {
-		ret = maildir_uidlist_sync_init(ctx->mbox->uidlist,
-						MAILDIR_UIDLIST_SYNC_PARTIAL,
-						&ctx->uidlist_sync_ctx);
-		i_assert(ret > 0); /* already locked, shouldn't fail */
-
-		if (maildir_sync_index_begin(ctx->mbox, NULL,
-					     &ctx->sync_ctx) < 0) {
+		if (maildir_transaction_save_commit_pre_sync(ctx) < 0) {
 			maildir_transaction_save_rollback(ctx);
 			return -1;
 		}
-
-		ctx->keywords_sync_ctx =
-			maildir_sync_get_keywords_sync_ctx(ctx->sync_ctx);
-
-		/* now that uidlist is locked, make sure all the existing mails
-		   have been added to index. we don't really look into the
-		   maildir, just add all the new mails listed in
-		   dovecot-uidlist to index. */
-		if (maildir_sync_index(ctx->sync_ctx, TRUE) < 0) {
-			maildir_transaction_save_rollback(ctx);
-			return -1;
-		}
-		sync_commit = TRUE;
-
-		/* if messages were added to index, assign them UIDs */
-		first_uid = maildir_uidlist_get_next_uid(ctx->mbox->uidlist);
-		i_assert(first_uid != 0);
-		mail_index_append_assign_uids(ctx->trans, first_uid, &next_uid);
-		i_assert(next_uid = first_uid + ctx->files_count);
-
-		/* these mails are all recent in our session */
-		for (uid = first_uid; uid < next_uid; uid++)
-			index_mailbox_set_recent_uid(&ctx->mbox->ibox, uid);
-
-		if (!ctx->mbox->ibox.keep_recent) {
-			/* maildir_sync_index() dropped recent flags from
-			   existing messages. we'll still need to drop recent
-			   flags from these newly added messages. */
-			mail_index_update_header(ctx->trans,
-				offsetof(struct mail_index_header,
-					 first_recent_uid),
-				&next_uid, sizeof(next_uid), FALSE);
-		}
-
-		/* this will work even if index isn't updated */
-		*t->ictx.first_saved_uid = first_uid;
-		*t->ictx.last_saved_uid = next_uid - 1;
 	} else {
 		/* since we couldn't lock uidlist, we'll have to drop the
 		   appends to index. */
@@ -746,11 +764,11 @@
 		mail_free(&ctx->mail);
 	}
 
-	if (sync_commit) {
+	if (ctx->locked) {
 		/* It doesn't matter if index syncing fails */
 		ctx->keywords_sync_ctx = NULL;
 		(void)maildir_sync_index_finish(&ctx->sync_ctx,
-						ret < 0, !sync_commit);
+						ret < 0, FALSE);
 	}
 
 	if (ret < 0) {
--- a/src/lib-storage/index/maildir/maildir-storage.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib-storage/index/maildir/maildir-storage.c	Mon Jun 09 05:11:18 2008 +0300
@@ -48,14 +48,15 @@
 maildir_list_rename_mailbox(struct mailbox_list *list,
 			    const char *oldname, const char *newname);
 static int
-maildir_list_iter_is_mailbox(struct mailbox_list_iterate_context *ctx
-			     	ATTR_UNUSED,
+maildir_list_iter_is_mailbox(struct mailbox_list_iterate_context *ctx,
 			     const char *dir, const char *fname,
+			     const char *mailbox_name,
 			     enum mailbox_list_file_type type,
 			     enum mailbox_info_flags *flags_r);
 static int
 maildirplusplus_iter_is_mailbox(struct mailbox_list_iterate_context *ctx,
 				const char *dir, const char *fname,
+				const char *mailbox_name,
 				enum mailbox_list_file_type type,
 				enum mailbox_info_flags *flags_r);
 
@@ -864,6 +865,7 @@
 maildir_list_iter_is_mailbox(struct mailbox_list_iterate_context *ctx
 			     	ATTR_UNUSED,
 			     const char *dir, const char *fname,
+			     const char *mailbox_name ATTR_UNUSED,
 			     enum mailbox_list_file_type type,
 			     enum mailbox_info_flags *flags_r)
 {
@@ -913,6 +915,7 @@
 static int
 maildirplusplus_iter_is_mailbox(struct mailbox_list_iterate_context *ctx,
 				const char *dir, const char *fname,
+				const char *mailbox_name ATTR_UNUSED,
 				enum mailbox_list_file_type type,
 				enum mailbox_info_flags *flags_r)
 {
--- a/src/lib-storage/index/maildir/maildir-storage.h	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib-storage/index/maildir/maildir-storage.h	Mon Jun 09 05:11:18 2008 +0300
@@ -57,6 +57,7 @@
 struct maildir_index_header {
 	uint32_t new_check_time, new_mtime, new_mtime_nsecs;
 	uint32_t cur_check_time, cur_mtime, cur_mtime_nsecs;
+	uint32_t uidlist_mtime, uidlist_mtime_nsecs, uidlist_size;
 };
 
 struct maildir_list_index_record {
--- a/src/lib-storage/index/maildir/maildir-sync-index.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib-storage/index/maildir/maildir-sync-index.c	Mon Jun 09 05:11:18 2008 +0300
@@ -186,6 +186,46 @@
 	return 0;
 }
 
+static bool
+maildir_index_header_has_changed(const struct maildir_index_header *old_hdr,
+				 const struct maildir_index_header *new_hdr)
+{
+#define DIR_DELAYED_REFRESH(hdr, name) \
+	((hdr)->name ## _check_time <= \
+		(hdr)->name ## _mtime + MAILDIR_SYNC_SECS)
+
+	if (old_hdr->new_mtime != new_hdr->new_mtime ||
+	    old_hdr->new_mtime_nsecs != new_hdr->new_mtime_nsecs ||
+	    old_hdr->cur_mtime != new_hdr->cur_mtime ||
+	    old_hdr->cur_mtime_nsecs != new_hdr->cur_mtime_nsecs ||
+	    old_hdr->uidlist_mtime != new_hdr->uidlist_mtime ||
+	    old_hdr->uidlist_mtime_nsecs != new_hdr->uidlist_mtime_nsecs ||
+	    old_hdr->uidlist_size != new_hdr->uidlist_size)
+		return TRUE;
+
+	return DIR_DELAYED_REFRESH(old_hdr, new) !=
+		DIR_DELAYED_REFRESH(new_hdr, new) ||
+		DIR_DELAYED_REFRESH(old_hdr, cur) !=
+		DIR_DELAYED_REFRESH(new_hdr, cur);
+}
+
+static void
+maildir_sync_index_update_ext_header(struct maildir_index_sync_context *ctx)
+{
+	struct maildir_mailbox *mbox = ctx->mbox;
+	const void *data;
+	size_t data_size;
+
+	mail_index_get_header_ext(mbox->ibox.view, mbox->maildir_ext_id,
+				  &data, &data_size);
+	if (data_size != sizeof(mbox->maildir_hdr) ||
+	    maildir_index_header_has_changed(data, &mbox->maildir_hdr)) {
+		mail_index_update_header_ext(ctx->trans, mbox->maildir_ext_id,
+					     0, &mbox->maildir_hdr,
+					     sizeof(mbox->maildir_hdr));
+	}
+}
+
 int maildir_sync_index_finish(struct maildir_index_sync_context **_ctx,
 			      bool failed, bool cancel)
 {
@@ -198,6 +238,8 @@
 	if (ret < 0 || cancel)
 		mail_index_sync_rollback(&ctx->sync_ctx);
 	else {
+		maildir_sync_index_update_ext_header(ctx);
+
 		/* Set syncing_commit=TRUE so that if any sync callbacks try
 		   to access mails which got lost (eg. expunge callback trying
 		   to open the file which was just unlinked) we don't try to
@@ -219,43 +261,6 @@
 	return ret;
 }
 
-static bool
-maildir_index_header_has_changed(const struct maildir_index_header *old_hdr,
-				 const struct maildir_index_header *new_hdr)
-{
-#define DIR_DELAYED_REFRESH(hdr, name) \
-	((hdr)->name ## _check_time <= \
-		(hdr)->name ## _mtime + MAILDIR_SYNC_SECS)
-
-	if (old_hdr->new_mtime != new_hdr->new_mtime ||
-	    old_hdr->cur_mtime != new_hdr->cur_mtime ||
-	    old_hdr->new_mtime_nsecs != new_hdr->new_mtime_nsecs ||
-	    old_hdr->cur_mtime_nsecs != new_hdr->cur_mtime_nsecs)
-		return TRUE;
-
-	return DIR_DELAYED_REFRESH(old_hdr, new) !=
-		DIR_DELAYED_REFRESH(new_hdr, new) ||
-		DIR_DELAYED_REFRESH(old_hdr, cur) !=
-		DIR_DELAYED_REFRESH(new_hdr, cur);
-}
-
-static void
-maildir_index_update_ext_header(struct maildir_mailbox *mbox,
-				struct mail_index_transaction *trans)
-{
-	const void *data;
-	size_t data_size;
-
-	mail_index_get_header_ext(mbox->ibox.view, mbox->maildir_ext_id,
-				  &data, &data_size);
-	if (data_size != sizeof(mbox->maildir_hdr) ||
-	    maildir_index_header_has_changed(data, &mbox->maildir_hdr)) {
-		mail_index_update_header_ext(trans, mbox->maildir_ext_id, 0,
-					     &mbox->maildir_hdr,
-					     sizeof(mbox->maildir_hdr));
-	}
-}
-
 int maildir_sync_index(struct maildir_index_sync_context *ctx,
 		       bool partial)
 {
@@ -272,6 +277,7 @@
 	const char *filename;
 	ARRAY_TYPE(keyword_indexes) idx_keywords;
 	uint32_t uid_validity, next_uid, hdr_next_uid, first_recent_uid;
+	uint32_t first_uid;
 	unsigned int changes = 0;
 	int ret = 0;
 	bool expunged, full_rescan = FALSE;
@@ -279,6 +285,7 @@
 	i_assert(!mbox->syncing_commit);
 	i_assert(maildir_uidlist_is_locked(mbox->uidlist));
 
+	first_uid = 1;
 	hdr = mail_index_get_header(view);
 	uid_validity = maildir_uidlist_get_uid_validity(mbox->uidlist);
 	if (uid_validity != hdr->uid_validity &&
@@ -289,8 +296,10 @@
 		i_warning("Maildir %s: UIDVALIDITY changed (%u -> %u)",
 			  mbox->path, hdr->uid_validity, uid_validity);
 		mail_index_reset(trans);
+		index_mailbox_reset_uidvalidity(&mbox->ibox);
 		maildir_uidlist_set_next_uid(mbox->uidlist, 1, TRUE);
 
+		first_uid = hdr->messages_count + 1;
 		memset(&empty_hdr, 0, sizeof(empty_hdr));
 		empty_hdr.next_uid = 1;
 		hdr = &empty_hdr;
@@ -438,8 +447,13 @@
 	   appended messages. */
 	view2 = mail_index_transaction_open_updated_view(trans);
 	if (mail_index_lookup_seq_range(view2, first_recent_uid, (uint32_t)-1,
-					&seq, &seq2))
+					&seq, &seq2) && seq2 >= first_uid) {
+		if (seq < first_uid) {
+			/* UIDVALIDITY changed, skip over the old messages */
+			seq = first_uid;
+		}
 		index_mailbox_set_recent_seq(&mbox->ibox, view2, seq, seq2);
+	}
 	mail_index_view_close(&view2);
 
 	if (ctx->uidlist_sync_ctx != NULL) {
@@ -452,7 +466,6 @@
 
 	if (ctx->changed)
 		mbox->maildir_hdr.cur_mtime = time(NULL);
-	maildir_index_update_ext_header(mbox, trans);
 
 	if (uid_validity == 0) {
 		uid_validity = hdr->uid_validity != 0 ?
--- a/src/lib-storage/index/maildir/maildir-sync.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib-storage/index/maildir/maildir-sync.c	Mon Jun 09 05:11:18 2008 +0300
@@ -403,19 +403,11 @@
 	if (new_dir) {
 		ctx->mbox->maildir_hdr.new_check_time = now;
 		ctx->mbox->maildir_hdr.new_mtime = st.st_mtime;
-#ifdef HAVE_STAT_TV_NSEC
-		ctx->mbox->maildir_hdr.new_mtime_nsecs = st.st_mtim.tv_nsec;
-#else
-		ctx->mbox->maildir_hdr.new_mtime_nsecs = 0;
-#endif
+		ctx->mbox->maildir_hdr.new_mtime_nsecs = ST_MTIME_NSEC(st);
 	} else {
 		ctx->mbox->maildir_hdr.cur_check_time = now;
 		ctx->mbox->maildir_hdr.cur_mtime = st.st_mtime;
-#ifdef HAVE_STAT_TV_NSEC
-		ctx->mbox->maildir_hdr.cur_mtime_nsecs = st.st_mtim.tv_nsec;
-#else
-		ctx->mbox->maildir_hdr.cur_mtime_nsecs = 0;
-#endif
+		ctx->mbox->maildir_hdr.cur_mtime_nsecs = ST_MTIME_NSEC(st);
 	}
 
 	src = t_str_new(1024);
@@ -512,7 +504,7 @@
 		(move_count <= MAILDIR_RENAME_RESCAN_COUNT ? 0 : 1);
 }
 
-static int maildir_header_refresh(struct maildir_mailbox *mbox)
+int maildir_sync_header_refresh(struct maildir_mailbox *mbox)
 {
 	const void *data;
 	size_t data_size;
@@ -529,10 +521,8 @@
 		return 0;
 	}
 
-	if (data_size != sizeof(mbox->maildir_hdr))
-		i_warning("Maildir %s: Invalid header record size", mbox->path);
-	else
-		memcpy(&mbox->maildir_hdr, data, sizeof(mbox->maildir_hdr));
+	memcpy(&mbox->maildir_hdr, data,
+	       I_MIN(sizeof(mbox->maildir_hdr), data_size));
 	return 0;
 }
 
@@ -540,13 +530,6 @@
 				    const char *new_dir, const char *cur_dir,
 				    bool *new_changed_r, bool *cur_changed_r)
 {
-#ifdef HAVE_STAT_TV_NSEC
-#  define DIR_NSECS_CHANGED(st, hdr, name) \
-	((unsigned int)(st).st_mtim.tv_nsec != (hdr)->name ## _mtime_nsecs)
-#else
-#  define DIR_NSECS_CHANGED(st, hdr, name) 0
-#endif
-
 #define DIR_DELAYED_REFRESH(hdr, name) \
 	((hdr)->name ## _check_time <= \
 		(hdr)->name ## _mtime + MAILDIR_SYNC_SECS && \
@@ -555,14 +538,14 @@
 
 #define DIR_MTIME_CHANGED(st, hdr, name) \
 	((st).st_mtime != (time_t)(hdr)->name ## _mtime || \
-	 DIR_NSECS_CHANGED(st, hdr, name))
+	 !ST_NTIMES_EQUAL(ST_MTIME_NSEC(st), (hdr)->name ## _mtime_nsecs))
 
 	struct maildir_index_header *hdr = &mbox->maildir_hdr;
 	struct stat new_st, cur_st;
 	bool refreshed = FALSE, check_new = FALSE, check_cur = FALSE;
 
 	if (mbox->maildir_hdr.new_mtime == 0) {
-		if (maildir_header_refresh(mbox) < 0)
+		if (maildir_sync_header_refresh(mbox) < 0)
 			return -1;
 		if (mbox->maildir_hdr.new_mtime == 0) {
 			/* first sync */
@@ -577,7 +560,7 @@
 	if (DIR_DELAYED_REFRESH(hdr, new) ||
 	    DIR_DELAYED_REFRESH(hdr, cur)) {
 		/* refresh index and try again */
-		if (maildir_header_refresh(mbox) < 0)
+		if (maildir_sync_header_refresh(mbox) < 0)
 			return -1;
 		refreshed = TRUE;
 
@@ -610,7 +593,7 @@
 			break;
 
 		/* refresh index and try again */
-		if (maildir_header_refresh(mbox) < 0)
+		if (maildir_sync_header_refresh(mbox) < 0)
 			return -1;
 		refreshed = TRUE;
 	}
@@ -760,7 +743,6 @@
 					&ctx->uidlist_sync_ctx);
 	if (ret <= 0) {
 		/* failure / timeout */
-		i_assert(ret < 0 || !forced);
 		return ret;
 	}
 	ctx->locked = maildir_uidlist_is_locked(ctx->mbox->uidlist);
--- a/src/lib-storage/index/maildir/maildir-sync.h	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib-storage/index/maildir/maildir-sync.h	Mon Jun 09 05:11:18 2008 +0300
@@ -24,6 +24,8 @@
 maildir_storage_sync_init(struct mailbox *box, enum mailbox_sync_flags flags);
 int maildir_storage_sync_force(struct maildir_mailbox *mbox, uint32_t uid);
 
+int maildir_sync_header_refresh(struct maildir_mailbox *mbox);
+
 int maildir_sync_index_begin(struct maildir_mailbox *mbox,
 			     struct maildir_sync_context *maildir_sync_ctx,
 			     struct maildir_index_sync_context **ctx_r);
--- a/src/lib-storage/index/maildir/maildir-uidlist.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib-storage/index/maildir/maildir-uidlist.c	Mon Jun 09 05:11:18 2008 +0300
@@ -49,6 +49,7 @@
 /* how many seconds to wait before overriding uidlist.lock */
 #define UIDLIST_LOCK_STALE_TIMEOUT (60*2)
 
+#define UIDLIST_VERSION 3
 #define UIDLIST_COMPRESS_PERCENTAGE 75
 
 #define UIDLIST_IS_LOCKED(uidlist) \
@@ -90,6 +91,7 @@
 
 	unsigned int recreate:1;
 	unsigned int initial_read:1;
+	unsigned int initial_hdr_read:1;
 	unsigned int initial_sync:1;
 };
 
@@ -124,7 +126,7 @@
 					  struct maildir_uidlist_rec **rec_r);
 
 static int maildir_uidlist_lock_timeout(struct maildir_uidlist *uidlist,
-					bool nonblock)
+					bool nonblock, bool refresh)
 {
 	struct mailbox *box = &uidlist->ibox->box;
 	const char *control_dir, *path;
@@ -170,22 +172,25 @@
 
 	uidlist->lock_count++;
 
-	/* make sure we have the latest changes before changing anything */
-	if (maildir_uidlist_refresh(uidlist) < 0) {
-		maildir_uidlist_unlock(uidlist);
-		return -1;
+	if (refresh) {
+		/* make sure we have the latest changes before
+		   changing anything */
+		if (maildir_uidlist_refresh(uidlist) < 0) {
+			maildir_uidlist_unlock(uidlist);
+			return -1;
+		}
 	}
 	return 1;
 }
 
 int maildir_uidlist_lock(struct maildir_uidlist *uidlist)
 {
-	return maildir_uidlist_lock_timeout(uidlist, FALSE);
+	return maildir_uidlist_lock_timeout(uidlist, FALSE, TRUE);
 }
 
 int maildir_uidlist_try_lock(struct maildir_uidlist *uidlist)
 {
-	return maildir_uidlist_lock_timeout(uidlist, TRUE);
+	return maildir_uidlist_lock_timeout(uidlist, TRUE, TRUE);
 }
 
 int maildir_uidlist_lock_touch(struct maildir_uidlist *uidlist)
@@ -293,6 +298,36 @@
 		(*rec1)->uid > (*rec2)->uid ? 1 : 0;
 }
 
+static void ATTR_FORMAT(2, 3)
+maildir_uidlist_set_corrupted(struct maildir_uidlist *uidlist,
+			      const char *fmt, ...)
+{
+	struct mail_storage *storage = uidlist->ibox->box.storage;
+	va_list args;
+
+	va_start(args, fmt);
+	mail_storage_set_critical(storage, "Broken file %s line %u: %s",
+				  uidlist->path, uidlist->read_line_count,
+				  t_strdup_vprintf(fmt, args));
+	va_end(args);
+}
+
+static void maildir_uidlist_update_hdr(struct maildir_uidlist *uidlist,
+				       const struct stat *st)
+{
+	struct maildir_index_header *mhdr;
+
+	if (uidlist->mbox == NULL) {
+		/* dbox is using this */
+		return;
+	}
+
+	mhdr = &uidlist->mbox->maildir_hdr;
+	mhdr->uidlist_mtime = st->st_mtime;
+	mhdr->uidlist_mtime_nsecs = ST_MTIME_NSEC(*st);
+	mhdr->uidlist_size = st->st_size;
+}
+
 static void
 maildir_uidlist_records_array_delete(struct maildir_uidlist *uidlist,
 				     struct maildir_uidlist_rec *rec)
@@ -341,22 +376,8 @@
 	return TRUE;
 }
 
-static void ATTR_FORMAT(2, 3)
-maildir_uidlist_set_corrupted(struct maildir_uidlist *uidlist,
-			      const char *fmt, ...)
-{
-	struct mail_storage *storage = uidlist->ibox->box.storage;
-	va_list args;
-
-	va_start(args, fmt);
-	mail_storage_set_critical(storage, "Broken file %s line %u: %s",
-				  uidlist->path, uidlist->read_line_count,
-				  t_strdup_vprintf(fmt, args));
-	va_end(args);
-}
-
-static int maildir_uidlist_next(struct maildir_uidlist *uidlist,
-				const char *line)
+static bool maildir_uidlist_next(struct maildir_uidlist *uidlist,
+				 const char *line)
 {
 	struct maildir_uidlist_rec *rec, *old_rec;
 	uint32_t uid;
@@ -371,19 +392,19 @@
 		/* invalid file */
 		maildir_uidlist_set_corrupted(uidlist, "Invalid data: %s",
 					      line);
-		return 0;
+		return FALSE;
 	}
 	if (uid <= uidlist->prev_read_uid) {
 		maildir_uidlist_set_corrupted(uidlist, 
 					      "UIDs not ordered (%u > %u)",
 					      uid, uidlist->prev_read_uid);
-		return 0;
+		return FALSE;
 	}
 	uidlist->prev_read_uid = uid;
 
 	if (uid <= uidlist->last_seen_uid) {
 		/* we already have this */
-		return 1;
+		return TRUE;
 	}
         uidlist->last_seen_uid = uid;
 
@@ -391,7 +412,7 @@
 		maildir_uidlist_set_corrupted(uidlist, 
 			"UID larger than next_uid (%u >= %u)",
 			uid, uidlist->next_uid);
-		return 0;
+		return FALSE;
 	}
 
 	rec = p_new(uidlist->record_pool, struct maildir_uidlist_rec, 1);
@@ -400,7 +421,7 @@
 
 	while (*line == ' ') line++;
 
-	if (uidlist->version == 3) {
+	if (uidlist->version == UIDLIST_VERSION) {
 		/* read extended fields */
 		bool ret;
 
@@ -411,16 +432,25 @@
 		if (!ret) {
 			maildir_uidlist_set_corrupted(uidlist, 
 				"Invalid extended fields: %s", line);
-			return 0;
+			return FALSE;
 		}
 	}
 
+	if (strchr(line, '/') != NULL) {
+		maildir_uidlist_set_corrupted(uidlist, 
+			"%s: Broken filename at line %u: %s",
+			uidlist->path, uidlist->read_line_count, line);
+		return FALSE;
+	}
+
 	old_rec = hash_lookup(uidlist->files, line);
 	if (old_rec != NULL) {
 		/* This can happen if expunged file is moved back and the file
 		   was appended to uidlist. */
-		i_warning("%s: Duplicate file entry at line %u: %s",
-			  uidlist->path, uidlist->read_line_count, line);
+		i_warning("%s: Duplicate file entry at line %u: "
+			  "%s (uid %u -> %u)",
+			  uidlist->path, uidlist->read_line_count, line,
+			  old_rec->uid, uid);
 		/* Delete the old UID */
 		maildir_uidlist_records_array_delete(uidlist, old_rec);
 		/* Replace the old record with this new one */
@@ -432,7 +462,7 @@
 	rec->filename = p_strdup(uidlist->record_pool, line);
 	hash_insert(uidlist->files, rec->filename, rec);
 	array_append(&uidlist->records, &rec, 1);
-	return 1;
+	return TRUE;
 }
 
 static int maildir_uidlist_read_header(struct maildir_uidlist *uidlist,
@@ -474,7 +504,7 @@
 			return 0;
 		}
 		break;
-	case 3:
+	case UIDLIST_VERSION:
 		ext_hdr = uidlist->hdr_extensions;
 		str_truncate(ext_hdr, 0);
 		while (*line != '\0') T_BEGIN {
@@ -623,6 +653,7 @@
 		uidlist->fd_ino = st.st_ino;
 		uidlist->fd_size = st.st_size;
 		uidlist->last_read_offset = input->v_offset;
+		maildir_uidlist_update_hdr(uidlist, &st);
         } else {
                 /* I/O error */
                 if (input->stream_errno == ESTALE && try_retry)
@@ -643,18 +674,15 @@
 }
 
 static int
-maildir_uidlist_has_changed(struct maildir_uidlist *uidlist, bool *recreated_r)
+maildir_uidlist_stat(struct maildir_uidlist *uidlist, struct stat *st_r)
 {
 	struct mail_storage *storage = uidlist->ibox->box.storage;
-        struct stat st;
-
-	*recreated_r = FALSE;
 
 	if ((storage->flags & MAIL_STORAGE_FLAG_NFS_FLUSH_STORAGE) != 0) {
 		nfs_flush_file_handle_cache(uidlist->path);
 		nfs_flush_attr_cache_unlocked(uidlist->path);
 	}
-	if (nfs_safe_stat(uidlist->path, &st) < 0) {
+	if (nfs_safe_stat(uidlist->path, st_r) < 0) {
 		if (errno != ENOENT) {
 			mail_storage_set_critical(storage,
 				"stat(%s) failed: %m", uidlist->path);
@@ -662,6 +690,20 @@
 		}
 		return 0;
 	}
+	return 1;
+}
+
+static int
+maildir_uidlist_has_changed(struct maildir_uidlist *uidlist, bool *recreated_r)
+{
+	struct mail_storage *storage = uidlist->ibox->box.storage;
+        struct stat st;
+	int ret;
+
+	*recreated_r = FALSE;
+
+	if ((ret = maildir_uidlist_stat(uidlist, &st)) <= 0)
+		return ret;
 
 	if (st.st_ino != uidlist->fd_ino ||
 	    !CMP_DEV_T(st.st_dev, uidlist->fd_dev)) {
@@ -717,11 +759,40 @@
 		/* ESTALE - try reopening and rereading */
 		maildir_uidlist_close(uidlist);
         }
-	if (ret >= 0)
+	if (ret >= 0) {
 		uidlist->initial_read = TRUE;
+		uidlist->initial_hdr_read = TRUE;
+	}
         return ret;
 }
 
+int maildir_uidlist_refresh_fast_init(struct maildir_uidlist *uidlist)
+{
+	const struct maildir_index_header *mhdr = &uidlist->mbox->maildir_hdr;
+	const struct mail_index_header *hdr;
+	struct stat st;
+	int ret;
+
+	if (uidlist->fd != -1)
+		return maildir_uidlist_refresh(uidlist);
+
+	if ((ret = maildir_uidlist_stat(uidlist, &st)) < 0)
+		return ret;
+
+	if (st.st_size == mhdr->uidlist_size &&
+	    st.st_mtime == mhdr->uidlist_mtime &&
+	    ST_NTIMES_EQUAL(ST_MTIME_NSEC(st), mhdr->uidlist_mtime_nsecs)) {
+		/* index is up-to-date */
+		hdr = mail_index_get_header(uidlist->mbox->ibox.view);
+		uidlist->uid_validity = hdr->uid_validity;
+		uidlist->next_uid = hdr->next_uid;
+		uidlist->initial_hdr_read = TRUE;
+		return 1;
+	} else {
+		return maildir_uidlist_refresh(uidlist);
+	}
+}
+
 static struct maildir_uidlist_rec *
 maildir_uidlist_lookup_rec(struct maildir_uidlist *uidlist, uint32_t uid,
 			   unsigned int *idx_r)
@@ -763,12 +834,16 @@
 
 	fname = maildir_uidlist_lookup_nosync(uidlist, uid, flags_r);
 	if (fname == NULL) {
-		if (uidlist->fd != -1 || uidlist->mbox == NULL)
-			return NULL;
-
-		/* the uidlist doesn't exist. */
-		if (maildir_storage_sync_force(uidlist->mbox, uid) < 0)
-			return NULL;
+		if (uidlist->fd != -1 || uidlist->mbox == NULL) {
+			/* refresh uidlist and check again in case it was added
+			   after the last mailbox sync */
+			if (maildir_uidlist_refresh(uidlist) < 0)
+				return NULL;
+		} else {
+			/* the uidlist doesn't exist. */
+			if (maildir_storage_sync_force(uidlist->mbox, uid) < 0)
+				return NULL;
+		}
 
 		/* try again */
 		fname = maildir_uidlist_lookup_nosync(uidlist, uid, flags_r);
@@ -823,7 +898,7 @@
 
 uint32_t maildir_uidlist_get_next_uid(struct maildir_uidlist *uidlist)
 {
-	return !uidlist->initial_read ? 0 : uidlist->next_uid;
+	return !uidlist->initial_hdr_read ? 0 : uidlist->next_uid;
 }
 
 void maildir_uidlist_set_uid_validity(struct maildir_uidlist *uidlist,
@@ -877,8 +952,10 @@
 			p += len;
 		}
 	}
-	buffer_append_c(buf, key);
-	buffer_append(buf, value, strlen(value) + 1);
+	if (value != NULL) {
+		buffer_append_c(buf, key);
+		buffer_append(buf, value, strlen(value) + 1);
+	}
 	buffer_append_c(buf, '\0');
 
 	rec->extensions = p_malloc(uidlist->record_pool, buf->used);
@@ -917,7 +994,7 @@
 
 	if (output->offset == 0) {
 		i_assert(first_idx == 0);
-		uidlist->version = 3;
+		uidlist->version = UIDLIST_VERSION;
 
 		i_assert(uidlist->uid_validity != 0);
 		i_assert(uidlist->next_uid > 0);
@@ -960,7 +1037,6 @@
 	if (ret < 0) {
 		mail_storage_set_critical(storage,
 			"o_stream_send(%s) failed: %m", path);
-		(void)close(fd);
 		return -1;
 	}
 
@@ -968,7 +1044,6 @@
 		if (fdatasync(fd) < 0) {
 			mail_storage_set_critical(storage,
 				"fdatasync(%s) failed: %m", path);
-			(void)close(fd);
 			return -1;
 		}
 	}
@@ -984,6 +1059,8 @@
 	uoff_t file_size;
 	int i, fd, ret;
 
+	i_assert(uidlist->initial_read);
+
 	control_dir = mailbox_list_get_path(box->storage->list, box->name,
 					    MAILBOX_LIST_PATH_TYPE_CONTROL);
 	temp_path = t_strconcat(control_dir,
@@ -1031,14 +1108,14 @@
 				"unlink(%s) failed: %m", temp_path);
 		}
 	} else if (fstat(fd, &st) < 0) {
-		i_error("fstat(%s) failed: %m", temp_path);
-		(void)close(fd);
+		mail_storage_set_critical(box->storage,
+			"fstat(%s) failed: %m", temp_path);
 		ret = -1;
 	} else if (file_size != (uoff_t)st.st_size) {
 		i_assert(!file_dotlock_is_locked(uidlist->dotlock));
-		i_error("Maildir uidlist dotlock overridden: %s",
+		mail_storage_set_critical(box->storage,
+			"Maildir uidlist dotlock overridden: %s",
 			uidlist->path);
-		(void)close(fd);
 		ret = -1;
 	} else {
 		maildir_uidlist_close(uidlist);
@@ -1048,7 +1125,10 @@
 		uidlist->fd_size = st.st_size;
 		uidlist->last_read_offset = st.st_size;
 		uidlist->recreate = FALSE;
+		maildir_uidlist_update_hdr(uidlist, &st);
 	}
+	if (ret < 0)
+		(void)close(fd);
 	return ret;
 }
 
@@ -1066,9 +1146,29 @@
 	return ret;
 }
 
+static bool maildir_uidlist_want_recreate(struct maildir_uidlist_sync_ctx *ctx)
+{
+	struct maildir_uidlist *uidlist = ctx->uidlist;
+	unsigned int min_rewrite_count;
+
+	if (!uidlist->initial_read)
+		return FALSE;
+
+	if (uidlist->recreate || uidlist->fd == -1 ||
+	    uidlist->version != UIDLIST_VERSION ||
+	    ctx->finish_change_counter != uidlist->change_counter)
+		return TRUE;
+
+	min_rewrite_count =
+		(uidlist->read_records_count + ctx->new_files_count) *
+		UIDLIST_COMPRESS_PERCENTAGE / 100;
+	return min_rewrite_count >= array_count(&uidlist->records);
+}
+
 static int maildir_uidlist_sync_update(struct maildir_uidlist_sync_ctx *ctx)
 {
 	struct maildir_uidlist *uidlist = ctx->uidlist;
+	struct stat st;
 	uoff_t file_size;
 
 	if (uidlist->uid_validity == 0) {
@@ -1080,13 +1180,22 @@
 			hdr->uid_validity : (uint32_t)ioloop_time;
 	}
 
-	if (ctx->uidlist->recreate || uidlist->fd == -1 ||
-	    uidlist->version != 3 ||
-	    ctx->finish_change_counter != ctx->uidlist->change_counter ||
-	    (uidlist->read_records_count + ctx->new_files_count) *
-	    UIDLIST_COMPRESS_PERCENTAGE / 100 >= array_count(&uidlist->records))
+
+	if (maildir_uidlist_want_recreate(ctx))
 		return maildir_uidlist_recreate(uidlist);
 
+	if (uidlist->fd == -1) {
+		/* NOREFRESH flag used. we're just appending some messages. */
+		i_assert(uidlist->initial_hdr_read);
+
+		uidlist->fd = nfs_safe_open(uidlist->path, O_RDWR);
+		if (uidlist->fd == -1) {
+			mail_storage_set_critical(uidlist->ibox->box.storage,
+				"open(%s) failed: %m", uidlist->path);
+			return -1;
+		}
+	}
+
 	i_assert(ctx->first_unwritten_pos != (unsigned int)-1);
 
 	if (lseek(uidlist->fd, 0, SEEK_END) < 0) {
@@ -1099,7 +1208,19 @@
 				     ctx->first_unwritten_pos, &file_size) < 0)
 		return -1;
 
-	uidlist->last_read_offset = file_size;
+	if (fstat(uidlist->fd, &st) < 0) {
+		mail_storage_set_critical(uidlist->ibox->box.storage,
+			"fstat(%s) failed: %m", uidlist->path);
+		return -1;
+	}
+	if ((uoff_t)st.st_size != file_size) {
+		i_warning("%s: file size changed unexpectedly after write",
+			  uidlist->path);
+	} else {
+		uidlist->fd_size = st.st_size;
+		uidlist->last_read_offset = st.st_size;
+		maildir_uidlist_update_hdr(uidlist, &st);
+	}
 	return 0;
 }
 
@@ -1124,27 +1245,26 @@
 			      struct maildir_uidlist_sync_ctx **sync_ctx_r)
 {
 	struct maildir_uidlist_sync_ctx *ctx;
-	bool locked;
+	bool nonblock, refresh, locked;
 	int ret;
 
-	if ((sync_flags & MAILDIR_UIDLIST_SYNC_TRYLOCK) == 0) {
-		if ((ret = maildir_uidlist_lock(uidlist)) <= 0)
+	nonblock = (sync_flags & MAILDIR_UIDLIST_SYNC_TRYLOCK) != 0;
+	refresh = (sync_flags & MAILDIR_UIDLIST_SYNC_NOREFRESH) == 0;
+
+	ret = maildir_uidlist_lock_timeout(uidlist, nonblock, refresh);
+	if (ret <= 0) {
+		if (ret < 0 || !nonblock)
 			return ret;
-	} else {
-		if ((ret = maildir_uidlist_try_lock(uidlist)) < 0)
-			return -1;
-		if (ret == 0) {
-			/* couldn't lock it */
-			if ((sync_flags & MAILDIR_UIDLIST_SYNC_FORCE) == 0)
-				return 0;
-			/* forcing the lock */
-		}
-	}
-	locked = ret > 0;
 
-	if (!locked) {
+		/* couldn't lock it */
+		if ((sync_flags & MAILDIR_UIDLIST_SYNC_FORCE) == 0)
+			return 0;
+		/* forcing the sync anyway */
 		if (maildir_uidlist_refresh(uidlist) < 0)
 			return -1;
+		locked = FALSE;
+	} else {
+		locked = TRUE;
 	}
 
 	*sync_ctx_r = ctx = i_new(struct maildir_uidlist_sync_ctx, 1);
@@ -1430,6 +1550,13 @@
 
 	ctx->finished = TRUE;
 	ctx->uidlist->initial_sync = TRUE;
+
+	i_assert(ctx->locked || !ctx->changed);
+	if ((ctx->changed || ctx->uidlist->recreate) &&
+	    !ctx->failed && ctx->locked) T_BEGIN {
+		if (maildir_uidlist_sync_update(ctx) < 0)
+			ctx->failed = TRUE;
+	} T_END;
 }
 
 int maildir_uidlist_sync_deinit(struct maildir_uidlist_sync_ctx **_ctx)
@@ -1441,18 +1568,8 @@
 
 	if (!ctx->finished)
 		maildir_uidlist_sync_finish(ctx);
-
 	if (ctx->partial)
 		maildir_uidlist_mark_all(ctx->uidlist, FALSE);
-
-	i_assert(ctx->locked || !ctx->changed);
-	if ((ctx->changed || ctx->uidlist->recreate) &&
-	    !ctx->failed && ctx->locked) {
-		T_BEGIN {
-			ret = maildir_uidlist_sync_update(ctx);
-		} T_END;
-	}
-
 	if (ctx->locked)
 		maildir_uidlist_unlock(ctx->uidlist);
 
--- a/src/lib-storage/index/maildir/maildir-uidlist.h	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib-storage/index/maildir/maildir-uidlist.h	Mon Jun 09 05:11:18 2008 +0300
@@ -11,7 +11,8 @@
 	MAILDIR_UIDLIST_SYNC_PARTIAL	= 0x01,
 	MAILDIR_UIDLIST_SYNC_KEEP_STATE	= 0x02,
 	MAILDIR_UIDLIST_SYNC_FORCE	= 0x04,
-	MAILDIR_UIDLIST_SYNC_TRYLOCK	= 0x08
+	MAILDIR_UIDLIST_SYNC_TRYLOCK	= 0x08,
+	MAILDIR_UIDLIST_SYNC_NOREFRESH	= 0x10
 };
 
 enum maildir_uidlist_rec_flag {
@@ -55,6 +56,9 @@
    and storage has NFS_FLUSH flag set, the NFS attribute cache is flushed to
    make sure that we see the latest uidlist file. */
 int maildir_uidlist_refresh(struct maildir_uidlist *uidlist);
+/* Like maildir_uidlist_refresh(), but if uidlist isn't opened yet, try to
+   fill in the uidvalidity/nextuid from index file instead. */
+int maildir_uidlist_refresh_fast_init(struct maildir_uidlist *uidlist);
 
 /* Returns uidlist record for given filename, or NULL if not found. */
 const char *
@@ -76,6 +80,7 @@
 void maildir_uidlist_set_next_uid(struct maildir_uidlist *uidlist,
 				  uint32_t next_uid, bool force);
 
+/* Update extended record. value=NULL removes the key. */
 void maildir_uidlist_set_ext(struct maildir_uidlist *uidlist, uint32_t uid,
 			     enum maildir_uidlist_rec_ext_key key,
 			     const char *value);
--- a/src/lib-storage/index/mbox/istream-raw-mbox.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib-storage/index/mbox/istream-raw-mbox.c	Mon Jun 09 05:11:18 2008 +0300
@@ -238,15 +238,19 @@
 				   FIXME: if From-line is longer than input
 				   buffer, we break. probably irrelevant.. */
 				i++;
-				from_after_pos = i;
-				from_start_pos = i - 6;
-				if (from_start_pos > 0 &&
-				    buf[from_start_pos-1] == '\r') {
-					/* CR also belongs to it. */
-					crlf_ending = TRUE;
-					from_start_pos--;
-				} else {
-					crlf_ending = FALSE;
+				if (rstream->hdr_offset + rstream->mail_size ==
+				    stream->istream.v_offset + i - 6 ||
+				    rstream->mail_size == (uoff_t)-1) {
+					from_after_pos = i;
+					from_start_pos = i - 6;
+					if (from_start_pos > 0 &&
+					    buf[from_start_pos-1] == '\r') {
+						/* CR also belongs to it. */
+						crlf_ending = TRUE;
+						from_start_pos--;
+					} else {
+						crlf_ending = FALSE;
+					}
 				}
 				fromp = mbox_from;
 			} else if (from_start_pos != (size_t)-1) {
@@ -290,6 +294,17 @@
 			new_pos--;
 	}
 
+	if (stream->istream.v_offset -
+	    rstream->hdr_offset + new_pos > rstream->mail_size) {
+		/* istream_raw_mbox_set_next_offset() used invalid
+		   cached next_offset? */
+		i_error("Next message unexpectedly lost from %"PRIuUOFF_T,
+			rstream->hdr_offset + rstream->mail_size);
+		rstream->eof = TRUE;
+		rstream->corrupted = TRUE;
+		return -1;
+	}
+
 	stream->buffer = buf;
 	if (new_pos == stream->pos) {
 		if (stream->istream.eof || ret > 0)
@@ -379,8 +394,7 @@
 	char *sender;
 
 	/* minimal: "From x Thu Nov 29 22:33:52 2001" = 31 chars */
-	if (i_stream_read_data(rstream->istream.parent, &data, &size, 30) == -1)
-		return -1;
+	(void)i_stream_read_data(rstream->istream.parent, &data, &size, 30);
 
 	if ((size == 1 && data[0] == '\n') ||
 	    (size == 2 && data[0] == '\r' && data[1] == '\n')) {
@@ -469,33 +483,41 @@
 	return rstream->body_offset;
 }
 
-uoff_t istream_raw_mbox_get_body_size(struct istream *stream, uoff_t body_size)
+uoff_t istream_raw_mbox_get_body_size(struct istream *stream,
+				      uoff_t expected_body_size)
 {
 	struct raw_mbox_istream *rstream =
 		(struct raw_mbox_istream *)stream->real_stream;
 	const unsigned char *data;
 	size_t size;
-	uoff_t old_offset;
+	uoff_t old_offset, body_size;
 
 	i_assert(rstream->hdr_offset != (uoff_t)-1);
 	i_assert(rstream->body_offset != (uoff_t)-1);
 
-	if (rstream->mail_size != (uoff_t)-1) {
-		return rstream->mail_size -
-			(rstream->body_offset - rstream->hdr_offset);
-	}
-
+	body_size = rstream->mail_size == (uoff_t)-1 ? (uoff_t)-1 :
+		rstream->mail_size - (rstream->body_offset -
+				      rstream->hdr_offset);
 	old_offset = stream->v_offset;
-	if (body_size != (uoff_t)-1) {
+	if (expected_body_size != (uoff_t)-1) {
+		/* if we already have the existing body size, use it as long as
+		   it's >= expected body_size. otherwise the previous parsing
+		   may have stopped at a From_-line that belongs to the body. */
+		if (body_size != (uoff_t)-1 && body_size >= expected_body_size)
+			return body_size;
+
 		i_stream_seek(rstream->istream.parent,
-			      rstream->body_offset + body_size);
+			      rstream->body_offset + expected_body_size);
 		if (istream_raw_mbox_is_valid_from(rstream) > 0) {
-			rstream->mail_size = body_size +
+			rstream->mail_size = expected_body_size +
 				(rstream->body_offset - rstream->hdr_offset);
 			i_stream_seek(stream, old_offset);
-			return body_size;
+			return expected_body_size;
 		}
+		/* invalid expected_body_size */
 	}
+	if (body_size != (uoff_t)-1)
+		return body_size;
 
 	/* have to read through the message body */
 	while (i_stream_read_data(stream, &data, &size, 0) > 0)
@@ -535,12 +557,13 @@
 	return rstream->crlf_ending;
 }
 
-void istream_raw_mbox_next(struct istream *stream, uoff_t body_size)
+void istream_raw_mbox_next(struct istream *stream, uoff_t expected_body_size)
 {
 	struct raw_mbox_istream *rstream =
 		(struct raw_mbox_istream *)stream->real_stream;
+	uoff_t body_size;
 
-	body_size = istream_raw_mbox_get_body_size(stream, body_size);
+	body_size = istream_raw_mbox_get_body_size(stream, expected_body_size);
 	rstream->mail_size = (uoff_t)-1;
 
 	rstream->received_time = rstream->next_received_time;
@@ -606,6 +629,16 @@
 	return rstream->corrupted ? -1 : 0;
 }
 
+void istream_raw_mbox_set_next_offset(struct istream *stream, uoff_t offset)
+{
+	struct raw_mbox_istream *rstream =
+		(struct raw_mbox_istream *)stream->real_stream;
+
+	i_assert(rstream->hdr_offset != (uoff_t)-1);
+
+	rstream->mail_size = offset - rstream->hdr_offset;
+}
+
 bool istream_raw_mbox_is_eof(struct istream *stream)
 {
 	struct raw_mbox_istream *rstream =
--- a/src/lib-storage/index/mbox/istream-raw-mbox.h	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib-storage/index/mbox/istream-raw-mbox.h	Mon Jun 09 05:11:18 2008 +0300
@@ -12,10 +12,11 @@
 /* Return offset to beginning of the body. */
 uoff_t istream_raw_mbox_get_body_offset(struct istream *stream);
 
-/* Return the number of bytes in the body of this message. If body_size isn't
-   (uoff_t)-1, we'll use it as potentially valid body size to avoid actually
-   reading through the whole message. */
-uoff_t istream_raw_mbox_get_body_size(struct istream *stream, uoff_t body_size);
+/* Return the number of bytes in the body of this message. If
+   expected_body_size isn't (uoff_t)-1, we'll use it as potentially valid body
+   size to avoid actually reading through the whole message. */
+uoff_t istream_raw_mbox_get_body_size(struct istream *stream,
+				      uoff_t expected_body_size);
 
 /* Return received time of current message, or (time_t)-1 if the timestamp is
    broken. */
@@ -26,14 +27,18 @@
 /* Return TRUE if the empty line between this and the next mail contains CR. */
 bool istream_raw_mbox_has_crlf_ending(struct istream *stream);
 
-/* Jump to next message. If body_size isn't (uoff_t)-1, we'll use it as
-   potentially valid body size. */
-void istream_raw_mbox_next(struct istream *stream, uoff_t body_size);
+/* Jump to next message. If expected_body_size isn't (uoff_t)-1, we'll use it
+   as potentially valid body size. */
+void istream_raw_mbox_next(struct istream *stream, uoff_t expected_body_size);
 
 /* Seek to message at given offset. offset must point to beginning of
    "\nFrom ", or 0 for beginning of file. Returns -1 if it offset doesn't
    contain a valid From-line. */
 int istream_raw_mbox_seek(struct istream *stream, uoff_t offset);
+/* Set next message's start offset. If this isn't set, read stops at the next
+   valid From_-line, even if it belongs to the current message's body
+   (Content-Length: header can be used to determine that). */
+void istream_raw_mbox_set_next_offset(struct istream *stream, uoff_t offset);
 
 /* Returns TRUE if we've read the whole mbox. */
 bool istream_raw_mbox_is_eof(struct istream *stream);
--- a/src/lib-storage/index/mbox/mbox-lock.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib-storage/index/mbox/mbox-lock.c	Mon Jun 09 05:11:18 2008 +0300
@@ -533,8 +533,15 @@
 				/* non-blocking lock trying failed */
 				return 0;
 			}
-			mbox_set_syscall_error(ctx->mbox, "fcntl()");
 			alarm(0);
+			if (errno != EACCES) {
+				mbox_set_syscall_error(ctx->mbox, "fcntl()");
+				return -1;
+			}
+			mail_storage_set_critical(&ctx->mbox->storage->storage,
+				"fcntl() failed with mbox file %s: "
+				"File is locked by another process (EACCES)",
+				ctx->mbox->path);
 			return -1;
 		}
 
--- a/src/lib-storage/index/mbox/mbox-mail.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib-storage/index/mbox/mbox-mail.c	Mon Jun 09 05:11:18 2008 +0300
@@ -166,12 +166,52 @@
 	return index_mail_get_special(_mail, field, value_r);
 }
 
+static bool
+mbox_mail_get_next_offset(struct index_mail *mail, uoff_t *next_offset_r)
+{
+	struct mbox_mailbox *mbox = (struct mbox_mailbox *)mail->ibox;
+	struct mail_index_view *view;
+	const struct mail_index_header *hdr;
+	uint32_t seq;
+	int trailer_size;
+	bool ret;
+
+	hdr = mail_index_get_header(mail->trans->trans_view);
+	if (mail->mail.mail.seq > hdr->messages_count) {
+		/* we're appending a new message */
+		return FALSE;
+	}
+
+	/* We can't really trust trans_view. The next message may already be
+	   expugned from it. hdr.sync_size may also be updated, but
+	   hdr.messages_count not. So refresh the index to get the latest
+	   changes and get the next message's offset using a new view. */
+	(void)mail_index_refresh(mail->ibox->index);
+
+	view = mail_index_view_open(mail->ibox->index);
+	hdr = mail_index_get_header(view);
+	if (!mail_index_lookup_seq(view, mail->mail.mail.uid, &seq))
+		i_panic("Message unexpectedly expunged from index");
+
+	if (seq == hdr->messages_count) {
+		/* last message, use the synced mbox size */
+		trailer_size = (mbox->storage->storage.flags &
+				MAIL_STORAGE_FLAG_SAVE_CRLF) != 0 ? 2 : 1;
+		*next_offset_r = hdr->sync_size - trailer_size;
+		ret = TRUE;
+	} else {
+		ret = mbox_file_lookup_offset(mbox, view, seq + 1,
+					      next_offset_r) > 0;
+	}
+	mail_index_view_close(&view);
+	return ret;
+}
+
 static int mbox_mail_get_physical_size(struct mail *_mail, uoff_t *size_r)
 {
 	struct index_mail *mail = (struct index_mail *)_mail;
 	struct index_mail_data *data = &mail->data;
 	struct mbox_mailbox *mbox = (struct mbox_mailbox *)mail->ibox;
-	const struct mail_index_header *hdr;
 	struct istream *input;
 	struct message_size hdr_size;
 	uoff_t old_offset, body_offset, body_size, next_offset;
@@ -194,26 +234,10 @@
 
 	/* use the next message's offset to avoid reading through the entire
 	   message body to find out its size */
-	hdr = mail_index_get_header(mail->trans->trans_view);
-	if (_mail->seq >= hdr->messages_count) {
-		if (_mail->seq == hdr->messages_count) {
-			/* last message, use the synced mbox size */
-			int trailer_size;
-
-			trailer_size = (mbox->storage->storage.flags &
-					MAIL_STORAGE_FLAG_SAVE_CRLF) != 0 ?
-				2 : 1;
-			body_size = hdr->sync_size - body_offset - trailer_size;
-		} else {
-			/* we're appending a new message */
-			body_size = (uoff_t)-1;
-		}
-	} else if (mbox_file_lookup_offset(mbox, mail->trans->trans_view,
-					   _mail->seq + 1, &next_offset) > 0) {
+	if (mbox_mail_get_next_offset(mail, &next_offset))
 		body_size = next_offset - body_offset;
-	} else {
+	else
 		body_size = (uoff_t)-1;
-	}
 
 	/* verify that the calculated body size is correct */
 	body_size = istream_raw_mbox_get_body_size(mbox->mbox_stream,
@@ -226,31 +250,53 @@
 	return 0;
 }
 
+static int mbox_mail_init_stream(struct index_mail *mail)
+{
+	struct mbox_mailbox *mbox = (struct mbox_mailbox *)mail->ibox;
+	struct istream *raw_stream;
+	uoff_t hdr_offset, next_offset;
+
+	if (mbox_mail_seek(mail) < 0)
+		return -1;
+
+	if (!mbox_mail_get_next_offset(mail, &next_offset)) {
+		if (mbox_mail_seek(mail) < 0)
+			return -1;
+		if (!mbox_mail_get_next_offset(mail, &next_offset)) {
+			i_warning("mbox %s: Can't find next message offset "
+				  "for uid=%u",
+				  mbox->path, mail->mail.mail.uid);
+			next_offset = (uoff_t)-1;
+		}
+	}
+
+	raw_stream = mbox->mbox_stream;
+	hdr_offset = istream_raw_mbox_get_header_offset(raw_stream);
+	i_stream_seek(raw_stream, hdr_offset);
+
+	if (next_offset != (uoff_t)-1)
+		istream_raw_mbox_set_next_offset(raw_stream, next_offset);
+
+	raw_stream = i_stream_create_limit(raw_stream, (uoff_t)-1);
+	mail->data.stream =
+		i_stream_create_header_filter(raw_stream,
+				HEADER_FILTER_EXCLUDE | HEADER_FILTER_NO_CR,
+				mbox_hide_headers, mbox_hide_headers_count,
+				null_header_filter_callback, NULL);
+	i_stream_unref(&raw_stream);
+	return 0;
+}
+
 static int mbox_mail_get_stream(struct mail *_mail,
 				struct message_size *hdr_size,
 				struct message_size *body_size,
 				struct istream **stream_r)
 {
 	struct index_mail *mail = (struct index_mail *)_mail;
-	struct index_mail_data *data = &mail->data;
-	struct mbox_mailbox *mbox = (struct mbox_mailbox *)mail->ibox;
-	struct istream *raw_stream;
-	uoff_t offset;
 
-	if (data->stream == NULL) {
-		if (mbox_mail_seek(mail) < 0)
+	if (mail->data.stream == NULL) {
+		if (mbox_mail_init_stream(mail) < 0)
 			return -1;
-
-		raw_stream = mbox->mbox_stream;
-		offset = istream_raw_mbox_get_header_offset(raw_stream);
-		i_stream_seek(raw_stream, offset);
-		raw_stream = i_stream_create_limit(raw_stream, (uoff_t)-1);
-		data->stream =
-			i_stream_create_header_filter(raw_stream,
-				HEADER_FILTER_EXCLUDE | HEADER_FILTER_NO_CR,
-				mbox_hide_headers, mbox_hide_headers_count,
-				null_header_filter_callback, NULL);
-		i_stream_unref(&raw_stream);
 	}
 
 	return index_mail_init_stream(mail, hdr_size, body_size, stream_r);
--- a/src/lib-storage/index/mbox/mbox-save.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib-storage/index/mbox/mbox-save.c	Mon Jun 09 05:11:18 2008 +0300
@@ -328,6 +328,7 @@
 		ctx->output = o_stream_create_fd_file(mbox->mbox_fd,
 						      ctx->append_offset,
 						      FALSE);
+		o_stream_cork(ctx->output);
 	}
 	return 0;
 }
@@ -381,7 +382,8 @@
 
 	/* filter out unwanted headers and keep track of headers' MD5 sum */
 	filter = i_stream_create_header_filter(input, HEADER_FILTER_EXCLUDE |
-					       HEADER_FILTER_NO_CR,
+					       HEADER_FILTER_NO_CR |
+					       HEADER_FILTER_ADD_MISSING_EOH,
 					       mbox_save_drop_headers,
 					       mbox_save_drop_headers_count,
 					       save_header_callback, ctx);
@@ -652,6 +654,10 @@
 {
 	struct mbox_save_context *ctx = (struct mbox_save_context *)_ctx;
 
+	/* make sure everything is written */
+	if (o_stream_flush(ctx->output) < 0)
+		return write_error(ctx);
+
 	ctx->finished = TRUE;
 	if (!ctx->failed) {
 		T_BEGIN {
@@ -670,8 +676,10 @@
 
 	if (ctx->failed && ctx->mail_offset != (uoff_t)-1) {
 		/* saving this mail failed - truncate back to beginning of it */
+		(void)o_stream_flush(ctx->output);
 		if (ftruncate(ctx->mbox->mbox_fd, (off_t)ctx->mail_offset) < 0)
 			mbox_set_syscall_error(ctx->mbox, "ftruncate()");
+		o_stream_seek(ctx->output, ctx->mail_offset);
 		ctx->mail_offset = (uoff_t)-1;
 	}
 
@@ -720,16 +728,20 @@
 
 		if (!ctx->mbox->mbox_sync_dirty && ret == 0) {
 			uint32_t sync_stamp = st.st_mtime;
-			uint64_t sync_size = st.st_size;
 
 			mail_index_update_header(ctx->trans,
 				offsetof(struct mail_index_header, sync_stamp),
 				&sync_stamp, sizeof(sync_stamp), TRUE);
+		}
+		if (ret == 0) {
+			/* sync_size is used in calculating the last message's
+			   size. it must be up-to-date. */
+			uint64_t sync_size = st.st_size;
+
 			mail_index_update_header(ctx->trans,
 				offsetof(struct mail_index_header, sync_size),
 				&sync_size, sizeof(sync_size), TRUE);
 		}
-
 		*t->ictx.last_saved_uid = ctx->next_uid - 1;
 	}
 
--- a/src/lib-storage/index/mbox/mbox-storage.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib-storage/index/mbox/mbox-storage.c	Mon Jun 09 05:11:18 2008 +0300
@@ -4,6 +4,7 @@
 #include "ioloop.h"
 #include "array.h"
 #include "istream.h"
+#include "restrict-access.h"
 #include "mkdir-parents.h"
 #include "unlink-directory.h"
 #include "home-expand.h"
@@ -71,6 +72,7 @@
 
 static int mbox_list_iter_is_mailbox(struct mailbox_list_iterate_context *ctx,
 				     const char *dir, const char *fname,
+				     const char *mailbox_name,
 				     enum mailbox_list_file_type type,
 				     enum mailbox_info_flags *flags);
 static int mbox_list_delete_mailbox(struct mailbox_list *list,
@@ -471,6 +473,12 @@
 
 	/* make sure inbox file itself exists */
 	fd = open(inbox_path, O_RDWR | O_CREAT | O_EXCL, 0660);
+	if (fd == -1 && errno == EACCES) {
+		/* try again with increased privileges */
+		(void)restrict_access_use_priv_gid();
+		fd = open(inbox_path, O_RDWR | O_CREAT | O_EXCL, 0660);
+		restrict_access_drop_priv_gid();
+	}
 	if (fd != -1)
 		(void)close(fd);
 	else if (errno == ENOTDIR &&
@@ -797,6 +805,7 @@
 
 static int mbox_list_iter_is_mailbox(struct mailbox_list_iterate_context *ctx,
 				     const char *dir, const char *fname,
+				     const char *mailbox_name ATTR_UNUSED,
 				     enum mailbox_list_file_type type,
 				     enum mailbox_info_flags *flags_r)
 {
--- a/src/lib-storage/index/mbox/mbox-sync-private.h	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib-storage/index/mbox/mbox-sync-private.h	Mon Jun 09 05:11:18 2008 +0300
@@ -143,6 +143,7 @@
 	unsigned int moved_offsets:1;
 	unsigned int ext_modified:1;
 	unsigned int index_reset:1;
+	unsigned int errors:1;
 };
 
 int mbox_sync(struct mbox_mailbox *mbox, enum mbox_sync_flags flags);
--- a/src/lib-storage/index/mbox/mbox-sync.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib-storage/index/mbox/mbox-sync.c	Mon Jun 09 05:11:18 2008 +0300
@@ -68,6 +68,7 @@
 {
 	va_list va;
 
+	sync_ctx->errors = TRUE;
 	if (sync_ctx->ext_modified) {
 		mail_storage_set_critical(&sync_ctx->mbox->storage->storage,
 			"mbox file %s was modified while we were syncing, "
@@ -1410,8 +1411,8 @@
 			&sync_size, sizeof(sync_size), TRUE);
 	}
 
-	first_recent_uid = !sync_ctx->mbox->ibox.keep_recent ? 0 :
-		sync_ctx->last_nonrecent_uid + 1;
+	first_recent_uid = !sync_ctx->mbox->ibox.keep_recent ?
+		sync_ctx->next_uid : sync_ctx->last_nonrecent_uid + 1;
 	if (sync_ctx->hdr->first_recent_uid < first_recent_uid) {
 		mail_index_update_header(sync_ctx->t,
 			offsetof(struct mail_index_header, first_recent_uid),
@@ -1441,6 +1442,7 @@
 		mail_index_reset(sync_ctx->t);
 		sync_ctx->reset_hdr.next_uid = 1;
 		sync_ctx->hdr = &sync_ctx->reset_hdr;
+		index_mailbox_reset_uidvalidity(&sync_ctx->mbox->ibox);
 	}
 
 	sync_ctx->prev_msg_uid = 0;
@@ -1454,6 +1456,7 @@
 
 	sync_ctx->dest_first_mail = TRUE;
 	sync_ctx->ext_modified = FALSE;
+	sync_ctx->errors = FALSE;
 }
 
 static int mbox_sync_do(struct mbox_sync_context *sync_ctx,
@@ -1500,16 +1503,25 @@
 	}
 
 	mbox_sync_restart(sync_ctx);
-	for (i = 0; i < 3; i++) {
+	for (i = 0;;) {
 		ret = mbox_sync_loop(sync_ctx, &mail_ctx, partial);
-		if (ret > 0)
+		if (ret > 0 && !sync_ctx->errors)
 			break;
 		if (ret < 0)
 			return -1;
 
-		/* partial syncing didn't work, do it again. we get here
-		   also if we ran out of UIDs. */
-		i_assert(sync_ctx->mbox->mbox_sync_dirty);
+		/* a) partial sync didn't work
+		   b) we ran out of UIDs
+		   c) syncing had errors */
+		if (sync_ctx->delay_writes && !sync_ctx->mbox->mbox_readonly &&
+		    (sync_ctx->errors || sync_ctx->renumber_uids)) {
+			/* fixing a broken mbox state, be sure to write
+			   the changes. */
+			sync_ctx->delay_writes = FALSE;
+		}
+		if (++i == 3)
+			break;
+
 		mbox_sync_restart(sync_ctx);
 		partial = FALSE;
 	}
@@ -1522,6 +1534,14 @@
 	   ignore them, as we've overwritten them above. */
 	index_sync_changes_reset(sync_ctx->sync_changes);
 
+	if (sync_ctx->base_uid_last != sync_ctx->next_uid-1 &&
+	    ret == 0 && !sync_ctx->delay_writes &&
+	    sync_ctx->base_uid_last_offset != 0) {
+		/* Rewrite uid_last in X-IMAPbase header if we've seen it
+		   (ie. the file isn't empty) */
+                ret = mbox_rewrite_base_uid_last(sync_ctx);
+	}
+
 	if (mbox_sync_update_index_header(sync_ctx) < 0)
 		return -1;
 
@@ -1644,7 +1664,6 @@
 		   before index syncing is started to avoid deadlocks, so we
 		   don't have much choice either (well, easy ones anyway). */
 		int lock_type = mbox->mbox_readonly ? F_RDLCK : F_WRLCK;
-		int ret;
 
 		if ((ret = mbox_lock(mbox, lock_type, &lock_id)) <= 0) {
 			if (ret == 0 || lock_type == F_RDLCK)
@@ -1714,7 +1733,7 @@
 	sync_ctx.sync_view = sync_view;
 	sync_ctx.t = trans;
 	sync_ctx.mail_keyword_pool =
-		pool_alloconly_create("mbox keywords", 256);
+		pool_alloconly_create("mbox keywords", 512);
 	sync_ctx.saved_keywords_pool =
 		pool_alloconly_create("mbox saved keywords", 4096);
 
@@ -1780,15 +1799,8 @@
 	sync_ctx.t = NULL;
 	sync_ctx.index_sync_ctx = NULL;
 
-	if (sync_ctx.base_uid_last != sync_ctx.next_uid-1 &&
-	    ret == 0 && !sync_ctx.delay_writes &&
-	    sync_ctx.base_uid_last_offset != 0) {
-		/* Rewrite uid_last in X-IMAPbase header if we've seen it
-		   (ie. the file isn't empty) */
-                ret = mbox_rewrite_base_uid_last(&sync_ctx);
-	}
-
-	if (ret == 0 && mbox->mbox_fd != -1 && mbox->ibox.keep_recent) {
+	if (ret == 0 && mbox->mbox_fd != -1 && mbox->ibox.keep_recent &&
+	    !sync_ctx.mbox->mbox_readonly) {
 		/* try to set atime back to its original value */
 		struct utimbuf buf;
 		struct stat st;
--- a/src/lib-storage/index/raw/raw-mail.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib-storage/index/raw/raw-mail.c	Mon Jun 09 05:11:18 2008 +0300
@@ -98,6 +98,9 @@
 	case MAIL_FETCH_FROM_ENVELOPE:
 		*value_r = mbox->envelope_sender;
 		return 0;
+	case MAIL_FETCH_UIDL_FILE_NAME:
+		*value_r = mbox->have_filename ? mbox->path : "";
+		return 0;
 	default:
 		return index_mail_get_special(_mail, field, value_r);
 	}
--- a/src/lib-storage/index/raw/raw-storage.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib-storage/index/raw/raw-storage.c	Mon Jun 09 05:11:18 2008 +0300
@@ -21,6 +21,7 @@
 static int raw_list_delete_mailbox(struct mailbox_list *list, const char *name);
 static int raw_list_iter_is_mailbox(struct mailbox_list_iterate_context *ctx,
 				    const char *dir, const char *fname,
+				    const char *mailbox_name,
 				    enum mailbox_list_file_type type,
 				    enum mailbox_info_flags *flags);
 
@@ -161,8 +162,10 @@
 
 	if (stream)
 		mbox->mtime = mbox->ctime = ioloop_time;
-	else
+	else {
 		mbox->mtime = mbox->ctime = (time_t)-1;
+		mbox->have_filename = TRUE;
+	}
 	mbox->size = (uoff_t)-1;
 
 	index_storage_mailbox_init(&mbox->ibox, name, flags, FALSE);
@@ -200,6 +203,7 @@
 
 static int raw_list_iter_is_mailbox(struct mailbox_list_iterate_context *ctx,
 				    const char *dir, const char *fname,
+				    const char *mailbox_name ATTR_UNUSED,
 				    enum mailbox_list_file_type type,
 				    enum mailbox_info_flags *flags_r)
 {
--- a/src/lib-storage/index/raw/raw-storage.h	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib-storage/index/raw/raw-storage.h	Mon Jun 09 05:11:18 2008 +0300
@@ -24,6 +24,7 @@
 	const char *envelope_sender;
 
 	unsigned int synced:1;
+	unsigned int have_filename:1;
 };
 
 extern struct mail_vfuncs raw_mail_vfuncs;
--- a/src/lib-storage/list/mailbox-list-fs-iter.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib-storage/list/mailbox-list-fs-iter.c	Mon Jun 09 05:11:18 2008 +0300
@@ -349,7 +349,7 @@
 	inbox_path = mailbox_list_get_path(ctx->ctx.list, "INBOX",
 					   MAILBOX_LIST_PATH_TYPE_DIR);
 	path_split(inbox_path, &dir, &fname);
-	if (ctx->ctx.list->v.iter_is_mailbox(&ctx->ctx, dir, fname,
+	if (ctx->ctx.list->v.iter_is_mailbox(&ctx->ctx, dir, fname, "INBOX",
 					     MAILBOX_LIST_FILE_TYPE_UNKNOWN,
 					     &ctx->info.flags) < 0)
 		ctx->ctx.failed = TRUE;
@@ -492,7 +492,7 @@
 	ctx->info.flags = 0;
 	ret = ctx->ctx.list->v.
 		iter_is_mailbox(&ctx->ctx, ctx->dir->real_path, fname,
-				entry->type, &ctx->info.flags);
+				list_path, entry->type, &ctx->info.flags);
 	if (ret <= 0)
 		return ret;
 
@@ -550,7 +550,9 @@
 	path = mailbox_list_get_path(ctx->ctx.list, ctx->info.name,
 				     MAILBOX_LIST_PATH_TYPE_DIR);
 	path_split(path, &dir, &fname);
+	ctx->info.flags = 0;
 	if (ctx->ctx.list->v.iter_is_mailbox(&ctx->ctx, dir, fname,
+					     ctx->info.name,
 					     MAILBOX_LIST_FILE_TYPE_UNKNOWN,
 					     &ctx->info.flags) < 0)
 		ctx->ctx.failed = TRUE;
@@ -572,9 +574,9 @@
 
 	if (dir->dirp != NULL) {
 		if (dir->next_entry != NULL) {
-			const struct list_dir_entry *ret = dir->next_entry;
+			const struct list_dir_entry *entry = dir->next_entry;
 			dir->next_entry = NULL;
-			return ret;
+			return entry;
 		}
 		d = readdir(dir->dirp);
 		if (d == NULL)
--- a/src/lib-storage/list/mailbox-list-maildir-iter.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib-storage/list/mailbox-list-maildir-iter.c	Mon Jun 09 05:11:18 2008 +0300
@@ -89,6 +89,33 @@
 	}
 }
 
+static void maildir_set_children(struct maildir_list_iterate_context *ctx,
+				 string_t *mailbox)
+{
+	struct mailbox_node *node;
+	const char *p, *mailbox_c;
+	char hierarchy_sep;
+
+	if ((ctx->ctx.flags & MAILBOX_LIST_ITER_VIRTUAL_NAMES) != 0)
+		hierarchy_sep = ctx->ctx.list->ns->sep;
+	else
+		hierarchy_sep = ctx->ctx.list->ns->real_sep;
+
+	/* mark the first existing parent as containing children */
+	mailbox_c = str_c(mailbox);
+	while ((p = strrchr(mailbox_c, hierarchy_sep)) != NULL) {
+		str_truncate(mailbox, (size_t) (p-mailbox_c));
+		mailbox_c = str_c(mailbox);
+
+		node = mailbox_tree_lookup(ctx->tree_ctx, mailbox_c);
+		if (node != NULL) {
+			node->flags &= ~MAILBOX_NOCHILDREN;
+			node->flags |= MAILBOX_CHILDREN;
+			break;
+		}
+	}
+}
+
 static int
 maildir_fill_readdir(struct maildir_list_iterate_context *ctx,
 		     struct imap_match_glob *glob, bool update_only)
@@ -156,6 +183,7 @@
 		T_BEGIN {
 			ret = ctx->ctx.list->v.
 				iter_is_mailbox(&ctx->ctx, ctx->dir, fname,
+					mailbox_name,
 					mailbox_list_get_file_type(d), &flags);
 		} T_END;
 		if (ret <= 0) {
@@ -191,6 +219,9 @@
 					node->flags |= MAILBOX_MATCHED;
 				node->flags |= flags;
 				node_fix_parents(node);
+			} else {
+				i_assert(update_only);
+				maildir_set_children(ctx, mailbox);
 			}
 		}
 	}
@@ -221,7 +252,7 @@
 		   imap_match(glob, "INBOX") == IMAP_MATCH_YES) {
 		/* see if INBOX exists. */
 		ret = ctx->ctx.list->v.
-			iter_is_mailbox(&ctx->ctx, ctx->dir, "",
+			iter_is_mailbox(&ctx->ctx, ctx->dir, "", "INBOX",
 					MAILBOX_LIST_FILE_TYPE_UNKNOWN, &flags);
 		if (ret > 0) {
 			node = mailbox_tree_get(ctx->tree_ctx,
--- a/src/lib-storage/list/mailbox-list-subscriptions.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib-storage/list/mailbox-list-subscriptions.c	Mon Jun 09 05:11:18 2008 +0300
@@ -13,10 +13,10 @@
 				     bool update_only)
 {
 	struct mail_namespace *ns = ctx->list->ns;
+	struct mailbox_list_iter_update_context update_ctx;
 	struct subsfile_list_context *subsfile_ctx;
 	const char *path, *name;
 	string_t *vname;
-	bool match_parents;
 
 	vname = t_str_new(256);
 	path = t_strconcat(ctx->list->set.control_dir != NULL ?
@@ -25,13 +25,19 @@
 			   "/", ctx->list->set.subscription_fname, NULL);
 	subsfile_ctx = subsfile_list_init(ctx->list, path);
 
-	match_parents =
+	memset(&update_ctx, 0, sizeof(update_ctx));
+	update_ctx.iter_ctx = ctx;
+	update_ctx.tree_ctx = tree_ctx;
+	update_ctx.glob = glob;
+	update_ctx.leaf_flags = MAILBOX_SUBSCRIBED;
+	update_ctx.parent_flags = MAILBOX_CHILD_SUBSCRIBED;
+	update_ctx.update_only = update_only;
+	update_ctx.match_parents =
 		(ctx->flags & MAILBOX_LIST_ITER_SELECT_RECURSIVEMATCH) != 0;
 
 	while ((name = subsfile_list_next(subsfile_ctx)) != NULL) {
 		name = mail_namespace_get_vname(ns, vname, name);
-		mailbox_list_iter_update(ctx, tree_ctx, glob, update_only,
-					 match_parents, name);
+		mailbox_list_iter_update(&update_ctx, name);
 	}
 	return subsfile_list_deinit(subsfile_ctx);
 }
--- a/src/lib-storage/mail-storage.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib-storage/mail-storage.c	Mon Jun 09 05:11:18 2008 +0300
@@ -542,7 +542,6 @@
 		 struct mailbox_status *status_r)
 {
 	struct mailbox_sync_context *ctx;
-        struct mailbox_sync_rec sync_rec;
 
 	if (array_count(&box->search_results) == 0) {
 		/* we don't care about mailbox's current state, so we might
@@ -551,8 +550,6 @@
 	}
 
 	ctx = mailbox_sync_init(box, flags);
-	while (mailbox_sync_next(ctx, &sync_rec))
-		;
 	return mailbox_sync_deinit(&ctx, status_items, status_r);
 }
 
@@ -703,9 +700,12 @@
 
 int mailbox_transaction_commit(struct mailbox_transaction_context **t)
 {
-	uint32_t tmp;
+	uint32_t uidvalidity, uid1, uid2;
 
-	return mailbox_transaction_commit_get_uids(t, &tmp, &tmp, &tmp);
+	/* Store the return values to separate temporary variables so that
+	   plugins overriding transaction_commit() can look at them. */
+	return mailbox_transaction_commit_get_uids(t, &uidvalidity,
+						   &uid1, &uid2);
 }
 
 int mailbox_transaction_commit_get_uids(struct mailbox_transaction_context **_t,
--- a/src/lib-storage/mail-storage.h	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib-storage/mail-storage.h	Mon Jun 09 05:11:18 2008 +0300
@@ -124,7 +124,8 @@
 	MAIL_FETCH_IMAP_ENVELOPE	= 0x00004000,
 	MAIL_FETCH_FROM_ENVELOPE	= 0x00008000,
 	MAIL_FETCH_HEADER_MD5		= 0x00010000,
-	MAIL_FETCH_UIDL_FILE_NAME	= 0x00020000
+	MAIL_FETCH_UIDL_FILE_NAME	= 0x00020000,
+	MAIL_FETCH_UIDL_BACKEND		= 0x00040000
 };
 
 enum mailbox_transaction_flags {
--- a/src/lib-storage/mailbox-list-private.h	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib-storage/mailbox-list-private.h	Mon Jun 09 05:11:18 2008 +0300
@@ -41,6 +41,7 @@
 	   flags may be updated (especially the children flags). */
 	int (*iter_is_mailbox)(struct mailbox_list_iterate_context *ctx,
 			       const char *dir, const char *fname,
+			       const char *mailbox_name,
 			       enum mailbox_list_file_type type,
 			       enum mailbox_info_flags *flags_r);
 
@@ -93,6 +94,17 @@
 	bool failed;
 };
 
+struct mailbox_list_iter_update_context {
+	struct mailbox_list_iterate_context *iter_ctx;
+	struct mailbox_tree_context *tree_ctx;
+			      
+	struct imap_match_glob *glob;
+	enum mailbox_info_flags leaf_flags, parent_flags;
+
+	unsigned int update_only:1;
+	unsigned int match_parents:1;
+};
+
 /* Modules should use do "my_id = mailbox_list_module_id++" and
    use objects' module_contexts[id] for their own purposes. */
 extern struct mailbox_list_module_register mailbox_list_module_register;
@@ -110,10 +122,8 @@
 int mailbox_list_delete_index_control(struct mailbox_list *list,
 				      const char *name);
 
-void mailbox_list_iter_update(struct mailbox_list_iterate_context *ctx,
-			      struct mailbox_tree_context *tree_ctx,
-			      struct imap_match_glob *glob, bool update_only,
-			      bool match_parents, const char *name);
+void mailbox_list_iter_update(struct mailbox_list_iter_update_context *ctx,
+			      const char *name);
 
 bool mailbox_list_name_is_too_large(const char *name, char sep);
 enum mailbox_list_file_type mailbox_list_get_file_type(const struct dirent *d);
--- a/src/lib-storage/mailbox-list.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib-storage/mailbox-list.c	Mon Jun 09 05:11:18 2008 +0300
@@ -474,38 +474,36 @@
 }
 
 static void
-mailbox_list_iter_update_real(struct mailbox_list_iterate_context *ctx,
-			      struct mailbox_tree_context *tree_ctx,
-			      struct imap_match_glob *glob, bool update_only,
-			      bool match_parents, const char *name)
+mailbox_list_iter_update_real(struct mailbox_list_iter_update_context *ctx,
+			      const char *name)
 {
-	struct mail_namespace *ns = ctx->list->ns;
+	struct mail_namespace *ns = ctx->iter_ctx->list->ns;
 	struct mailbox_node *node;
-	enum mailbox_info_flags create_flags, always_flags;
+	enum mailbox_info_flags create_flags = 0, always_flags;
 	enum imap_match_result match;
 	const char *p;
 	bool created, add_matched;
 
-	create_flags = (update_only ||
-			(ctx->flags & MAILBOX_LIST_ITER_RETURN_NO_FLAGS) == 0) ?
-		(MAILBOX_NONEXISTENT | MAILBOX_NOCHILDREN) : 0;
-	always_flags = MAILBOX_SUBSCRIBED;
+	if (ctx->update_only ||
+	    (ctx->iter_ctx->flags & MAILBOX_LIST_ITER_RETURN_NO_FLAGS) == 0)
+		create_flags = MAILBOX_NONEXISTENT | MAILBOX_NOCHILDREN;
+	always_flags = ctx->leaf_flags;
 	add_matched = TRUE;
 
 	for (;;) {
 		created = FALSE;
-		match = imap_match(glob, name);
+		match = imap_match(ctx->glob, name);
 		if (match == IMAP_MATCH_YES) {
-			node = update_only ?
-				mailbox_tree_lookup(tree_ctx, name) :
-				mailbox_tree_get(tree_ctx, name, &created);
+			node = ctx->update_only ?
+				mailbox_tree_lookup(ctx->tree_ctx, name) :
+				mailbox_tree_get(ctx->tree_ctx, name, &created);
 			if (created) {
 				node->flags = create_flags;
 				if (create_flags != 0)
 					node_fix_parents(node);
 			}
 			if (node != NULL) {
-				if (!update_only && add_matched)
+				if (!ctx->update_only && add_matched)
 					node->flags |= MAILBOX_MATCHED;
 				node->flags |= always_flags;
 			}
@@ -521,7 +519,7 @@
 			   return the parent mailbox. */
 		}
 
-		if (!match_parents)
+		if (!ctx->match_parents)
 			break;
 
 		/* see if parent matches */
@@ -531,18 +529,15 @@
 
 		name = t_strdup_until(name, p);
 		create_flags &= ~MAILBOX_NOCHILDREN;
-		always_flags = MAILBOX_CHILDREN | MAILBOX_CHILD_SUBSCRIBED;
+		always_flags = MAILBOX_CHILDREN | ctx->parent_flags;
 	}
 }
 
-void mailbox_list_iter_update(struct mailbox_list_iterate_context *ctx,
-			      struct mailbox_tree_context *tree_ctx,
-			      struct imap_match_glob *glob, bool update_only,
-			      bool match_parents, const char *name)
+void mailbox_list_iter_update(struct mailbox_list_iter_update_context *ctx,
+			      const char *name)
 {
 	T_BEGIN {
-		mailbox_list_iter_update_real(ctx, tree_ctx, glob, update_only,
-					      match_parents, name);
+		mailbox_list_iter_update_real(ctx, name);
 	} T_END;
 }
 
--- a/src/lib-storage/mailbox-tree.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib-storage/mailbox-tree.c	Mon Jun 09 05:11:18 2008 +0300
@@ -200,7 +200,7 @@
 		str_truncate(ctx->path_str, ctx->parent_pos);
 		if (ctx->first_child) {
 			ctx->first_child = FALSE;
-			if (ctx->parent_pos != 0) {
+			if (node->parent != NULL) {
 				str_append_c(ctx->path_str, ctx->separator);
 				ctx->parent_pos++;
 			}
--- a/src/lib/compat.h	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib/compat.h	Mon Jun 09 05:11:18 2008 +0300
@@ -63,17 +63,37 @@
 #  error I do not know how to compare dev_t
 #endif
 
-#ifdef HAVE_STAT_TV_NSEC
-#  define CMP_ST_MTIME(st1, st2) \
+#ifdef HAVE_STAT_XTIM
+#  define HAVE_ST_NSECS
+#  define ST_ATIME_NSEC(st) ((unsigned long)(st).st_atim.tv_nsec)
+#  define ST_MTIME_NSEC(st) ((unsigned long)(st).st_mtim.tv_nsec)
+#  define ST_CTIME_NSEC(st) ((unsigned long)(st).st_ctim.tv_nsec)
+#elif defined (HAVE_STAT_XTIMESPEC)
+#  define HAVE_ST_NSECS
+#  define ST_ATIME_NSEC(st) ((unsigned long)(st).st_atimespec.tv_nsec)
+#  define ST_MTIME_NSEC(st) ((unsigned long)(st).st_mtimespec.tv_nsec)
+#  define ST_CTIME_NSEC(st) ((unsigned long)(st).st_ctimespec.tv_nsec)
+#else
+#  define ST_ATIME_NSEC(st) 0UL
+#  define ST_MTIME_NSEC(st) 0UL
+#  define ST_CTIME_NSEC(st) 0UL
+#endif
+
+#ifdef HAVE_ST_NSECS
+/* TRUE if a nanosecond timestamp from struct stat matches another nanosecond.
+   If nanoseconds aren't supported in struct stat, returns always TRUE (useful
+   with NFS if some hosts support nanoseconds and others don't). */
+#  define ST_NTIMES_EQUAL(ns1, ns2) ((ns1) == (ns2))
+#else
+#  define ST_NTIMES_EQUAL(ns1, ns2) TRUE
+#endif
+
+#define CMP_ST_MTIME(st1, st2) \
 	((st1)->st_mtime == (st2)->st_mtime && \
-	 (st1)->st_mtim.tv_nsec == (st2)->st_mtim.tv_nsec)
-#  define CMP_ST_CTIME(st1, st2) \
+	 ST_NTIMES_EQUAL(ST_MTIME_NSEC(*(st1)), ST_MTIME_NSEC(*(st2))))
+#define CMP_ST_CTIME(st1, st2) \
 	((st1)->st_ctime == (st2)->st_ctime && \
-	 (st1)->st_ctim.tv_nsec == (st2)->st_ctim.tv_nsec)
-#else
-#  define CMP_ST_MTIME(st1, st2) ((st1)->st_mtime == (st2)->st_mtime)
-#  define CMP_ST_CTIME(st1, st2) ((st1)->st_ctime == (st2)->st_ctime)
-#endif
+	 ST_NTIMES_EQUAL(ST_CTIME_NSEC(*(st1)), ST_CTIME_NSEC(*(st2))))
 
 /* strcasecmp(), strncasecmp() */
 #ifndef HAVE_STRCASECMP
--- a/src/lib/data-stack.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib/data-stack.c	Mon Jun 09 05:11:18 2008 +0300
@@ -67,11 +67,38 @@
 static bool clean_after_pop = FALSE;
 static bool outofmem = FALSE;
 
-union {
+static union {
 	struct stack_block block;
 	unsigned char data[128];
 } outofmem_area;
 
+static void data_stack_last_buffer_reset(void)
+{
+	if (last_buffer_block != NULL) {
+#ifdef DEBUG
+		const unsigned char *p;
+		unsigned int i;
+
+		p = STACK_BLOCK_DATA(current_block) +
+			(current_block->size - current_block->left) +
+			MEM_ALIGN(sizeof(size_t)) + MEM_ALIGN(last_buffer_size);
+#endif
+		/* reset t_buffer_get() mark - not really needed but makes it
+		   easier to notice if t_malloc()/t_push()/t_pop() is called
+		   between t_buffer_get() and t_buffer_alloc().
+		   do this before we get to i_panic() to avoid recursive
+		   panics. */
+		last_buffer_block = NULL;
+
+#ifdef DEBUG
+		for (i = 0; i < SENTRY_COUNT; i++) {
+			if (p[i] != CLEAR_CHR)
+				i_panic("t_buffer_get(): buffer overflow");
+		}
+#endif
+	}
+}
+
 unsigned int t_push(void)
 {
         struct stack_frame_block *frame_block;
@@ -107,6 +134,7 @@
 		frame_block->prev = current_frame_block;
 		current_frame_block = frame_block;
 	}
+	data_stack_last_buffer_reset();
 
 	/* mark our current position */
 	current_frame_block->block[frame_pos] = current_block;
@@ -186,7 +214,6 @@
 unsigned int t_pop(void)
 {
 	struct stack_frame_block *frame_block;
-	int popped_frame_pos;
 
 	if (unlikely(frame_pos < 0))
 		i_panic("t_pop() called with empty stack");
@@ -194,6 +221,7 @@
 #ifdef DEBUG
 	t_pop_verify();
 #endif
+	data_stack_last_buffer_reset();
 
 	/* update the current block */
 	current_block = current_frame_block->block[frame_pos];
@@ -215,7 +243,6 @@
 		current_block->next = NULL;
 	}
 
-	popped_frame_pos = frame_pos;
 	if (frame_pos > 0)
 		frame_pos--;
 	else {
@@ -290,10 +317,7 @@
 		data_stack_init();
 	}
 
-	/* reset t_buffer_get() mark - not really needed but makes it easier
-	   to notice if t_malloc() is called between t_buffer_get() and
-	   t_buffer_alloc() */
-        last_buffer_block = NULL;
+	data_stack_last_buffer_reset();
 
 	/* allocate only aligned amount of memory so alignment comes
 	   always properly */
--- a/src/lib/env-util.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib/env-util.c	Mon Jun 09 05:11:18 2008 +0300
@@ -9,9 +9,10 @@
 
 void env_put(const char *env)
 {
-	if (pool == NULL)
-		pool = pool_alloconly_create("Environment", 2048);
-
+	if (pool == NULL) {
+		pool = pool_alloconly_create(MEMPOOL_GROWING"Environment",
+					     2048);
+	}
 	if (putenv(p_strdup(pool, env)) != 0)
 		i_fatal("putenv(%s) failed: %m", env);
 }
--- a/src/lib/failures.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib/failures.c	Mon Jun 09 05:11:18 2008 +0300
@@ -98,7 +98,7 @@
 		/* wait until we can write more. this can happen at least
 		   when writing to terminal, even if fd is blocking. */
 		ioloop = io_loop_create();
-		io = io_add(IO_WRITE, fd, log_fd_flush_stop, ioloop);
+		io = io_add(fd, IO_WRITE, log_fd_flush_stop, ioloop);
 		io_loop_run(ioloop);
 		io_remove(&io);
 		io_loop_destroy(&ioloop);
@@ -138,15 +138,11 @@
 	return ret;
 }
 
-void default_fatal_handler(enum log_type type, int status,
-			   const char *format, va_list args)
+static void ATTR_NORETURN
+default_fatal_finish(enum log_type type, int status)
 {
 	const char *backtrace;
 
-	if (default_handler(failure_log_type_prefixes[type], log_fd, format,
-			    args) < 0 && status == FATAL_DEFAULT)
-		status = FATAL_LOGWRITE;
-
 	if (type == LOG_TYPE_PANIC || status == FATAL_OUTOFMEM) {
 		if (backtrace_get(&backtrace) == 0)
 			i_error("Raw backtrace: %s", backtrace);
@@ -158,6 +154,16 @@
 		failure_exit(status);
 }
 
+void default_fatal_handler(enum log_type type, int status,
+			   const char *format, va_list args)
+{
+	if (default_handler(failure_log_type_prefixes[type], log_fd, format,
+			    args) < 0 && status == FATAL_DEFAULT)
+		status = FATAL_LOGWRITE;
+
+	default_fatal_finish(type, status);
+}
+
 void default_error_handler(enum log_type type, const char *format, va_list args)
 {
 	int fd = type == LOG_TYPE_INFO ? log_info_fd : log_fd;
@@ -292,18 +298,11 @@
 void i_syslog_fatal_handler(enum log_type type, int status,
 			    const char *fmt, va_list args)
 {
-	const char *backtrace;
 	if (syslog_handler(LOG_CRIT, type, fmt, args) < 0 &&
 	    status == FATAL_DEFAULT)
 		status = FATAL_LOGERROR;
 
-	if (type == LOG_TYPE_PANIC) {
-		if (backtrace_get(&backtrace) == 0)
-			i_error("Raw backtrace: %s", backtrace);
-		abort();
-	} else {
-		failure_exit(status);
-	}
+	default_fatal_finish(type, status);
 }
 
 void i_syslog_error_handler(enum log_type type, const char *fmt, va_list args)
@@ -358,7 +357,7 @@
 		if (*fd == -1) {
 			*fd = STDERR_FILENO;
 			i_snprintf(buf, sizeof(buf),
-				   "Can't open log file %s: %m", path);
+				   "Can't open log file %s: %m\n", path);
 			(void)write_full(STDERR_FILENO, buf, strlen(buf));
 			failure_exit(FATAL_LOGOPEN);
 		}
@@ -412,19 +411,11 @@
 i_internal_fatal_handler(enum log_type type, int status,
 			 const char *fmt, va_list args)
 {
-	const char *backtrace;
-
 	if (internal_handler(log_type_internal_chars[type], fmt, args) < 0 &&
 	    status == FATAL_DEFAULT)
 		status = FATAL_LOGERROR;
 
-	if (type == LOG_TYPE_PANIC) {
-		if (backtrace_get(&backtrace) == 0)
-			i_error("Raw backtrace: %s", backtrace);
-		abort();
-	} else {
-		failure_exit(status);
-	}
+	default_fatal_finish(type, status);
 }
 
 static void ATTR_FORMAT(2, 0)
--- a/src/lib/file-dotlock.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib/file-dotlock.c	Mon Jun 09 05:11:18 2008 +0300
@@ -236,8 +236,10 @@
 	if (lock_info->have_pid) {
 		/* we've local PID. Check if it exists. */
 		if (kill(pid, 0) == 0 || errno != ESRCH) {
-			if (pid != getpid())
+			if (pid != getpid()) {
+				/* process exists, don't override */
 				return 0;
+			}
 			/* it's us. either we're locking it again, or it's a
 			   stale lock file with same pid than us. either way,
 			   recreate it.. */
@@ -427,13 +429,15 @@
 		/* the lock file doesn't exist anymore, don't sleep */
 		io_loop_destroy(&ioloop);
 		return;
-	case IO_NOTIFY_DISABLED:
+	case IO_NOTIFY_NOSUPPORT:
 		/* listening for files not supported */
 		io_loop_destroy(&ioloop);
 		lock_info->use_io_notify = FALSE;
 		usleep(LOCK_RANDOM_USLEEP_TIME);
 		return;
 	}
+	/* timeout after a random time even when using notify, since it
+	   doesn't work reliably with e.g. NFS. */
 	to = timeout_add(LOCK_RANDOM_USLEEP_TIME/1000,
 			 dotlock_wait_end, ioloop);
 	io_loop_run(ioloop);
--- a/src/lib/file-lock.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib/file-lock.c	Mon Jun 09 05:11:18 2008 +0300
@@ -39,6 +39,7 @@
 		i_fatal("fcntl() locks not supported");
 #else
 		struct flock fl;
+		const char *errstr;
 
 		fl.l_type = lock_type;
 		fl.l_whence = SEEK_SET;
@@ -64,10 +65,12 @@
 			errno = EAGAIN;
 			return 0;
 		}
-		i_error("fcntl(%s) locking failed for file %s: %m",
+		errstr = errno != EACCES ? strerror(errno) :
+			"File is locked by another process (EACCES)";
+		i_error("fcntl(%s) locking failed for file %s: %s",
 			lock_type == F_UNLCK ? "unlock" :
 			lock_type == F_RDLCK ? "read-lock" : "write-lock",
-			path);
+			path, errstr);
 		return -1;
 #endif
 	}
--- a/src/lib/ioloop-notify-dn.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib/ioloop-notify-dn.c	Mon Jun 09 05:11:18 2008 +0300
@@ -29,6 +29,21 @@
 
 static struct ioloop_notify_handler_context *io_loop_notify_handler_init(void);
 
+static void ioloop_dnotify_disable(struct ioloop_notify_handler_context *ctx)
+{
+	if (ctx->disabled)
+		return;
+
+	if (--sigrt_refcount == 0)
+		signal(SIGRTMIN, SIG_IGN);
+
+	if (close(ctx->event_pipe[0]) < 0)
+		i_error("close(dnotify pipe[0]) failed: %m");
+	if (close(ctx->event_pipe[1]) < 0)
+		i_error("close(dnotify pipe[1]) failed: %m");
+	ctx->disabled = TRUE;
+}
+
 static void sigrt_handler(int signo ATTR_UNUSED, siginfo_t *si,
 			  void *data ATTR_UNUSED)
 {
@@ -37,9 +52,14 @@
 	int saved_errno = errno;
 	int ret;
 
+	if (ctx->disabled)
+		return;
+
 	ret = write(ctx->event_pipe[1], &si->si_fd, sizeof(int));
-	if (ret < 0 && errno != EINTR && errno != EAGAIN)
-		i_fatal("write(event_pipe) failed: %m");
+	if (ret < 0 && errno != EINTR && errno != EAGAIN) {
+		i_error("write(dnotify pipe) failed: %m");
+		ioloop_dnotify_disable(ctx);
+	}
 
 	i_assert(ret <= 0 || ret == sizeof(int));
 
@@ -55,9 +75,9 @@
 
 	ret = read(ctx->event_pipe[0], fd_buf, sizeof(fd_buf));
 	if (ret < 0)
-		i_fatal("read(event_pipe) failed: %m");
+		i_fatal("read(dnotify pipe) failed: %m");
 	if ((ret % sizeof(fd_buf[0])) != 0)
-		i_fatal("read(event_pipe) returned %d", ret);
+		i_fatal("read(dnotify pipe) returned %d", ret);
 	ret /= sizeof(fd_buf[0]);
 
 	if (gettimeofday(&ioloop_timeval, &ioloop_timezone) < 0)
@@ -77,14 +97,14 @@
 {
 	struct ioloop_notify_handler_context *ctx =
 		current_ioloop->notify_handler_context;
-	int fd, ret;
+	int fd;
 
 	*io_r = NULL;
 
 	if (ctx == NULL)
 		ctx = io_loop_notify_handler_init();
 	if (ctx->disabled)
-		return IO_NOTIFY_DISABLED;
+		return IO_NOTIFY_NOSUPPORT;
 
 	fd = open(path, O_RDONLY);
 	if (fd == -1) {
@@ -99,26 +119,24 @@
 		/* EINVAL means there's no realtime signals and no dnotify */
 		if (errno != EINVAL)
 			i_error("fcntl(F_SETSIG) failed: %m");
-		ctx->disabled = TRUE;
+		ioloop_dnotify_disable(ctx);
 		(void)close(fd);
-		return IO_NOTIFY_DISABLED;
+		return IO_NOTIFY_NOSUPPORT;
 	}
 	if (fcntl(fd, F_NOTIFY, DN_CREATE | DN_DELETE | DN_RENAME |
 		  DN_MULTISHOT) < 0) {
 		if (errno == ENOTDIR) {
 			/* we're trying to add dnotify to a non-directory fd.
 			   fail silently. */
-			ret = IO_NOTIFY_NOTFOUND;
 		} else {
 			/* dnotify not in kernel. disable it. */
 			if (errno != EINVAL)
 				i_error("fcntl(F_NOTIFY) failed: %m");
-			ctx->disabled = TRUE;
-			ret = IO_NOTIFY_DISABLED;
+			ioloop_dnotify_disable(ctx);
 		}
 		(void)fcntl(fd, F_SETSIG, 0);
 		(void)close(fd);
-		return ret;
+		return IO_NOTIFY_NOSUPPORT;
 	}
 
 	if (ctx->event_io == NULL) {
@@ -181,7 +199,7 @@
 			if (errno == EINVAL) {
 				/* kernel is too old to understand even RT
 				   signals, so there's no way dnotify works */
-				ctx->disabled = TRUE;
+				ioloop_dnotify_disable(ctx);
 			} else {
 				i_fatal("sigaction(SIGRTMIN) failed: %m");
 			}
@@ -195,14 +213,7 @@
 	struct ioloop_notify_handler_context *ctx =
 		ioloop->notify_handler_context;
 
-	if (--sigrt_refcount == 0)
-		signal(SIGRTMIN, SIG_IGN);
-
-	if (close(ctx->event_pipe[0]) < 0)
-		i_error("close(event_pipe[0]) failed: %m");
-	if (close(ctx->event_pipe[1]) < 0)
-		i_error("close(event_pipe[1]) failed: %m");
-
+	ioloop_dnotify_disable(ctx);
 	i_free(ctx);
 }
 
--- a/src/lib/ioloop-notify-inotify.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib/ioloop-notify-inotify.c	Mon Jun 09 05:11:18 2008 +0300
@@ -95,7 +95,7 @@
 	if (ctx == NULL)
 		ctx = io_loop_notify_handler_init();
 	if (ctx->disabled)
-		return IO_NOTIFY_DISABLED;
+		return IO_NOTIFY_NOSUPPORT;
 
 	wd = inotify_add_watch(ctx->inotify_fd, path,
 			       IN_CREATE | IN_DELETE | IN_DELETE_SELF |
@@ -106,8 +106,9 @@
 		if (errno == ENOENT || errno == ESTALE)
 			return IO_NOTIFY_NOTFOUND;
 
+		i_error("inotify_add_watch(%s) failed: %m", path);
 		ctx->disabled = TRUE;
-		return IO_NOTIFY_DISABLED;
+		return IO_NOTIFY_NOSUPPORT;
 	}
 
 	if (ctx->event_io == NULL) {
@@ -153,7 +154,8 @@
 			i_error("inotify_init() failed: %m");
 		else {
 			i_warning("Inotify instance limit for user exceeded, "
-				  "disabling.");
+				  "disabling. Increase "
+				  "/proc/sys/fs/inotify/max_user_instances");
 		}
 		ctx->disabled = TRUE;
 	} else {
--- a/src/lib/ioloop-notify-kqueue.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib/ioloop-notify-kqueue.c	Mon Jun 09 05:11:18 2008 +0300
@@ -118,7 +118,9 @@
 
 	fd = open(path, O_RDONLY);
 	if (fd == -1) {
-		if (errno != ENOENT)
+		/* ESTALE could happen with NFS. Don't bother giving an error
+		   message then. */
+		if (errno != ENOENT && errno != ESTALE)
 			i_error("open(%s) for kq notify failed: %m", path);
 		return IO_NOTIFY_NOTFOUND;
 	}
@@ -140,7 +142,7 @@
 		i_error("kevent(%d, %s) for notify failed: %m", fd, path);
 		(void)close(fd);
 		i_free(io);
-		return IO_NOTIFY_DISABLED;
+		return IO_NOTIFY_NOSUPPORT;
 	}
 
 	if (ctx->event_io == NULL) {
--- a/src/lib/ioloop-notify-none.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib/ioloop-notify-none.c	Mon Jun 09 05:11:18 2008 +0300
@@ -12,7 +12,7 @@
 	      void *context ATTR_UNUSED, struct io **io_r)
 {
 	*io_r = NULL;
-	return IO_NOTIFY_DISABLED;
+	return IO_NOTIFY_NOSUPPORT;
 }
 
 void io_loop_notify_remove(struct ioloop *ioloop ATTR_UNUSED,
--- a/src/lib/ioloop.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib/ioloop.c	Mon Jun 09 05:11:18 2008 +0300
@@ -144,9 +144,16 @@
 		/* if we came here from io_loop_handle_timeouts(),
 		   next_run must be larger than tv_now or we could go to
 		   infinite loop */
-		if (++timeout->next_run.tv_usec == 0)
+		timeout->next_run.tv_usec += 1000;
+		if (timeout->next_run.tv_usec >= 1000000) {
 			timeout->next_run.tv_sec++;
+			timeout->next_run.tv_usec -= 1000000;
+		}
 	}
+	i_assert(tv_now == NULL ||
+		 timeout->next_run.tv_sec > tv_now->tv_sec ||
+		 (timeout->next_run.tv_sec == tv_now->tv_sec &&
+		  timeout->next_run.tv_usec > tv_now->tv_usec));
 	priorityq_remove(current_ioloop->timeouts, &timeout->item);
 	priorityq_add(current_ioloop->timeouts, &timeout->item);
 }
--- a/src/lib/ioloop.h	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib/ioloop.h	Mon Jun 09 05:11:18 2008 +0300
@@ -20,9 +20,13 @@
 };
 
 enum io_notify_result {
+	/* Notify added successfully */
 	IO_NOTIFY_ADDED,
+	/* Specified file doesn't exist, can't wait on it */
 	IO_NOTIFY_NOTFOUND,
-	IO_NOTIFY_DISABLED
+	/* Can't add notify for specified file. Main reasons for this:
+	   a) No notify support at all, b) Only directory notifies supported */
+	IO_NOTIFY_NOSUPPORT
 };
 
 typedef void io_callback_t(void *context);
--- a/src/lib/mempool-alloconly.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib/mempool-alloconly.c	Mon Jun 09 05:11:18 2008 +0300
@@ -40,10 +40,18 @@
 #define SIZEOF_POOLBLOCK (MEM_ALIGN(sizeof(struct pool_block)))
 
 #define POOL_BLOCK_DATA(block) \
-	((char *) (block) + SIZEOF_POOLBLOCK)
+	((unsigned char *) (block) + SIZEOF_POOLBLOCK)
 
 #define DEFAULT_BASE_SIZE MEM_ALIGN(sizeof(struct alloconly_pool))
 
+#ifdef DEBUG
+#  define CLEAR_CHR 0xde
+#  define SENTRY_COUNT 8
+#else
+#  define SENTRY_COUNT 0
+#  define CLEAR_CHR 0
+#endif
+
 static const char *pool_alloconly_get_name(pool_t pool);
 static void pool_alloconly_ref(pool_t pool);
 static void pool_alloconly_unref(pool_t *pool);
@@ -79,28 +87,48 @@
 };
 
 #ifdef DEBUG
-static void check_nuls(struct pool_block *block)
+static void check_sentries(struct pool_block *block)
 {
-	const char *data = POOL_BLOCK_DATA(block);
-	size_t i;
+	const unsigned char *data = POOL_BLOCK_DATA(block);
+	size_t i, max_pos, alloc_size, used_size;
+
+	used_size = block->size - block->left;
+	for (i = 0; i < used_size; ) {
+		alloc_size = *(size_t *)(data + i);
+		if (alloc_size == 0 || used_size - i < alloc_size)
+			i_panic("mempool-alloconly: saved alloc size broken");
+		i += MEM_ALIGN(sizeof(alloc_size));
+		max_pos = i + MEM_ALIGN(alloc_size + SENTRY_COUNT);
+		i += alloc_size;
 
-	for (i = block->size - block->left; i < block->size; i++) {
+		for (; i < max_pos; i++) {
+			if (data[i] != CLEAR_CHR)
+				i_panic("mempool-alloconly: buffer overflow");
+		}
+	}
+
+	if (i != used_size)
+		i_panic("mempool-alloconly: used_size wrong");
+
+	/* The unused data must be NULs */
+	for (; i < block->size; i++) {
 		if (data[i] != '\0')
 			i_unreached();
 	}
 	if (block->prev != NULL)
-		check_nuls(block->prev);
+		check_sentries(block->prev);
 }
 #endif
 
 pool_t pool_alloconly_create(const char *name ATTR_UNUSED, size_t size)
 {
 	struct alloconly_pool apool, *new_apool;
-	size_t min_alloc = MEM_ALIGN(sizeof(struct alloconly_pool)) +
-		SIZEOF_POOLBLOCK;
+	size_t min_alloc = SIZEOF_POOLBLOCK +
+		MEM_ALIGN(sizeof(struct alloconly_pool) + SENTRY_COUNT);
 
 #ifdef DEBUG
-	min_alloc += MEM_ALIGN(strlen(name) + 1);
+	min_alloc += MEM_ALIGN(strlen(name) + 1 + SENTRY_COUNT) +
+		sizeof(size_t)*2;
 #endif
 
 	/* create a fake alloconly_pool so we can call block_alloc() */
@@ -115,8 +143,6 @@
 	/* now allocate the actual alloconly_pool from the created block */
 	new_apool = p_new(&apool.pool, struct alloconly_pool, 1);
 	*new_apool = apool;
-	/* the pool allocation must be from the first block */
-	i_assert(apool.block->prev == NULL);
 #ifdef DEBUG
 	if (strncmp(name, MEMPOOL_GROWING, strlen(MEMPOOL_GROWING)) == 0) {
 		name += strlen(MEMPOOL_GROWING);
@@ -128,6 +154,8 @@
 	new_apool->base_size = new_apool->block->size - new_apool->block->left;
 	new_apool->block->last_alloc_size = 0;
 #endif
+	/* the first pool allocations must be from the first block */
+	i_assert(new_apool->block->prev == NULL);
 
 	return &new_apool->pool;
 }
@@ -153,10 +181,12 @@
 	/* destroy the last block */
 	block = apool->block;
 #ifdef DEBUG
-	safe_memset(block, 0xde, SIZEOF_POOLBLOCK + apool->block->size);
+	safe_memset(block, CLEAR_CHR, SIZEOF_POOLBLOCK + apool->block->size);
 #else
-	if (apool->clean_frees)
-		safe_memset(block, 0, SIZEOF_POOLBLOCK + apool->block->size);
+	if (apool->clean_frees) {
+		safe_memset(block, CLEAR_CHR,
+			    SIZEOF_POOLBLOCK + apool->block->size);
+	}
 #endif
 
 #ifndef USE_GC
@@ -238,22 +268,34 @@
 {
 	struct alloconly_pool *apool = (struct alloconly_pool *)pool;
 	void *mem;
+	size_t alloc_size;
 
 	if (unlikely(size == 0 || size > SSIZE_T_MAX))
 		i_panic("Trying to allocate %"PRIuSIZE_T" bytes", size);
 
-	size = MEM_ALIGN(size);
+#ifndef DEBUG
+	alloc_size = MEM_ALIGN(size);
+#else
+	alloc_size = MEM_ALIGN(sizeof(size)) + MEM_ALIGN(size + SENTRY_COUNT);
+#endif
 
-	if (apool->block->left < size) {
+	if (apool->block->left < alloc_size) {
 		/* we need a new block */
-		block_alloc(apool, size + SIZEOF_POOLBLOCK);
+		block_alloc(apool, alloc_size + SIZEOF_POOLBLOCK);
 	}
 
 	mem = POOL_BLOCK_DATA(apool->block) +
 		(apool->block->size - apool->block->left);
 
-	apool->block->left -= size;
-	apool->block->last_alloc_size = size;
+	apool->block->left -= alloc_size;
+	apool->block->last_alloc_size = alloc_size;
+#ifdef DEBUG
+	memcpy(mem, &size, sizeof(size));
+	mem = PTR_OFFSET(mem, MEM_ALIGN(sizeof(size)));
+	/* write CLEAR_CHRs to sentry */
+	memset(PTR_OFFSET(mem, size), CLEAR_CHR,
+	       MEM_ALIGN(size + SENTRY_COUNT) - size);
+#endif
 	return mem;
 }
 
@@ -325,7 +367,7 @@
 	size_t base_size, avail_size;
 
 #ifdef DEBUG
-	check_nuls(apool->block);
+	check_sentries(apool->block);
 #endif
 
 	/* destroy all blocks but the oldest, which contains the
@@ -335,10 +377,12 @@
 		apool->block = block->prev;
 
 #ifdef DEBUG
-		safe_memset(block, 0xde, SIZEOF_POOLBLOCK + block->size);
+		safe_memset(block, CLEAR_CHR, SIZEOF_POOLBLOCK + block->size);
 #else
-		if (apool->clean_frees)
-			safe_memset(block, 0, SIZEOF_POOLBLOCK + block->size);
+		if (apool->clean_frees) {
+			safe_memset(block, CLEAR_CHR,
+				    SIZEOF_POOLBLOCK + block->size);
+		}
 #endif
 #ifndef USE_GC
 		free(block);
--- a/src/lib/module-dir.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib/module-dir.c	Mon Jun 09 05:11:18 2008 +0300
@@ -250,9 +250,9 @@
 
 	module_pos = &modules;
 	for (i = 0; i < count; i++) T_BEGIN {
-		const char *name = names_p[i];
 		const char *path, *stripped_name;
 
+		name = names_p[i];
 		stripped_name = module_file_get_name(name);
 		if (!module_want_load(module_names_arr, stripped_name))
 			module = NULL;
--- a/src/lib/network.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib/network.c	Mon Jun 09 05:11:18 2008 +0300
@@ -703,3 +703,52 @@
 
 	return TRUE;
 }
+
+bool net_is_in_network(const struct ip_addr *ip,
+		       const struct ip_addr *net_ip, unsigned int bits)
+{
+	const uint32_t *ip1, *ip2;
+	uint32_t mask, i1, i2;
+	unsigned int pos, i;
+
+	if (IPADDR_IS_V4(ip) != IPADDR_IS_V4(net_ip)) {
+		/* one is IPv6 and one is IPv4 */
+		return FALSE;
+	}
+	i_assert(IPADDR_IS_V6(ip) == IPADDR_IS_V6(net_ip));
+
+	if (IPADDR_IS_V4(ip)) {
+		ip1 = &ip->u.ip4.s_addr;
+		ip2 = &net_ip->u.ip4.s_addr;
+	} else {
+#ifdef HAVE_IPV6
+		ip1 = (const void *)&ip->u.ip6;
+		ip2 = (const void *)&net_ip->u.ip6;
+#else
+		/* shouldn't get here */
+		return FALSE;
+#endif
+	}
+
+	/* check first the full 32bit ints */
+	for (pos = 0, i = 0; pos + 32 <= bits; pos += 32, i++) {
+		if (ip1[i] != ip2[i])
+			return FALSE;
+	}
+	i1 = htonl(ip1[i]);
+	i2 = htonl(ip2[i]);
+
+	/* check the last full bytes */
+	for (mask = 0xff000000; pos + 8 <= bits; pos += 8, mask >>= 8) {
+		if ((i1 & mask) != (i2 & mask))
+			return FALSE;
+	}
+
+	/* check the last bits, they're reversed in bytes */
+	bits -= pos;
+	for (mask = 0x80000000 >> (pos % 32); bits > 0; bits--, mask >>= 1) {
+		if ((i1 & mask) != (i2 & mask))
+			return FALSE;
+	}
+	return TRUE;
+}
--- a/src/lib/network.h	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib/network.h	Mon Jun 09 05:11:18 2008 +0300
@@ -110,4 +110,8 @@
 bool is_ipv4_address(const char *addr);
 bool is_ipv6_address(const char *addr);
 
+/* Returns TRUE if ip is in net_ip/bits network. */
+bool net_is_in_network(const struct ip_addr *ip,
+		       const struct ip_addr *net_ip, unsigned int bits);
+
 #endif
--- a/src/lib/randgen.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib/randgen.c	Mon Jun 09 05:11:18 2008 +0300
@@ -7,6 +7,8 @@
 
 #ifdef HAVE_DEV_URANDOM
 
+#define URANDOM_PATH "/dev/urandom"
+
 #include "fd-close-on-exec.h"
 #include <unistd.h>
 #include <fcntl.h>
@@ -22,10 +24,16 @@
 	i_assert(init_refcount > 0);
 	i_assert(size < SSIZE_T_MAX);
 
-	for (pos = 0; pos < size; pos += ret) {
+	for (pos = 0; pos < size; ) {
 		ret = read(urandom_fd, (char *) buf + pos, size - pos);
-		if (unlikely(ret < 0 && errno != EINTR))
-			i_fatal("Error reading from /dev/urandom: %m");
+		if (unlikely(ret <= 0)) {
+			if (ret == 0)
+				i_fatal("EOF when reading from "URANDOM_PATH);
+			else if (errno != EINTR)
+				i_fatal("read("URANDOM_PATH") failed: %m");
+		} else {
+			pos += ret;
+		}
 	}
 }
 
@@ -36,13 +44,13 @@
 	if (init_refcount++ > 0)
 		return;
 
-	urandom_fd = open("/dev/urandom", O_RDONLY);
+	urandom_fd = open(URANDOM_PATH, O_RDONLY);
 	if (urandom_fd == -1) {
 		if (errno == ENOENT) {
-			i_fatal("/dev/urandom doesn't exist, "
+			i_fatal(URANDOM_PATH" doesn't exist, "
 				"currently we require it");
 		} else {
-			i_fatal("Can't open /dev/urandom: %m");
+			i_fatal("Can't open "URANDOM_PATH": %m");
 		}
 	}
 
--- a/src/lib/restrict-access.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib/restrict-access.c	Mon Jun 09 05:11:18 2008 +0300
@@ -12,8 +12,9 @@
 #include <time.h>
 #include <grp.h>
 
-static gid_t primary_gid = (gid_t)-1, privileged_gid = (gid_t)-1;
-static bool using_priv_gid = FALSE;
+static gid_t process_primary_gid = (gid_t)-1;
+static gid_t process_privileged_gid = (gid_t)-1;
+static bool process_using_priv_gid = FALSE;
 
 void restrict_access_set_env(const char *user, uid_t uid,
 			     gid_t gid, gid_t privileged_gid,
@@ -160,7 +161,7 @@
 	/* if we're using a privileged GID, we can temporarily drop our
 	   effective GID. we still want to be able to use its privileges,
 	   so add it to supplementary groups. */
-	add_primary_gid = privileged_gid != (gid_t)-1;
+	add_primary_gid = process_privileged_gid != (gid_t)-1;
 
 	tmp = extra_groups == NULL ? &empty :
 		t_strsplit_spaces(extra_groups, ", ");
@@ -171,7 +172,7 @@
 				       have_root_group);
 		/* see if the list already contains the primary GID */
 		for (i = 0; i < gid_count; i++) {
-			if (gid_list[i] == primary_gid) {
+			if (gid_list[i] == process_primary_gid) {
 				add_primary_gid = FALSE;
 				break;
 			}
@@ -184,7 +185,7 @@
 		/* Some OSes don't like an empty groups list,
 		   so use the primary GID as the only one. */
 		gid_list = t_new(gid_t, 2);
-		gid_list[0] = primary_gid;
+		gid_list[0] = process_primary_gid;
 		gid_count = 1;
 		add_primary_gid = FALSE;
 	}
@@ -195,11 +196,11 @@
 		memcpy(gid_list2, gid_list, gid_count * sizeof(gid_t));
 		for (; *tmp != NULL; tmp++) {
 			gid = get_group_id(*tmp);
-			if (gid != primary_gid)
+			if (gid != process_primary_gid)
 				gid_list2[gid_count++] = gid;
 		}
 		if (add_primary_gid)
-			gid_list2[gid_count++] = primary_gid;
+			gid_list2[gid_count++] = process_primary_gid;
 		gid_list = gid_list2;
 	}
 
@@ -224,28 +225,30 @@
 
 	/* set the primary/privileged group */
 	env = getenv("RESTRICT_SETGID");
-	primary_gid = env == NULL || *env == '\0' ? (gid_t)-1 :
+	process_primary_gid = env == NULL || *env == '\0' ? (gid_t)-1 :
 		(gid_t)strtoul(env, NULL, 10);
 	env = getenv("RESTRICT_SETGID_PRIV");
-	privileged_gid = env == NULL || *env == '\0' ? (gid_t)-1 :
+	process_privileged_gid = env == NULL || *env == '\0' ? (gid_t)-1 :
 		(gid_t)strtoul(env, NULL, 10);
 
-	have_root_group = primary_gid == 0;
-	if (primary_gid != (gid_t)-1 || privileged_gid != (gid_t)-1) {
-		if (primary_gid == (gid_t)-1)
-			primary_gid = getegid();
-		restrict_init_groups(primary_gid, privileged_gid);
+	have_root_group = process_primary_gid == 0;
+	if (process_primary_gid != (gid_t)-1 ||
+	    process_privileged_gid != (gid_t)-1) {
+		if (process_primary_gid == (gid_t)-1)
+			process_primary_gid = getegid();
+		restrict_init_groups(process_primary_gid,
+				     process_privileged_gid);
 	} else {
-		if (primary_gid == (gid_t)-1)
-			primary_gid = getegid();
+		if (process_primary_gid == (gid_t)-1)
+			process_primary_gid = getegid();
 	}
 
 	/* set system user's groups */
 	env = getenv("RESTRICT_USER");
 	if (env != NULL && *env != '\0' && is_root) {
-		if (initgroups(env, primary_gid) < 0) {
+		if (initgroups(env, process_primary_gid) < 0) {
 			i_fatal("initgroups(%s, %s) failed: %m",
-				env, dec2str(primary_gid));
+				env, dec2str(process_primary_gid));
 		}
 		preserve_groups = TRUE;
 	}
@@ -303,18 +306,18 @@
 	env = getenv("RESTRICT_GID_FIRST");
 	if (env != NULL && atoi(env) != 0)
 		allow_root_gid = FALSE;
-	else if (primary_gid == 0 || privileged_gid == 0)
+	else if (process_primary_gid == 0 || process_privileged_gid == 0)
 		allow_root_gid = TRUE;
 	else
 		allow_root_gid = FALSE;
 
 	if (!allow_root_gid && uid != 0) {
 		if (getgid() == 0 || getegid() == 0 || setgid(0) == 0) {
-			if (primary_gid == 0)
+			if (process_primary_gid == 0)
 				i_fatal("GID 0 isn't permitted");
 			i_fatal("We couldn't drop root group privileges "
 				"(wanted=%s, gid=%s, egid=%s)",
-				dec2str(primary_gid),
+				dec2str(process_primary_gid),
 				dec2str(getgid()), dec2str(getegid()));
 		}
 	}
@@ -323,7 +326,7 @@
 	env_put("RESTRICT_USER=");
 	env_put("RESTRICT_CHROOT=");
 	env_put("RESTRICT_SETUID=");
-	if (privileged_gid == (gid_t)-1) {
+	if (process_privileged_gid == (gid_t)-1) {
 		/* if we're dropping privileges before executing and
 		   a privileged group is set, the groups must be fixed
 		   after exec */
@@ -337,29 +340,29 @@
 
 int restrict_access_use_priv_gid(void)
 {
-	i_assert(!using_priv_gid);
+	i_assert(!process_using_priv_gid);
 
-	if (privileged_gid == (gid_t)-1)
+	if (process_privileged_gid == (gid_t)-1)
 		return 0;
-	if (setegid(privileged_gid) < 0) {
+	if (setegid(process_privileged_gid) < 0) {
 		i_error("setegid(privileged) failed: %m");
 		return -1;
 	}
-	using_priv_gid = TRUE;
+	process_using_priv_gid = TRUE;
 	return 0;
 }
 
 void restrict_access_drop_priv_gid(void)
 {
-	if (!using_priv_gid)
+	if (!process_using_priv_gid)
 		return;
 
-	if (setegid(primary_gid) < 0)
+	if (setegid(process_primary_gid) < 0)
 		i_fatal("setegid(primary) failed: %m");
-	using_priv_gid = FALSE;
+	process_using_priv_gid = FALSE;
 }
 
 bool restrict_access_have_priv_gid(void)
 {
-	return privileged_gid != (gid_t)-1;
+	return process_privileged_gid != (gid_t)-1;
 }
--- a/src/lib/str-find.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib/str-find.c	Mon Jun 09 05:11:18 2008 +0300
@@ -1,5 +1,7 @@
 /* Copyright (c) 2007-2008 Dovecot authors, see the included COPYING file */
 
+/* @UNSAFE: whole file */
+
 #include "lib.h"
 #include "str-find.h"
 
@@ -53,7 +55,7 @@
 	unsigned int j, *suffixes;
 	int i;
 
-	suffixes = t_buffer_get(ctx->key_len);
+	suffixes = t_buffer_get(sizeof(*suffixes) * ctx->key_len);
 	init_suffixes(ctx, suffixes);
 
 	for (i = 0; i < (int)ctx->key_len; i++)
@@ -71,7 +73,7 @@
 	for (i = 0; i <= (int)ctx->key_len - 2; i++)
 		ctx->goodtab[len_1 - suffixes[i]] = len_1 - i;
 }
- 
+
 struct str_find_context *str_find_init(pool_t pool, const char *key)
 {
 	struct str_find_context *ctx;
@@ -80,7 +82,7 @@
 	ctx = p_malloc(pool, sizeof(struct str_find_context) +
 		       sizeof(ctx->goodtab[0]) * key_len);
 	ctx->pool = pool;
-	ctx->matches = p_malloc(pool, key_len);
+	ctx->matches = p_new(pool, unsigned int, key_len);
 	ctx->key_len = key_len;
 	ctx->key = p_malloc(pool, key_len);
 	memcpy(ctx->key, key, key_len);
--- a/src/lib/var-expand.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/lib/var-expand.c	Mon Jun 09 05:11:18 2008 +0300
@@ -10,6 +10,7 @@
 #include "var-expand.h"
 
 #include <stdlib.h>
+#include <ctype.h>
 
 struct var_expand_context {
 	int offset;
@@ -81,7 +82,8 @@
 	return str_c(hash);
 }
 
-static const char *m_str_md5(const char *str, struct var_expand_context *ctx ATTR_UNUSED)
+static const char *
+m_str_md5(const char *str, struct var_expand_context *ctx ATTR_UNUSED)
 {
 	unsigned char digest[16];
 
@@ -90,7 +92,8 @@
 	return binary_to_hex(digest, sizeof(digest));
 }
 
-static const char *m_str_ldap_dn(const char *str, struct var_expand_context *ctx ATTR_UNUSED)
+static const char *
+m_str_ldap_dn(const char *str, struct var_expand_context *ctx ATTR_UNUSED)
 {
 	string_t *ret = t_str_new(256);
 
@@ -105,6 +108,17 @@
 	return str_free_without_data(&ret);
 }
 
+static const char *
+m_str_trim(const char *str, struct var_expand_context *ctx ATTR_UNUSED)
+{
+	unsigned int len;
+
+	len = strlen(str);
+	while (len > 0 && i_isspace(str[len-1]))
+		len--;
+	return t_strndup(str, len);
+}
+
 #define MAX_MODIFIER_COUNT 10
 static const struct var_expand_modifier modifiers[] = {
 	{ 'L', m_str_lcase },
@@ -115,6 +129,7 @@
 	{ 'H', m_str_hash },
 	{ 'M', m_str_md5 },
 	{ 'D', m_str_ldap_dn },
+	{ 'T', m_str_trim },
 	{ '\0', NULL }
 };
 
--- a/src/login-common/ssl-proxy-openssl.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/login-common/ssl-proxy-openssl.c	Mon Jun 09 05:11:18 2008 +0300
@@ -309,8 +309,11 @@
 	size_t err_size = 256;
 
 	err = ERR_get_error();
-	if (err == 0)
-		return strerror(errno);
+	if (err == 0) {
+		if (errno != 0)
+			return strerror(errno);
+		return "Unknown error";
+	}
 
 	buf = t_malloc(err_size);
 	buf[err_size-1] = '\0';
@@ -806,6 +809,8 @@
 
 	ssl_free_parameters(&ssl_params);
 	SSL_CTX_free(ssl_ctx);
+	EVP_cleanup();
+	ERR_free_strings();
 }
 
 #endif
--- a/src/master/auth-process.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/master/auth-process.c	Mon Jun 09 05:11:18 2008 +0300
@@ -342,6 +342,7 @@
 	if (!p->initialized && io_loop_is_running(ioloop) && !p->external) {
 		/* log the process exit and kill ourself */
 		child_processes_deinit();
+		log_deinit();
 		i_fatal("Auth process died too early - shutting down");
 	}
 
--- a/src/master/dict-process.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/master/dict-process.c	Mon Jun 09 05:11:18 2008 +0300
@@ -25,7 +25,7 @@
 	struct io *io;
 };
 
-static struct dict_process *process;
+static struct dict_process *dict_process;
 
 static void dict_process_unlisten(struct dict_process *process);
 
@@ -84,6 +84,12 @@
 	child_process_init_env();
 	env_put(t_strconcat("DICT_LISTEN_FROM_FD=", process->path, NULL));
 
+	if (settings_root->defaults->dict_db_config != NULL) {
+		env_put(t_strconcat("DB_CONFIG=",
+				    settings_root->defaults->dict_db_config,
+				    NULL));
+	}
+
 	dicts = array_get(&settings_root->dicts, &count);
 	i_assert((count % 2) == 0);
 	for (i = 0; i < count; i += 2)
@@ -175,7 +181,9 @@
 
 void dict_process_init(void)
 {
-	process = i_new(struct dict_process, 1);
+	struct dict_process *process;
+
+	process = dict_process = i_new(struct dict_process, 1);
 	process->process.type = PROCESS_TYPE_DICT;
 	process->fd = -1;
 	process->path = i_strconcat(settings_root->defaults->base_dir,
@@ -188,6 +196,8 @@
 
 void dict_process_deinit(void)
 {
+	struct dict_process *process = dict_process;
+
 	dict_process_unlisten(process);
 	if (process->log != NULL)
 		log_unref(process->log);
@@ -197,6 +207,8 @@
 
 void dict_process_kill(void)
 {
+	struct dict_process *process = dict_process;
+
 	if (process->log != NULL) {
 		log_unref(process->log);
 		process->log = NULL;
--- a/src/master/listener.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/master/listener.c	Mon Jun 09 05:11:18 2008 +0300
@@ -130,7 +130,7 @@
 }
 
 static void
-listener_init(const char *set_name, const char *listen,
+listener_init(const char *set_name, const char *listen_list,
 	      unsigned int default_port, ARRAY_TYPE(listener) *listens_arr)
 {
 	const char *const *tmp;
@@ -148,7 +148,7 @@
 	l.fd = -1;
 	l.wanted = TRUE;
 
-	for (tmp = t_strsplit_spaces(listen, ", "); *tmp != NULL; tmp++) {
+	for (tmp = t_strsplit_spaces(listen_list, ", "); *tmp != NULL; tmp++) {
 		l.port = default_port;
 		resolve_ip(set_name, *tmp, &l.ip, &l.port);
 
@@ -204,7 +204,7 @@
 {
 	const char *const *proto;
 	unsigned int default_port;
-	bool listen = FALSE, ssl_listen = FALSE;
+	bool nonssl_listen = FALSE, ssl_listen = FALSE;
 
 	if (set == NULL)
 		return;
@@ -214,14 +214,14 @@
 	for (; *proto != NULL; proto++) {
 		if (strcasecmp(*proto, "imap") == 0) {
 			if (set->protocol == MAIL_PROTOCOL_IMAP)
-				listen = TRUE;
+				nonssl_listen = TRUE;
 		} else if (strcasecmp(*proto, "imaps") == 0) {
 			if (set->protocol == MAIL_PROTOCOL_IMAP &&
 			    !set->ssl_disable)
 				ssl_listen = TRUE;
 		} else if (strcasecmp(*proto, "pop3") == 0) {
 			if (set->protocol == MAIL_PROTOCOL_POP3)
-				listen = TRUE;
+				nonssl_listen = TRUE;
 		} else if (strcasecmp(*proto, "pop3s") == 0) {
 			if (set->protocol == MAIL_PROTOCOL_POP3 &&
 			    !set->ssl_disable)
@@ -229,7 +229,7 @@
 		}
 	}
 
-	if (!listen)
+	if (!nonssl_listen)
 		listener_close_fds(&set->listens);
 	else {
 		default_port = set->protocol == MAIL_PROTOCOL_IMAP ? 143 : 110;
--- a/src/master/login-process.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/master/login-process.c	Mon Jun 09 05:11:18 2008 +0300
@@ -689,7 +689,10 @@
 	fd_limit = 16 + listen_count + ssl_listen_count +
 		2 * (group->set->login_process_per_connection ? 1 :
 		     group->set->login_max_connections);
-	restrict_fd_limit(fd_limit);
+#ifdef DEBUG
+	if (!gdb)
+#endif
+		restrict_fd_limit(fd_limit);
 
 	/* make sure we don't leak syslog fd, but do it last so that
 	   any errors above will be logged */
--- a/src/master/mail-process.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/master/mail-process.c	Mon Jun 09 05:11:18 2008 +0300
@@ -519,7 +519,7 @@
 
 enum master_login_status
 create_mail_process(enum process_type process_type, struct settings *set,
-		    int socket, const struct ip_addr *local_ip,
+		    int socket_fd, const struct ip_addr *local_ip,
 		    const struct ip_addr *remote_ip,
 		    const char *user, const char *const *args,
 		    bool dump_capability)
@@ -535,8 +535,8 @@
 	uid_t uid;
 	gid_t gid;
 	ARRAY_DEFINE(extra_args, const char *);
-	unsigned int i, count, left, process_count, throttle;
-	int ret, log_fd, nice, chdir_errno;
+	unsigned int i, len, count, left, process_count, throttle;
+	int ret, log_fd, nice_value, chdir_errno;
 	bool home_given, nfs_check;
 
 	i_assert(process_type == PROCESS_TYPE_IMAP ||
@@ -558,7 +558,7 @@
 
 	t_array_init(&extra_args, 16);
 	mail = home_dir = chroot_dir = system_user = "";
-	uid = (uid_t)-1; gid = (gid_t)-1; nice = 0;
+	uid = (uid_t)-1; gid = (gid_t)-1; nice_value = 0;
 	home_given = FALSE;
 	for (; *args != NULL; args++) {
 		if (strncmp(*args, "home=", 5) == 0) {
@@ -569,7 +569,7 @@
 		else if (strncmp(*args, "chroot=", 7) == 0)
 			chroot_dir = *args + 7;
 		else if (strncmp(*args, "nice=", 5) == 0)
-			nice = atoi(*args + 5);
+			nice_value = atoi(*args + 5);
 		else if (strncmp(*args, "system_user=", 12) == 0)
 			system_user = *args + 12;
 		else if (strncmp(*args, "uid=", 4) == 0) {
@@ -638,6 +638,12 @@
 			chroot_dir, user);
 		return MASTER_LOGIN_STATUS_INTERNAL_ERROR;
 	}
+	len = strlen(chroot_dir);
+	if (len > 2 && strcmp(chroot_dir + len - 2, "/.") == 0 &&
+	    strncmp(home_dir, chroot_dir, len - 2) == 0) {
+		/* strip chroot dir from home dir */
+		home_dir += len - 2;
+	}
 
 	if (!dump_capability) {
 		throttle = set->mail_debug ? 0 :
@@ -699,9 +705,9 @@
 	}
 
 #ifdef HAVE_SETPRIORITY
-	if (nice != 0) {
-		if (setpriority(PRIO_PROCESS, 0, nice) < 0)
-			i_error("setpriority(%d) failed: %m", nice);
+	if (nice_value != 0) {
+		if (setpriority(PRIO_PROCESS, 0, nice_value) < 0)
+			i_error("setpriority(%d) failed: %m", nice_value);
 	}
 #endif
 
@@ -714,9 +720,9 @@
 	child_process_init_env();
 
 	/* move the client socket into stdin and stdout fds, log to stderr */
-	if (dup2(dump_capability ? null_fd : socket, 0) < 0)
+	if (dup2(dump_capability ? null_fd : socket_fd, 0) < 0)
 		i_fatal("dup2(stdin) failed: %m");
-	if (dup2(socket, 1) < 0)
+	if (dup2(socket_fd, 1) < 0)
 		i_fatal("dup2(stdout) failed: %m");
 	if (dup2(log_fd, 2) < 0)
 		i_fatal("dup2(stderr) failed: %m");
@@ -736,7 +742,7 @@
 	if (dump_capability)
 		env_put("DUMP_CAPABILITY=1");
 
-	if (*home_dir == '\0') {
+	if (*home_dir == '\0' && *chroot_dir == '\0') {
 		full_home_dir = "";
 		ret = -1;
 	} else {
--- a/src/master/mail-process.h	Sat May 17 17:50:54 2008 +0300
+++ b/src/master/mail-process.h	Mon Jun 09 05:11:18 2008 +0300
@@ -10,7 +10,7 @@
 
 enum master_login_status
 create_mail_process(enum process_type process_type, struct settings *set,
-		    int socket, const struct ip_addr *local_ip,
+		    int socket_fd, const struct ip_addr *local_ip,
 		    const struct ip_addr *remote_ip,
 		    const char *user, const char *const *args,
 		    bool dump_capability);
--- a/src/master/master-settings-defs.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/master/master-settings-defs.c	Mon Jun 09 05:11:18 2008 +0300
@@ -124,5 +124,8 @@
 	DEF_STR(pop3_client_workarounds),
 	DEF_STR(pop3_logout_format),
 
+	/* dict */
+	DEF_STR(dict_db_config),
+
 	{ 0, NULL, 0 }
 };
--- a/src/master/master-settings.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/master/master-settings.c	Mon Jun 09 05:11:18 2008 +0300
@@ -290,6 +290,9 @@
 	MEMBER(pop3_client_workarounds) "",
 	MEMBER(pop3_logout_format) "top=%t/%p, retr=%r/%b, del=%d/%m, size=%s",
 
+	/* dict */
+	MEMBER(dict_db_config) NULL,
+
 	/* .. */
 };
 
@@ -1647,7 +1650,7 @@
 {
 	const struct auth_passdb_settings *passdb;
 	const struct auth_userdb_settings *userdb;
-	const struct auth_socket_settings *socket;
+	const struct auth_socket_settings *socket_set;
 	const void *sets[2], *sets2[2];
 	const void *empty_defaults;
 
@@ -1679,23 +1682,23 @@
 				      nondefaults, 4);
 		}
 
-		socket = auth->sockets;
-		for (; socket != NULL; socket = socket->next) {
+		socket_set = auth->sockets;
+		for (; socket_set != NULL; socket_set = socket_set->next) {
 			printf("  socket:\n");
-			sets2[1] = socket;
+			sets2[1] = socket_set;
 			settings_dump(auth_socket_setting_defs, sets2, NULL, 2,
 				      nondefaults, 4);
 
-			if (socket->client.used) {
+			if (socket_set->client.used) {
 				printf("    client:\n");
-				sets2[1] = &socket->client;
+				sets2[1] = &socket_set->client;
 				settings_dump(socket_setting_defs, sets2, NULL,
 					      2, nondefaults, 6);
 			}
 
-			if (socket->master.used) {
+			if (socket_set->master.used) {
 				printf("    master:\n");
-				sets2[1] = &socket->master;
+				sets2[1] = &socket_set->master;
 				settings_dump(socket_setting_defs, sets2, NULL,
 					      2, nondefaults, 6);
 			}
--- a/src/master/master-settings.h	Sat May 17 17:50:54 2008 +0300
+++ b/src/master/master-settings.h	Mon Jun 09 05:11:18 2008 +0300
@@ -136,6 +136,9 @@
 	const char *pop3_client_workarounds;
 	const char *pop3_logout_format;
 
+	/* dict */
+	const char *dict_db_config;
+
 	/* .. */
 	ARRAY_TYPE(listener) listens;
 	ARRAY_TYPE(listener) ssl_listens;
--- a/src/plugins/acl/acl-backend-vfile-acllist.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/plugins/acl/acl-backend-vfile-acllist.c	Mon Jun 09 05:11:18 2008 +0300
@@ -203,7 +203,12 @@
 	   the file at the same time the result should be the same. */
 	fd = safe_mkstemp(path, mode, (uid_t)-1, gid);
 	if (fd == -1) {
-		i_error("safe_mkstemp(%s) failed: %m", str_c(path));
+		if (errno == EACCES) {
+			/* Ignore silently if we can't create it */
+			return 0;
+		}
+		i_error("dovecot-acl-list creation failed: "
+			"safe_mkstemp(%s) failed: %m", str_c(path));
 		return -1;
 	}
 	output = o_stream_create_fd_file(fd, 0, FALSE);
--- a/src/plugins/acl/acl-backend-vfile.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/plugins/acl/acl-backend-vfile.c	Mon Jun 09 05:11:18 2008 +0300
@@ -94,9 +94,16 @@
 	return 0;
 }
 
-static void acl_backend_vfile_deinit(struct acl_backend *backend)
+static void acl_backend_vfile_deinit(struct acl_backend *_backend)
 {
-	pool_unref(&backend->pool);
+	struct acl_backend_vfile *backend =
+		(struct acl_backend_vfile *)_backend;
+
+	if (backend->acllist_pool != NULL) {
+		array_free(&backend->acllist);
+		pool_unref(&backend->acllist_pool);
+	}
+	pool_unref(&backend->backend.pool);
 }
 
 static struct acl_object *
--- a/src/plugins/acl/acl-cache.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/plugins/acl/acl-cache.c	Mon Jun 09 05:11:18 2008 +0300
@@ -62,6 +62,8 @@
 	struct acl_cache *cache = *_cache;
 
 	*_cache = NULL;
+
+	acl_cache_flush_all(cache);
 	array_free(&cache->right_idx_name_map);
 	hash_destroy(&cache->right_name_idx_map);
 	hash_destroy(&cache->objects);
--- a/src/plugins/acl/acl-cache.h	Sat May 17 17:50:54 2008 +0300
+++ b/src/plugins/acl/acl-cache.h	Mon Jun 09 05:11:18 2008 +0300
@@ -13,9 +13,8 @@
 	/* variable length bitmask */
 	unsigned char mask[1];
 };
-#define SIZEOF_ACL_MASK(count) \
-	(sizeof(pool_t) + sizeof(unsigned int) + \
-	 (count + CHAR_BIT-1) / CHAR_BIT)
+#define SIZEOF_ACL_MASK(bitmask_size) \
+	(sizeof(pool_t) + sizeof(unsigned int) + (bitmask_size))
 
 struct acl_cache *acl_cache_init(struct acl_backend *backend,
 				 size_t validity_rec_size);
--- a/src/plugins/acl/acl-mailbox-list.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/plugins/acl/acl-mailbox-list.c	Mon Jun 09 05:11:18 2008 +0300
@@ -29,7 +29,6 @@
 	struct mailbox_list_iterate_context *super_ctx;
 
 	struct mailbox_tree_context *tree;
-	struct mailbox_tree_iterate_context *tree_iter;
 	struct mailbox_info info;
 };
 
@@ -63,7 +62,7 @@
 						 can_see_r);
 }
 
-static bool
+static void
 acl_mailbox_try_list_fast(struct acl_mailbox_list_iterate_context *ctx,
 			  const char *const *patterns)
 {
@@ -73,19 +72,20 @@
 		alist->rights.acl_storage_right_idx + ACL_STORAGE_RIGHT_LOOKUP;
 	const struct acl_mask *acl_mask;
 	struct acl_mailbox_list_context *nonowner_list_ctx;
-	struct imap_match_glob *glob;
 	struct mail_namespace *ns = ctx->ctx.list->ns;
+	struct mailbox_list_iter_update_context update_ctx;
 	const char *name;
 	string_t *vname;
 	char sep;
 	int try, ret;
 
-	if ((ctx->ctx.flags & MAILBOX_LIST_ITER_RAW_LIST) != 0)
-		return FALSE;
+	if ((ctx->ctx.flags & (MAILBOX_LIST_ITER_RAW_LIST |
+			       MAILBOX_LIST_ITER_SELECT_SUBSCRIBED)) != 0)
+		return;
 
 	if (acl_backend_get_default_rights(backend, &acl_mask) < 0 ||
 	    acl_cache_mask_isset(acl_mask, *idxp))
-		return FALSE;
+		return;
 
 	/* default is to not list mailboxes. we can optimize this. */
 	if ((ctx->ctx.flags & MAILBOX_LIST_ITER_VIRTUAL_NAMES) != 0) {
@@ -95,13 +95,19 @@
 		sep = ns->real_sep;
 		vname = NULL;
 	}
-	glob = imap_match_init_multiple(pool_datastack_create(), patterns,
-					TRUE, sep);
+
+	memset(&update_ctx, 0, sizeof(update_ctx));
+	update_ctx.iter_ctx = &ctx->ctx;
+	update_ctx.glob =
+		imap_match_init_multiple(pool_datastack_create(), patterns,
+					 TRUE, sep);;
+	update_ctx.match_parents = TRUE;
 
 	for (try = 0; try < 2; try++) {
 		nonowner_list_ctx =
 			acl_backend_nonowner_lookups_iter_init(backend);
 		ctx->tree = mailbox_tree_init(sep);
+		update_ctx.tree_ctx = ctx->tree;
 
 		while ((ret = acl_backend_nonowner_lookups_iter_next(
 					nonowner_list_ctx, &name)) > 0) {
@@ -109,22 +115,16 @@
 				name = mail_namespace_get_vname(ns, vname,
 								name);
 			}
-			mailbox_list_iter_update(&ctx->ctx, ctx->tree,
-						 glob, FALSE, TRUE, name);
+			mailbox_list_iter_update(&update_ctx, name);
 		}
+		acl_backend_nonowner_lookups_iter_deinit(&nonowner_list_ctx);
+
 		if (ret == 0)
 			break;
 
 		/* try again */
 		mailbox_tree_deinit(&ctx->tree);
-		acl_backend_nonowner_lookups_iter_deinit(&nonowner_list_ctx);
 	}
-	if (ret < 0)
-		return FALSE;
-
-	ctx->tree_iter = mailbox_tree_iterate_init(ctx->tree, NULL,
-						   MAILBOX_FLAG_MATCHED);
-	return TRUE;
 }
 
 static struct mailbox_list_iterate_context *
@@ -134,19 +134,16 @@
 {
 	struct acl_mailbox_list *alist = ACL_LIST_CONTEXT(list);
 	struct acl_mailbox_list_iterate_context *ctx;
-	bool ret;
 
 	ctx = i_new(struct acl_mailbox_list_iterate_context, 1);
 	ctx->ctx.list = list;
 	ctx->ctx.flags = flags;
 
 	T_BEGIN {
-		ret = acl_mailbox_try_list_fast(ctx, patterns);
+		acl_mailbox_try_list_fast(ctx, patterns);
 	} T_END;
-	if (!ret) {
-		ctx->super_ctx = alist->module_ctx.super.
-			iter_init(list, patterns, flags);
-	}
+	ctx->super_ctx = alist->module_ctx.super.
+		iter_init(list, patterns, flags);
 	return &ctx->ctx;
 }
 
@@ -154,16 +151,34 @@
 acl_mailbox_list_iter_next_info(struct acl_mailbox_list_iterate_context *ctx)
 {
 	struct acl_mailbox_list *alist = ACL_LIST_CONTEXT(ctx->ctx.list);
-	struct mailbox_node *node;
+	const struct mailbox_info *info;
 
-	if (ctx->tree_iter == NULL)
-		return alist->module_ctx.super.iter_next(ctx->super_ctx);
+	do {
+		info = alist->module_ctx.super.iter_next(ctx->super_ctx);
+		if (info == NULL)
+			return NULL;
+		/* if the mailbox isn't in shared mailboxes list, it's not
+		   visible to us. */
+	} while (ctx->tree != NULL &&
+		 mailbox_tree_lookup(ctx->tree, info->name) == NULL);
+
+	return info;
+}
 
-	node = mailbox_tree_iterate_next(ctx->tree_iter, &ctx->info.name);
-	if (node == NULL)
-		return NULL;
-	ctx->info.flags = node->flags;
-	return &ctx->info;
+static const char *
+acl_mailbox_list_iter_get_name(struct mailbox_list_iterate_context *ctx,
+			       const char *name)
+{
+	struct mail_namespace *ns = ctx->list->ns;
+
+	if ((ctx->flags & MAILBOX_LIST_ITER_VIRTUAL_NAMES) == 0)
+		return name;
+
+	/* Mailbox names contain namespace prefix,
+	   except when listing INBOX. */
+	if (strncmp(name, ns->prefix, ns->prefix_len) == 0)
+		name += ns->prefix_len;
+	return mail_namespace_fix_sep(ns, name);
 }
 
 static int
@@ -171,7 +186,6 @@
 				 const struct mailbox_info *info)
 {
 	struct acl_mailbox_list *alist = ACL_LIST_CONTEXT(ctx->ctx.list);
-	struct mail_namespace *ns = ctx->ctx.list->ns;
 	const char *acl_name;
 	int ret;
 
@@ -180,15 +194,7 @@
 		return 1;
 	}
 
-	acl_name = info->name;
-	if ((ctx->ctx.flags & MAILBOX_LIST_ITER_VIRTUAL_NAMES) != 0) {
-		/* Mailbox names contain namespace prefix,
-		   except when listing INBOX. */
-		if (strncmp(acl_name, ns->prefix, ns->prefix_len) == 0)
-			acl_name += ns->prefix_len;
-		acl_name = mail_namespace_fix_sep(ns, acl_name);
-	}
-
+	acl_name = acl_mailbox_list_iter_get_name(&ctx->ctx, info->name);
 	ret = acl_mailbox_list_have_right(alist, acl_name,
 					  ACL_STORAGE_RIGHT_LOOKUP,
 					  NULL);
@@ -233,6 +239,27 @@
 }
 
 static int
+acl_mailbox_list_iter_is_mailbox(struct mailbox_list_iterate_context *ctx,
+				 const char *dir, const char *fname,
+				 const char *mailbox_name,
+				 enum mailbox_list_file_type type,
+				 enum mailbox_info_flags *flags_r)
+{
+	struct acl_mailbox_list *alist = ACL_LIST_CONTEXT(ctx->list);
+	int ret;
+
+	ret = alist->module_ctx.super.iter_is_mailbox(ctx, dir, fname,
+						      mailbox_name,
+						      type, flags_r);
+	if (ret <= 0 || (ctx->flags & MAILBOX_LIST_ITER_RAW_LIST) != 0)
+		return ret;
+
+	mailbox_name = acl_mailbox_list_iter_get_name(ctx, mailbox_name);
+	return acl_mailbox_list_have_right(alist, mailbox_name,
+					   ACL_STORAGE_RIGHT_LOOKUP, NULL);
+}
+
+static int
 acl_mailbox_list_iter_deinit(struct mailbox_list_iterate_context *_ctx)
 {
 	struct acl_mailbox_list_iterate_context *ctx =
@@ -240,12 +267,10 @@
 	struct acl_mailbox_list *alist = ACL_LIST_CONTEXT(_ctx->list);
 	int ret = ctx->ctx.failed ? -1 : 0;
 
-	if (ctx->super_ctx != NULL) {
-		if (alist->module_ctx.super.iter_deinit(ctx->super_ctx) < 0)
-			ret = -1;
-	}
-	if (ctx->tree_iter != NULL)
-		mailbox_tree_iterate_deinit(&ctx->tree_iter);
+	if (alist->module_ctx.super.iter_deinit(ctx->super_ctx) < 0)
+		ret = -1;
+	if (ctx->tree != NULL)
+		mailbox_tree_deinit(&ctx->tree);
 
 	i_free(ctx);
 	return ret;
@@ -387,9 +412,6 @@
 	const char *acl_env, *current_username, *owner_username;
 	bool owner = TRUE;
 
-	if (acl_next_hook_mailbox_list_created != NULL)
-		acl_next_hook_mailbox_list_created(list);
-
 	acl_env = getenv("ACL");
 	i_assert(acl_env != NULL);
 
@@ -430,6 +452,7 @@
 	list->v.iter_init = acl_mailbox_list_iter_init;
 	list->v.iter_next = acl_mailbox_list_iter_next;
 	list->v.iter_deinit = acl_mailbox_list_iter_deinit;
+	list->v.iter_is_mailbox = acl_mailbox_list_iter_is_mailbox;
 	list->v.get_mailbox_name_status = acl_get_mailbox_name_status;
 	list->v.delete_mailbox = acl_mailbox_list_delete;
 	list->v.rename_mailbox = acl_mailbox_list_rename;
@@ -437,4 +460,7 @@
 	acl_storage_rights_ctx_init(&alist->rights, backend);
 
 	MODULE_CONTEXT_SET(list, acl_mailbox_list_module, alist);
+
+	if (acl_next_hook_mailbox_list_created != NULL)
+		acl_next_hook_mailbox_list_created(list);
 }
--- a/src/plugins/acl/acl-mailbox.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/plugins/acl/acl-mailbox.c	Mon Jun 09 05:11:18 2008 +0300
@@ -155,7 +155,9 @@
 		/* handle this by first removing the allowed flags and
 		   then adding the allowed flags */
 		acl_mail_update_flags(_mail, MODIFY_REMOVE, ~flags);
-		acl_mail_update_flags(_mail, MODIFY_ADD, flags);
+		if (flags != 0)
+			acl_mail_update_flags(_mail, MODIFY_ADD, flags);
+		return;
 	}
 
 	amail->super.update_flags(_mail, modify_type, flags);
@@ -295,6 +297,36 @@
 		transaction_commit(ctx, uid_validity_r,
 				   first_saved_uid_r, last_saved_uid_r);
 }
+
+static int
+acl_keywords_create(struct mailbox *box, const char *const keywords[],
+		    struct mail_keywords **keywords_r, bool skip_invalid)
+{
+	struct acl_mailbox *abox = ACL_CONTEXT(box);
+	int ret;
+
+	ret = mailbox_acl_right_lookup(box, ACL_STORAGE_RIGHT_WRITE);
+	if (ret < 0) {
+		if (!skip_invalid)
+			return -1;
+		/* we can't return failure. assume we don't have permissions. */
+		ret = 0;
+	}
+
+	if (ret == 0) {
+		/* no permission to update any flags. just return empty
+		   keywords list. */
+		const char *null = NULL;
+
+		return abox->module_ctx.super.keywords_create(box, &null,
+							      keywords_r,
+							      skip_invalid);
+	}
+
+	return abox->module_ctx.super.keywords_create(box, keywords,
+						      keywords_r, skip_invalid);
+}
+
 struct mailbox *acl_mailbox_open_box(struct mailbox *box)
 {
 	struct acl_mail_storage *astorage = ACL_CONTEXT(box->storage);
@@ -311,6 +343,7 @@
 	box->v.close = acl_mailbox_close;
 	box->v.mail_alloc = acl_mail_alloc;
 	box->v.save_init = acl_save_init;
+	box->v.keywords_create = acl_keywords_create;
 	box->v.copy = acl_copy;
 	box->v.transaction_commit = acl_transaction_commit;
 	MODULE_CONTEXT_SET(box, acl_storage_module, abox);
--- a/src/plugins/acl/acl-storage.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/plugins/acl/acl-storage.c	Mon Jun 09 05:11:18 2008 +0300
@@ -160,9 +160,6 @@
 	struct acl_mail_storage *astorage;
 	struct acl_backend *backend;
 
-	if (acl_next_hook_mail_storage_created != NULL)
-		acl_next_hook_mail_storage_created(storage);
-
 	astorage = p_new(storage->pool, struct acl_mail_storage, 1);
 	astorage->module_ctx.super = storage->v;
 	storage->v.destroy = acl_storage_destroy;
@@ -173,5 +170,8 @@
 	acl_storage_rights_ctx_init(&astorage->rights, backend);
 
 	MODULE_CONTEXT_SET(storage, acl_storage_module, astorage);
+
+	if (acl_next_hook_mail_storage_created != NULL)
+		acl_next_hook_mail_storage_created(storage);
 }
 
--- a/src/plugins/convert/convert-plugin.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/plugins/convert/convert-plugin.c	Mon Jun 09 05:11:18 2008 +0300
@@ -12,19 +12,12 @@
 static void (*convert_next_hook_mail_namespaces_created)
 	(struct mail_namespace *namespaces);
 
-static void
-convert_hook_mail_namespaces_created(struct mail_namespace *namespaces)
+static void convert_mail_storage(struct mail_namespace *namespaces,
+				 const char *convert_mail)
 {
-	const char *convert_mail, *str;
+	const char *str;
 	struct convert_settings set;
 
-	if (convert_next_hook_mail_namespaces_created != NULL)
-		convert_next_hook_mail_namespaces_created(namespaces);
-
-	convert_mail = getenv("CONVERT_MAIL");
-	if (convert_mail == NULL)
-		return;
-
 	memset(&set, 0, sizeof(set));
 	set.user = getenv("USER");
 	if (set.user == NULL)
@@ -44,6 +37,19 @@
 		i_fatal("Mailbox conversion failed, exiting");
 }
 
+static void
+convert_hook_mail_namespaces_created(struct mail_namespace *namespaces)
+{
+	const char *convert_mail;
+
+	convert_mail = getenv("CONVERT_MAIL");
+	if (convert_mail != NULL)
+		convert_mail_storage(namespaces, convert_mail);
+
+	if (convert_next_hook_mail_namespaces_created != NULL)
+		convert_next_hook_mail_namespaces_created(namespaces);
+}
+
 void convert_plugin_init(void)
 {
 	convert_next_hook_mail_namespaces_created =
--- a/src/plugins/convert/convert-storage.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/plugins/convert/convert-storage.c	Mon Jun 09 05:11:18 2008 +0300
@@ -148,6 +148,15 @@
 	bool t;
 	int ret;
 
+	/* create as non-selectable mailbox so the dbox-Mails directory
+	   isn't created yet */
+	if (mail_storage_mailbox_create(dest_storage, dest_name, TRUE) < 0) {
+		i_error("Mailbox conversion: "
+			"Couldn't create mailbox %s: %s",
+			dest_name, storage_error(dest_storage));
+		return -1;
+	}
+
 	src_path = mail_storage_get_mailbox_path(src_storage, src_name, &t);
 	dest_path = mail_storage_get_mailbox_path(dest_storage, dest_name, &t);
 
--- a/src/plugins/expire/auth-client.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/plugins/expire/auth-client.c	Mon Jun 09 05:11:18 2008 +0300
@@ -130,20 +130,24 @@
 		return;
 	}
 
+	if (uid != conn->current_uid && conn->current_uid != 0) {
+		if (seteuid(0) != 0)
+			i_fatal("seteuid(0) failed: %m");
+		conn->current_uid = 0;
+	}
+
+	/* change GID */
+	restrict_access_by_env(FALSE);
+
 	/* we'll change only effective UID. This is a bit unfortunate since
 	   it allows reverting back to root, but we'll have to be able to
 	   access different users' mailboxes.. */
 	if (uid != conn->current_uid) {
-		if (conn->current_uid != 0) {
-			if (seteuid(0) != 0)
-				i_fatal("seteuid(0) failed: %m");
-		}
 		if (seteuid(uid) < 0)
 			i_fatal("seteuid(%s) failed: %m", dec2str(uid));
 		conn->current_uid = uid;
 	}
 
-	restrict_access_by_env(FALSE);
 	conn->return_value = 1;
 }
 
--- a/src/plugins/expire/expire-env.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/plugins/expire/expire-env.c	Mon Jun 09 05:11:18 2008 +0300
@@ -105,10 +105,17 @@
 	return expunge_min > 0 || altmove_min > 0;
 }
 
-unsigned int expire_box_find_min_secs(struct expire_env *env, const char *name)
+unsigned int expire_box_find_min_secs(struct expire_env *env, const char *name,
+				      bool *altmove_r)
 {
 	unsigned int secs1, secs2;
 
 	(void)expire_box_find(env, name, &secs1, &secs2);
-	return secs1 < secs2 && secs1 != 0 ? secs1 : secs2;
+	if (secs1 != 0 && (secs1 < secs2 || secs2 == 0)) {
+		*altmove_r = FALSE;
+		return secs1;
+	} else {
+		*altmove_r = TRUE;
+		return secs2;
+	}
 }
--- a/src/plugins/expire/expire-env.h	Sat May 17 17:50:54 2008 +0300
+++ b/src/plugins/expire/expire-env.h	Mon Jun 09 05:11:18 2008 +0300
@@ -10,6 +10,7 @@
 		     unsigned int *expunge_secs_r,
 		     unsigned int *altmove_secs_r);
 
-unsigned int expire_box_find_min_secs(struct expire_env *env, const char *name);
+unsigned int expire_box_find_min_secs(struct expire_env *env, const char *name,
+				      bool *altmove_r);
 
 #endif
--- a/src/plugins/expire/expire-plugin.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/plugins/expire/expire-plugin.c	Mon Jun 09 05:11:18 2008 +0300
@@ -29,6 +29,7 @@
 struct expire_mailbox {
 	union mailbox_module_context module_ctx;
 	time_t expire_secs;
+	unsigned int altmove:1;
 };
 
 struct expire_transaction_context {
@@ -78,16 +79,16 @@
 	for (seq = 2; seq <= hdr->messages_count; seq++) {
 		if (!mail_index_is_expunged(view, seq)) {
 			mail_set_seq(mail, seq);
-			if (mail_get_save_date(mail, stamp_r) == 0) {
-				mail_free(&mail);
-				return;
-			}
+			if (mail_get_save_date(mail, stamp_r) == 0)
+				break;
 		}
 	}
 	mail_free(&mail);
 
-	/* everything expunged */
-	*stamp_r = 0;
+	if (seq > hdr->messages_count) {
+		/* everything expunged */
+		*stamp_r = 0;
+	}
 }
 
 static int
@@ -103,7 +104,9 @@
 	bool update_dict = FALSE;
 	int ret;
 
-	if (xt->first_expunged) {
+	if (xpr_box->altmove) {
+		/* only moving mails - don't update the move stamps */
+	} else if (xt->first_expunged) {
 		/* first mail expunged. dict needs updating. */
 		first_nonexpunged_timestamp(t, &new_stamp);
 		update_dict = TRUE;
@@ -126,7 +129,8 @@
 			   this is the first mail in the database */
 			ret = dict_lookup(expire.db, pool_datastack_create(),
 					  key, &value);
-			update_dict = ret == 0 || strtoul(value, NULL, 10) == 0;
+			update_dict = ret == 0 ||
+				(ret > 0 && strtoul(value, NULL, 10) == 0);
 			/* may not be exactly the first message's save time
 			   but a few second difference doesn't matter */
 			new_stamp = ioloop_time;
@@ -219,7 +223,8 @@
 		copy(t, mail, flags, keywords, dest_mail);
 }
 
-static void mailbox_expire_hook(struct mailbox *box, time_t expire_secs)
+static void
+mailbox_expire_hook(struct mailbox *box, time_t expire_secs, bool altmove)
 {
 	struct expire_mailbox *xpr_box;
 
@@ -233,6 +238,7 @@
 	box->v.save_finish = expire_save_finish;
 	box->v.copy = expire_copy;
 
+	xpr_box->altmove = altmove;
 	xpr_box->expire_secs = expire_secs;
 
 	MODULE_CONTEXT_SET(box, expire_storage_module, xpr_box);
@@ -247,15 +253,17 @@
 	struct mailbox *box;
 	string_t *vname;
 	unsigned int secs;
+	bool altmove;
 
 	box = xpr_storage->super.mailbox_open(storage, name, input, flags);
 	if (box != NULL) {
 		vname = t_str_new(128);
 		(void)mail_namespace_get_vname(storage->ns, vname, name);
 
-		secs = expire_box_find_min_secs(expire.env, str_c(vname));
+		secs = expire_box_find_min_secs(expire.env, str_c(vname),
+						&altmove);
 		if (secs != 0)
-			mailbox_expire_hook(box, secs);
+			mailbox_expire_hook(box, secs, altmove);
 	}
 	return box;
 }
@@ -264,15 +272,15 @@
 {
 	union mail_storage_module_context *xpr_storage;
 
-	if (expire.next_hook_mail_storage_created != NULL)
-		expire.next_hook_mail_storage_created(storage);
-
 	xpr_storage =
 		p_new(storage->pool, union mail_storage_module_context, 1);
 	xpr_storage->super = storage->v;
 	storage->v.mailbox_open = expire_mailbox_open;
 
 	MODULE_CONTEXT_SET_SELF(storage, expire_storage_module, xpr_storage);
+
+	if (expire.next_hook_mail_storage_created != NULL)
+		expire.next_hook_mail_storage_created(storage);
 }
 
 void expire_plugin_init(void)
@@ -286,9 +294,11 @@
 		if (dict_uri == NULL)
 			i_fatal("expire plugin: expire_dict setting missing");
 
+		expire.username = getenv("USER");
 		expire.env = expire_env_init(expunge_env, altmove_env);
-		expire.db = dict_init(dict_uri, DICT_DATA_TYPE_UINT32, NULL);
-		expire.username = getenv("USER");
+		expire.db = dict_init(dict_uri, DICT_DATA_TYPE_UINT32, expire.username);
+		if (expire.db == NULL)
+			i_fatal("expire plugin: dict_init() failed");
 
 		expire.next_hook_mail_storage_created =
 			hook_mail_storage_created;
--- a/src/plugins/expire/expire-tool.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/plugins/expire/expire-tool.c	Mon Jun 09 05:11:18 2008 +0300
@@ -204,6 +204,9 @@
 	ctx.namespace_pool = pool_alloconly_create("namespaces", 1024);
 	env = expire_env_init(getenv("EXPIRE"), getenv("EXPIRE_ALTMOVE"));
 	dict = dict_init(getenv("EXPIRE_DICT"), DICT_DATA_TYPE_UINT32, "");
+	if (dict == NULL)
+		i_fatal("dict_init() failed");
+
 	trans = dict_transaction_begin(dict);
 	iter = dict_iterate_init(dict, DICT_PATH_SHARED,
 				 DICT_ITERATE_FLAG_SORT_BY_VALUE);
--- a/src/plugins/fts-squat/fts-backend-squat.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/plugins/fts-squat/fts-backend-squat.c	Mon Jun 09 05:11:18 2008 +0300
@@ -7,6 +7,8 @@
 #include "squat-trie.h"
 #include "fts-squat-plugin.h"
 
+#include <stdlib.h>
+
 #define SQUAT_FILE_PREFIX "dovecot.index.search"
 
 struct squat_fts_backend {
@@ -19,12 +21,39 @@
 	struct squat_trie_build_context *build_ctx;
 };
 
+static void
+fts_backend_squat_set(struct squat_fts_backend *backend, const char *str)
+{
+	const char *const *tmp;
+	int len;
+
+	for (tmp = t_strsplit_spaces(str, " "); *tmp != NULL; tmp++) {
+		if (strncmp(*tmp, "partial=", 8) == 0) {
+			len = atoi(*tmp + 8);
+			if (len <= 0) {
+				i_fatal("fts_squat: Invalid partial len: %s",
+					*tmp + 8);
+			}
+			squat_trie_set_partial_len(backend->trie, len);
+		} else if (strncmp(*tmp, "full=", 5) == 0) {
+			len = atoi(*tmp + 5);
+			if (len <= 0) {
+				i_fatal("fts_squat: Invalid full len: %s",
+					*tmp + 5);
+			}
+			squat_trie_set_full_len(backend->trie, len);
+		} else {
+			i_fatal("fts_squat: Invalid setting: %s", *tmp);
+		}
+	}
+}
+
 static struct fts_backend *fts_backend_squat_init(struct mailbox *box)
 {
 	struct squat_fts_backend *backend;
 	struct mail_storage *storage;
 	struct mailbox_status status;
-	const char *path;
+	const char *path, *env;
 	enum squat_index_flags flags = 0;
 
 	storage = mailbox_get_storage(box);
@@ -50,6 +79,10 @@
 		squat_trie_init(t_strconcat(path, "/"SQUAT_FILE_PREFIX, NULL),
 				status.uidvalidity, storage->lock_method,
 				flags);
+
+	env = getenv("FTS_SQUAT");
+	if (env != NULL)
+		fts_backend_squat_set(backend, env);
 	return &backend->backend;
 }
 
--- a/src/plugins/fts-squat/squat-trie-private.h	Sat May 17 17:50:54 2008 +0300
+++ b/src/plugins/fts-squat/squat-trie-private.h	Mon Jun 09 05:11:18 2008 +0300
@@ -133,6 +133,8 @@
 	size_t mmap_size;
 
 	unsigned char default_normalize_map[256];
+	unsigned int default_partial_len;
+	unsigned int default_full_len;
 
 	unsigned int corrupted:1;
 };
--- a/src/plugins/fts-squat/squat-trie.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/plugins/fts-squat/squat-trie.c	Mon Jun 09 05:11:18 2008 +0300
@@ -7,6 +7,7 @@
 #include "istream.h"
 #include "ostream.h"
 #include "unichar.h"
+#include "nfs-workarounds.h"
 #include "file-cache.h"
 #include "seq-range-array.h"
 #include "squat-uidlist.h"
@@ -145,6 +146,8 @@
 	trie->dotlock_set.nfs_flush = (flags & SQUAT_INDEX_FLAG_NFS_FLUSH) != 0;
 	trie->dotlock_set.timeout = SQUAT_TRIE_LOCK_TIMEOUT;
 	trie->dotlock_set.stale_timeout = SQUAT_TRIE_DOTLOCK_STALE_TIMEOUT;
+	trie->default_partial_len = DEFAULT_PARTIAL_LEN;
+	trie->default_full_len = DEFAULT_FULL_LEN;
 	return trie;
 }
 
@@ -190,14 +193,24 @@
 	i_free(trie);
 }
 
+void squat_trie_set_partial_len(struct squat_trie *trie, unsigned int len)
+{
+	trie->default_partial_len = len;
+}
+
+void squat_trie_set_full_len(struct squat_trie *trie, unsigned int len)
+{
+	trie->default_full_len = len;
+}
+
 static void squat_trie_header_init(struct squat_trie *trie)
 {
 	memset(&trie->hdr, 0, sizeof(trie->hdr));
 	trie->hdr.version = SQUAT_TRIE_VERSION;
 	trie->hdr.indexid = time(NULL);
 	trie->hdr.uidvalidity = trie->uidvalidity;
-	trie->hdr.partial_len = DEFAULT_PARTIAL_LEN;
-	trie->hdr.full_len = DEFAULT_FULL_LEN;
+	trie->hdr.partial_len = trie->default_partial_len;
+	trie->hdr.full_len = trie->default_full_len;
 
 	i_assert(sizeof(trie->hdr.normalize_map) ==
 		 sizeof(trie->default_normalize_map));
@@ -234,7 +247,9 @@
 {
 	struct stat st, st2;
 
-	if (stat(trie->path, &st) < 0) {
+	if ((trie->flags & SQUAT_INDEX_FLAG_NFS_FLUSH) != 0)
+		nfs_flush_file_handle_cache(trie->path);
+	if (nfs_safe_stat(trie->path, &st) < 0) {
 		if (errno == ENOENT)
 			return 1;
 
@@ -242,6 +257,8 @@
 		return -1;
 	}
 	if (fstat(trie->fd, &st2) < 0) {
+		if (errno == ESTALE)
+			return 1;
 		i_error("fstat(%s) failed: %m", trie->path);
 		return -1;
 	}
@@ -269,7 +286,7 @@
 	*file_lock_r = NULL;
 	*dotlock_r = NULL;
 
-	while (trie->fd != -1) {
+	for (;;) {
 		if (trie->lock_method != FILE_LOCK_METHOD_DOTLOCK) {
 			ret = file_wait_lock(trie->fd, trie->path, lock_type,
 					     trie->lock_method,
@@ -290,7 +307,7 @@
 		   file and try to lock again */
 		ret = squat_trie_is_file_stale(trie);
 		if (ret == 0)
-			return 1;
+			break;
 
 		if (*file_lock_r != NULL)
 			file_unlock(file_lock_r);
@@ -302,8 +319,13 @@
 		squat_trie_close(trie);
 		if (squat_trie_open_fd(trie) < 0)
 			return -1;
+		if (trie->fd == -1)
+			return 0;
 	}
-	return 0;
+
+	if ((trie->flags & SQUAT_INDEX_FLAG_NFS_FLUSH) != 0)
+		nfs_flush_read_cache_locked(trie->path, trie->fd);
+	return 1;
 }
 
 static void
@@ -621,17 +643,17 @@
 {
 	struct squat_node *child;
 	unsigned char *str;
-	unsigned int uid, idx, str_len = node->leaf_string_length;
+	unsigned int uid, idx, leafstr_len = node->leaf_string_length;
 
-	i_assert(str_len > 0);
+	i_assert(leafstr_len > 0);
 
 	/* make a copy of the leaf string and convert to normal node by
 	   removing it. */
-	str = t_malloc(str_len);
+	str = t_malloc(leafstr_len);
 	if (!NODE_IS_DYNAMIC_LEAF(node))
-		memcpy(str, node->children.static_leaf_string, str_len);
+		memcpy(str, node->children.static_leaf_string, leafstr_len);
 	else {
-		memcpy(str, node->children.leaf_string, str_len);
+		memcpy(str, node->children.leaf_string, leafstr_len);
 		i_free(node->children.leaf_string);
 	}
 	node->leaf_string_length = 0;
@@ -649,16 +671,17 @@
 	}
 
 	i_assert(!child->have_sequential && child->children.data == NULL);
-	if (str_len > 1) {
+	if (leafstr_len > 1) {
 		/* make the child a leaf string */
-		str_len--;
-		child->leaf_string_length = str_len;
+		leafstr_len--;
+		child->leaf_string_length = leafstr_len;
 		if (!NODE_IS_DYNAMIC_LEAF(child)) {
 			memcpy(child->children.static_leaf_string,
-			       str + 1, str_len);
+			       str + 1, leafstr_len);
 		} else {
-			child->children.leaf_string = i_malloc(str_len);
-			memcpy(child->children.leaf_string, str + 1, str_len);
+			child->children.leaf_string = i_malloc(leafstr_len);
+			memcpy(child->children.leaf_string,
+			       str + 1, leafstr_len);
 		}
 	}
 }
@@ -669,10 +692,10 @@
 			      const unsigned char *data, unsigned int data_len)
 {
 	const unsigned char *str = NODE_LEAF_STRING(node);
-	const unsigned int str_len = node->leaf_string_length;
+	const unsigned int leafstr_len = node->leaf_string_length;
 	unsigned int i;
 
-	if (data_len != str_len) {
+	if (data_len != leafstr_len) {
 		/* different lengths, can't match */
 		T_BEGIN {
 			node_split_string(ctx, node);
@@ -1735,15 +1758,15 @@
 				return -1;
 		}
 		if (node->leaf_string_length != 0) {
-			unsigned int str_len = node->leaf_string_length;
+			unsigned int len = node->leaf_string_length;
 			const unsigned char *str;
 
-			if (str_len > sizeof(node->children.static_leaf_string))
+			if (len > sizeof(node->children.static_leaf_string))
 				str = node->children.leaf_string;
 			else
 				str = node->children.static_leaf_string;
 
-			if (size > str_len || memcmp(data, str, size) != 0)
+			if (size > len || memcmp(data, str, size) != 0)
 				return 0;
 
 			/* match */
@@ -2003,6 +2026,7 @@
 		squat_trie_filter_type(type, &ctx.tmp_uids,
 				       definite_uids);
 	}
+	seq_range_array_remove_seq_range(maybe_uids, definite_uids);
 	squat_trie_add_unknown(trie, maybe_uids);
 	array_free(&ctx.tmp_uids);
 	array_free(&ctx.tmp_uids2);
--- a/src/plugins/fts-squat/squat-trie.h	Sat May 17 17:50:54 2008 +0300
+++ b/src/plugins/fts-squat/squat-trie.h	Mon Jun 09 05:11:18 2008 +0300
@@ -23,6 +23,9 @@
 		enum squat_index_flags flags);
 void squat_trie_deinit(struct squat_trie **trie);
 
+void squat_trie_set_partial_len(struct squat_trie *trie, unsigned int len);
+void squat_trie_set_full_len(struct squat_trie *trie, unsigned int len);
+
 void squat_trie_refresh(struct squat_trie *trie);
 
 int squat_trie_build_init(struct squat_trie *trie, uint32_t *last_uid_r,
--- a/src/plugins/fts/fts-storage.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/plugins/fts/fts-storage.c	Mon Jun 09 05:11:18 2008 +0300
@@ -657,17 +657,9 @@
 	return ret;
 }
 
-void fts_mailbox_opened(struct mailbox *box)
+static void fts_mailbox_init(struct mailbox *box, const char *env)
 {
 	struct fts_mailbox *fbox;
-	const char *env;
-
-	if (fts_next_hook_mailbox_opened != NULL)
-		fts_next_hook_mailbox_opened(box);
-
-	env = getenv("FTS");
-	if (env == NULL)
-		return;
 
 	fbox = i_new(struct fts_mailbox, 1);
 	fbox->env = env;
@@ -684,3 +676,15 @@
 
 	MODULE_CONTEXT_SET(box, fts_storage_module, fbox);
 }
+
+void fts_mailbox_opened(struct mailbox *box)
+{
+	const char *env;
+
+	env = getenv("FTS");
+	if (env != NULL)
+		fts_mailbox_init(box, env);
+
+	if (fts_next_hook_mailbox_opened != NULL)
+		fts_next_hook_mailbox_opened(box);
+}
--- a/src/plugins/lazy-expunge/lazy-expunge-plugin.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/plugins/lazy-expunge/lazy-expunge-plugin.c	Mon Jun 09 05:11:18 2008 +0300
@@ -472,7 +472,7 @@
 	return 0;
 }
 
-static void lazy_expunge_mail_storage_created(struct mail_storage *storage)
+static void lazy_expunge_mail_storage_init(struct mail_storage *storage)
 {
 	struct lazy_expunge_mailbox_list *llist =
 		LAZY_EXPUNGE_LIST_CONTEXT(storage->list);
@@ -480,13 +480,6 @@
 	const char *const *p;
 	unsigned int i;
 
-	if (lazy_expunge_next_hook_mail_storage_created != NULL)
-		lazy_expunge_next_hook_mail_storage_created(storage);
-
-	/* only maildir supported for now */
-	if (strcmp(storage->name, "maildir") != 0)
-		return;
-
 	/* if this is one of our internal storages, mark it as such before
 	   quota plugin sees it */
 	p = t_strsplit_spaces(getenv("LAZY_EXPUNGE"), " ");
@@ -506,6 +499,16 @@
 	MODULE_CONTEXT_SET(storage, lazy_expunge_mail_storage_module, lstorage);
 }
 
+static void lazy_expunge_mail_storage_created(struct mail_storage *storage)
+{
+	/* only maildir supported for now */
+	if (strcmp(storage->name, "maildir") == 0)
+		lazy_expunge_mail_storage_init(storage);
+
+	if (lazy_expunge_next_hook_mail_storage_created != NULL)
+		lazy_expunge_next_hook_mail_storage_created(storage);
+}
+
 static void lazy_expunge_mailbox_list_created(struct mailbox_list *list)
 {
 	struct lazy_expunge_mailbox_list *llist;
--- a/src/plugins/mail-log/mail-log-plugin.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/plugins/mail-log/mail-log-plugin.c	Mon Jun 09 05:11:18 2008 +0300
@@ -499,29 +499,29 @@
 {
 	union mail_storage_module_context *lstorage;
 
-	if (mail_log_next_hook_mail_storage_created != NULL)
-		mail_log_next_hook_mail_storage_created(storage);
-
 	lstorage = p_new(storage->pool, union mail_storage_module_context, 1);
 	lstorage->super = storage->v;
 	storage->v.mailbox_open = mail_log_mailbox_open;
 
 	MODULE_CONTEXT_SET_SELF(storage, mail_log_storage_module, lstorage);
+
+	if (mail_log_next_hook_mail_storage_created != NULL)
+		mail_log_next_hook_mail_storage_created(storage);
 }
 
 static void mail_log_mailbox_list_created(struct mailbox_list *list)
 {
 	union mailbox_list_module_context *llist;
 
-	if (mail_log_next_hook_mailbox_list_created != NULL)
-		mail_log_next_hook_mailbox_list_created(list);
-
 	llist = p_new(list->pool, union mailbox_list_module_context, 1);
 	llist->super = list->v;
 	list->v.delete_mailbox = mail_log_mailbox_list_delete;
 	list->v.rename_mailbox = mail_log_mailbox_list_rename;
 
 	MODULE_CONTEXT_SET_SELF(list, mail_log_mailbox_list_module, llist);
+
+	if (mail_log_next_hook_mailbox_list_created != NULL)
+		mail_log_next_hook_mailbox_list_created(list);
 }
 
 static enum mail_log_field mail_log_parse_fields(const char *str)
--- a/src/plugins/mbox-snarf/mbox-snarf-plugin.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/plugins/mbox-snarf/mbox-snarf-plugin.c	Mon Jun 09 05:11:18 2008 +0300
@@ -165,9 +165,6 @@
 {
 	struct mbox_snarf_mail_storage *mstorage;
 
-	if (mbox_snarf_next_hook_mail_storage_created != NULL)
-		mbox_snarf_next_hook_mail_storage_created(storage);
-
 	mstorage = p_new(storage->pool, struct mbox_snarf_mail_storage, 1);
 	mstorage->snarf_inbox_path =
 		p_strdup(storage->pool, home_expand(getenv("MBOX_SNARF")));
@@ -175,6 +172,9 @@
 	storage->v.mailbox_open = mbox_snarf_mailbox_open;
 
 	MODULE_CONTEXT_SET(storage, mbox_snarf_storage_module, mstorage);
+
+	if (mbox_snarf_next_hook_mail_storage_created != NULL)
+		mbox_snarf_next_hook_mail_storage_created(storage);
 }
 
 void mbox_snarf_plugin_init(void)
--- a/src/plugins/quota/quota-count.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/plugins/quota/quota-count.c	Mon Jun 09 05:11:18 2008 +0300
@@ -6,9 +6,11 @@
 #include "mail-storage.h"
 #include "quota-private.h"
 
-static int quota_count_mailbox(struct mail_storage *storage, const char *name,
-			       uint64_t *bytes_r, uint64_t *count_r)
+static int
+quota_count_mailbox(struct quota_root *root, struct mail_storage *storage,
+		    const char *name, uint64_t *bytes_r, uint64_t *count_r)
 {
+	struct quota_rule *rule;
 	struct mailbox *box;
 	struct mailbox_transaction_context *trans;
 	struct mail_search_context *ctx;
@@ -17,6 +19,12 @@
 	uoff_t size;
 	int ret = 0;
 
+	rule = quota_root_rule_find(root, name);
+	if (rule != NULL && rule->ignore) {
+		/* mailbox not included in quota */
+		return 0;
+	}
+
 	box = mailbox_open(storage, name, NULL,
 			   MAILBOX_OPEN_READONLY | MAILBOX_OPEN_KEEP_RECENT);
 	if (box == NULL)
@@ -53,8 +61,9 @@
 	return ret;
 }
 
-static int quota_count_storage(struct mail_storage *storage,
-			       uint64_t *bytes, uint64_t *count)
+static int
+quota_count_storage(struct quota_root *root, struct mail_storage *storage,
+		    uint64_t *bytes, uint64_t *count)
 {
 	struct mailbox_list_iterate_context *ctx;
 	const struct mailbox_info *info;
@@ -65,7 +74,7 @@
 	while ((info = mailbox_list_iter_next(ctx)) != NULL) {
 		if ((info->flags & (MAILBOX_NONEXISTENT |
 				    MAILBOX_NOSELECT)) == 0) {
-			ret = quota_count_mailbox(storage, info->name,
+			ret = quota_count_mailbox(root, storage, info->name,
 						  bytes, count);
 			if (ret < 0)
 				break;
@@ -77,7 +86,7 @@
 	return ret;
 }
 
-int quota_count(struct quota *quota, uint64_t *bytes_r, uint64_t *count_r)
+int quota_count(struct quota_root *root, uint64_t *bytes_r, uint64_t *count_r)
 {
 	struct mail_storage *const *storages;
 	unsigned int i, count;
@@ -85,9 +94,9 @@
 
 	*bytes_r = *count_r = 0;
 
-	storages = array_get(&quota->storages, &count);
+	storages = array_get(&root->quota->storages, &count);
 	for (i = 0; i < count; i++) {
-		ret = quota_count_storage(storages[i], bytes_r, count_r);
+		ret = quota_count_storage(root, storages[i], bytes_r, count_r);
 		if (ret < 0)
 			break;
 	}
--- a/src/plugins/quota/quota-dict.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/plugins/quota/quota-dict.c	Mon Jun 09 05:11:18 2008 +0300
@@ -78,7 +78,7 @@
 	struct dict_transaction_context *dt;
 	uint64_t bytes, count;
 
-	if (quota_count(root->root.quota, &bytes, &count) < 0)
+	if (quota_count(&root->root, &bytes, &count) < 0)
 		return -1;
 
 	T_BEGIN {
--- a/src/plugins/quota/quota-fs.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/plugins/quota/quota-fs.c	Mon Jun 09 05:11:18 2008 +0300
@@ -203,7 +203,7 @@
 	/* if there are more unused quota roots, copy this mount to them */
 	roots = array_get(&root->root.quota->roots, &count);
 	for (i = 0; i < count; i++) {
-		struct fs_quota_root *root = (struct fs_quota_root *)roots[i];
+		root = (struct fs_quota_root *)roots[i];
 		if (QUOTA_ROOT_MATCH(root, mount) && root->mount == NULL) {
 			mount->refcount++;
 			root->mount = mount;
--- a/src/plugins/quota/quota-maildir.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/plugins/quota/quota-maildir.c	Mon Jun 09 05:11:18 2008 +0300
@@ -36,6 +36,7 @@
 
 struct maildir_list_context {
 	struct mail_storage *storage;
+	struct maildir_quota_root *root;
 	struct mailbox_list_iterate_context *iter;
 	const struct mailbox_info *info;
 
@@ -121,12 +122,14 @@
 }
 
 static struct maildir_list_context *
-maildir_list_init(struct mail_storage *storage)
+maildir_list_init(struct maildir_quota_root *root,
+		  struct mail_storage *storage)
 {
 	struct maildir_list_context *ctx;
 
 	ctx = i_new(struct maildir_list_context, 1);
 	ctx->storage = storage;
+	ctx->root = root;
 	ctx->path = str_new(default_pool, 512);
 	ctx->iter = mailbox_list_iter_init(mail_storage_get_list(storage), "*",
 					   MAILBOX_LIST_ITER_RETURN_NO_FLAGS);
@@ -136,6 +139,7 @@
 static const char *
 maildir_list_next(struct maildir_list_context *ctx, time_t *mtime_r)
 {
+	struct quota_rule *rule;
 	struct stat st;
 	bool is_file;
 
@@ -144,6 +148,13 @@
 			ctx->info = mailbox_list_iter_next(ctx->iter);
 			if (ctx->info == NULL)
 				return NULL;
+
+			rule = quota_root_rule_find(&ctx->root->root,
+						    ctx->info->name);
+			if (rule != NULL && rule->ignore) {
+				/* mailbox not included in quota */
+				continue;
+			}
 		}
 
 		T_BEGIN {
@@ -185,13 +196,14 @@
 }
 
 static int
-maildirs_check_have_changed(struct mail_storage *storage, time_t latest_mtime)
+maildirs_check_have_changed(struct maildir_quota_root *root,
+			    struct mail_storage *storage, time_t latest_mtime)
 {
 	struct maildir_list_context *ctx;
 	time_t mtime;
 	int ret = 0;
 
-	ctx = maildir_list_init(storage);
+	ctx = maildir_list_init(root, storage);
 	while (maildir_list_next(ctx, &mtime) != NULL) {
 		if (mtime > latest_mtime) {
 			ret = 1;
@@ -270,7 +282,7 @@
 	time_t mtime;
 	int ret = 0;
 
-	ctx = maildir_list_init(storage);
+	ctx = maildir_list_init(root, storage);
 	while ((dir = maildir_list_next(ctx, &mtime)) != NULL) {
 		if (mtime > root->recalc_last_stamp)
 			root->recalc_last_stamp = mtime;
@@ -332,7 +344,7 @@
 	if (ret == 0) {
 		/* check if any of the directories have changed */
 		for (i = 0; i < count; i++) {
-			ret = maildirs_check_have_changed(storages[i],
+			ret = maildirs_check_have_changed(root, storages[i],
 						root->recalc_last_stamp);
 			if (ret != 0)
 				break;
@@ -726,8 +738,13 @@
 		/* no limits */
 		return 0;
 	}
-	/* make sure the latest file is opened. */
-	(void)maildirsize_open(root);
+
+	/* even though we don't really care about the limits in here ourself,
+	   we do want to make sure the header gets updated if the limits have
+	   changed. also this makes sure the maildirsize file is created if
+	   it doesn't exist. */
+	if (maildirquota_refresh(root) < 0)
+		return -1;
 
 	if (root->fd == -1 || ctx->recalculate ||
 	    maildirsize_update(root, ctx->count_used, ctx->bytes_used) < 0)
--- a/src/plugins/quota/quota-plugin.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/plugins/quota/quota-plugin.c	Mon Jun 09 05:11:18 2008 +0300
@@ -31,8 +31,8 @@
 			break;
 
 		if (quota_root_add_rule(root, rule, &error) < 0) {
-			i_fatal("Quota root %s: Invalid rule: %s",
-				root_name, rule);
+			i_fatal("Quota root %s: Invalid rule %s: %s",
+				root_name, rule, error);
 		}
 		rule_name = t_strdup_printf("%s_RULE%d", root_name, i);
 	}
--- a/src/plugins/quota/quota-private.h	Sat May 17 17:50:54 2008 +0300
+++ b/src/plugins/quota/quota-private.h	Mon Jun 09 05:11:18 2008 +0300
@@ -25,6 +25,9 @@
 	int64_t bytes_limit, count_limit;
 	/* relative to default_rule */
 	unsigned int bytes_percent, count_percent;
+
+	/* Don't include this mailbox in quota */
+	unsigned int ignore:1;
 };
 
 struct quota_warning_rule {
@@ -103,7 +106,10 @@
 void quota_remove_user_storage(struct quota *quota, 
 			       struct mail_storage *storage);
 
+struct quota_rule *
+quota_root_rule_find(struct quota_root *root, const char *name);
+
 void quota_root_recalculate_relative_rules(struct quota_root *root);
-int quota_count(struct quota *quota, uint64_t *bytes_r, uint64_t *count_r);
+int quota_count(struct quota_root *root, uint64_t *bytes_r, uint64_t *count_r);
 
 #endif
--- a/src/plugins/quota/quota-storage.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/plugins/quota/quota-storage.c	Mon Jun 09 05:11:18 2008 +0300
@@ -455,9 +455,6 @@
 	struct quota_mailbox_list *qlist = QUOTA_LIST_CONTEXT(storage->list);
 	union mail_storage_module_context *qstorage;
 
-	if (quota_next_hook_mail_storage_created != NULL)
-		quota_next_hook_mail_storage_created(storage);
-
 	qlist->storage = storage;
 
 	qstorage = p_new(storage->pool, union mail_storage_module_context, 1);
@@ -472,18 +469,21 @@
 		/* register to user's quota roots */
 		quota_add_user_storage(quota_set, storage);
 	}
+
+	if (quota_next_hook_mail_storage_created != NULL)
+		quota_next_hook_mail_storage_created(storage);
 }
 
 void quota_mailbox_list_created(struct mailbox_list *list)
 {
 	struct quota_mailbox_list *qlist;
 
-	if (quota_next_hook_mailbox_list_created != NULL)
-		quota_next_hook_mailbox_list_created(list);
-
 	qlist = p_new(list->pool, struct quota_mailbox_list, 1);
 	qlist->module_ctx.super = list->v;
 	list->v.delete_mailbox = quota_mailbox_list_delete;
 
 	MODULE_CONTEXT_SET(list, quota_mailbox_list_module, qlist);
+
+	if (quota_next_hook_mailbox_list_created != NULL)
+		quota_next_hook_mailbox_list_created(list);
 }
--- a/src/plugins/quota/quota.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/plugins/quota/quota.c	Mon Jun 09 05:11:18 2008 +0300
@@ -176,7 +176,7 @@
 	pool_unref(&pool);
 }
 
-static struct quota_rule *
+struct quota_rule *
 quota_root_rule_find(struct quota_root *root, const char *name)
 {
 	struct quota_rule *rules;
@@ -353,8 +353,17 @@
 		}
 	}
 
+	if (strcmp(p, "ignore") == 0) {
+		rule->ignore = TRUE;
+		if (root->quota->debug) {
+			i_info("Quota rule: root=%s mailbox=%s ignored",
+			       root->name, mailbox_name);
+		}
+		return 0;
+	}
+
 	if (strncmp(p, "backend=", 8) == 0) {
-		if (!root->backend.v.parse_rule(root, rule, p, error_r))
+		if (!root->backend.v.parse_rule(root, rule, p + 8, error_r))
 			ret = -1;
 	} else {
 		bool allow_negative = rule != &root->default_rule;
@@ -368,7 +377,7 @@
 	if (root->quota->debug) {
 		i_info("Quota rule: root=%s mailbox=%s "
 		       "bytes=%lld (%u%%) messages=%lld (%u%%)", root->name,
-		       rule->mailbox_name != NULL ? rule->mailbox_name : "",
+		       mailbox_name,
 		       (long long)rule->bytes_limit, rule->bytes_percent,
 		       (long long)rule->count_limit, rule->count_percent);
 	}
@@ -393,8 +402,13 @@
 
 	rule = quota_root_rule_find(root, mailbox_name);
 	if (rule != NULL) {
-		bytes_limit += rule->bytes_limit;
-		count_limit += rule->count_limit;
+		if (!rule->ignore) {
+			bytes_limit += rule->bytes_limit;
+			count_limit += rule->count_limit;
+		} else {
+			bytes_limit = 0;
+			count_limit = 0;
+		}
 		found = TRUE;
 	}
 
@@ -724,8 +738,10 @@
 int quota_transaction_commit(struct quota_transaction_context **_ctx)
 {
 	struct quota_transaction_context *ctx = *_ctx;
+	struct quota_rule *rule;
 	struct quota_root *const *roots;
 	unsigned int i, count;
+	const char *mailbox_name;
 	int ret = 0;
 
 	*_ctx = NULL;
@@ -734,8 +750,15 @@
 		ret = -1;
 	else if (ctx->bytes_used != 0 || ctx->count_used != 0 ||
 		 ctx->recalculate) {
+		mailbox_name = mailbox_get_name(ctx->box);
 		roots = array_get(&ctx->quota->roots, &count);
 		for (i = 0; i < count; i++) {
+			rule = quota_root_rule_find(roots[i], mailbox_name);
+			if (rule != NULL && rule->ignore) {
+				/* mailbox not included in quota */
+				continue;
+			}
+
 			if (roots[i]->backend.v.update(roots[i], ctx) < 0)
 				ret = -1;
 		}
--- a/src/plugins/zlib/zlib-plugin.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/plugins/zlib/zlib-plugin.c	Mon Jun 09 05:11:18 2008 +0300
@@ -26,18 +26,32 @@
 				  &mail_storage_module_register);
 static MODULE_CONTEXT_DEFINE_INIT(zlib_mail_module, &mail_module_register);
 
+static int zlib_mail_is_compressed(struct istream *mail)
+{
+	const unsigned char *zheader;
+	size_t header_size;
+
+	/* Peek in to the mail and see if it looks like it's compressed
+	   (it has the correct 2 byte zlib header). This also means that users
+	   can try to exploit security holes in zlib by APPENDing a specially
+	   crafted mail. So let's hope zlib is free of holes. */
+	if (i_stream_read_data(mail, &zheader, &header_size, 2) <= 0)
+		return 0;
+	i_stream_seek(mail, 0);
+
+	return header_size >= 2 && zheader &&
+		zheader[0] == 31 && zheader[1] == 139;
+}
+
 static int zlib_maildir_get_stream(struct mail *_mail,
 				   struct message_size *hdr_size,
 				   struct message_size *body_size,
 				   struct istream **stream_r)
 {
-	struct maildir_mailbox *mbox = (struct maildir_mailbox *)_mail->box;
 	struct mail_private *mail = (struct mail_private *)_mail;
 	struct index_mail *imail = (struct index_mail *)mail;
 	union mail_module_context *zmail = ZLIB_MAIL_CONTEXT(mail);
 	struct istream *input;
-	const char *fname, *p;
-        enum maildir_uidlist_rec_flag flags;
 	int fd;
 
 	if (imail->data.stream != NULL) {
@@ -49,10 +63,7 @@
 		return -1;
 	i_assert(input == imail->data.stream);
 
-	fname = maildir_uidlist_lookup(mbox->uidlist, _mail->uid, &flags);
-	p = fname == NULL ? NULL : strstr(fname, ":2,");
-	if (p != NULL && strchr(p + 3, 'Z') != NULL) {
-		/* has a Z flag - it's compressed */
+	if (zlib_mail_is_compressed(imail->data.stream)) {
 		fd = dup(i_stream_get_fd(imail->data.stream));
 		if (fd == -1)
 			i_error("zlib plugin: dup() failed: %m");
@@ -140,14 +151,14 @@
 {
 	union mail_storage_module_context *qstorage;
 
-	if (zlib_next_hook_mail_storage_created != NULL)
-		zlib_next_hook_mail_storage_created(storage);
-
 	qstorage = p_new(storage->pool, union mail_storage_module_context, 1);
 	qstorage->super = storage->v;
 	storage->v.mailbox_open = zlib_mailbox_open;
 
 	MODULE_CONTEXT_SET_SELF(storage, zlib_storage_module, qstorage);
+
+	if (zlib_next_hook_mail_storage_created != NULL)
+		zlib_next_hook_mail_storage_created(storage);
 }
 
 void zlib_plugin_init(void)
--- a/src/pop3-login/client-authenticate.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/pop3-login/client-authenticate.c	Mon Jun 09 05:11:18 2008 +0300
@@ -170,7 +170,7 @@
 		}
 
 		client_send_line(client, "+OK Logged in.");
-		client_destroy(client, "Login");
+		client_destroy_success(client, "Login");
 		break;
 	case SASL_SERVER_REPLY_AUTH_FAILED:
 	case SASL_SERVER_REPLY_CLIENT_ERROR:
@@ -197,7 +197,7 @@
 		else {
 			client_send_line(client,
 				t_strconcat("-ERR [IN-USE] ", data, NULL));
-			client_destroy(client, data);
+			client_destroy_success(client, data);
 		}
 		break;
 	case SASL_SERVER_REPLY_CONTINUE:
--- a/src/pop3-login/client.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/pop3-login/client.c	Mon Jun 09 05:11:18 2008 +0300
@@ -150,9 +150,7 @@
 		client_destroy(client, "Aborted login "
 			"(tried to use disabled plaintext authentication)");
 	} else {
-		client_destroy(client, t_strdup_printf(
-			"Aborted login (%u authentication attempts)",
-			client->common.auth_attempts));
+		client_destroy(client, "Aborted login");
 	}
 	return TRUE;
 }
@@ -341,12 +339,22 @@
 	return &client->common;
 }
 
+void client_destroy_success(struct pop3_client *client, const char *reason)
+{
+	client->login_success = TRUE;
+	client_destroy(client, reason);
+}
+
 void client_destroy(struct pop3_client *client, const char *reason)
 {
 	if (client->destroyed)
 		return;
 	client->destroyed = TRUE;
 
+	if (!client->login_success && reason != NULL) {
+		reason = t_strdup_printf("%s (auth failed, %u attempts)",
+					 reason, client->common.auth_attempts);
+	}
 	if (reason != NULL)
 		client_syslog(&client->common, reason);
 
--- a/src/pop3-login/client.h	Sat May 17 17:50:54 2008 +0300
+++ b/src/pop3-login/client.h	Mon Jun 09 05:11:18 2008 +0300
@@ -28,12 +28,14 @@
 	char *apop_challenge;
 	struct auth_connect_id auth_id;
 
+	unsigned int login_success:1;
 	unsigned int authenticating:1;
 	unsigned int auth_connected:1;
 	unsigned int destroyed:1;
 };
 
 void client_destroy(struct pop3_client *client, const char *reason);
+void client_destroy_success(struct pop3_client *client, const char *reason);
 void client_destroy_internal_failure(struct pop3_client *client);
 
 void client_send_line(struct pop3_client *client, const char *line);
--- a/src/pop3-login/pop3-proxy.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/pop3-login/pop3-proxy.c	Mon Jun 09 05:11:18 2008 +0300
@@ -32,7 +32,7 @@
 		/* failed for some reason, probably server disconnected */
 		client_send_line(client,
 				 "-ERR [IN-USE] Temporary login failure.");
-		client_destroy(client, NULL);
+		client_destroy_success(client, NULL);
 		return;
 	}
 
@@ -47,7 +47,7 @@
 		return;
 	case -1:
 		/* disconnected */
-		client_destroy(client, "Proxy: Remote disconnected");
+		client_destroy_success(client, "Proxy: Remote disconnected");
 		return;
 	}
 
@@ -99,8 +99,8 @@
 			break;
 
 		/* Login successful. Send this line to client. */
+		line = t_strconcat(line, "\r\n", NULL);
 		(void)o_stream_send_str(client->output, line);
-		(void)o_stream_send(client->output, "\r\n", 2);
 
 		msg = t_strdup_printf("proxy(%s): started proxying to %s:%u",
 				      client->common.virtual_user,
@@ -114,7 +114,7 @@
 		client->input = NULL;
 		client->output = NULL;
 		client->common.fd = -1;
-		client_destroy(client, msg);
+		client_destroy_success(client, msg);
 		return;
 	}
 
--- a/src/pop3/client.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/pop3/client.c	Mon Jun 09 05:11:18 2008 +0300
@@ -241,11 +241,20 @@
 	return str_c(str);
 }
 
+static const char *client_get_disconnect_reason(struct client *client)
+{
+	errno = client->input->stream_errno != 0 ?
+		client->input->stream_errno :
+		client->output->stream_errno;
+	return errno == 0 || errno == EPIPE ? "Connection closed" :
+		t_strdup_printf("Connection closed: %m");
+}
+
 void client_destroy(struct client *client, const char *reason)
 {
 	if (!client->disconnected) {
 		if (reason == NULL)
-			reason = "Disconnected";
+			reason = client_get_disconnect_reason(client);
 		i_info("%s %s", reason, client_stats(client));
 	}
 
--- a/src/pop3/commands.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/pop3/commands.c	Mon Jun 09 05:11:18 2008 +0300
@@ -507,6 +507,49 @@
 	struct mail_search_arg search_arg;
 };
 
+static void pop3_get_uid(struct cmd_uidl_context *ctx,
+			 struct var_expand_table *tab, string_t *str)
+{
+	char uid_str[MAX_INT_STRLEN];
+	const char *uidl;
+
+	if (mail_get_special(ctx->mail, MAIL_FETCH_UIDL_BACKEND, &uidl) == 0 &&
+	    *uidl != '\0') {
+		str_append(str, uidl);
+		return;
+	}
+
+	if (reuse_xuidl &&
+	    mail_get_first_header(ctx->mail, "X-UIDL", &uidl) > 0) {
+		str_append(str, uidl);
+		return;
+	}
+
+	if ((uidl_keymask & UIDL_UID) != 0) {
+		i_snprintf(uid_str, sizeof(uid_str), "%u",
+			   ctx->mail->uid);
+		tab[1].value = uid_str;
+	}
+	if ((uidl_keymask & UIDL_MD5) != 0) {
+		if (mail_get_special(ctx->mail, MAIL_FETCH_HEADER_MD5,
+				     &tab[2].value) < 0 ||
+		    *tab[2].value == '\0') {
+			/* broken */
+			i_fatal("UIDL: Header MD5 not found");
+		}
+	}
+	if ((uidl_keymask & UIDL_FILE_NAME) != 0) {
+		if (mail_get_special(ctx->mail,
+				     MAIL_FETCH_UIDL_FILE_NAME,
+				     &tab[3].value) < 0 ||
+		    *tab[3].value == '\0') {
+			/* broken */
+			i_fatal("UIDL: File name not found");
+		}
+	}
+	var_expand(str, uidl_format, tab);
+}
+
 static bool list_uids_iter(struct client *client, struct cmd_uidl_context *ctx)
 {
 	static struct var_expand_table static_tab[] = {
@@ -518,8 +561,6 @@
 	};
 	struct var_expand_table *tab;
 	string_t *str;
-	char uid_str[MAX_INT_STRLEN];
-	const char *uidl;
 	int ret;
 	bool found = FALSE;
 
@@ -537,38 +578,11 @@
 		}
 		found = TRUE;
 
-		if ((uidl_keymask & UIDL_UID) != 0) {
-			i_snprintf(uid_str, sizeof(uid_str), "%u",
-				   ctx->mail->uid);
-			tab[1].value = uid_str;
-		}
-		if ((uidl_keymask & UIDL_MD5) != 0) {
-			if (mail_get_special(ctx->mail, MAIL_FETCH_HEADER_MD5,
-					     &tab[2].value) < 0 ||
-			    *tab[2].value == '\0') {
-				/* broken */
-				i_fatal("UIDL: Header MD5 not found");
-			}
-		}
-		if ((uidl_keymask & UIDL_FILE_NAME) != 0) {
-			if (mail_get_special(ctx->mail,
-					     MAIL_FETCH_UIDL_FILE_NAME,
-					     &tab[3].value) < 0 ||
-			    *tab[3].value == '\0') {
-				/* broken */
-				i_fatal("UIDL: File name not found");
-			}
-		}
-
 		str_truncate(str, 0);
 		str_printfa(str, ctx->message == 0 ? "%u " : "+OK %u ",
 			    ctx->mail->seq);
+		pop3_get_uid(ctx, tab, str);
 
-		if (reuse_xuidl &&
-		    mail_get_first_header(ctx->mail, "X-UIDL", &uidl) > 0)
-			str_append(str, uidl);
-		else
-			var_expand(str, uidl_format, tab);
 		ret = client_send_line(client, "%s", str_c(str));
 		if (ret < 0)
 			break;
--- a/src/tests/Makefile.am	Sat May 17 17:50:54 2008 +0300
+++ b/src/tests/Makefile.am	Mon Jun 09 05:11:18 2008 +0300
@@ -44,4 +44,6 @@
 	$(RAND_LIBS) \
 	libtest.a \
 	../lib-imap/libimap.a \
+	../lib-mail/libmail.a \
+	../lib-charset/libcharset.a \
 	../lib/liblib.a
--- a/src/tests/test-lib.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/tests/test-lib.c	Mon Jun 09 05:11:18 2008 +0300
@@ -6,6 +6,7 @@
 #include "base64.h"
 #include "bsearch-insert-pos.h"
 #include "aqueue.h"
+#include "network.h"
 #include "priorityq.h"
 #include "seq-range-array.h"
 #include "str-sanitize.h"
@@ -143,6 +144,7 @@
 	buf = buffer_create_dynamic(default_pool, 1);
 	for (i = 0; i < BUF_TEST_SIZE; i++)
 		testdata[i] = random();
+	memset(shadowbuf, 0, sizeof(shadowbuf));
 
 	srand(1);
 	shadowbuf_size = 0;
@@ -366,6 +368,50 @@
 	test_out("mempool_alloconly", success);
 }
 
+struct test_net_is_in_network_input {
+	const char *ip;
+	const char *net;
+	unsigned int bits;
+	bool ret;
+};
+
+static void test_net_is_in_network(void)
+{
+	static struct test_net_is_in_network_input input[] = {
+		{ "1.2.3.4", "1.2.3.4", 32, TRUE },
+		{ "1.2.3.4", "1.2.3.3", 32, FALSE },
+		{ "1.2.3.4", "1.2.3.5", 32, FALSE },
+		{ "1.2.3.4", "1.2.2.4", 32, FALSE },
+		{ "1.2.3.4", "1.1.3.4", 32, FALSE },
+		{ "1.2.3.4", "0.2.3.4", 32, FALSE },
+		{ "1.2.3.253", "1.2.3.254", 31, FALSE },
+		{ "1.2.3.254", "1.2.3.254", 31, TRUE },
+		{ "1.2.3.255", "1.2.3.254", 31, TRUE },
+		{ "1.2.3.255", "1.2.3.0", 24, TRUE },
+		{ "1.2.255.255", "1.2.254.0", 23, TRUE },
+		{ "255.255.255.255", "128.0.0.0", 1, TRUE },
+		{ "255.255.255.255", "127.0.0.0", 1, FALSE }
+#ifdef HAVE_IPV6
+		,
+		{ "1234:5678::abcf", "1234:5678::abce", 127, TRUE },
+		{ "1234:5678::abcd", "1234:5678::abce", 127, FALSE },
+		{ "123e::ffff", "123e::0", 15, TRUE },
+		{ "123d::ffff", "123e::0", 15, FALSE }
+#endif
+	};
+	struct ip_addr ip, net_ip;
+	unsigned int i;
+	bool success;
+
+	for (i = 0; i < N_ELEMENTS(input); i++) {
+		net_addr2ip(input[i].ip, &ip);
+		net_addr2ip(input[i].net, &net_ip);
+		success = net_is_in_network(&ip, &net_ip, input[i].bits) ==
+			input[i].ret;
+		test_out(t_strdup_printf("net_is_in_network(%u)", i), success);
+	}
+}
+
 struct pq_test_item {
 	struct priorityq_item item;
 	int num;
@@ -680,6 +726,7 @@
 		test_bsearch_insert_pos,
 		test_buffer,
 		test_mempool_alloconly,
+		test_net_is_in_network,
 		test_priorityq,
 		test_seq_range_array,
 		test_str_sanitize,
--- a/src/util/idxview.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/util/idxview.c	Mon Jun 09 05:11:18 2008 +0300
@@ -17,17 +17,18 @@
 struct maildir_index_header {
 	uint32_t new_check_time, new_mtime, new_mtime_nsecs;
 	uint32_t cur_check_time, cur_mtime, cur_mtime_nsecs;
+	uint32_t uidlist_mtime, uidlist_mtime_nsecs, uidlist_size;
 };
 struct dbox_index_header {
 	uint32_t last_dirty_flush_stamp;
 };
 
-static const char *unixdate2str(time_t time)
+static const char *unixdate2str(time_t timestamp)
 {
 	static char buf[64];
 	struct tm *tm;
 
-	tm = localtime(&time);
+	tm = localtime(&timestamp);
 	strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M", tm);
 	return buf;
 }
@@ -80,12 +81,15 @@
 		const struct maildir_index_header *hdr = data;
 
 		printf("header\n");
-		printf(" - new_check_time  = %s\n", unixdate2str(hdr->new_check_time));
-		printf(" - new_mtime ..... = %s\n", unixdate2str(hdr->new_mtime));
-		printf(" - new_mtime_nsecs = %u\n", hdr->new_mtime_nsecs);
-		printf(" - cur_check_time  = %s\n", unixdate2str(hdr->cur_check_time));
-		printf(" - cur_mtime ..... = %s\n", unixdate2str(hdr->cur_mtime));
-		printf(" - cur_mtime_nsecs = %u\n", hdr->cur_mtime_nsecs);
+		printf(" - new_check_time .... = %s\n", unixdate2str(hdr->new_check_time));
+		printf(" - new_mtime ......... = %s\n", unixdate2str(hdr->new_mtime));
+		printf(" - new_mtime_nsecs ... = %u\n", hdr->new_mtime_nsecs);
+		printf(" - cur_check_time .... = %s\n", unixdate2str(hdr->cur_check_time));
+		printf(" - cur_mtime ......... = %s\n", unixdate2str(hdr->cur_mtime));
+		printf(" - cur_mtime_nsecs.... = %u\n", hdr->cur_mtime_nsecs);
+		printf(" - uidlist_mtime ..... = %s\n", unixdate2str(hdr->uidlist_mtime));
+		printf(" - uidlist_mtime_nsecs = %u\n", hdr->uidlist_mtime_nsecs);
+		printf(" - uidlist_size ...... = %u\n", hdr->uidlist_size);
 	} else if (strcmp(ext->name, "dbox-hdr") == 0) {
 		const struct dbox_index_header *hdr = data;
 
--- a/src/util/logview.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/util/logview.c	Mon Jun 09 05:11:18 2008 +0300
@@ -171,6 +171,7 @@
 		const struct mail_transaction_ext_reset *reset = data;
 
 		printf(" - new_reset_id = %u\n", reset->new_reset_id);
+		printf(" - preserve_data = %u\n", reset->preserve_data);
 		break;
 	}
 	case MAIL_TRANSACTION_EXT_HDR_UPDATE: {
--- a/src/util/rawlog.c	Sat May 17 17:50:54 2008 +0300
+++ b/src/util/rawlog.c	Mon Jun 09 05:11:18 2008 +0300
@@ -274,15 +274,15 @@
 
 static void rawlog_open(enum rawlog_flags flags)
 {
-	const char *chroot, *home, *path;
+	const char *chroot_dir, *home, *path;
 	struct stat st;
 	int sfd[2];
 	pid_t pid;
 
-	chroot = getenv("RESTRICT_CHROOT");
+	chroot_dir = getenv("RESTRICT_CHROOT");
 	home = getenv("HOME");
-	if (chroot != NULL)
-		home = t_strconcat(chroot, home, NULL);
+	if (chroot_dir != NULL)
+		home = t_strconcat(chroot_dir, home, NULL);
 	else if (home == NULL)
 		home = ".";
 
@@ -296,9 +296,9 @@
 	if (!S_ISDIR(st.st_mode))
 		return;
 
-	if (chroot != NULL) {
+	if (chroot_dir != NULL) {
 		/* we'll chroot soon. skip over the chroot in the path. */
-		path += strlen(chroot);
+		path += strlen(chroot_dir);
 	}
 
 	if (socketpair(AF_UNIX, SOCK_STREAM, 0, sfd) < 0)