restrict-access.c revision 5137c64be6bca222026a4aa9d8b0d71a5becb25e
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen/* Copyright (c) 2002-2007 Dovecot authors, see the included COPYING file */
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen#include "lib.h"
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen#include "restrict-access.h"
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen#include "env-util.h"
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen#include <stdlib.h>
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen#include <unistd.h>
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen#include <time.h>
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen#include <grp.h>
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainenvoid restrict_access_set_env(const char *user, uid_t uid, gid_t gid,
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen const char *chroot_dir,
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen gid_t first_valid_gid, gid_t last_valid_gid,
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen const char *extra_groups)
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen{
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen if (user != NULL && *user != '\0')
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen env_put(t_strconcat("RESTRICT_USER=", user, NULL));
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen if (chroot_dir != NULL && *chroot_dir != '\0')
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen env_put(t_strconcat("RESTRICT_CHROOT=", chroot_dir, NULL));
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen env_put(t_strdup_printf("RESTRICT_SETUID=%s", dec2str(uid)));
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen env_put(t_strdup_printf("RESTRICT_SETGID=%s", dec2str(gid)));
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen if (extra_groups != NULL && *extra_groups != '\0') {
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen env_put(t_strconcat("RESTRICT_SETEXTRAGROUPS=",
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen extra_groups, NULL));
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen }
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen if (first_valid_gid != 0) {
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen env_put(t_strdup_printf("RESTRICT_GID_FIRST=%s",
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen dec2str(first_valid_gid)));
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen }
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen if (last_valid_gid != 0) {
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen env_put(t_strdup_printf("RESTRICT_GID_LAST=%s",
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen dec2str(last_valid_gid)));
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen }
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen}
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainenstatic gid_t *get_groups_list(unsigned int *gid_count_r)
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen{
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen gid_t *gid_list;
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen int ret, gid_count;
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen if ((gid_count = getgroups(0, NULL)) < 0)
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen i_fatal("getgroups() failed: %m");
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen /* @UNSAFE */
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen gid_list = t_new(gid_t, gid_count);
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen if ((ret = getgroups(gid_count, gid_list)) < 0)
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen i_fatal("getgroups() failed: %m");
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen *gid_count_r = ret;
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen return gid_list;
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen}
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainenstatic bool drop_restricted_groups(gid_t *gid_list, unsigned int *gid_count,
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen bool *have_root_group)
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen{
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen /* @UNSAFE */
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen gid_t first_valid, last_valid;
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen const char *env;
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen unsigned int i, used;
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen env = getenv("RESTRICT_GID_FIRST");
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen first_valid = env == NULL ? 0 : (gid_t)strtoul(env, NULL, 10);
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen env = getenv("RESTRICT_GID_LAST");
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen last_valid = env == NULL ? (gid_t)-1 : (gid_t)strtoul(env, NULL, 10);
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen for (i = 0, used = 0; i < *gid_count; i++) {
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen if (gid_list[i] >= first_valid &&
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen (last_valid == (gid_t)-1 || gid_list[i] <= last_valid)) {
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen if (gid_list[i] == 0)
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen *have_root_group = TRUE;
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen gid_list[used++] = gid_list[i];
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen }
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen }
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen if (*gid_count == used)
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen return FALSE;
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen *gid_count = used;
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen return TRUE;
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen}
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainenstatic gid_t get_group_id(const char *name)
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen{
f8a86fdfb0048f9c87bf223373b35416ceb5856bTimo Sirainen struct group *group;
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen if (is_numeric(name, '\0'))
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen return (gid_t)strtoul(name, NULL, 10);
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen group = getgrnam(name);
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen if (group == NULL)
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen i_fatal("unknown group name in extra_groups: %s", name);
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen return group->gr_gid;
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen}
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainenstatic void fix_groups_list(const char *extra_groups,
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen bool preserve_existing, bool *have_root_group)
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen{
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen gid_t *gid_list;
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen const char *const *tmp, *empty = NULL;
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen 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 {
gid_list = t_new(gid_t, 1);
gid_count = 0;
}
/* add extra groups to gids list */
for (; *tmp != NULL; tmp++) {
if (!t_try_realloc(gid_list, (gid_count+1) * sizeof(gid_t)))
i_unreached();
gid_list[gid_count++] = get_group_id(*tmp);
}
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");
}
}
}
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;
is_root = geteuid() == 0;
/* set the primary group */
env = getenv("RESTRICT_SETGID");
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()));
}
}
/* set system user's groups */
env = getenv("RESTRICT_USER");
if (env != NULL && *env != '\0' && is_root) {
if (initgroups(env, gid) < 0) {
i_fatal("initgroups(%s, %s) failed: %m",
env, dec2str(gid));
}
preserve_groups = TRUE;
}
/* add extra groups. if we set system user's groups, drop the
restricted groups at the same time. */
env = getenv("RESTRICT_SETEXTRAGROUPS");
if (is_root) {
T_FRAME(
fix_groups_list(env, preserve_groups, &have_root_group);
);
}
/* chrooting */
env = getenv("RESTRICT_CHROOT");
if (env != NULL && *env != '\0') {
/* kludge: localtime() must be called before chroot(),
or the timezone isn't known */
const char *home = getenv("HOME");
time_t t = 0;
(void)localtime(&t);
if (chroot(env) != 0)
i_fatal("chroot(%s) failed: %m", env);
if (home != NULL) {
if (chdir(home) < 0) {
i_error("chdir(%s) failed: %m", home);
home = NULL;
}
}
if (home == NULL) {
if (chdir("/") != 0)
i_fatal("chdir(/) failed: %m");
}
}
/* uid last */
env = getenv("RESTRICT_SETUID");
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",
dec2str(uid), dec2str(geteuid()));
}
}
/* verify that we actually dropped the privileges */
if (uid != 0 || disallow_root) {
if (setuid(0) == 0) {
if (uid == 0)
i_fatal("Running as root isn't permitted");
i_fatal("We couldn't drop root privileges");
}
}
env = getenv("RESTRICT_GID_FIRST");
if ((!have_root_group || (env != NULL && atoi(env) != 0)) && uid != 0) {
if (getgid() == 0 || getegid() == 0 || setgid(0) == 0) {
if (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),
dec2str(getgid()), dec2str(getegid()));
}
}
/* clear the environment, so we don't fail if we get back here */
env_put("RESTRICT_USER=");
env_put("RESTRICT_CHROOT=");
env_put("RESTRICT_SETUID=");
env_put("RESTRICT_SETGID=");
env_put("RESTRICT_SETEXTRAGROUPS=");
env_put("RESTRICT_GID_FIRST=");
env_put("RESTRICT_GID_LAST=");
}