Mercurial > dovecot > core-2.2
changeset 9657:dfe15bb24fdb HEAD
master: Handle better chdir()ing to home dir.
author | Timo Sirainen <tss@iki.fi> |
---|---|
date | Sun, 26 Jul 2009 14:37:13 -0400 |
parents | dc6d35b51b68 |
children | f29a25420a82 |
files | src/master/service-process.c |
diffstat | 1 files changed, 68 insertions(+), 8 deletions(-) [+] |
line wrap: on
line diff
--- a/src/master/service-process.c Sun Jul 26 14:17:30 2009 -0400 +++ b/src/master/service-process.c Sun Jul 26 14:37:13 2009 -0400 @@ -15,6 +15,7 @@ #include "fd-close-on-exec.h" #include "restrict-access.h" #include "restrict-process-size.h" +#include "eacces-error.h" #include "master-service.h" #include "master-service-settings.h" #include "dup2-array.h" @@ -34,6 +35,12 @@ #include <signal.h> #include <sys/wait.h> +/* Timeout chdir() completely after this many seconds */ +#define CHDIR_TIMEOUT 30 +/* Give a warning about chdir() taking a while if it took longer than this + many seconds to finish. */ +#define CHDIR_WARN_SECS 10 + static void service_dup_fds(struct service *service, int auth_fd, int std_fd, bool give_anvil_fd) @@ -247,6 +254,65 @@ auth_success_written = TRUE; } +static void chdir_to_home(const struct restrict_access_settings *rset, + const char *user, const char *home) +{ + unsigned int left; + int ret, chdir_errno; + + if (*home != '/') { + i_fatal("user %s: Relative home directory paths not supported: " + "%s", user, home); + } + + /* if home directory is NFS-mounted, we might not have access to it as + root. Change the effective UID and GID temporarily to make it + work. */ + if (rset->uid != master_uid) { + if (setegid(rset->gid) < 0) + i_fatal("setegid(%s) failed: %m", dec2str(rset->gid)); + if (seteuid(rset->uid) < 0) + i_fatal("seteuid(%s) failed: %m", dec2str(rset->uid)); + } + + alarm(CHDIR_TIMEOUT); + ret = chdir(home); + chdir_errno = errno; + if ((left = alarm(0)) < CHDIR_TIMEOUT - CHDIR_WARN_SECS) { + i_warning("user %s: chdir(%s) blocked for %u secs", + user, home, CHDIR_TIMEOUT - left); + } + + errno = chdir_errno; + if (ret == 0) { + /* chdir succeeded */ + } else if ((errno == ENOENT || errno == ENOTDIR || errno == EINTR) && + rset->chroot_dir == NULL) { + /* Not chrooted, fallback to using /tmp. + + ENOENT: No home directory yet, but it might be automatically + created by the service process, so don't complain. + ENOTDIR: This check is mainly for /dev/null home directory. + EINTR: chdir() timed out. */ + } else if (errno == EACCES) { + i_fatal("user %s: %s", user, eacces_error_get("chdir", home)); + } else { + i_fatal("user %s: chdir(%s) failed with uid %s: %m", + user, home, dec2str(rset->uid)); + } + /* Change UID back. No need to change GID back, it doesn't + really matter. */ + if (rset->uid != master_uid && seteuid(master_uid) < 0) + i_fatal("seteuid(%s) failed: %m", dec2str(master_uid)); + + if (ret < 0) { + /* We still have to change to some directory where we have + rx-access. /tmp should exist everywhere. */ + if (chdir("/tmp") < 0) + i_fatal("chdir(/tmp) failed: %m"); + } +} + static void drop_privileges(struct service *service, const char *const *auth_args) { @@ -286,14 +352,8 @@ validate_uid_gid(master_set, rset.uid, rset.gid, user); } - if (home != NULL) { - if (*home != '/') { - i_fatal("Relative home directory paths not supported " - "(user %s): %s", user, home); - } - if (chdir(home) < 0 && errno != ENOENT) - i_error("chdir(%s) failed: %m", home); - } + if (home != NULL) + chdir_to_home(&rset, user, home); if (service->set->drop_priv_before_exec) { disallow_root = service->type == SERVICE_TYPE_AUTH_SERVER ||