changeset 7341:af998ae4254b HEAD

Replaced mail_extra_groups setting with mail_privileged_group and mail_access_groups settings. mail_privileged_group allows temporary access to the group when creating mbox INBOX dotlocks.
author Timo Sirainen <tss@iki.fi>
date Tue, 04 Mar 2008 07:54:53 +0200
parents 09cdd4330d73
children c1c977afe06c
files dovecot-example.conf src/lib-storage/index/mbox/mbox-lock.c src/lib-storage/index/mbox/mbox-lock.h src/lib-storage/index/mbox/mbox-storage.c src/lib-storage/index/mbox/mbox-storage.h src/lib/restrict-access.c src/lib/restrict-access.h src/master/auth-process.c src/master/login-process.c src/master/mail-process.c src/master/master-settings-defs.c src/master/master-settings.c src/master/master-settings.h
diffstat 13 files changed, 354 insertions(+), 37 deletions(-) [+]
line wrap: on
line diff
--- a/dovecot-example.conf	Tue Mar 04 07:54:41 2008 +0200
+++ b/dovecot-example.conf	Tue Mar 04 07:54:53 2008 +0200
@@ -269,12 +269,17 @@
 #mail_uid =
 #mail_gid =
 
-# 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.
-# WARNING: If your users can create symlinks, this will allow the users to
-# read any files that are group-readable by one of these groups! Make sure at
-# least all the common mailboxes have 0600 permissions (or a different group).
-#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
--- a/src/lib-storage/index/mbox/mbox-lock.c	Tue Mar 04 07:54:41 2008 +0200
+++ b/src/lib-storage/index/mbox/mbox-lock.c	Tue Mar 04 07:54:53 2008 +0200
@@ -1,6 +1,7 @@
 /* Copyright (c) 2002-2008 Dovecot authors, see the included COPYING file */
 
 #include "lib.h"
+#include "restrict-access.h"
 #include "nfs-workarounds.h"
 #include "mail-index-private.h"
 #include "mbox-storage.h"
@@ -38,6 +39,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];
@@ -46,6 +53,7 @@
 	int lock_type;
 	bool dotlock_last_stale;
 	bool fcntl_locked;
+	bool using_privileges;
 };
 
 struct mbox_lock_data {
@@ -200,6 +208,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. */
@@ -231,9 +242,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_int(struct mbox_lock_context *ctx, int lock_type, bool try)
 {
@@ -245,7 +339,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;
 		}
@@ -269,6 +371,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) {
 		if ((ENOSPACE(errno) || errno == EACCES) && try)
 			return 1;
@@ -643,3 +752,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);
+	}
+}
--- a/src/lib-storage/index/mbox/mbox-lock.h	Tue Mar 04 07:54:41 2008 +0200
+++ b/src/lib-storage/index/mbox/mbox-lock.h	Tue Mar 04 07:54:53 2008 +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
--- a/src/lib-storage/index/mbox/mbox-storage.c	Tue Mar 04 07:54:41 2008 +0200
+++ b/src/lib-storage/index/mbox/mbox-storage.c	Tue Mar 04 07:54:53 2008 +0200
@@ -396,6 +396,33 @@
 	return &storage->storage;
 }
 
+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_existing_name(struct mailbox_list *list, const char *name)
+{
+	struct mbox_storage *storage = MBOX_LIST_CONTEXT(list);
+
+	return storage->list_module_ctx.super.
+		is_valid_existing_name(list, name) &&
+		!mbox_name_is_dotlock(name);
+}
+
+static bool
+mbox_is_valid_create_name(struct mailbox_list *list, const char *name)
+{
+	struct mbox_storage *storage = MBOX_LIST_CONTEXT(list);
+
+	return storage->list_module_ctx.super.
+		is_valid_create_name(list, name) &&
+		!mbox_name_is_dotlock(name);
+}
+
 static int mbox_create(struct mail_storage *_storage, const char *data,
 		       const char **error_r)
 {
@@ -419,6 +446,8 @@
 	}
 	_storage->list->v.iter_is_mailbox = mbox_list_iter_is_mailbox;
 	_storage->list->v.delete_mailbox = mbox_list_delete_mailbox;
+	_storage->list->v.is_valid_existing_name = mbox_is_valid_existing_name;
+	_storage->list->v.is_valid_create_name = mbox_is_valid_create_name;
 
 	MODULE_CONTEXT_SET_FULL(_storage->list, mbox_mailbox_list_module,
 				storage, &storage->list_module_ctx);
@@ -492,7 +521,7 @@
 
 static void mbox_lock_touch_timeout(struct mbox_mailbox *mbox)
 {
-	(void)file_dotlock_touch(mbox->mbox_dotlock);
+	mbox_dotlock_touch(mbox);
 }
 
 static struct mbox_mailbox *
@@ -553,7 +582,7 @@
 	struct mail_storage *_storage = &storage->storage;
 	struct mbox_mailbox *mbox;
 	struct mail_index *index;
-	const char *path;
+	const char *path, *rootdir;
 
 	path = mailbox_list_get_path(_storage->list, name,
 				     MAILBOX_LIST_PATH_TYPE_MAILBOX);
@@ -570,6 +599,14 @@
 		}
 	}
 
+	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 */
+		rootdir = mailbox_list_get_path(_storage->list, NULL,
+						MAILBOX_LIST_PATH_TYPE_DIR);
+		if (strncmp(path, rootdir, strlen(rootdir)) != 0)
+			mbox->mbox_privileged_locking = TRUE;
+	}
 	return &mbox->ibox.box;
 }
 
--- a/src/lib-storage/index/mbox/mbox-storage.h	Tue Mar 04 07:54:41 2008 +0200
+++ b/src/lib-storage/index/mbox/mbox-storage.h	Tue Mar 04 07:54:53 2008 +0200
@@ -47,6 +47,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;
 	unsigned int syncing:1;
 };
 
--- a/src/lib/restrict-access.c	Tue Mar 04 07:54:41 2008 +0200
+++ b/src/lib/restrict-access.c	Tue Mar 04 07:54:53 2008 +0200
@@ -1,15 +1,22 @@
 /* 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,6 +47,53 @@
 	}
 }
 
+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;
@@ -145,31 +203,33 @@
 void restrict_access_by_env(bool disallow_root)
 {
 	const char *env;
-	gid_t gid;
 	uid_t uid;
 	bool is_root, have_root_group, preserve_groups = FALSE;
+	bool allow_root_gid;
 
 	is_root = geteuid() == 0;
 
-	/* set the primary group */
+	/* set the primary/privileged group */
 	env = getenv("RESTRICT_SETGID");
-	gid = env == NULL || *env == '\0' ? (gid_t)-1 :
+	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 = gid == 0;
-	if (gid != (gid_t)-1 && (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()));
-		}
+
+	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, gid) < 0) {
+		if (initgroups(env, primary_gid) < 0) {
 			i_fatal("initgroups(%s, %s) failed: %m",
-				env, dec2str(gid));
+				env, dec2str(primary_gid));
 		}
 		preserve_groups = TRUE;
 	}
@@ -179,7 +239,7 @@
 	env = getenv("RESTRICT_SETEXTRAGROUPS");
 	if (is_root) {
 		T_BEGIN {
-			fix_groups_list(env, gid, preserve_groups,
+			fix_groups_list(env, primary_gid, preserve_groups,
 					&have_root_group);
 		} T_END;
 	}
@@ -228,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()));
 		}
 	}
@@ -242,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;
+}
--- a/src/lib/restrict-access.h	Tue Mar 04 07:54:41 2008 +0200
+++ b/src/lib/restrict-access.h	Tue Mar 04 07:54:53 2008 +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
--- a/src/master/auth-process.c	Tue Mar 04 07:54:41 2008 +0200
+++ b/src/master/auth-process.c	Tue Mar 04 07:54:53 2008 +0200
@@ -421,8 +421,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");
--- a/src/master/login-process.c	Tue Mar 04 07:54:41 2008 +0200
+++ b/src/master/login-process.c	Tue Mar 04 07:54:53 2008 +0200
@@ -516,7 +516,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);
 
--- a/src/master/mail-process.c	Tue Mar 04 07:54:41 2008 +0200
+++ b/src/master/mail-process.c	Tue Mar 04 07:54:53 2008 +0200
@@ -724,9 +724,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);
 
@@ -834,8 +835,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",
--- a/src/master/master-settings-defs.c	Tue Mar 04 07:54:41 2008 +0200
+++ b/src/master/master-settings-defs.c	Tue Mar 04 07:54:53 2008 +0200
@@ -64,6 +64,8 @@
 	DEF_INT(first_valid_gid),
 	DEF_INT(last_valid_gid),
 	DEF_STR(mail_extra_groups),
+	DEF_STR(mail_access_groups),
+	DEF_STR(mail_privileged_group),
 	DEF_STR(mail_uid),
 	DEF_STR(mail_gid),
 
--- a/src/master/master-settings.c	Tue Mar 04 07:54:41 2008 +0200
+++ b/src/master/master-settings.c	Tue Mar 04 07:54:53 2008 +0200
@@ -226,6 +226,8 @@
 	MEMBER(first_valid_gid) 1,
 	MEMBER(last_valid_gid) 0,
 	MEMBER(mail_extra_groups) "",
+	MEMBER(mail_access_groups) "",
+	MEMBER(mail_privileged_group) "",
 	MEMBER(mail_uid) "",
 	MEMBER(mail_gid) "",
 
@@ -701,6 +703,7 @@
 
 	set->mail_uid_t = (uid_t)-1;
 	set->mail_gid_t = (gid_t)-1;
+	set->mail_priv_gid_t = (gid_t)-1;
 
 	if (*set->mail_uid != '\0') {
 		if (!parse_uid(set->mail_uid, &set->mail_uid_t)) {
@@ -714,6 +717,29 @@
 			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_ANY &&
 	    access(t_strcut(set->mail_executable, ' '), X_OK) < 0) {
--- a/src/master/master-settings.h	Tue Mar 04 07:54:41 2008 +0200
+++ b/src/master/master-settings.h	Tue Mar 04 07:54:53 2008 +0200
@@ -76,6 +76,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 *mail_uid;
 	const char *mail_gid;
 
@@ -139,7 +141,7 @@
 	ARRAY_TYPE(listener) ssl_listens;
 
 	uid_t login_uid, mail_uid_t;
-	gid_t mail_gid_t;
+	gid_t mail_gid_t, mail_priv_gid_t;
 
 	const char *imap_generated_capability;
 
@@ -254,6 +256,7 @@
 	ARRAY_DEFINE(dicts, const char *);
 
 	gid_t login_gid;
+	unsigned int warned_mail_extra_groups:1;
 };
 
 extern struct server_settings *settings_root;