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 +#include #include "lib.h" #include "restrict-access.h" #include "env-util.h" #include -#include #include #include -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 #include #include +#include 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;