restrict-access.c revision e2e71ea6440b1758adba6d374eabcfd26358a7da
af0732a2bf24fbea12a085b855224577e7101851kiirala/* Copyright (c) 2002-2003 Timo Sirainen */
af0732a2bf24fbea12a085b855224577e7101851kiirala
af0732a2bf24fbea12a085b855224577e7101851kiirala#include "lib.h"
af0732a2bf24fbea12a085b855224577e7101851kiirala#include "restrict-access.h"
af0732a2bf24fbea12a085b855224577e7101851kiirala#include "env-util.h"
af0732a2bf24fbea12a085b855224577e7101851kiirala
af0732a2bf24fbea12a085b855224577e7101851kiirala#include <stdlib.h>
af0732a2bf24fbea12a085b855224577e7101851kiirala#include <unistd.h>
af0732a2bf24fbea12a085b855224577e7101851kiirala#include <time.h>
af0732a2bf24fbea12a085b855224577e7101851kiirala#include <grp.h>
af0732a2bf24fbea12a085b855224577e7101851kiirala
af0732a2bf24fbea12a085b855224577e7101851kiirala#define HARD_MAX_GROUPS 10240
af0732a2bf24fbea12a085b855224577e7101851kiirala
af0732a2bf24fbea12a085b855224577e7101851kiirala#ifndef NGROUPS_MAX
af0732a2bf24fbea12a085b855224577e7101851kiirala# define NGROUPS_MAX 128
af0732a2bf24fbea12a085b855224577e7101851kiirala#endif
af0732a2bf24fbea12a085b855224577e7101851kiirala
af0732a2bf24fbea12a085b855224577e7101851kiiralavoid restrict_access_set_env(const char *user, uid_t uid, gid_t gid,
af0732a2bf24fbea12a085b855224577e7101851kiirala const char *chroot_dir,
259fbef23dd30285e7355b13c6ce524565fa0cb6Krzysztof Kosiński gid_t first_valid_gid, gid_t last_valid_gid,
259fbef23dd30285e7355b13c6ce524565fa0cb6Krzysztof Kosiński const char *extra_groups)
259fbef23dd30285e7355b13c6ce524565fa0cb6Krzysztof Kosiński{
259fbef23dd30285e7355b13c6ce524565fa0cb6Krzysztof Kosiński if (user != NULL && *user != '\0')
af0732a2bf24fbea12a085b855224577e7101851kiirala env_put(t_strconcat("RESTRICT_USER=", user, NULL));
d9a7c806ee7f408ddb61ff4f233c9d96111ee2b5johanengelen if (chroot_dir != NULL && *chroot_dir != '\0')
d9a7c806ee7f408ddb61ff4f233c9d96111ee2b5johanengelen env_put(t_strconcat("RESTRICT_CHROOT=", chroot_dir, NULL));
af0732a2bf24fbea12a085b855224577e7101851kiirala
af0732a2bf24fbea12a085b855224577e7101851kiirala env_put(t_strdup_printf("RESTRICT_SETUID=%s", dec2str(uid)));
af0732a2bf24fbea12a085b855224577e7101851kiirala env_put(t_strdup_printf("RESTRICT_SETGID=%s", dec2str(gid)));
259fbef23dd30285e7355b13c6ce524565fa0cb6Krzysztof Kosiński if (extra_groups != NULL && *extra_groups != '\0') {
259fbef23dd30285e7355b13c6ce524565fa0cb6Krzysztof Kosiński env_put(t_strconcat("RESTRICT_SETEXTRAGROUPS=",
259fbef23dd30285e7355b13c6ce524565fa0cb6Krzysztof Kosiński extra_groups, NULL));
259fbef23dd30285e7355b13c6ce524565fa0cb6Krzysztof Kosiński }
259fbef23dd30285e7355b13c6ce524565fa0cb6Krzysztof Kosiński
259fbef23dd30285e7355b13c6ce524565fa0cb6Krzysztof Kosiński if (first_valid_gid != 0) {
af0732a2bf24fbea12a085b855224577e7101851kiirala env_put(t_strdup_printf("RESTRICT_GID_FIRST=%s",
af0732a2bf24fbea12a085b855224577e7101851kiirala dec2str(first_valid_gid)));
af0732a2bf24fbea12a085b855224577e7101851kiirala }
af0732a2bf24fbea12a085b855224577e7101851kiirala if (last_valid_gid != 0) {
af0732a2bf24fbea12a085b855224577e7101851kiirala env_put(t_strdup_printf("RESTRICT_GID_LAST=%s",
af0732a2bf24fbea12a085b855224577e7101851kiirala dec2str(last_valid_gid)));
af0732a2bf24fbea12a085b855224577e7101851kiirala }
af0732a2bf24fbea12a085b855224577e7101851kiirala}
af0732a2bf24fbea12a085b855224577e7101851kiirala
af0732a2bf24fbea12a085b855224577e7101851kiiralastatic gid_t *get_groups_list(int *gid_count_r)
af0732a2bf24fbea12a085b855224577e7101851kiirala{
af0732a2bf24fbea12a085b855224577e7101851kiirala /* @UNSAFE */
af0732a2bf24fbea12a085b855224577e7101851kiirala gid_t *gid_list;
af0732a2bf24fbea12a085b855224577e7101851kiirala int ret, gid_count;
d9a7c806ee7f408ddb61ff4f233c9d96111ee2b5johanengelen
d9a7c806ee7f408ddb61ff4f233c9d96111ee2b5johanengelen gid_count = NGROUPS_MAX;
af0732a2bf24fbea12a085b855224577e7101851kiirala gid_list = t_buffer_get(sizeof(gid_t) * gid_count);
af0732a2bf24fbea12a085b855224577e7101851kiirala while ((ret = getgroups(gid_count, gid_list)) < 0) {
af0732a2bf24fbea12a085b855224577e7101851kiirala if (errno != EINVAL ||
af0732a2bf24fbea12a085b855224577e7101851kiirala gid_count < HARD_MAX_GROUPS)
af0732a2bf24fbea12a085b855224577e7101851kiirala i_fatal("getgroups() failed: %m");
af0732a2bf24fbea12a085b855224577e7101851kiirala
af0732a2bf24fbea12a085b855224577e7101851kiirala gid_count *= 2;
af0732a2bf24fbea12a085b855224577e7101851kiirala gid_list = t_buffer_reget(gid_list, sizeof(gid_t) * gid_count);
af0732a2bf24fbea12a085b855224577e7101851kiirala }
af0732a2bf24fbea12a085b855224577e7101851kiirala t_buffer_alloc(sizeof(gid_t) * ret);
af0732a2bf24fbea12a085b855224577e7101851kiirala
af0732a2bf24fbea12a085b855224577e7101851kiirala *gid_count_r = ret;
return gid_list;
}
static void drop_restricted_groups(void)
{
/* @UNSAFE */
const char *env;
gid_t *gid_list, first_valid_gid, last_valid_gid;
int i, used, gid_count;
env = getenv("RESTRICT_GID_FIRST");
first_valid_gid = env == NULL ? 0 : (gid_t)atol(env);
env = getenv("RESTRICT_GID_LAST");
last_valid_gid = env == NULL ? 0 : (gid_t)atol(env);
if (first_valid_gid == 0 && last_valid_gid == 0)
return;
t_push();
gid_list = get_groups_list(&gid_count);
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))
gid_list[used++] = gid_list[i];
}
if (used != gid_count) {
/* it did contain restricted groups, remove it */
if (setgroups(gid_count, gid_list) < 0)
i_fatal("setgroups() failed: %m");
}
t_pop();
}
static gid_t get_group_id(const char *name)
{
struct group *group;
if (is_numeric(name, '\0'))
return (gid_t)atol(name);
group = getgrnam(name);
if (group == NULL)
i_fatal("unknown group name in extra_groups: %s", name);
return group->gr_gid;
}
static void grant_extra_groups(const char *groups)
{
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_panic("won't happen");
gid_list[gid_count++] = get_group_id(*tmp);
}
if (setgroups(gid_count, gid_list) < 0)
i_fatal("setgroups() failed: %m");
t_pop();
}
void restrict_access_by_env(int disallow_root)
{
const char *env;
gid_t gid;
uid_t uid;
/* 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)atol(env);
if (gid != 0 && (gid != getgid() || gid != getegid())) {
if (setgid(gid) != 0)
i_fatal("setgid(%s) failed: %m", dec2str(gid));
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();
}
}
/* grant additional groups to process */
env = getenv("RESTRICT_SETEXTRAGROUPS");
if (env != NULL && *env != '\0')
grant_extra_groups(env);
/* chrooting */
env = getenv("RESTRICT_CHROOT");
if (env != NULL && *env != '\0') {
/* kludge: localtime() must be called before chroot(),
or the timezone isn't known */
time_t t = 0;
(void)localtime(&t);
if (chroot(env) != 0)
i_fatal("chroot(%s) failed: %m", env);
if (chdir("/") != 0)
i_fatal("chdir(/) failed: %m");
}
/* uid last */
env = getenv("RESTRICT_SETUID");
uid = env == NULL ? 0 : (uid_t)atol(env);
if (uid != 0) {
if (setuid(uid) != 0)
i_fatal("setuid(%s) failed: %m", dec2str(uid));
}
/* verify that we actually dropped the privileges */
if (uid != 0 || disallow_root) {
if (setuid(0) == 0)
i_fatal("We couldn't drop root privileges");
}
if (gid != 0) {
if (getgid() == 0 || getegid() == 0 || setgid(0) == 0)
i_fatal("We couldn't drop root group privileges");
}
/* 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=");
}