changeset 9537:b373de4973cd HEAD

Whenever file's group changing fails, show the group origin in the error message.
author Timo Sirainen <tss@iki.fi>
date Sat, 27 Jun 2009 20:39:38 -0400
parents 5a413a1beb60
children b20513ab8f5a
files src/lda/main.c src/lib-index/mail-index-private.h src/lib-index/mail-index-strmap.c src/lib-index/mail-index.c src/lib-index/mail-index.h src/lib-storage/index/cydir/cydir-storage.c src/lib-storage/index/dbox/dbox-file.c src/lib-storage/index/dbox/dbox-map.c src/lib-storage/index/dbox/dbox-storage.c src/lib-storage/index/dbox/dbox-storage.h src/lib-storage/index/index-storage.c src/lib-storage/index/maildir/maildir-keywords.c src/lib-storage/index/maildir/maildir-save.c src/lib-storage/index/maildir/maildir-storage.c src/lib-storage/index/maildir/maildir-uidlist.c src/lib-storage/index/maildir/maildir-util.c src/lib-storage/index/mbox/mbox-storage.c src/lib-storage/list/mailbox-list-fs.c src/lib-storage/list/subscription-file.c src/lib-storage/mail-storage-private.h src/lib-storage/mail-storage.c src/lib-storage/mailbox-list-private.h src/lib-storage/mailbox-list.c src/lib-storage/mailbox-list.h src/lib/eacces-error.c src/lib/eacces-error.h src/lib/file-dotlock.c src/lib/file-dotlock.h src/lib/mkdir-parents.c src/lib/mkdir-parents.h src/lib/safe-mkstemp.c src/lib/safe-mkstemp.h src/plugins/acl/acl-backend-vfile-acllist.c src/plugins/acl/acl-backend-vfile.c src/plugins/lazy-expunge/lazy-expunge-plugin.c src/plugins/quota/quota-maildir.c
diffstat 36 files changed, 410 insertions(+), 148 deletions(-) [+]
line wrap: on
line diff
--- a/src/lda/main.c	Sat Jun 27 19:55:44 2009 -0400
+++ b/src/lda/main.c	Sat Jun 27 20:39:38 2009 -0400
@@ -90,6 +90,7 @@
 static int deliver_create_dir(struct mail_user *user, const char *dir)
 {
 	struct mail_namespace *ns;
+	const char *origin;
 	mode_t mode;
 	gid_t gid;
 
@@ -97,8 +98,8 @@
 	if (ns == NULL)
 		ns = user->namespaces;
 
-	mailbox_list_get_dir_permissions(ns->list, NULL, &mode, &gid);
-	if (mkdir_parents_chown(dir, mode, (uid_t)-1, gid) == 0) {
+	mailbox_list_get_dir_permissions(ns->list, NULL, &mode, &gid, &origin);
+	if (mkdir_parents_chgrp(dir, mode, gid, origin) == 0) {
 		return 0;
 	} else if (errno == EACCES) {
 		i_error("%s", eacces_error_get_creating("mkdir_parents_chown",
--- a/src/lib-index/mail-index-private.h	Sat Jun 27 19:55:44 2009 -0400
+++ b/src/lib-index/mail-index-private.h	Sat Jun 27 20:39:38 2009 -0400
@@ -174,6 +174,7 @@
 	enum mail_index_sync_type fsync_mask;
 	mode_t mode;
 	gid_t gid;
+	char *gid_origin;
 
 	pool_t extension_pool;
 	ARRAY_DEFINE(extensions, struct mail_index_registered_ext);
--- a/src/lib-index/mail-index-strmap.c	Sat Jun 27 19:55:44 2009 -0400
+++ b/src/lib-index/mail-index-strmap.c	Sat Jun 27 20:39:38 2009 -0400
@@ -998,8 +998,9 @@
 
 	str = t_str_new(256);
 	str_append(str, strmap->path);
-	fd = safe_mkstemp_hostpid(str, view->view->index->mode,
-				  (uid_t)-1, view->view->index->gid);
+	fd = safe_mkstemp_hostpid_group(str, view->view->index->mode,
+					view->view->index->gid,
+					view->view->index->gid_origin);
 	temp_path = str_c(str);
 
 	if (fd == -1) {
--- a/src/lib-index/mail-index.c	Sat Jun 27 19:55:44 2009 -0400
+++ b/src/lib-index/mail-index.c	Sat Jun 27 20:39:38 2009 -0400
@@ -73,6 +73,7 @@
 	array_free(&index->keywords);
 	array_free(&index->module_contexts);
 
+	i_free(index->gid_origin);
 	i_free(index->error);
 	i_free(index->dir);
 	i_free(index->prefix);
@@ -86,10 +87,13 @@
 }
 
 void mail_index_set_permissions(struct mail_index *index,
-				mode_t mode, gid_t gid)
+				mode_t mode, gid_t gid, const char *gid_origin)
 {
 	index->mode = mode & 0666;
 	index->gid = gid;
+
+	i_free(index->gid_origin);
+	index->gid_origin = i_strdup(gid_origin);
 }
 
 uint32_t mail_index_ext_register(struct mail_index *index, const char *name,
@@ -662,7 +666,13 @@
 		   really matter. ignore silently. */
 		return;
 	}
-	mail_index_file_set_syscall_error(index, path, "fchown()");
+	if (errno != EACCES)
+		mail_index_file_set_syscall_error(index, path, "fchown()");
+	else {
+		mail_index_set_error(index, "%s",
+			eperm_error_get_chgrp("fchown", path, index->gid,
+					      index->gid_origin));
+	}
 
 	/* continue, but change permissions so that only the common
 	   subset of group and world is used. this makes sure no one
--- a/src/lib-index/mail-index.h	Sat Jun 27 19:55:44 2009 -0400
+++ b/src/lib-index/mail-index.h	Sat Jun 27 20:39:38 2009 -0400
@@ -195,7 +195,7 @@
 void mail_index_set_fsync_types(struct mail_index *index,
 				enum mail_index_sync_type fsync_mask);
 void mail_index_set_permissions(struct mail_index *index,
-				mode_t mode, gid_t gid);
+				mode_t mode, gid_t gid, const char *gid_origin);
 
 /* Open index. Returns 1 if ok, 0 if index doesn't exist and CREATE flags
    wasn't given, -1 if error. */
--- a/src/lib-storage/index/cydir/cydir-storage.c	Sat Jun 27 19:55:44 2009 -0400
+++ b/src/lib-storage/index/cydir/cydir-storage.c	Sat Jun 27 20:39:38 2009 -0400
@@ -51,11 +51,12 @@
 static int create_cydir(struct mail_storage *storage, struct mailbox_list *list,
 			const char *path)
 {
+	const char *origin;
 	mode_t mode;
 	gid_t gid;
 
-	mailbox_list_get_dir_permissions(list, NULL, &mode, &gid);
-	if (mkdir_parents_chown(path, mode, (uid_t)-1, gid) < 0 &&
+	mailbox_list_get_dir_permissions(list, NULL, &mode, &gid, &origin);
+	if (mkdir_parents_chgrp(path, mode, gid, origin) < 0 &&
 	    errno != EEXIST) {
 		if (!mail_storage_set_error_from_errno(storage)) {
 			mail_storage_set_critical(storage,
--- a/src/lib-storage/index/dbox/dbox-file.c	Sat Jun 27 19:55:44 2009 -0400
+++ b/src/lib-storage/index/dbox/dbox-file.c	Sat Jun 27 20:39:38 2009 -0400
@@ -10,6 +10,7 @@
 #include "file-lock.h"
 #include "mkdir-parents.h"
 #include "fdatasync-path.h"
+#include "eacces-error.h"
 #include "str.h"
 #include "dbox-storage.h"
 #include "dbox-file.h"
@@ -441,13 +442,20 @@
 	if (fd == -1) {
 		mail_storage_set_critical(&storage->storage,
 			"open(%s, O_CREAT) failed: %m", path);
-	} else if (storage->create_gid != (gid_t)-1) {
-		if (fchown(fd, (uid_t)-1, storage->create_gid) < 0) {
+	} else if (storage->create_gid == (gid_t)-1) {
+		/* no group change */
+	} else if (fchown(fd, (uid_t)-1, storage->create_gid) < 0) {
+		if (errno == EPERM) {
+			mail_storage_set_critical(&storage->storage, "%s",
+				eperm_error_get_chgrp("fchown", path,
+					storage->create_gid,
+					storage->create_gid_origin));
+		} else {
 			mail_storage_set_critical(&storage->storage,
 				"fchown(%s, -1, %ld) failed: %m",
 				path, (long)storage->create_gid);
-			/* continue anyway */
 		}
+		/* continue anyway */
 	}
 	return fd;
 }
--- a/src/lib-storage/index/dbox/dbox-map.c	Sat Jun 27 19:55:44 2009 -0400
+++ b/src/lib-storage/index/dbox/dbox-map.c	Sat Jun 27 20:39:38 2009 -0400
@@ -69,8 +69,9 @@
 
 static int dbox_map_mkdir_storage(struct dbox_storage *storage)
 {
-	if (mkdir_parents_chown(storage->storage_dir, storage->create_mode,
-				(uid_t)-1, storage->create_gid) < 0 &&
+	if (mkdir_parents_chgrp(storage->storage_dir, storage->create_mode,
+				storage->create_gid,
+				storage->create_gid_origin) < 0 &&
 	    errno != EEXIST) {
 		mail_storage_set_critical(&storage->storage,
 			"mkdir(%s) failed: %m", storage->storage_dir);
--- a/src/lib-storage/index/dbox/dbox-storage.c	Sat Jun 27 19:55:44 2009 -0400
+++ b/src/lib-storage/index/dbox/dbox-storage.c	Sat Jun 27 20:39:38 2009 -0400
@@ -56,7 +56,7 @@
 		    const char **error_r)
 {
 	struct dbox_storage *storage = (struct dbox_storage *)_storage;
-	const char *dir;
+	const char *dir, *origin;
 
 	storage->set = mail_storage_get_driver_settings(_storage);
 	i_assert(storage->set->dbox_max_open_files >= 2);
@@ -80,7 +80,8 @@
 
 	storage->map = dbox_map_init(storage);
 	mailbox_list_get_dir_permissions(ns->list, NULL, &storage->create_mode,
-					 &storage->create_gid);
+					 &storage->create_gid, &origin);
+	storage->create_gid_origin = p_strdup(_storage->pool, origin);
 	return 0;
 }
 
@@ -282,12 +283,13 @@
 				       const struct mailbox_update *update)
 {
 	struct dbox_mailbox *mbox = (struct dbox_mailbox *)box;
+	const char *origin;
 	mode_t mode;
 	gid_t gid;
 	int ret;
 
-	mailbox_list_get_dir_permissions(box->list, NULL, &mode, &gid);
-	if (mkdir_parents_chown(box->path, mode, (uid_t)-1, gid) == 0) {
+	mailbox_list_get_dir_permissions(box->list, NULL, &mode, &gid, &origin);
+	if (mkdir_parents_chgrp(box->path, mode, gid, origin) == 0) {
 		/* create indexes immediately with the dbox header */
 		if (index_storage_mailbox_open(box) < 0)
 			return -1;
@@ -404,7 +406,7 @@
 dbox_mailbox_create(struct mailbox *box, const struct mailbox_update *update,
 		    bool directory)
 {
-	const char *path, *alt_path;
+	const char *path, *alt_path, *origin;
 	struct stat st;
 
 	path = mailbox_list_get_path(box->list, box->name,
@@ -420,8 +422,9 @@
 		mode_t mode;
 		gid_t gid;
 
-		mailbox_list_get_dir_permissions(box->list, NULL, &mode, &gid);
-		if (mkdir_parents_chown(path, mode, (uid_t)-1, gid) == 0)
+		mailbox_list_get_dir_permissions(box->list, NULL, &mode,
+						 &gid, &origin);
+		if (mkdir_parents_chgrp(path, mode, gid, origin) == 0)
 			return 0;
 		else if (errno == EEXIST) {
 			mail_storage_set_error(box->storage, MAIL_ERROR_EXISTS,
@@ -560,11 +563,13 @@
 	if (ret < 0 && errno == ENOENT) {
 		/* either source mailbox doesn't exist or trash directory
 		   doesn't exist. try creating the trash and retrying. */
+		const char *origin;
 		mode_t mode;
 		gid_t gid;
 
-		mailbox_list_get_dir_permissions(list, NULL, &mode, &gid);
-		if (mkdir_parents_chown(trash_dir, mode, (uid_t)-1, gid) < 0 &&
+		mailbox_list_get_dir_permissions(list, NULL, &mode,
+						 &gid, &origin);
+		if (mkdir_parents_chgrp(trash_dir, mode, gid, origin) < 0 &&
 		    errno != EEXIST) {
 			mailbox_list_set_critical(list,
 				"mkdir(%s) failed: %m", trash_dir);
--- a/src/lib-storage/index/dbox/dbox-storage.h	Sat Jun 27 19:55:44 2009 -0400
+++ b/src/lib-storage/index/dbox/dbox-storage.h	Sat Jun 27 20:39:38 2009 -0400
@@ -51,6 +51,7 @@
 	/* mode/gid to use for new dbox storage files */
 	mode_t create_mode;
 	gid_t create_gid;
+	const char *create_gid_origin;
 
 	ARRAY_DEFINE(open_files, struct dbox_file *);
 
--- a/src/lib-storage/index/index-storage.c	Sat Jun 27 19:55:44 2009 -0400
+++ b/src/lib-storage/index/index-storage.c	Sat Jun 27 20:39:38 2009 -0400
@@ -73,6 +73,7 @@
 static int create_missing_index_dir(struct mailbox *box)
 {
 	const char *root_dir, *index_dir, *p, *parent_dir;
+	const char *origin, *parent_origin;
 	mode_t mode, parent_mode;
 	gid_t gid, parent_gid;
 	int n = 0;
@@ -84,8 +85,9 @@
 	if (strcmp(index_dir, root_dir) == 0 || *index_dir == '\0')
 		return 0;
 
-	mailbox_list_get_dir_permissions(box->list, box->name, &mode, &gid);
-	while (mkdir_chown(index_dir, mode, (uid_t)-1, gid) < 0) {
+	mailbox_list_get_dir_permissions(box->list, box->name, &mode,
+					 &gid, &origin);
+	while (mkdir_chgrp(index_dir, mode, gid, origin) < 0) {
 		if (errno == EEXIST)
 			break;
 
@@ -97,10 +99,11 @@
 		}
 		/* create the parent directory first */
 		mailbox_list_get_dir_permissions(box->list, NULL,
-						 &parent_mode, &parent_gid);
+						 &parent_mode, &parent_gid,
+						 &parent_origin);
 		parent_dir = t_strdup_until(index_dir, p);
-		if (mkdir_parents_chown(parent_dir, parent_mode,
-					(uid_t)-1, parent_gid) < 0 &&
+		if (mkdir_parents_chgrp(parent_dir, parent_mode,
+					parent_gid, parent_origin) < 0 &&
 		    errno != EEXIST) {
 			mail_storage_set_critical(box->storage,
 				"mkdir(%s) failed: %m", parent_dir);
@@ -449,6 +452,7 @@
 	struct mailbox *box = &ibox->box;
 	const char *path;
 	gid_t dir_gid;
+	const char *origin, *dir_origin;
 
 	if (name != NULL)
 		box->name = p_strdup(box->pool, name);
@@ -484,13 +488,14 @@
 	if (box->file_create_mode == 0) {
 		mailbox_list_get_permissions(box->list, name,
 					     &box->file_create_mode,
-					     &box->file_create_gid);
+					     &box->file_create_gid, &origin);
+		box->file_create_gid_origin = p_strdup(box->pool, origin);
 		mailbox_list_get_dir_permissions(box->list, name,
 						 &box->dir_create_mode,
-						 &dir_gid);
+						 &dir_gid, &dir_origin);
 		mail_index_set_permissions(ibox->index,
 					   box->file_create_mode,
-					   box->file_create_gid);
+					   box->file_create_gid, origin);
 	}
 }
 
--- a/src/lib-storage/index/maildir/maildir-keywords.c	Sat Jun 27 19:55:44 2009 -0400
+++ b/src/lib-storage/index/maildir/maildir-keywords.c	Sat Jun 27 20:39:38 2009 -0400
@@ -10,6 +10,7 @@
 #include "hash.h"
 #include "str.h"
 #include "istream.h"
+#include "eacces-error.h"
 #include "file-dotlock.h"
 #include "write-full.h"
 #include "nfs-workarounds.h"
@@ -285,6 +286,7 @@
 				     const char *path, int fd)
 {
 	struct maildir_mailbox *mbox = mk->mbox;
+	struct mailbox *box = &mbox->ibox.box;
 	const char *const *keywords;
 	unsigned int i, count;
 	string_t *str;
@@ -308,11 +310,18 @@
 		return -1;
 	}
 
-	if (st.st_gid != mbox->ibox.box.file_create_gid &&
-	    mbox->ibox.box.file_create_gid != (gid_t)-1) {
-		if (fchown(fd, (uid_t)-1, mbox->ibox.box.file_create_gid) < 0) {
-			mail_storage_set_critical(mk->storage,
-				"fchown(%s) failed: %m", path);
+	if (st.st_gid != box->file_create_gid &&
+	    box->file_create_gid != (gid_t)-1) {
+		if (fchown(fd, (uid_t)-1, box->file_create_gid) < 0) {
+			if (errno == EPERM) {
+				mail_storage_set_critical(mk->storage, "%s",
+					eperm_error_get_chgrp("fchown", path,
+						box->file_create_gid,
+						box->file_create_gid_origin));
+			} else {
+				mail_storage_set_critical(mk->storage,
+					"fchown(%s) failed: %m", path);
+			}
 		}
 	}
 
--- a/src/lib-storage/index/maildir/maildir-save.c	Sat Jun 27 19:55:44 2009 -0400
+++ b/src/lib-storage/index/maildir/maildir-save.c	Sat Jun 27 20:39:38 2009 -0400
@@ -8,6 +8,7 @@
 #include "istream-crlf.h"
 #include "ostream.h"
 #include "fdatasync-path.h"
+#include "eacces-error.h"
 #include "str.h"
 #include "index-mail.h"
 #include "maildir-storage.h"
@@ -340,8 +341,16 @@
 		}
 	} else if (box->file_create_gid != (gid_t)-1) {
 		if (fchown(fd, (uid_t)-1, box->file_create_gid) < 0) {
-			mail_storage_set_critical(box->storage,
-				"fchown(%s) failed: %m", str_c(path));
+			if (errno == EPERM) {
+				mail_storage_set_critical(box->storage, "%s",
+					eperm_error_get_chgrp("fchown",
+						str_c(path),
+						box->file_create_gid,
+						box->file_create_gid_origin));
+			} else {
+				mail_storage_set_critical(box->storage,
+					"fchown(%s) failed: %m", str_c(path));
+			}
 		}
 	}
 
--- a/src/lib-storage/index/maildir/maildir-storage.c	Sat Jun 27 19:55:44 2009 -0400
+++ b/src/lib-storage/index/maildir/maildir-storage.c	Sat Jun 27 20:39:38 2009 -0400
@@ -6,6 +6,7 @@
 #include "hostpid.h"
 #include "str.h"
 #include "mkdir-parents.h"
+#include "eacces-error.h"
 #include "unlink-directory.h"
 #include "unlink-old-files.h"
 #include "mailbox-uidvalidity.h"
@@ -213,8 +214,10 @@
 	return TRUE;
 }
 
-static int mkdir_verify(struct mail_storage *storage, struct mail_namespace *ns,
-			const char *dir, mode_t mode, gid_t gid, bool verify)
+static int
+mkdir_verify(struct mail_storage *storage, struct mail_namespace *ns,
+	     const char *dir, mode_t mode, gid_t gid, const char *gid_origin,
+	     bool verify)
 {
 	struct stat st;
 
@@ -229,7 +232,7 @@
 		}
 	}
 
-	if (mkdir_parents_chown(dir, mode, (uid_t)-1, gid) == 0)
+	if (mkdir_parents_chgrp(dir, mode, gid, gid_origin) == 0)
 		return 0;
 
 	if (errno == EEXIST) {
@@ -289,7 +292,8 @@
 /* create or fix maildir, ignore if it already exists */
 static int
 create_maildir(struct mail_storage *storage, struct mail_namespace *ns,
-	       const char *dir, mode_t mode, gid_t gid, bool verify)
+	       const char *dir, mode_t mode, gid_t gid, const char *gid_origin,
+	       bool verify)
 {
 	const char *path;
 	unsigned int i;
@@ -308,7 +312,8 @@
 
 	for (i = 0; i < N_ELEMENTS(maildir_subdirs); i++) {
 		path = t_strconcat(dir, "/", maildir_subdirs[i], NULL);
-		if (mkdir_verify(storage, ns, path, mode, gid, verify) < 0)
+		if (mkdir_verify(storage, ns, path, mode, gid,
+				 gid_origin, verify) < 0)
 			return -1;
 	}
 	return 0;
@@ -360,21 +365,26 @@
 {
 	struct maildir_mailbox *mbox = (struct maildir_mailbox *)box;
 	struct stat st;
+	const char *shared_path;
 
 	/* for shared mailboxes get the create mode from the
 	   permissions of dovecot-shared file. */
-	if (stat(t_strconcat(box->path, "/dovecot-shared", NULL), &st) == 0) {
+	shared_path = t_strconcat(box->path, "/dovecot-shared", NULL);
+	if (stat(shared_path, &st) == 0) {
 		if ((st.st_mode & S_ISGID) != 0 ||
 		    (st.st_mode & 0060) == 0) {
 			/* Ignore GID */
 			st.st_gid = (gid_t)-1;
 		}
 		mail_index_set_permissions(mbox->ibox.index,
-					   st.st_mode & 0666, st.st_gid);
+					   st.st_mode & 0666, st.st_gid,
+					   shared_path);
 
 		box->file_create_mode = st.st_mode & 0666;
 		box->dir_create_mode = get_dir_mode(st.st_mode & 0666);
 		box->file_create_gid = st.st_gid;
+		mbox->ibox.box.file_create_gid_origin =
+			p_strdup(box->pool, shared_path);
 		box->private_flags_mask = MAIL_SEEN;
 	}
 
@@ -395,6 +405,7 @@
 static int maildir_mailbox_open(struct mailbox *box)
 {
 	struct stat st;
+	const char *gid_origin;
 	mode_t mode;
 	gid_t gid;
 	int ret;
@@ -423,9 +434,9 @@
 	if (inbox || stat(box->path, &st) == 0) {
 		/* yes, we'll need to create the missing dirs */
 		mailbox_list_get_dir_permissions(box->list, box->name,
-						 &mode, &gid);
+						 &mode, &gid, &gid_origin);
 		if (create_maildir(box->storage, box->list->ns, box->path,
-				   mode, gid, TRUE) < 0)
+				   mode, gid, gid_origin, TRUE) < 0)
 			return -1;
 
 		return maildir_mailbox_open_existing(box);
@@ -442,7 +453,8 @@
 
 static int
 maildir_create_shared(struct mail_storage *storage, struct mail_namespace *ns,
-		      const char *dir, mode_t mode, gid_t gid)
+		      const char *dir, mode_t mode, gid_t gid,
+		      const char *gid_origin)
 {
 	const char *path;
 	mode_t old_mask;
@@ -453,7 +465,7 @@
 	if ((mode & 0060) != 0) mode |= 0010;
 	if ((mode & 0006) != 0) mode |= 0001;
 
-	if (create_maildir(storage, ns, dir, mode, gid, FALSE) < 0)
+	if (create_maildir(storage, ns, dir, mode, gid, gid_origin, FALSE) < 0)
 		return -1;
 
 	old_mask = umask(0777 ^ mode);
@@ -467,8 +479,14 @@
 	}
 
 	if (fchown(fd, (uid_t)-1, gid) < 0) {
-		mail_storage_set_critical(storage, "fchown(%s) failed: %m",
-					  path);
+		if (errno == EPERM) {
+			mail_storage_set_critical(storage, "%s",
+				eperm_error_get_chgrp("fchown", path,
+						      gid, gid_origin));
+		} else {
+			mail_storage_set_critical(storage,
+				"fchown(%s) failed: %m", path);
+		}
 	}
 	(void)close(fd);
 	return 0;
@@ -504,7 +522,7 @@
 		       bool directory)
 {
 	struct stat st;
-	const char *path, *root_dir, *shared_path;
+	const char *path, *root_dir, *shared_path, *gid_origin;
 	mode_t old_mask;
 	int fd;
 
@@ -517,14 +535,17 @@
 	   its permissions and gid, and copy the dovecot-shared inside it. */
 	shared_path = t_strconcat(root_dir, "/dovecot-shared", NULL);
 	if (stat(shared_path, &st) == 0) {
+		gid_origin = shared_path;
 		if (maildir_create_shared(box->storage, box->list->ns, path,
-					  st.st_mode & 0666, st.st_gid) < 0)
+					  st.st_mode & 0666, st.st_gid,
+					  gid_origin) < 0)
 			return -1;
 	} else {
-		mailbox_list_get_dir_permissions(box->list, NULL,
-						 &st.st_mode, &st.st_gid);
+		mailbox_list_get_dir_permissions(box->list, NULL, &st.st_mode,
+						 &st.st_gid, &gid_origin);
 		if (create_maildir(box->storage, box->list->ns, path,
-				   st.st_mode, st.st_gid, FALSE) < 0)
+				   st.st_mode, st.st_gid, gid_origin,
+				   FALSE) < 0)
 			return -1;
 	}
 
@@ -536,8 +557,15 @@
 	umask(old_mask);
 	if (fd != -1) {
 		/* if dovecot-shared exists, use the same group */
-		if (st.st_gid != (gid_t)-1 &&
-		    fchown(fd, (uid_t)-1, st.st_gid) < 0) {
+		if (st.st_gid == (gid_t)-1) {
+			/* doesn't exist */
+		} else if (fchown(fd, (uid_t)-1, st.st_gid) == 0) {
+			/* ok */
+		} else if (errno == EPERM) {
+			mail_storage_set_critical(box->storage, "%s",
+				eperm_error_get_chgrp("fchown", path,
+						     st.st_gid, gid_origin));
+		} else {
 			mail_storage_set_critical(box->storage,
 				"fchown(%s) failed: %m", path);
 		}
--- a/src/lib-storage/index/maildir/maildir-uidlist.c	Sat Jun 27 19:55:44 2009 -0400
+++ b/src/lib-storage/index/maildir/maildir-uidlist.c	Sat Jun 27 20:39:38 2009 -0400
@@ -1316,8 +1316,14 @@
 			return -1;
 	}
 
-	if (box->file_create_gid != (gid_t)-1) {
-		if (fchown(fd, (uid_t)-1, box->file_create_gid) < 0) {
+	if (box->file_create_gid != (gid_t)-1 &&
+	    fchown(fd, (uid_t)-1, box->file_create_gid) < 0) {
+		if (errno == EPERM) {
+			mail_storage_set_critical(box->storage, "%s",
+				eperm_error_get_chgrp("fchown", temp_path,
+						box->file_create_gid,
+						box->file_create_gid_origin));
+		} else {
 			mail_storage_set_critical(box->storage,
 				"fchown(%s) failed: %m", temp_path);
 		}
--- a/src/lib-storage/index/maildir/maildir-util.c	Sat Jun 27 19:55:44 2009 -0400
+++ b/src/lib-storage/index/maildir/maildir-util.c	Sat Jun 27 20:39:38 2009 -0400
@@ -133,12 +133,12 @@
 static int maildir_create_path(struct mailbox *box, const char *path,
 			       bool is_mail_dir)
 {
-	const char *p, *parent;
+	const char *p, *parent, *origin;
 	mode_t parent_mode;
 	gid_t parent_gid;
 
-	if (mkdir_chown(path, box->dir_create_mode,
-			(uid_t)-1, box->file_create_gid) == 0)
+	if (mkdir_chgrp(path, box->dir_create_mode, box->file_create_gid,
+			box->file_create_gid_origin) == 0)
 		return 0;
 
 	switch (errno) {
@@ -153,10 +153,11 @@
 		}
 		/* create index/control root directory */
 		parent = t_strdup_until(path, p);
-		mailbox_list_get_dir_permissions(box->list, NULL,
-						 &parent_mode, &parent_gid);
-		if (mkdir_parents_chown(parent, parent_mode, (uid_t)-1,
-					parent_gid) == 0 || errno == EEXIST) {
+		mailbox_list_get_dir_permissions(box->list, NULL, &parent_mode,
+						 &parent_gid, &origin);
+		if (mkdir_parents_chgrp(parent, parent_mode, parent_gid,
+					origin) == 0 ||
+		    errno == EEXIST) {
 			/* should work now, try again */
 			return maildir_create_path(box, path, TRUE);
 		}
--- a/src/lib-storage/index/mbox/mbox-storage.c	Sat Jun 27 19:55:44 2009 -0400
+++ b/src/lib-storage/index/mbox/mbox-storage.c	Sat Jun 27 20:39:38 2009 -0400
@@ -509,7 +509,7 @@
 		    bool directory)
 {
 	struct mail_storage *storage = box->storage;
-	const char *path, *p;
+	const char *path, *p, *origin;
 	struct stat st;
 	mode_t mode;
 	gid_t gid;
@@ -539,8 +539,9 @@
 	p = directory ? path + strlen(path) : strrchr(path, '/');
 	if (p != NULL) {
 		p = t_strdup_until(path, p);
-		mailbox_list_get_dir_permissions(box->list, NULL, &mode, &gid);
-		if (mkdir_parents_chown(p, mode, (uid_t)-1, gid) < 0 &&
+		mailbox_list_get_dir_permissions(box->list, NULL, &mode, &gid,
+						 &origin);
+		if (mkdir_parents_chgrp(p, mode, gid, origin) < 0 &&
 		    errno != EEXIST) {
 			if (!mail_storage_set_error_from_errno(storage)) {
 				mail_storage_set_critical(storage,
--- a/src/lib-storage/list/mailbox-list-fs.c	Sat Jun 27 19:55:44 2009 -0400
+++ b/src/lib-storage/list/mailbox-list-fs.c	Sat Jun 27 20:39:38 2009 -0400
@@ -314,7 +314,7 @@
 				  const char *newname, bool rename_children)
 {
 	struct mail_storage *oldstorage;
-	const char *oldpath, *newpath, *p;
+	const char *oldpath, *newpath, *p, *origin;
 	enum mailbox_list_path_type path_type;
 	struct stat st;
 	mode_t mode;
@@ -342,9 +342,10 @@
 	/* create the hierarchy */
 	p = strrchr(newpath, '/');
 	if (p != NULL) {
-		mailbox_list_get_dir_permissions(newlist, NULL, &mode, &gid);
+		mailbox_list_get_dir_permissions(newlist, NULL, &mode,
+						 &gid, &origin);
 		p = t_strdup_until(newpath, p);
-		if (mkdir_parents_chown(p, mode, (uid_t)-1, gid) < 0 &&
+		if (mkdir_parents_chgrp(p, mode, gid, origin) < 0 &&
 		    errno != EEXIST) {
 			if (mailbox_list_set_error_from_errno(oldlist))
 				return -1;
--- a/src/lib-storage/list/subscription-file.c	Sat Jun 27 19:55:44 2009 -0400
+++ b/src/lib-storage/list/subscription-file.c	Sat Jun 27 20:39:38 2009 -0400
@@ -89,7 +89,7 @@
 {
 	struct dotlock_settings dotlock_set;
 	struct dotlock *dotlock;
-	const char *line, *p, *dir;
+	const char *line, *p, *dir, *origin;
 	struct istream *input;
 	struct ostream *output;
 	int fd_in, fd_out;
@@ -107,22 +107,22 @@
 	dotlock_set.timeout = SUBSCRIPTION_FILE_LOCK_TIMEOUT;
 	dotlock_set.stale_timeout = SUBSCRIPTION_FILE_CHANGE_TIMEOUT;
 
-	mailbox_list_get_permissions(list, NULL, &mode, &gid);
-	mailbox_list_get_dir_permissions(list, NULL, &dir_mode, &gid);
-	fd_out = file_dotlock_open_mode(&dotlock_set, path, 0,
-					mode, (uid_t)-1, gid, &dotlock);
+	mailbox_list_get_permissions(list, NULL, &mode, &gid, &origin);
+	mailbox_list_get_dir_permissions(list, NULL, &dir_mode, &gid, &origin);
+	fd_out = file_dotlock_open_group(&dotlock_set, path, 0,
+					 mode, gid, origin, &dotlock);
 	if (fd_out == -1 && errno == ENOENT) {
 		/* directory hasn't been created yet. */
 		p = strrchr(path, '/');
 		dir = p == NULL ? NULL : t_strdup_until(path, p);
 		if (dir != NULL &&
-		    mkdir_parents_chown(dir, dir_mode, (uid_t)-1, gid) < 0 &&
+		    mkdir_parents_chgrp(dir, dir_mode, gid, origin) < 0 &&
 		    errno != EEXIST) {
 			subswrite_set_syscall_error(list, "mkdir()", dir);
 			return -1;
 		}
-		fd_out = file_dotlock_open_mode(&dotlock_set, path, 0,
-						mode, (uid_t)-1, gid, &dotlock);
+		fd_out = file_dotlock_open_group(&dotlock_set, path, 0,
+						 mode, gid, origin, &dotlock);
 	}
 	if (fd_out == -1) {
 		if (errno == EAGAIN) {
--- a/src/lib-storage/mail-storage-private.h	Sat Jun 27 19:55:44 2009 -0400
+++ b/src/lib-storage/mail-storage-private.h	Sat Jun 27 20:39:38 2009 -0400
@@ -235,6 +235,8 @@
 	/* mode and GID to use for newly created files/dirs */
 	mode_t file_create_mode, dir_create_mode;
 	gid_t file_create_gid;
+	/* origin (e.g. path) where the file_create_gid was got from */
+	const char *file_create_gid_origin;
 
 	/* Mailbox notification settings: */
 	unsigned int notify_min_interval;
--- a/src/lib-storage/mail-storage.c	Sat Jun 27 19:55:44 2009 -0400
+++ b/src/lib-storage/mail-storage.c	Sat Jun 27 20:39:38 2009 -0400
@@ -161,7 +161,7 @@
 mail_storage_create_root(struct mailbox_list *list,
 			 enum mail_storage_flags flags, const char **error_r)
 {
-	const char *root_dir;
+	const char *root_dir, *origin;
 	struct stat st;
 	mode_t mode;
 	gid_t gid;
@@ -187,8 +187,8 @@
 	}
 
 	/* we need to create the root directory. */
-	mailbox_list_get_dir_permissions(list, NULL, &mode, &gid);
-	if (mkdir_parents_chown(root_dir, mode, (uid_t)-1, gid) < 0 &&
+	mailbox_list_get_dir_permissions(list, NULL, &mode, &gid, &origin);
+	if (mkdir_parents_chgrp(root_dir, mode, gid, origin) < 0 &&
 	    errno != EEXIST) {
 		*error_r = mail_error_create_eacces_msg("mkdir", root_dir);
 		return -1;
--- a/src/lib-storage/mailbox-list-private.h	Sat Jun 27 19:55:44 2009 -0400
+++ b/src/lib-storage/mailbox-list-private.h	Sat Jun 27 20:39:38 2009 -0400
@@ -92,6 +92,8 @@
 	/* -1 if not set yet. use mailbox_list_get_permissions() to set them */
 	mode_t file_create_mode, dir_create_mode;
 	gid_t file_create_gid;
+	/* origin (e.g. path) where the file_create_gid was got from */
+	const char *file_create_gid_origin;
 
 	char *error_string;
 	enum mail_error error;
--- a/src/lib-storage/mailbox-list.c	Sat Jun 27 19:55:44 2009 -0400
+++ b/src/lib-storage/mailbox-list.c	Sat Jun 27 20:39:38 2009 -0400
@@ -327,7 +327,7 @@
 static void
 mailbox_list_get_permissions_full(struct mailbox_list *list, const char *name,
 				  mode_t *file_mode_r, mode_t *dir_mode_r,
-				  gid_t *gid_r)
+				  gid_t *gid_r, const char **gid_origin_r)
 {
 	const char *path;
 	struct stat st;
@@ -345,16 +345,19 @@
 			/* return defaults */
 			mailbox_list_get_permissions_full(list, NULL,
 							  file_mode_r,
-							  dir_mode_r, gid_r);
+							  dir_mode_r, gid_r,
+							  gid_origin_r);
 			return;
 		}
 		/* return safe defaults */
 		*file_mode_r = 0600;
 		*dir_mode_r = 0700;
 		*gid_r = (gid_t)-1;
+		*gid_origin_r = "defaults";
 	} else {
 		*file_mode_r = st.st_mode & 0666;
 		*dir_mode_r = st.st_mode & 0777;
+		*gid_origin_r = path;
 
 		if (!S_ISDIR(st.st_mode)) {
 			/* we're getting permissions from a file.
@@ -382,6 +385,8 @@
 		list->file_create_mode = *file_mode_r;
 		list->dir_create_mode = *dir_mode_r;
 		list->file_create_gid = *gid_r;
+		list->file_create_gid_origin =
+			p_strdup(list->pool, *gid_origin_r);
 	}
 
 	if (list->mail_set->mail_debug && name == NULL) {
@@ -393,34 +398,40 @@
 	}
 }
 
-void mailbox_list_get_permissions(struct mailbox_list *list, const char *name,
-				  mode_t *mode_r, gid_t *gid_r)
+void mailbox_list_get_permissions(struct mailbox_list *list,
+				  const char *name,
+				  mode_t *mode_r, gid_t *gid_r,
+				  const char **gid_origin_r)
 {
 	mode_t dir_mode;
 
 	if (list->file_create_mode != (mode_t)-1 && name == NULL) {
 		*mode_r = list->file_create_mode;
 		*gid_r = list->file_create_gid;
+		*gid_origin_r = list->file_create_gid_origin;
 		return;
 	}
 
-	mailbox_list_get_permissions_full(list, name, mode_r, &dir_mode, gid_r);
+	mailbox_list_get_permissions_full(list, name, mode_r, &dir_mode, gid_r,
+					  gid_origin_r);
 }
 
 void mailbox_list_get_dir_permissions(struct mailbox_list *list,
 				      const char *name,
-				      mode_t *mode_r, gid_t *gid_r)
+				      mode_t *mode_r, gid_t *gid_r,
+				      const char **gid_origin_r)
 {
 	mode_t file_mode;
 
 	if (list->dir_create_mode != (mode_t)-1 && name == NULL) {
 		*mode_r = list->dir_create_mode;
 		*gid_r = list->file_create_gid;
+		*gid_origin_r = list->file_create_gid_origin;
 		return;
 	}
 
 	mailbox_list_get_permissions_full(list, name, &file_mode,
-					  mode_r, gid_r);
+					  mode_r, gid_r, gid_origin_r);
 }
 
 bool mailbox_list_is_valid_pattern(struct mailbox_list *list,
--- a/src/lib-storage/mailbox-list.h	Sat Jun 27 19:55:44 2009 -0400
+++ b/src/lib-storage/mailbox-list.h	Sat Jun 27 20:39:38 2009 -0400
@@ -145,13 +145,16 @@
 /* Returns the mode and GID that should be used when creating new files to
    the specified mailbox, or to mailbox list root if name is NULL. (gid_t)-1 is
    returned if it's not necessary to change the default gid. */
-void mailbox_list_get_permissions(struct mailbox_list *list, const char *name,
-				  mode_t *mode_r, gid_t *gid_r);
+void mailbox_list_get_permissions(struct mailbox_list *list,
+				  const char *name,
+				  mode_t *mode_r, gid_t *gid_r,
+				  const char **gid_origin_r);
 /* Like mailbox_list_get_permissions(), but add execute-bits for mode
    if either read or write bit is set (e.g. 0640 -> 0750). */
 void mailbox_list_get_dir_permissions(struct mailbox_list *list,
 				      const char *name,
-				      mode_t *mode_r, gid_t *gid_r);
+				      mode_t *mode_r, gid_t *gid_r,
+				      const char **gid_origin_r);
 
 /* Returns TRUE if the name doesn't contain any invalid characters.
    The create name check can be more strict. */
--- a/src/lib/eacces-error.c	Sat Jun 27 19:55:44 2009 -0400
+++ b/src/lib/eacces-error.c	Sat Jun 27 20:39:38 2009 -0400
@@ -88,8 +88,9 @@
 	const struct group *group;
 	string_t *errmsg;
 	struct stat st, dir_st;
-	int ret = -1;
+	int orig_errno, ret = -1;
 
+	orig_errno = errno;
 	errmsg = t_str_new(256);
 	str_printfa(errmsg, "%s(%s) failed: Permission denied (euid=%s",
 		    func, path, dec2str(geteuid()));
@@ -145,6 +146,7 @@
 			str_printfa(errmsg, " UNIX perms seem ok, ACL problem?");
 	}
 	str_append_c(errmsg, ')');
+	errno = orig_errno;
 	return str_c(errmsg);
 }
 
@@ -157,3 +159,29 @@
 {
 	return eacces_error_get_full(func, path, TRUE);
 }
+
+const char *eperm_error_get_chgrp(const char *func, const char *path,
+				  gid_t gid, const char *gid_origin)
+{
+	string_t *errmsg;
+	const struct group *group;
+	int orig_errno = errno;
+
+	errmsg = t_str_new(256);
+	
+	str_printfa(errmsg, "%s(%s, -1, %s", func, path, dec2str(gid));
+	group = getgrgid(gid);
+	if (group != NULL)
+		str_printfa(errmsg, "(%s)", group->gr_name);
+
+	str_printfa(errmsg, ") failed: Operation not permitted (egid=%s",
+		    dec2str(getegid()));
+	group = getgrgid(getegid());
+	if (group != NULL)
+		str_printfa(errmsg, "(%s)", group->gr_name);
+	if (gid_origin != NULL)
+		str_printfa(errmsg, ", group based on %s", gid_origin);
+	str_append_c(errmsg, ')');
+	errno = orig_errno;
+	return str_c(errmsg);
+}
--- a/src/lib/eacces-error.h	Sat Jun 27 19:55:44 2009 -0400
+++ b/src/lib/eacces-error.h	Sat Jun 27 20:39:38 2009 -0400
@@ -4,5 +4,10 @@
 /* Return a user-friendly error message for EACCES failures. */
 const char *eacces_error_get(const char *func, const char *path);
 const char *eacces_error_get_creating(const char *func, const char *path);
+/* Return a user-friendly error message for fchown() or chown() EPERM
+   failures when only the group is being changed. gid_origin specifies why
+   exactly this group is being used. */
+const char *eperm_error_get_chgrp(const char *func, const char *path,
+				  gid_t gid, const char *gid_origin);
 
 #endif
--- a/src/lib/file-dotlock.c	Sat Jun 27 19:55:44 2009 -0400
+++ b/src/lib/file-dotlock.c	Sat Jun 27 20:39:38 2009 -0400
@@ -6,6 +6,7 @@
 #include "hex-binary.h"
 #include "hostpid.h"
 #include "randgen.h"
+#include "eacces-error.h"
 #include "write-full.h"
 #include "safe-mkstemp.h"
 #include "nfs-workarounds.h"
@@ -765,10 +766,11 @@
 	return dotlock->fd;
 }
 
-int file_dotlock_open_mode(const struct dotlock_settings *set, const char *path,
-			   enum dotlock_create_flags flags,
-			   mode_t mode, uid_t uid, gid_t gid,
-			   struct dotlock **dotlock_r)
+static int
+file_dotlock_open_mode_full(const struct dotlock_settings *set, const char *path,
+			    enum dotlock_create_flags flags,
+			    mode_t mode, uid_t uid, gid_t gid,
+			    const char *gid_origin, struct dotlock **dotlock_r)
 {
 	struct dotlock *dotlock;
 	mode_t old_mask;
@@ -780,9 +782,15 @@
 
 	if (fd != -1) {
 		if (fchown(fd, uid, gid) < 0) {
-			i_error("fchown(%s, %ld, %ld) failed: %m",
-				file_dotlock_get_lock_path(dotlock),
-				(long)uid, (long)gid);
+			if (errno == EPERM && uid == (uid_t)-1) {
+				i_error("%s", eperm_error_get_chgrp("fchown",
+					file_dotlock_get_lock_path(dotlock),
+					gid, gid_origin));
+			} else {
+				i_error("fchown(%s, %ld, %ld) failed: %m",
+					file_dotlock_get_lock_path(dotlock),
+					(long)uid, (long)gid);
+			}
 			file_dotlock_delete(&dotlock);
 			return -1;
 		}
@@ -791,6 +799,24 @@
 	return fd;
 }
 
+int file_dotlock_open_mode(const struct dotlock_settings *set, const char *path,
+			   enum dotlock_create_flags flags,
+			   mode_t mode, uid_t uid, gid_t gid,
+			   struct dotlock **dotlock_r)
+{
+	return file_dotlock_open_mode_full(set, path, flags, mode, uid, gid,
+					   NULL, dotlock_r);
+}
+
+int file_dotlock_open_group(const struct dotlock_settings *set, const char *path,
+			    enum dotlock_create_flags flags,
+			    mode_t mode, gid_t gid, const char *gid_origin,
+			    struct dotlock **dotlock_r)
+{
+	return file_dotlock_open_mode_full(set, path, flags, mode, (uid_t)-1,
+					   gid, gid_origin, dotlock_r);
+}
+
 int file_dotlock_replace(struct dotlock **dotlock_p,
 			 enum dotlock_replace_flags flags)
 {
--- a/src/lib/file-dotlock.h	Sat Jun 27 19:55:44 2009 -0400
+++ b/src/lib/file-dotlock.h	Sat Jun 27 20:39:38 2009 -0400
@@ -73,6 +73,10 @@
 			   enum dotlock_create_flags flags,
 			   mode_t mode, uid_t uid, gid_t gid,
 			   struct dotlock **dotlock_r);
+int file_dotlock_open_group(const struct dotlock_settings *set, const char *path,
+			    enum dotlock_create_flags flags,
+			    mode_t mode, gid_t gid, const char *gid_origin,
+			    struct dotlock **dotlock_r);
 /* Replaces the file dotlock protects with the dotlock file itself. */
 int file_dotlock_replace(struct dotlock **dotlock,
 			 enum dotlock_replace_flags flags);
--- a/src/lib/mkdir-parents.c	Sat Jun 27 19:55:44 2009 -0400
+++ b/src/lib/mkdir-parents.c	Sat Jun 27 20:39:38 2009 -0400
@@ -1,15 +1,22 @@
 /* Copyright (c) 2003-2009 Dovecot authors, see the included COPYING file */
 
 #include "lib.h"
+#include "str.h"
+#include "eacces-error.h"
 #include "mkdir-parents.h"
 
 #include <sys/stat.h>
 #include <unistd.h>
+#include <pwd.h>
+#include <grp.h>
 
-int mkdir_chown(const char *path, mode_t mode, uid_t uid, gid_t gid)
+static int
+mkdir_chown_full(const char *path, mode_t mode, uid_t uid,
+		 gid_t gid, const char *gid_origin)
 {
+	string_t *str;
 	mode_t old_mask;
-	int ret;
+	int ret, orig_errno;
 
 	old_mask = umask(0);
 	ret = mkdir(path, mode);
@@ -26,20 +33,57 @@
 		return -1;
 	}
 	if (chown(path, uid, gid) < 0) {
-		i_error("chown(%s, %ld, %ld) failed: %m", path,
-			uid == (uid_t)-1 ? -1L : (long)uid,
-			gid == (gid_t)-1 ? -1L : (long)gid);
+		if (errno == EPERM && uid == (uid_t)-1) {
+			i_error("%s", eperm_error_get_chgrp("chown", path, gid,
+							    gid_origin));
+			return -1;
+		}
+		orig_errno = errno;
+
+		str = t_str_new(256);
+		str_printfa(str, "chown(%s, %ld", path,
+			    uid == (uid_t)-1 ? -1L : (long)uid);
+		if (uid != (uid_t)-1) {
+			struct passwd *pw = getpwuid(uid);
+
+			if (pw != NULL)
+				str_printfa(str, "(%s)", pw->pw_name);
+
+		}
+		str_printfa(str, ", %ld",
+			    gid == (gid_t)-1 ? -1L : (long)gid);
+		if (gid != (gid_t)-1) {
+			struct group *gr = getgrgid(uid);
+
+			if (gr != NULL)
+				str_printfa(str, "(%s)", gr->gr_name);
+		}
+		errno = orig_errno;
+		i_error("%s) failed: %m", str_c(str));
 		return -1;
 	}
 	return 0;
 }
 
-int mkdir_parents_chown(const char *path, mode_t mode, uid_t uid, gid_t gid)
+int mkdir_chown(const char *path, mode_t mode, uid_t uid, gid_t gid)
+{
+	return mkdir_chown_full(path, mode, uid, gid, NULL);
+}
+
+int mkdir_chgrp(const char *path, mode_t mode,
+		gid_t gid, const char *gid_origin)
+{
+	return mkdir_chown_full(path, mode, (uid_t)-1, gid, gid_origin);
+}
+
+static int
+mkdir_parents_chown_full(const char *path, mode_t mode, uid_t uid, gid_t gid,
+			 const char *gid_origin)
 {
 	const char *p;
 	int ret;
 
-	if (mkdir_chown(path, mode, uid, gid) < 0) {
+	if (mkdir_chown_full(path, mode, uid, gid, gid_origin) < 0) {
 		if (errno != ENOENT)
 			return -1;
 
@@ -49,19 +93,31 @@
 			return -1; /* shouldn't happen */
 
 		T_BEGIN {
-			ret = mkdir_parents_chown(t_strdup_until(path, p),
-						  mode, uid, gid);
+			ret = mkdir_parents_chown_full(t_strdup_until(path, p),
+						       mode, uid,
+						       gid, gid_origin);
 		} T_END;
 		if (ret < 0)
 			return -1;
 
 		/* should work now */
-		if (mkdir_chown(path, mode, uid, gid) < 0)
+		if (mkdir_chown_full(path, mode, uid, gid, gid_origin) < 0)
 			return -1;
 	}
 	return 0;
 }
 
+int mkdir_parents_chown(const char *path, mode_t mode, uid_t uid, gid_t gid)
+{
+	return mkdir_parents_chown_full(path, mode, uid, gid, NULL);
+}
+
+int mkdir_parents_chgrp(const char *path, mode_t mode,
+			gid_t gid, const char *gid_origin)
+{
+	return mkdir_parents_chown_full(path, mode, (uid_t)-1, gid, gid_origin);
+}
+
 int mkdir_parents(const char *path, mode_t mode)
 {
 	return mkdir_parents_chown(path, mode, (uid_t)-1, (gid_t)-1);
--- a/src/lib/mkdir-parents.h	Sat Jun 27 19:55:44 2009 -0400
+++ b/src/lib/mkdir-parents.h	Sat Jun 27 20:39:38 2009 -0400
@@ -7,10 +7,17 @@
 int mkdir_parents(const char *path, mode_t mode);
 
 /* Like mkdir_parents(), but use the given uid/gid for newly created
-   directories. */
+   directories. (uid_t)-1 or (gid_t)-1 can be used to indicate that it
+   doesn't need to be changed. */
 int mkdir_parents_chown(const char *path, mode_t mode, uid_t uid, gid_t gid);
+/* Like mkdir_parents_chown(), but change only group. If chown() fails with
+   EACCES, use gid_origin in the error message. */
+int mkdir_parents_chgrp(const char *path, mode_t mode,
+			gid_t gid, const char *gid_origin);
 
 /* Like mkdir_parents_chown(), but don't actually create any parents. */
 int mkdir_chown(const char *path, mode_t mode, uid_t uid, gid_t gid);
+int mkdir_chgrp(const char *path, mode_t mode,
+		gid_t gid, const char *gid_origin);
 
 #endif
--- a/src/lib/safe-mkstemp.c	Sat Jun 27 19:55:44 2009 -0400
+++ b/src/lib/safe-mkstemp.c	Sat Jun 27 20:39:38 2009 -0400
@@ -5,13 +5,16 @@
 #include "hex-binary.h"
 #include "randgen.h"
 #include "hostpid.h"
+#include "eacces-error.h"
 #include "safe-mkstemp.h"
 
 #include <unistd.h>
 #include <fcntl.h>
 #include <sys/stat.h>
 
-int safe_mkstemp(string_t *prefix, mode_t mode, uid_t uid, gid_t gid)
+static int
+safe_mkstemp_full(string_t *prefix, mode_t mode, uid_t uid, gid_t gid,
+		  const char *gid_origin)
 {
 	size_t prefix_len;
 	struct stat st;
@@ -45,22 +48,46 @@
 			return -1;
 		}
 	}
-	if (uid != (uid_t)-1 || gid != (gid_t)-1) {
-		if (fchown(fd, uid, gid) < 0) {
+	if (uid == (uid_t)-1 && gid == (gid_t)-1)
+		return fd;
+
+	if (fchown(fd, uid, gid) < 0) {
+		if (errno == EPERM) {
+			i_error("%s", eperm_error_get_chgrp("fchown",
+					str_c(prefix), gid, gid_origin));
+		} else {
 			i_error("fchown(%s, %ld, %ld) failed: %m",
 				str_c(prefix),
 				uid == (uid_t)-1 ? -1L : (long)uid,
 				gid == (gid_t)-1 ? -1L : (long)gid);
-			(void)close(fd);
-			(void)unlink(str_c(prefix));
-			return -1;
 		}
+		(void)close(fd);
+		(void)unlink(str_c(prefix));
+		return -1;
 	}
 	return fd;
 }
 
+int safe_mkstemp(string_t *prefix, mode_t mode, uid_t uid, gid_t gid)
+{
+	return safe_mkstemp_full(prefix, mode, uid, gid, NULL);
+}
+
+int safe_mkstemp_group(string_t *prefix, mode_t mode,
+		       gid_t gid, const char *gid_origin)
+{
+	return safe_mkstemp_full(prefix, mode, (uid_t)-1, gid, gid_origin);
+}
+
 int safe_mkstemp_hostpid(string_t *prefix, mode_t mode, uid_t uid, gid_t gid)
 {
 	str_printfa(prefix, "%s.%s.", my_hostname, my_pid);
 	return safe_mkstemp(prefix, mode, uid, gid);
 }
+
+int safe_mkstemp_hostpid_group(string_t *prefix, mode_t mode,
+			       gid_t gid, const char *gid_origin)
+{
+	str_printfa(prefix, "%s.%s.", my_hostname, my_pid);
+	return safe_mkstemp_group(prefix, mode, gid, gid_origin);
+}
--- a/src/lib/safe-mkstemp.h	Sat Jun 27 19:55:44 2009 -0400
+++ b/src/lib/safe-mkstemp.h	Sat Jun 27 20:39:38 2009 -0400
@@ -5,7 +5,11 @@
    created filename. uid and gid can be (uid_t)-1 and (gid_t)-1 to use the
    defaults. */
 int safe_mkstemp(string_t *prefix, mode_t mode, uid_t uid, gid_t gid);
+int safe_mkstemp_group(string_t *prefix, mode_t mode,
+		       gid_t gid, const char *gid_origin);
 /* Append host and PID to the prefix. */
 int safe_mkstemp_hostpid(string_t *prefix, mode_t mode, uid_t uid, gid_t gid);
+int safe_mkstemp_hostpid_group(string_t *prefix, mode_t mode,
+			       gid_t gid, const char *gid_origin);
 
 #endif
--- a/src/plugins/acl/acl-backend-vfile-acllist.c	Sat Jun 27 19:55:44 2009 -0400
+++ b/src/plugins/acl/acl-backend-vfile-acllist.c	Sat Jun 27 20:39:38 2009 -0400
@@ -171,7 +171,7 @@
 	struct mail_namespace *ns;
 	struct mailbox_list_iterate_context *iter;
 	const struct mailbox_info *info;
-	const char *rootdir, *acllist_path;
+	const char *rootdir, *acllist_path, *origin;
 	struct ostream *output;
 	struct stat st;
 	string_t *path;
@@ -198,8 +198,8 @@
 	/* Build it into a temporary file and rename() over. There's no need
 	   to use locking, because even if multiple processes are rebuilding
 	   the file at the same time the result should be the same. */
-	mailbox_list_get_permissions(list, NULL, &mode, &gid);
-	fd = safe_mkstemp(path, mode, (uid_t)-1, gid);
+	mailbox_list_get_permissions(list, NULL, &mode, &gid, &origin);
+	fd = safe_mkstemp_group(path, mode, gid, origin);
 	if (fd == -1) {
 		if (errno == EACCES) {
 			/* Ignore silently if we can't create it */
--- a/src/plugins/acl/acl-backend-vfile.c	Sat Jun 27 19:55:44 2009 -0400
+++ b/src/plugins/acl/acl-backend-vfile.c	Sat Jun 27 20:39:38 2009 -0400
@@ -846,18 +846,18 @@
 					  struct dotlock **dotlock_r)
 {
 	struct acl_object *_aclobj = &aclobj->aclobj;
+	const char *gid_origin;
 	mode_t mode;
 	gid_t gid;
 	int fd;
 
 	/* first lock the ACL file */
 	mailbox_list_get_permissions(_aclobj->backend->list, _aclobj->name,
-				     &mode, &gid);
-	fd = file_dotlock_open_mode(&dotlock_set, aclobj->local_path, 0,
-				    mode, (uid_t)-1, gid, dotlock_r);
+				     &mode, &gid, &gid_origin);
+	fd = file_dotlock_open_group(&dotlock_set, aclobj->local_path, 0,
+				     mode, gid, gid_origin, dotlock_r);
 	if (fd == -1) {
-		i_error("file_dotlock_open_mode(%s) failed: %m",
-			aclobj->local_path);
+		i_error("file_dotlock_open(%s) failed: %m", aclobj->local_path);
 		return -1;
 	}
 
--- a/src/plugins/lazy-expunge/lazy-expunge-plugin.c	Sat Jun 27 19:55:44 2009 -0400
+++ b/src/plugins/lazy-expunge/lazy-expunge-plugin.c	Sat Jun 27 20:39:38 2009 -0400
@@ -266,18 +266,18 @@
 mailbox_move(struct mailbox_list *src_list, const char *src_name,
 	     struct mailbox_list *dest_list, const char **_dest_name)
 {
-	const char *dir, *dest_name = *_dest_name;
+	const char *dir, *origin, *dest_name = *_dest_name;
 	enum mail_error error;
 	mode_t mode;
 	gid_t gid;
 
 	/* make sure the destination root directory exists */
-	mailbox_list_get_dir_permissions(dest_list, NULL, &mode, &gid);
+	mailbox_list_get_dir_permissions(dest_list, NULL, &mode, &gid, &origin);
 	dir = mailbox_list_get_path(dest_list, NULL, MAILBOX_LIST_PATH_TYPE_DIR);
-	if (mkdir_parents_chown(dir, mode, (uid_t)-1, gid) < 0 &&
+	if (mkdir_parents_chgrp(dir, mode, gid, origin) < 0 &&
 	    errno != EEXIST) {
 		mailbox_list_set_critical(src_list,
-			"mkdir_parents_chown(%s) failed: %m", dir);
+			"mkdir_parents(%s) failed: %m", dir);
 		return -1;
 	}
 
--- a/src/plugins/quota/quota-maildir.c	Sat Jun 27 19:55:44 2009 -0400
+++ b/src/plugins/quota/quota-maildir.c	Sat Jun 27 20:39:38 2009 -0400
@@ -222,7 +222,7 @@
 	struct mail_namespace *const *namespaces;
 	unsigned int i, count;
 	struct dotlock *dotlock;
-	const char *p, *dir;
+	const char *p, *dir, *gid_origin, *dir_gid_origin;
 	string_t *str;
 	mode_t mode, dir_mode;
 	gid_t gid, dir_gid;
@@ -232,43 +232,41 @@
 
 	/* figure out what permissions we should use for maildirsize.
 	   use the inbox namespace's permissions if possible. */
-	mode = 0600; dir_mode = 0700;
+	mode = 0600; dir_mode = 0700; gid_origin = "default";
 	gid = dir_gid = (gid_t)-1;
 	namespaces = array_get(&root->root.quota->namespaces, &count);
 	i_assert(count > 0);
 	for (i = 0; i < count; i++) {
 		if ((namespaces[i]->flags & NAMESPACE_FLAG_INBOX) != 0) {
 			mailbox_list_get_permissions(namespaces[i]->list,
-						     NULL, &mode, &gid);
+						     NULL, &mode, &gid,
+						     &gid_origin);
 			mailbox_list_get_dir_permissions(namespaces[i]->list,
 							 NULL,
-							 &dir_mode, &dir_gid);
+							 &dir_mode, &dir_gid,
+							 &dir_gid_origin);
 			break;
 		}
 	}
 
 	dotlock_settings.use_excl_lock = set->dotlock_use_excl;
 	dotlock_settings.nfs_flush = set->mail_nfs_storage;
-	fd = file_dotlock_open_mode(&dotlock_settings, path,
-				    DOTLOCK_CREATE_FLAG_NONBLOCK,
-				    mode, (uid_t)-1, gid, &dotlock);
+	fd = file_dotlock_open_group(&dotlock_settings, path,
+				     DOTLOCK_CREATE_FLAG_NONBLOCK,
+				     mode, gid, gid_origin, &dotlock);
 	if (fd == -1 && errno == ENOENT) {
 		/* the control directory doesn't exist yet? create it */
 		p = strrchr(path, '/');
 		dir = t_strdup_until(path, p);
-		if (mkdir_parents(dir, dir_mode) < 0 && errno != EEXIST) {
+		if (mkdir_parents_chgrp(dir, dir_mode, dir_gid,
+					dir_gid_origin) < 0 &&
+		    errno != EEXIST) {
 			i_error("mkdir_parents(%s) failed: %m", dir);
 			return -1;
 		}
-		if (dir_gid != (gid_t)-1) {
-			if (chown(dir, (uid_t)-1, dir_gid) < 0) {
-				i_error("chown(%s,-1,%ld) failed: %m",
-					dir, (long)dir_gid);
-			}
-		}
-		fd = file_dotlock_open_mode(&dotlock_settings, path,
-					    DOTLOCK_CREATE_FLAG_NONBLOCK,
-					    mode, (uid_t)-1, gid, &dotlock);
+		fd = file_dotlock_open_group(&dotlock_settings, path,
+					     DOTLOCK_CREATE_FLAG_NONBLOCK,
+					     mode, gid, gid_origin, &dotlock);
 	}
 	if (fd == -1) {
 		if (errno == EAGAIN) {