diff -ru dovecot-1.0.10/dovecot-example.conf dovecot-1.0.10-fixed/dovecot-example.conf
--- dovecot-1.0.10/dovecot-example.conf	2007-12-11 20:52:08.000000000 +0200
+++ dovecot-1.0.10-fixed/dovecot-example.conf	2008-03-04 08:14:02.000000000 +0200
@@ -252,9 +252,17 @@
    #hidden = yes
 #}
 
-# Grant access to these extra groups for mail processes. Typical use would be
-# to give "mail" group write access to /var/mail to be able to create dotlocks.
-#mail_extra_groups =
+# Group to enable temporarily for privileged operations. Currently this is
+# used only for creating mbox dotlock files when creation fails for INBOX.
+# Typically this is set to "mail" to give access to /var/mail.
+#mail_privileged_group =
+
+# Grant access to these supplementary groups for mail processes. Typically
+# these are used to set up access to shared mailboxes. Note that it may be
+# dangerous to set these if users can create symlinks (e.g. if "mail" group is
+# set here, ln -s /var/mail ~/mail/var could allow a user to delete others'
+# mailboxes, or ln -s /secret/shared/box ~/mail/mybox would allow reading it).
+#mail_access_groups =
 
 # Allow full filesystem access to clients. There's no access checks other than
 # what the operating system does for the active UID/GID. It works with both
Only in dovecot-1.0.10-fixed: dovecot-example.conf.orig
diff -ru dovecot-1.0.10/src/lib/file-dotlock.c dovecot-1.0.10-fixed/src/lib/file-dotlock.c
--- dovecot-1.0.10/src/lib/file-dotlock.c	2007-12-11 20:52:08.000000000 +0200
+++ dovecot-1.0.10-fixed/src/lib/file-dotlock.c	2008-03-05 00:38:36.000000000 +0200
@@ -262,7 +262,8 @@
 			break;
 
 		if (errno != EEXIST) {
-			i_error("open(%s) failed: %m", str_c(path));
+			if (errno != EACCES)
+				i_error("open(%s) failed: %m", str_c(path));
 			return -1;
 		}
 	}
@@ -319,8 +320,10 @@
 		if (errno == EEXIST)
 			return 0;
 
-		i_error("link(%s, %s) failed: %m",
-			lock_info->temp_path, lock_info->lock_path);
+		if (errno != EACCES) {
+			i_error("link(%s, %s) failed: %m",
+				lock_info->temp_path, lock_info->lock_path);
+		}
 		return -1;
 	}
 
@@ -342,7 +345,8 @@
 		if (errno == EEXIST)
 			return 0;
 
-		i_error("open(%s) failed: %m", lock_info->lock_path);
+		if (errno != EACCES)
+			i_error("open(%s) failed: %m", lock_info->lock_path);
 		return -1;
 	}
 
@@ -633,7 +637,6 @@
 			 enum dotlock_replace_flags flags)
 {
 	struct dotlock *dotlock;
-	struct stat st, st2;
 	const char *lock_path;
 	int fd;
 
@@ -645,28 +648,14 @@
 		dotlock->fd = -1;
 
 	lock_path = file_dotlock_get_lock_path(dotlock);
-	if ((flags & DOTLOCK_REPLACE_FLAG_VERIFY_OWNER) != 0) {
-		if (fstat(fd, &st) < 0) {
-			i_error("fstat(%s) failed: %m", lock_path);
-			file_dotlock_free(dotlock);
-			return -1;
-		}
-
-		if (lstat(lock_path, &st2) < 0) {
-			i_error("lstat(%s) failed: %m", lock_path);
-			file_dotlock_free(dotlock);
-			return -1;
-		}
-
-		if (st.st_ino != st2.st_ino ||
-		    !CMP_DEV_T(st.st_dev, st2.st_dev)) {
-			i_warning("Our dotlock file %s was overridden "
-				  "(kept it %d secs)", lock_path,
-				  (int)(time(NULL) - dotlock->lock_time));
-			errno = EEXIST;
-			file_dotlock_free(dotlock);
-			return 0;
-		}
+	if ((flags & DOTLOCK_REPLACE_FLAG_VERIFY_OWNER) != 0 &&
+	    !file_dotlock_is_locked(dotlock)) {
+		i_warning("Our dotlock file %s was overridden "
+			  "(kept it %d secs)", lock_path,
+			  (int)(time(NULL) - dotlock->lock_time));
+		errno = EEXIST;
+		file_dotlock_free(dotlock);
+		return 0;
 	}
 
 	if (rename(lock_path, dotlock->path) < 0) {
@@ -701,6 +690,24 @@
 	return ret;
 }
 
+bool file_dotlock_is_locked(struct dotlock *dotlock)
+{
+	struct stat st, st2;
+	const char *lock_path;
+
+	lock_path = file_dotlock_get_lock_path(dotlock);
+	if (fstat(dotlock->fd, &st) < 0) {
+		i_error("fstat(%s) failed: %m", lock_path);
+		return FALSE;
+	}
+
+	if (lstat(lock_path, &st2) < 0) {
+		i_error("lstat(%s) failed: %m", lock_path);
+		return FALSE;
+	}
+	return st.st_ino == st2.st_ino && CMP_DEV_T(st.st_dev, st2.st_dev);
+}
+
 const char *file_dotlock_get_lock_path(struct dotlock *dotlock)
 {
 	if (dotlock->lock_path == NULL) {
diff -ru dovecot-1.0.10/src/lib/file-dotlock.h dovecot-1.0.10-fixed/src/lib/file-dotlock.h
--- dovecot-1.0.10/src/lib/file-dotlock.h	2007-12-11 20:52:08.000000000 +0200
+++ dovecot-1.0.10-fixed/src/lib/file-dotlock.h	2008-03-04 08:13:48.000000000 +0200
@@ -70,6 +70,8 @@
    it's a good idea to update it once in a while so others won't override it.
    If the timestamp is less than a second old, it's not updated. */
 int file_dotlock_touch(struct dotlock *dotlock);
+/* Returns TRUE if the lock is still ok, FALSE if it's been overridden. */
+bool file_dotlock_is_locked(struct dotlock *dotlock);
 
 /* Returns the lock file path. */
 const char *file_dotlock_get_lock_path(struct dotlock *dotlock);
diff -ru dovecot-1.0.10/src/lib/restrict-access.c dovecot-1.0.10-fixed/src/lib/restrict-access.c
--- dovecot-1.0.10/src/lib/restrict-access.c	2007-12-11 20:52:09.000000000 +0200
+++ dovecot-1.0.10-fixed/src/lib/restrict-access.c	2008-03-04 08:13:48.000000000 +0200
@@ -1,15 +1,22 @@
-/* Copyright (c) 2002-2004 Timo Sirainen */
+/* Copyright (c) 2002-2008 Dovecot authors, see the included COPYING file */
+
+#define _GNU_SOURCE /* setresgid() */
+#include <sys/types.h>
+#include <unistd.h>
 
 #include "lib.h"
 #include "restrict-access.h"
 #include "env-util.h"
 
 #include <stdlib.h>
-#include <unistd.h>
 #include <time.h>
 #include <grp.h>
 
-void restrict_access_set_env(const char *user, uid_t uid, gid_t gid,
+static gid_t primary_gid = (gid_t)-1, privileged_gid = (gid_t)-1;
+static bool using_priv_gid = FALSE;
+
+void restrict_access_set_env(const char *user, uid_t uid,
+			     gid_t gid, gid_t privileged_gid,
 			     const char *chroot_dir,
 			     gid_t first_valid_gid, gid_t last_valid_gid,
 			     const char *extra_groups)
@@ -21,6 +28,10 @@
 
 	env_put(t_strdup_printf("RESTRICT_SETUID=%s", dec2str(uid)));
 	env_put(t_strdup_printf("RESTRICT_SETGID=%s", dec2str(gid)));
+	if (privileged_gid != (gid_t)-1) {
+		env_put(t_strdup_printf("RESTRICT_SETGID_PRIV=%s",
+					dec2str(privileged_gid)));
+	}
 	if (extra_groups != NULL && *extra_groups != '\0') {
 		env_put(t_strconcat("RESTRICT_SETEXTRAGROUPS=",
 				    extra_groups, NULL));
@@ -36,7 +47,54 @@
 	}
 }
 
-static gid_t *get_groups_list(int *gid_count_r)
+static void restrict_init_groups(gid_t primary_gid, gid_t privileged_gid)
+{
+	if (privileged_gid == (gid_t)-1) {
+		if (primary_gid == getgid() && primary_gid == getegid()) {
+			/* everything is already set */
+			return;
+		}
+
+		if (setgid(primary_gid) != 0) {
+			i_fatal("setgid(%s) failed with euid=%s, "
+				"gid=%s, egid=%s: %m",
+				dec2str(primary_gid), dec2str(geteuid()),
+				dec2str(getgid()), dec2str(getegid()));
+		}
+		return;
+	}
+
+	if (getegid() != 0 && primary_gid == getgid() &&
+	    primary_gid == getegid()) {
+		/* privileged_gid is hopefully in saved ID. if not,
+		   there's nothing we can do about it. */
+		return;
+	}
+
+#ifdef HAVE_SETRESGID
+	if (setresgid(primary_gid, primary_gid, privileged_gid) != 0) {
+		i_fatal("setresgid(%s,%s,%s) failed with euid=%s: %m",
+			dec2str(primary_gid), dec2str(primary_gid),
+			dec2str(privileged_gid), dec2str(geteuid()));
+	}
+#else
+	/* real: primary_gid
+	   effective: privileged_gid
+	   saved: privileged_gid */
+	if (setregid(primary_gid, privileged_gid) != 0) {
+		i_fatal("setregid(%s,%s) failed with euid=%s: %m",
+			dec2str(primary_gid), dec2str(privileged_gid),
+			dec2str(geteuid()));
+	}
+	/* effective: privileged_gid -> primary_gid */
+	if (setegid(privileged_gid) != 0) {
+		i_fatal("setegid(%s) failed with euid=%s: %m",
+			dec2str(privileged_gid), dec2str(geteuid()));
+	}
+#endif
+}
+
+static gid_t *get_groups_list(unsigned int *gid_count_r)
 {
 	gid_t *gid_list;
 	int ret, gid_count;
@@ -53,39 +111,31 @@
 	return gid_list;
 }
 
-static void drop_restricted_groups(bool *have_root_group)
+static bool drop_restricted_groups(gid_t *gid_list, unsigned int *gid_count,
+				   bool *have_root_group)
 {
 	/* @UNSAFE */
+	gid_t first_valid, last_valid;
 	const char *env;
-	gid_t *gid_list, first_valid_gid, last_valid_gid;
-	int i, used, gid_count;
+	unsigned int i, used;
 
 	env = getenv("RESTRICT_GID_FIRST");
-	first_valid_gid = env == NULL ? 0 : (gid_t)strtoul(env, NULL, 10);
+	first_valid = env == NULL ? 0 : (gid_t)strtoul(env, NULL, 10);
 	env = getenv("RESTRICT_GID_LAST");
-	last_valid_gid = env == NULL ? 0 : (gid_t)strtoul(env, NULL, 10);
-
-	if (first_valid_gid == 0 && last_valid_gid == 0)
-		return;
-
-	t_push();
-	gid_list = get_groups_list(&gid_count);
+	last_valid = env == NULL ? (gid_t)-1 : (gid_t)strtoul(env, NULL, 10);
 
-	for (i = 0, used = 0; i < gid_count; i++) {
-		if (gid_list[i] >= first_valid_gid &&
-		    (last_valid_gid == 0 || gid_list[i] <= last_valid_gid)) {
+	for (i = 0, used = 0; i < *gid_count; i++) {
+		if (gid_list[i] >= first_valid &&
+		    (last_valid == (gid_t)-1 || gid_list[i] <= last_valid)) {
 			if (gid_list[i] == 0)
 				*have_root_group = TRUE;
 			gid_list[used++] = gid_list[i];
 		}
 	}
-
-	if (used != gid_count) {
-		/* it did contain restricted groups, remove it */
-		if (setgroups(used, gid_list) < 0)
-			i_fatal("setgroups() failed: %m");
-	}
-	t_pop();
+	if (*gid_count == used)
+		return FALSE;
+	*gid_count = used;
+	return TRUE;
 }
 
 static gid_t get_group_id(const char *name)
@@ -101,71 +151,98 @@
 	return group->gr_gid;
 }
 
-static void grant_extra_groups(const char *groups)
+static void fix_groups_list(const char *extra_groups, gid_t egid,
+			    bool preserve_existing, bool *have_root_group)
 {
-	const char *const *tmp;
-	gid_t *gid_list;
-	int gid_count;
-
-	t_push();
-	tmp = t_strsplit(groups, ", ");
-	gid_list = get_groups_list(&gid_count);
-	for (; *tmp != NULL; tmp++) {
-		if (**tmp == '\0')
-			continue;
-
-		if (!t_try_realloc(gid_list, (gid_count+1) * sizeof(gid_t)))
-			i_unreached();
-		gid_list[gid_count++] = get_group_id(*tmp);
+	gid_t *gid_list, *gid_list2;
+	const char *const *tmp, *empty = NULL;
+	unsigned int gid_count;
+
+	tmp = extra_groups == NULL ? &empty :
+		t_strsplit_spaces(extra_groups, ", ");
+
+	if (preserve_existing) {
+		gid_list = get_groups_list(&gid_count);
+		if (!drop_restricted_groups(gid_list, &gid_count,
+					    have_root_group) &&
+		    *tmp == NULL) {
+			/* nothing dropped, no extra groups to grant. */
+			return;
+		}
+	} else {
+		if (egid == (gid_t)-1 && *tmp == NULL) {
+			/* nothing to do */
+			return;
+		}
+		/* Some OSes don't like an empty groups list,
+		   so use the effective GID as the only one. */
+		gid_list = t_new(gid_t, 2);
+		gid_list[0] = egid != (gid_t)-1 ? egid : getegid();
+		gid_count = 1;
+	}
+
+	if (*tmp != NULL) {
+		/* @UNSAFE: add extra groups to gids list */
+		gid_list2 = t_new(gid_t, gid_count + strarray_length(tmp));
+		memcpy(gid_list2, gid_list, gid_count * sizeof(gid_t));
+		for (; *tmp != NULL; tmp++)
+			gid_list2[gid_count++] = get_group_id(*tmp);
+		gid_list = gid_list2;
+	}
+
+	if (setgroups(gid_count, gid_list) < 0) {
+		if (errno == EINVAL) {
+			i_fatal("setgroups(%s) failed: Too many extra groups",
+				extra_groups == NULL ? "" : extra_groups);
+		} else {
+			i_fatal("setgroups() failed: %m");
+		}
 	}
-
-	if (setgroups(gid_count, gid_list) < 0)
-		i_fatal("setgroups() failed: %m");
-
-	t_pop();
 }
 
 void restrict_access_by_env(bool disallow_root)
 {
 	const char *env;
-	gid_t gid;
 	uid_t uid;
-	bool have_root_group;
+	bool is_root, have_root_group, preserve_groups = FALSE;
+	bool allow_root_gid;
 
-	/* groups - the getgid() checks are just so we don't fail if we're
-	   not running as root and try to just use our own GID. Do this
-	   before chrooting so initgroups() actually works. */
-	env = getenv("RESTRICT_SETGID");
-	gid = env == NULL ? 0 : (gid_t)strtoul(env, NULL, 10);
-	have_root_group = gid == 0;
-	if (gid != 0 && (gid != getgid() || gid != getegid())) {
-		if (setgid(gid) != 0) {
-			i_fatal("setgid(%s) failed with euid=%s, egid=%s: %m",
-				dec2str(gid), dec2str(geteuid()),
-				dec2str(getegid()));
-		}
+	is_root = geteuid() == 0;
 
-		env = getenv("RESTRICT_USER");
-		if (env == NULL) {
-			/* user not known, use only this one group */
-			if (setgroups(1, &gid) < 0) {
-				i_fatal("setgroups(%s) failed: %m",
-					dec2str(gid));
-			}
-		} else {
-			if (initgroups(env, gid) != 0) {
-				i_fatal("initgroups(%s, %s) failed: %m",
-					env, dec2str(gid));
-			}
-
-                        drop_restricted_groups(&have_root_group);
+	/* set the primary/privileged group */
+	env = getenv("RESTRICT_SETGID");
+	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 :
+		(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);
+	}
+
+	/* set system user's groups */
+	env = getenv("RESTRICT_USER");
+	if (env != NULL && *env != '\0' && is_root) {
+		if (initgroups(env, primary_gid) < 0) {
+			i_fatal("initgroups(%s, %s) failed: %m",
+				env, dec2str(primary_gid));
 		}
+		preserve_groups = TRUE;
 	}
 
-	/* grant additional groups to process */
+	/* add extra groups. if we set system user's groups, drop the
+	   restricted groups at the same time. */
 	env = getenv("RESTRICT_SETEXTRAGROUPS");
-	if (env != NULL && *env != '\0')
-		grant_extra_groups(env);
+	if (is_root) {
+		t_push();
+		fix_groups_list(env, primary_gid, preserve_groups,
+				&have_root_group);
+		t_pop();
+	}
 
 	/* chrooting */
 	env = getenv("RESTRICT_CHROOT");
@@ -193,7 +270,7 @@
 
 	/* uid last */
 	env = getenv("RESTRICT_SETUID");
-	uid = env == NULL ? 0 : (uid_t)strtoul(env, NULL, 10);
+	uid = env == NULL || *env == '\0' ? 0 : (uid_t)strtoul(env, NULL, 10);
 	if (uid != 0) {
 		if (setuid(uid) != 0) {
 			i_fatal("setuid(%s) failed with euid=%s: %m",
@@ -211,12 +288,20 @@
 	}
 
 	env = getenv("RESTRICT_GID_FIRST");
-	if ((!have_root_group || (env != NULL && atoi(env) != 0)) && uid != 0) {
+	if (env != NULL && atoi(env) != 0)
+		allow_root_gid = FALSE;
+	else if (primary_gid == 0 || 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 (gid == 0)
+			if (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(gid),
+				"(wanted=%s, gid=%s, egid=%s)",
+				dec2str(primary_gid),
 				dec2str(getgid()), dec2str(getegid()));
 		}
 	}
@@ -225,8 +310,43 @@
 	env_put("RESTRICT_USER=");
 	env_put("RESTRICT_CHROOT=");
 	env_put("RESTRICT_SETUID=");
-	env_put("RESTRICT_SETGID=");
+	if (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 */
+		env_put("RESTRICT_SETGID=");
+		env_put("RESTRICT_SETGID_PRIV=");
+	}
 	env_put("RESTRICT_SETEXTRAGROUPS=");
 	env_put("RESTRICT_GID_FIRST=");
 	env_put("RESTRICT_GID_LAST=");
 }
+
+int restrict_access_use_priv_gid(void)
+{
+	i_assert(!using_priv_gid);
+
+	if (privileged_gid == (gid_t)-1)
+		return 0;
+	if (setegid(privileged_gid) < 0) {
+		i_error("setegid(privileged) failed: %m");
+		return -1;
+	}
+	using_priv_gid = TRUE;
+	return 0;
+}
+
+void restrict_access_drop_priv_gid(void)
+{
+	if (!using_priv_gid)
+		return;
+
+	if (setegid(primary_gid) < 0)
+		i_fatal("setegid(primary) failed: %m");
+	using_priv_gid = FALSE;
+}
+
+bool restrict_access_have_priv_gid(void)
+{
+	return privileged_gid != (gid_t)-1;
+}
diff -ru dovecot-1.0.10/src/lib/restrict-access.h dovecot-1.0.10-fixed/src/lib/restrict-access.h
--- dovecot-1.0.10/src/lib/restrict-access.h	2007-12-11 20:52:09.000000000 +0200
+++ dovecot-1.0.10-fixed/src/lib/restrict-access.h	2008-03-04 08:13:48.000000000 +0200
@@ -2,8 +2,10 @@
 #define __RESTRICT_ACCESS_H
 
 /* set environment variables so they can be read with
-   restrict_access_by_env() */
-void restrict_access_set_env(const char *user, uid_t uid, gid_t gid,
+   restrict_access_by_env(). If privileged_gid != (gid_t)-1,
+   the privileged GID can be temporarily enabled/disabled. */
+void restrict_access_set_env(const char *user, uid_t uid,
+			     gid_t gid, gid_t privileged_gid,
 			     const char *chroot_dir,
 			     gid_t first_valid_gid, gid_t last_valid_gid,
 			     const char *extra_groups);
@@ -13,4 +15,11 @@
    environment settings and we have root uid or gid. */
 void restrict_access_by_env(bool disallow_root);
 
+/* If privileged_gid was set, these functions can be used to temporarily
+   gain access to the group. */
+int restrict_access_use_priv_gid(void);
+void restrict_access_drop_priv_gid(void);
+/* Returns TRUE if privileged GID exists for this process. */
+bool restrict_access_have_priv_gid(void);
+
 #endif
diff -ru dovecot-1.0.10/src/lib-storage/index/mbox/mbox-lock.c dovecot-1.0.10-fixed/src/lib-storage/index/mbox/mbox-lock.c
--- dovecot-1.0.10/src/lib-storage/index/mbox/mbox-lock.c	2007-12-11 20:52:08.000000000 +0200
+++ dovecot-1.0.10-fixed/src/lib-storage/index/mbox/mbox-lock.c	2008-03-04 08:13:48.000000000 +0200
@@ -1,6 +1,7 @@
 /* Copyright (C) 2002 Timo Sirainen */
 
 #include "lib.h"
+#include "restrict-access.h"
 #include "mail-index-private.h"
 #include "mbox-storage.h"
 #include "mbox-file.h"
@@ -36,6 +37,12 @@
 	MBOX_LOCK_COUNT
 };
 
+enum mbox_dotlock_op {
+	MBOX_DOTLOCK_OP_LOCK,
+	MBOX_DOTLOCK_OP_UNLOCK,
+	MBOX_DOTLOCK_OP_TOUCH
+};
+
 struct mbox_lock_context {
 	struct mbox_mailbox *mbox;
 	int lock_status[MBOX_LOCK_COUNT];
@@ -43,6 +50,7 @@
 
 	int lock_type;
 	bool dotlock_last_stale;
+	bool using_privileges;
 };
 
 struct mbox_lock_data {
@@ -190,6 +198,9 @@
 	enum mbox_lock_type *lock_types;
 	int i;
 
+	if (ctx->using_privileges)
+		restrict_access_drop_priv_gid();
+
 	if (stale && !ctx->dotlock_last_stale) {
 		/* get next index we wish to try locking. it's the one after
 		   dotlocking. */
@@ -221,9 +232,92 @@
 				  MAILBOX_LOCK_NOTIFY_MAILBOX_OVERRIDE :
 				  MAILBOX_LOCK_NOTIFY_MAILBOX_ABORT,
 				  secs_left);
+	if (ctx->using_privileges) {
+		if (restrict_access_use_priv_gid() < 0) {
+			/* shouldn't get here */
+			return FALSE;
+		}
+	}
 	return TRUE;
 }
 
+static int mbox_dotlock_privileged_op(struct mbox_mailbox *mbox,
+				      struct dotlock_settings *set,
+				      enum mbox_dotlock_op op)
+{
+	const char *dir, *fname;
+	int ret = -1, orig_dir_fd;
+
+	orig_dir_fd = open(".", O_RDONLY);
+	if (orig_dir_fd == -1) {
+		i_error("open(.) failed: %m");
+		return -1;
+	}
+
+	/* allow dotlocks to be created only for files we can read while we're
+	   unprivileged. to make sure there are no race conditions we first
+	   have to chdir to the mbox file's directory and then use relative
+	   paths. unless this is done, users could:
+	    - create *.lock files to any directory writable by the
+	      privileged group
+	    - DoS other users by dotlocking their mailboxes infinitely
+	*/
+	fname = strrchr(mbox->path, '/');
+	if (fname == NULL) {
+		/* already relative */
+		fname = mbox->path;
+	} else {
+		dir = t_strdup_until(mbox->path, fname);
+		if (chdir(dir) < 0) {
+			i_error("chdir(%s) failed: %m", dir);
+			(void)close(orig_dir_fd);
+			return -1;
+		}
+		fname++;
+	}
+	if (op == MBOX_DOTLOCK_OP_LOCK) {
+		if (access(fname, R_OK) < 0) {
+			i_error("access(%s) failed: %m", mbox->path);
+			return -1;
+		}
+	}
+
+	if (restrict_access_use_priv_gid() < 0) {
+		(void)close(orig_dir_fd);
+		return -1;
+	}
+
+	switch (op) {
+	case MBOX_DOTLOCK_OP_LOCK:
+		/* we're now privileged - avoid doing as much as possible */
+		ret = file_dotlock_create(set, fname, 0, &mbox->mbox_dotlock);
+		if (ret > 0)
+			mbox->mbox_used_privileges = TRUE;
+		break;
+	case MBOX_DOTLOCK_OP_UNLOCK:
+		/* we're now privileged - avoid doing as much as possible */
+		ret = file_dotlock_delete(&mbox->mbox_dotlock);
+		mbox->mbox_used_privileges = FALSE;
+		break;
+	case MBOX_DOTLOCK_OP_TOUCH:
+		if (!file_dotlock_is_locked(mbox->mbox_dotlock)) {
+			file_dotlock_delete(&mbox->mbox_dotlock);
+			mbox->mbox_used_privileges = TRUE;
+			ret = -1;
+		} else {
+			ret = file_dotlock_touch(mbox->mbox_dotlock);
+		}
+		break;
+	}
+
+	restrict_access_drop_priv_gid();
+
+	if (fchdir(orig_dir_fd) < 0)
+		i_error("fchdir() failed: %m");
+	(void)close(orig_dir_fd);
+	return ret;
+}
+
 static int mbox_lock_dotlock(struct mbox_lock_context *ctx, int lock_type,
 			     time_t max_wait_time __attr_unused__)
 {
@@ -235,7 +329,15 @@
 		if (!mbox->mbox_dotlocked)
 			return 1;
 
-		if (file_dotlock_delete(&mbox->mbox_dotlock) <= 0) {
+		if (!mbox->mbox_used_privileges)
+			ret = file_dotlock_delete(&mbox->mbox_dotlock);
+		else {
+			ctx->using_privileges = TRUE;
+			ret = mbox_dotlock_privileged_op(mbox, NULL,
+							MBOX_DOTLOCK_OP_UNLOCK);
+			ctx->using_privileges = FALSE;
+		}
+		if (ret <= 0) {
 			mbox_set_syscall_error(mbox, "file_dotlock_delete()");
 			ret = -1;
 		}
@@ -257,6 +359,13 @@
 	set.context = ctx;
 
 	ret = file_dotlock_create(&set, mbox->path, 0, &mbox->mbox_dotlock);
+	if (ret < 0 && errno == EACCES && restrict_access_have_priv_gid() &&
+	    mbox->mbox_privileged_locking) {
+		/* try again, this time with extra privileges */
+		ret = mbox_dotlock_privileged_op(mbox, &set,
+						 MBOX_DOTLOCK_OP_LOCK);
+	}
+
 	if (ret < 0) {
 		mbox_set_syscall_error(mbox, "file_lock_dotlock()");
 		return -1;
@@ -601,3 +710,16 @@
 
 	return mbox_unlock_files(&ctx);
 }
+
+void mbox_dotlock_touch(struct mbox_mailbox *mbox)
+{
+	if (mbox->mbox_dotlock == NULL)
+		return;
+
+	if (!mbox->mbox_used_privileges)
+		(void)file_dotlock_touch(mbox->mbox_dotlock);
+	else {
+		(void)mbox_dotlock_privileged_op(mbox, NULL,
+						 MBOX_DOTLOCK_OP_TOUCH);
+	}
+}
diff -ru dovecot-1.0.10/src/lib-storage/index/mbox/mbox-lock.h dovecot-1.0.10-fixed/src/lib-storage/index/mbox/mbox-lock.h
--- dovecot-1.0.10/src/lib-storage/index/mbox/mbox-lock.h	2007-12-11 20:52:08.000000000 +0200
+++ dovecot-1.0.10-fixed/src/lib-storage/index/mbox/mbox-lock.h	2008-03-04 08:13:48.000000000 +0200
@@ -7,4 +7,6 @@
 	      unsigned int *lock_id_r);
 int mbox_unlock(struct mbox_mailbox *mbox, unsigned int lock_id);
 
+void mbox_dotlock_touch(struct mbox_mailbox *mbox);
+
 #endif
diff -ru dovecot-1.0.10/src/lib-storage/index/mbox/mbox-storage.c dovecot-1.0.10-fixed/src/lib-storage/index/mbox/mbox-storage.c
--- dovecot-1.0.10/src/lib-storage/index/mbox/mbox-storage.c	2007-12-11 20:52:08.000000000 +0200
+++ dovecot-1.0.10-fixed/src/lib-storage/index/mbox/mbox-storage.c	2008-03-04 08:13:48.000000000 +0200
@@ -443,6 +443,13 @@
 	return TRUE;
 }
 
+static bool mbox_name_is_dotlock(const char *name)
+{
+	unsigned int len = strlen(name);
+
+	return len >= 5 && strcmp(name + len - 5, ".lock") == 0;
+}
+
 static bool mbox_is_valid_create_name(struct mail_storage *storage,
 				      const char *name)
 {
@@ -458,7 +465,7 @@
 		    return FALSE;
 	}
 
-	return mbox_is_valid_mask(storage, name);
+	return mbox_is_valid_mask(storage, name) && !mbox_name_is_dotlock(name);
 }
 
 static bool mbox_is_valid_existing_name(struct mail_storage *storage,
@@ -470,7 +477,7 @@
 	if (name[0] == '\0' || name[len-1] == '/')
 		return FALSE;
 
-	return mbox_is_valid_mask(storage, name);
+	return mbox_is_valid_mask(storage, name) && !mbox_name_is_dotlock(name);
 }
 
 static const char *mbox_get_index_dir(struct index_storage *storage,
@@ -597,7 +604,7 @@
 {
 	struct mbox_mailbox *mbox = context;
 
-	(void)file_dotlock_touch(mbox->mbox_dotlock);
+	mbox_dotlock_touch(mbox);
 }
 
 static struct mbox_mailbox *
@@ -697,6 +704,12 @@
 		}
 	}
 
+	if (strcmp(name, "INBOX") == 0) {
+		/* if INBOX isn't under the root directory, it's probably in
+		   /var/mail and we want to allow privileged dotlocking */
+		if (strncmp(path, istorage->dir, strlen(istorage->dir)) != 0)
+			mbox->mbox_privileged_locking = TRUE;
+	}
 	return &mbox->ibox.box;
 }
 
diff -ru dovecot-1.0.10/src/lib-storage/index/mbox/mbox-storage.h dovecot-1.0.10-fixed/src/lib-storage/index/mbox/mbox-storage.h
--- dovecot-1.0.10/src/lib-storage/index/mbox/mbox-storage.h	2007-12-11 20:52:08.000000000 +0200
+++ dovecot-1.0.10-fixed/src/lib-storage/index/mbox/mbox-storage.h	2008-03-04 08:13:48.000000000 +0200
@@ -48,6 +48,8 @@
 	unsigned int mbox_very_dirty_syncs:1;
 	unsigned int mbox_save_md5:1;
 	unsigned int mbox_dotlocked:1;
+	unsigned int mbox_used_privileges:1;
+	unsigned int mbox_privileged_locking:1;
 };
 
 struct mbox_transaction_context {
diff -ru dovecot-1.0.10/src/master/auth-process.c dovecot-1.0.10-fixed/src/master/auth-process.c
--- dovecot-1.0.10/src/master/auth-process.c	2007-12-11 20:52:09.000000000 +0200
+++ dovecot-1.0.10-fixed/src/master/auth-process.c	2008-03-04 08:13:48.000000000 +0200
@@ -413,8 +413,8 @@
 	int i;
 
 	/* setup access environment */
-	restrict_access_set_env(set->user, set->uid, set->gid, set->chroot,
-				0, 0, NULL);
+	restrict_access_set_env(set->user, set->uid, set->gid,
+				(gid_t)-1, set->chroot, 0, 0, NULL);
 
 	/* set other environment */
 	env_put("DOVECOT_MASTER=1");
diff -ru dovecot-1.0.10/src/master/login-process.c dovecot-1.0.10-fixed/src/master/login-process.c
--- dovecot-1.0.10/src/master/login-process.c	2007-12-11 20:52:09.000000000 +0200
+++ dovecot-1.0.10-fixed/src/master/login-process.c	2008-03-04 08:13:48.000000000 +0200
@@ -519,7 +519,7 @@
 	   parameter since we don't want to call initgroups() for login
 	   processes. */
 	restrict_access_set_env(NULL, set->login_uid,
-				set->server->login_gid,
+				set->server->login_gid, (gid_t)-1,
 				set->login_chroot ? set->login_dir : NULL,
 				0, 0, NULL);
 
diff -ru dovecot-1.0.10/src/master/mail-process.c dovecot-1.0.10-fixed/src/master/mail-process.c
--- dovecot-1.0.10/src/master/mail-process.c	2007-12-20 22:51:23.000000000 +0200
+++ dovecot-1.0.10-fixed/src/master/mail-process.c	2008-03-04 08:13:48.000000000 +0200
@@ -589,9 +589,10 @@
 
 	/* setup environment - set the most important environment first
 	   (paranoia about filling up environment without noticing) */
-	restrict_access_set_env(system_user, uid, gid, chroot_dir,
+	restrict_access_set_env(system_user, uid, gid, set->mail_priv_gid_t,
+				chroot_dir,
 				set->first_valid_gid, set->last_valid_gid,
-				set->mail_extra_groups);
+				set->mail_access_groups);
 
 	restrict_process_size(set->mail_process_size, (unsigned int)-1);
 
@@ -699,8 +700,13 @@
 	   any errors above will be logged */
 	closelog();
 
-	if (set->mail_drop_priv_before_exec)
+	if (set->mail_drop_priv_before_exec) {
 		restrict_access_by_env(TRUE);
+		/* privileged GID is now only in saved-GID. if we want to
+		   preserve it accross exec, it needs to be temporarily
+		   in effective gid */
+		restrict_access_use_priv_gid();
+	}
 
 	client_process_exec(set->mail_executable, title);
 	i_fatal_status(FATAL_EXEC, "execv(%s) failed: %m",
diff -ru dovecot-1.0.10/src/master/master-settings.c dovecot-1.0.10-fixed/src/master/master-settings.c
--- dovecot-1.0.10/src/master/master-settings.c	2007-12-21 17:10:24.000000000 +0200
+++ dovecot-1.0.10-fixed/src/master/master-settings.c	2008-03-04 08:13:48.000000000 +0200
@@ -21,6 +21,7 @@
 #include <sys/stat.h>
 #include <sys/wait.h>
 #include <pwd.h>
+#include <grp.h>
 
 enum settings_type {
 	SETTINGS_TYPE_ROOT,
@@ -207,6 +208,8 @@
 	MEMBER(first_valid_gid) 1,
 	MEMBER(last_valid_gid) 0,
 	MEMBER(mail_extra_groups) "",
+	MEMBER(mail_access_groups) "",
+	MEMBER(mail_privileged_group) "",
 
 	MEMBER(default_mail_env) "",
 	MEMBER(mail_location) "",
@@ -365,6 +368,25 @@
 	return TRUE;
 }
 
+static bool parse_gid(const char *str, gid_t *gid_r)
+{
+	struct group *gr;
+	char *p;
+
+	if (*str >= '0' && *str <= '9') {
+		*gid_r = (gid_t)strtoul(str, &p, 10);
+		if (*p == '\0')
+			return TRUE;
+	}
+
+	gr = getgrnam(str);
+	if (gr == NULL)
+		return FALSE;
+
+	*gid_r = gr->gr_gid;
+	return TRUE;
+}
+
 static bool auth_settings_verify(struct auth_settings *auth)
 {
 	struct passwd *pw;
@@ -628,9 +650,35 @@
 	const char *dir;
 	int facility;
 
+	set->mail_priv_gid_t = (gid_t)-1;
+
 	if (!get_login_uid(set))
 		return FALSE;
 
+	if (*set->mail_privileged_group != '\0') {
+		if (!parse_gid(set->mail_privileged_group,
+			       &set->mail_priv_gid_t)) {
+			i_error("Non-existing mail_privileged_group: %s",
+				set->mail_privileged_group);
+			return FALSE;
+		}
+	}
+	if (*set->mail_extra_groups != '\0') {
+		if (*set->mail_access_groups != '\0') {
+			i_error("Can't set both mail_extra_groups "
+				"and mail_access_groups");
+			return FALSE;
+		}
+		if (!set->server->warned_mail_extra_groups) {
+			set->server->warned_mail_extra_groups = TRUE;
+			i_warning("mail_extra_groups setting was often used "
+				  "insecurely so it is now deprecated, "
+				  "use mail_access_groups or "
+				  "mail_privileged_group instead");
+		}
+		set->mail_access_groups = set->mail_extra_groups;
+	}
+
 	if (set->protocol == MAIL_PROTOCOL_POP3 &&
 	    *set->pop3_uidl_format == '\0') {
 		i_error("POP3 enabled but pop3_uidl_format not set");
diff -ru dovecot-1.0.10/src/master/master-settings-defs.c dovecot-1.0.10-fixed/src/master/master-settings-defs.c
--- dovecot-1.0.10/src/master/master-settings-defs.c	2007-12-11 20:52:09.000000000 +0200
+++ dovecot-1.0.10-fixed/src/master/master-settings-defs.c	2008-03-04 08:13:48.000000000 +0200
@@ -58,6 +58,8 @@
 	DEF(SET_INT, first_valid_gid),
 	DEF(SET_INT, last_valid_gid),
 	DEF(SET_STR, mail_extra_groups),
+	DEF(SET_STR, mail_access_groups),
+	DEF(SET_STR, mail_privileged_group),
 
 	DEF(SET_STR, default_mail_env),
 	DEF(SET_STR, mail_location),
diff -ru dovecot-1.0.10/src/master/master-settings.h dovecot-1.0.10-fixed/src/master/master-settings.h
--- dovecot-1.0.10/src/master/master-settings.h	2007-12-11 20:52:09.000000000 +0200
+++ dovecot-1.0.10-fixed/src/master/master-settings.h	2008-03-04 08:13:48.000000000 +0200
@@ -66,6 +66,8 @@
 	unsigned int first_valid_uid, last_valid_uid;
 	unsigned int first_valid_gid, last_valid_gid;
 	const char *mail_extra_groups;
+	const char *mail_access_groups;
+	const char *mail_privileged_group;
 
 	const char *default_mail_env;
 	const char *mail_location;
@@ -125,6 +127,7 @@
 	int listen_fd, ssl_listen_fd;
 
 	uid_t login_uid;
+	gid_t mail_priv_gid_t;
 
 	struct ip_addr listen_ip, ssl_listen_ip;
 	unsigned int listen_port, ssl_listen_port;
@@ -235,6 +238,7 @@
 	array_t ARRAY_DEFINE(dicts, const char *);
 
 	gid_t login_gid;
+	unsigned int warned_mail_extra_groups:1;
 };
 
 extern struct server_settings *settings_root;
