restrict-access.c revision e8c0c0ba05a76410ef908ab5b694559d25f98ae7
76b43e4417bab52e913da39b5f5bc2a130d3f149Timo Sirainen/* Copyright (c) 2002-2010 Dovecot authors, see the included COPYING file */
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen#define _GNU_SOURCE /* setresgid() */
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen#include <stdio.h> /* for AIX */
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen#include <sys/types.h>
cbc61fcb33b370d049c16a3c44568b4deb4e2b33Timo Sirainen#include <unistd.h>
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen
00d58fcfe8191d6ce7efa801d289a5c0fe88d1aeTimo Sirainen#include "lib.h"
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen#include "restrict-access.h"
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen#include "env-util.h"
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen#include <stdlib.h>
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen#include <time.h>
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen#include <pwd.h>
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen#include <grp.h>
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen#ifdef HAVE_PR_SET_DUMPABLE
8e371a3ce32bd64288786855b8ce0cb63f19f7d1Timo Sirainen# include <sys/prctl.h>
8e371a3ce32bd64288786855b8ce0cb63f19f7d1Timo Sirainen#endif
8e371a3ce32bd64288786855b8ce0cb63f19f7d1Timo Sirainen
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainenstatic gid_t process_primary_gid = (gid_t)-1;
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainenstatic gid_t process_privileged_gid = (gid_t)-1;
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainenstatic bool process_using_priv_gid = FALSE;
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainenstatic char *chroot_dir = NULL;
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainenvoid restrict_access_init(struct restrict_access_settings *set)
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen{
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen memset(set, 0, sizeof(*set));
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen set->uid = (uid_t)-1;
8e371a3ce32bd64288786855b8ce0cb63f19f7d1Timo Sirainen set->gid = (gid_t)-1;
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen set->privileged_gid = (gid_t)-1;
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen}
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainenstatic const char *get_uid_str(uid_t uid)
8e371a3ce32bd64288786855b8ce0cb63f19f7d1Timo Sirainen{
8e371a3ce32bd64288786855b8ce0cb63f19f7d1Timo Sirainen const struct passwd *pw;
cbc61fcb33b370d049c16a3c44568b4deb4e2b33Timo Sirainen const char *ret;
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen int old_errno = errno;
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen pw = getpwuid(uid);
f6c1297c26b355c4aec2a08978f51ec3efecb351Timo Sirainen if (pw == NULL)
f6c1297c26b355c4aec2a08978f51ec3efecb351Timo Sirainen ret = dec2str(uid);
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen else
8e371a3ce32bd64288786855b8ce0cb63f19f7d1Timo Sirainen ret = t_strdup_printf("%s(%s)", dec2str(uid), pw->pw_name);
8e371a3ce32bd64288786855b8ce0cb63f19f7d1Timo Sirainen errno = old_errno;
8e371a3ce32bd64288786855b8ce0cb63f19f7d1Timo Sirainen return ret;
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen}
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainenstatic const char *get_gid_str(gid_t gid)
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen{
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen const struct group *group;
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen const char *ret;
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen int old_errno = errno;
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen group = getgrgid(gid);
8e371a3ce32bd64288786855b8ce0cb63f19f7d1Timo Sirainen if (group == NULL)
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen ret = dec2str(gid);
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen else
8e371a3ce32bd64288786855b8ce0cb63f19f7d1Timo Sirainen ret = t_strdup_printf("%s(%s)", dec2str(gid), group->gr_name);
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen errno = old_errno;
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen return ret;
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen}
73b50eecfc31750a312e2f940023f522eb07178cTimo Sirainen
73b50eecfc31750a312e2f940023f522eb07178cTimo Sirainenstatic void restrict_init_groups(gid_t primary_gid, gid_t privileged_gid)
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen{
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen if (privileged_gid == (gid_t)-1) {
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen if (primary_gid == getgid() && primary_gid == getegid()) {
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen /* everything is already set */
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen return;
cbc61fcb33b370d049c16a3c44568b4deb4e2b33Timo Sirainen }
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen if (setgid(primary_gid) != 0) {
cbc61fcb33b370d049c16a3c44568b4deb4e2b33Timo Sirainen i_fatal("setgid(%s) failed with "
cbc61fcb33b370d049c16a3c44568b4deb4e2b33Timo Sirainen "euid=%s, gid=%s, egid=%s: %m "
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen "(This binary should probably be called with "
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen "process group set to %s instead of %s)",
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen get_gid_str(primary_gid), get_uid_str(geteuid()),
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen get_gid_str(getgid()), get_gid_str(getegid()),
73b50eecfc31750a312e2f940023f522eb07178cTimo Sirainen get_gid_str(primary_gid), get_uid_str(geteuid()));
cbc61fcb33b370d049c16a3c44568b4deb4e2b33Timo Sirainen }
cbc61fcb33b370d049c16a3c44568b4deb4e2b33Timo Sirainen return;
cbc61fcb33b370d049c16a3c44568b4deb4e2b33Timo Sirainen }
73b50eecfc31750a312e2f940023f522eb07178cTimo Sirainen
cbc61fcb33b370d049c16a3c44568b4deb4e2b33Timo Sirainen if (getegid() != 0 && primary_gid == getgid() &&
73b50eecfc31750a312e2f940023f522eb07178cTimo Sirainen primary_gid == getegid()) {
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen /* privileged_gid is hopefully in saved ID. if not,
cbc61fcb33b370d049c16a3c44568b4deb4e2b33Timo Sirainen there's nothing we can do about it. */
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen return;
73b50eecfc31750a312e2f940023f522eb07178cTimo Sirainen }
73b50eecfc31750a312e2f940023f522eb07178cTimo Sirainen
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen#ifdef HAVE_SETRESGID
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen if (setresgid(primary_gid, primary_gid, privileged_gid) != 0) {
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen i_fatal("setresgid(%s,%s,%s) failed with euid=%s: %m",
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen get_gid_str(primary_gid), get_gid_str(primary_gid),
b04e691711fd026fc82ba3e0b411420e7da4ec7eTimo Sirainen get_gid_str(privileged_gid), get_uid_str(geteuid()));
4d2211dac61c615c5bdfd501ea54d46c89d41b0fTimo Sirainen }
4d2211dac61c615c5bdfd501ea54d46c89d41b0fTimo Sirainen#else
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen if (geteuid() == 0) {
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen /* real, effective, saved -> privileged_gid */
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen if (setgid(privileged_gid) < 0) {
eddd9bf1a1369aea4a2715f6be1137da6d17d293Timo Sirainen i_fatal("setgid(%s) failed: %m",
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen get_gid_str(privileged_gid));
eddd9bf1a1369aea4a2715f6be1137da6d17d293Timo Sirainen }
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen }
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen /* real, effective -> primary_gid
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen saved -> keep */
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen if (setregid(primary_gid, primary_gid) != 0) {
73b50eecfc31750a312e2f940023f522eb07178cTimo Sirainen i_fatal("setregid(%s,%s) failed with euid=%s: %m",
73b50eecfc31750a312e2f940023f522eb07178cTimo Sirainen get_gid_str(primary_gid), get_gid_str(privileged_gid),
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen get_uid_str(geteuid()));
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen }
4d2211dac61c615c5bdfd501ea54d46c89d41b0fTimo Sirainen#endif
63f36c2b47217fc2dc4ed49cfc1907311d5ed366Timo Sirainen}
b04e691711fd026fc82ba3e0b411420e7da4ec7eTimo Sirainen
eddd9bf1a1369aea4a2715f6be1137da6d17d293Timo Sirainengid_t *restrict_get_groups_list(unsigned int *gid_count_r)
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen{
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen gid_t *gid_list;
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen int ret, gid_count;
cbc61fcb33b370d049c16a3c44568b4deb4e2b33Timo Sirainen
eddd9bf1a1369aea4a2715f6be1137da6d17d293Timo Sirainen if ((gid_count = getgroups(0, NULL)) < 0)
eddd9bf1a1369aea4a2715f6be1137da6d17d293Timo Sirainen i_fatal("getgroups() failed: %m");
eddd9bf1a1369aea4a2715f6be1137da6d17d293Timo Sirainen
eddd9bf1a1369aea4a2715f6be1137da6d17d293Timo Sirainen /* @UNSAFE */
cbc61fcb33b370d049c16a3c44568b4deb4e2b33Timo Sirainen gid_list = t_new(gid_t, gid_count);
eddd9bf1a1369aea4a2715f6be1137da6d17d293Timo Sirainen if ((ret = getgroups(gid_count, gid_list)) < 0)
eddd9bf1a1369aea4a2715f6be1137da6d17d293Timo Sirainen i_fatal("getgroups() failed: %m");
eddd9bf1a1369aea4a2715f6be1137da6d17d293Timo Sirainen
eddd9bf1a1369aea4a2715f6be1137da6d17d293Timo Sirainen *gid_count_r = ret;
eddd9bf1a1369aea4a2715f6be1137da6d17d293Timo Sirainen return gid_list;
cbc61fcb33b370d049c16a3c44568b4deb4e2b33Timo Sirainen}
cbc61fcb33b370d049c16a3c44568b4deb4e2b33Timo Sirainen
cbc61fcb33b370d049c16a3c44568b4deb4e2b33Timo Sirainenstatic void drop_restricted_groups(const struct restrict_access_settings *set,
eddd9bf1a1369aea4a2715f6be1137da6d17d293Timo Sirainen gid_t *gid_list, unsigned int *gid_count,
eddd9bf1a1369aea4a2715f6be1137da6d17d293Timo Sirainen bool *have_root_group)
eddd9bf1a1369aea4a2715f6be1137da6d17d293Timo Sirainen{
eddd9bf1a1369aea4a2715f6be1137da6d17d293Timo Sirainen /* @UNSAFE */
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen unsigned int i, used;
eddd9bf1a1369aea4a2715f6be1137da6d17d293Timo Sirainen
cbc61fcb33b370d049c16a3c44568b4deb4e2b33Timo Sirainen for (i = 0, used = 0; i < *gid_count; i++) {
cbc61fcb33b370d049c16a3c44568b4deb4e2b33Timo Sirainen if (gid_list[i] >= set->first_valid_gid &&
cbc61fcb33b370d049c16a3c44568b4deb4e2b33Timo Sirainen (set->last_valid_gid == 0 ||
cbc61fcb33b370d049c16a3c44568b4deb4e2b33Timo Sirainen gid_list[i] <= set->last_valid_gid)) {
cbc61fcb33b370d049c16a3c44568b4deb4e2b33Timo Sirainen if (gid_list[i] == 0)
cbc61fcb33b370d049c16a3c44568b4deb4e2b33Timo Sirainen *have_root_group = TRUE;
cbc61fcb33b370d049c16a3c44568b4deb4e2b33Timo Sirainen gid_list[used++] = gid_list[i];
eddd9bf1a1369aea4a2715f6be1137da6d17d293Timo Sirainen }
eddd9bf1a1369aea4a2715f6be1137da6d17d293Timo Sirainen }
19e8adccba16ff419f5675b1575358c2956dce83Timo Sirainen *gid_count = used;
eddd9bf1a1369aea4a2715f6be1137da6d17d293Timo Sirainen}
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainenstatic gid_t get_group_id(const char *name)
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen{
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen struct group *group;
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen gid_t gid;
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen if (str_to_gid(name, &gid) == 0)
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen return gid;
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen
8e371a3ce32bd64288786855b8ce0cb63f19f7d1Timo Sirainen group = getgrnam(name);
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen if (group == NULL)
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen i_fatal("unknown group name in extra_groups: %s", name);
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen return group->gr_gid;
73b50eecfc31750a312e2f940023f522eb07178cTimo Sirainen}
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainenstatic void fix_groups_list(const struct restrict_access_settings *set,
8e371a3ce32bd64288786855b8ce0cb63f19f7d1Timo Sirainen bool preserve_existing, bool *have_root_group)
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen{
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen gid_t gid, *gid_list, *gid_list2;
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen const char *const *tmp, *empty = NULL;
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen unsigned int i, gid_count;
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen bool add_primary_gid;
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen /* if we're using a privileged GID, we can temporarily drop our
73b50eecfc31750a312e2f940023f522eb07178cTimo Sirainen effective GID. we still want to be able to use its privileges,
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen so add it to supplementary groups. */
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen add_primary_gid = process_privileged_gid != (gid_t)-1;
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen tmp = set->extra_groups == NULL ? &empty :
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen t_strsplit_spaces(set->extra_groups, ", ");
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen if (preserve_existing) {
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen gid_list = restrict_get_groups_list(&gid_count);
8e371a3ce32bd64288786855b8ce0cb63f19f7d1Timo Sirainen drop_restricted_groups(set, gid_list, &gid_count,
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen have_root_group);
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen /* see if the list already contains the primary GID */
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen for (i = 0; i < gid_count; i++) {
8e371a3ce32bd64288786855b8ce0cb63f19f7d1Timo Sirainen if (gid_list[i] == process_primary_gid) {
8e371a3ce32bd64288786855b8ce0cb63f19f7d1Timo Sirainen add_primary_gid = FALSE;
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen break;
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen }
8e371a3ce32bd64288786855b8ce0cb63f19f7d1Timo Sirainen }
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen } else {
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen gid_list = NULL;
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen gid_count = 0;
8e371a3ce32bd64288786855b8ce0cb63f19f7d1Timo Sirainen }
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen if (gid_count == 0) {
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen /* Some OSes don't like an empty groups list,
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen so use the primary GID as the only one. */
cbc61fcb33b370d049c16a3c44568b4deb4e2b33Timo Sirainen gid_list = t_new(gid_t, 2);
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen gid_list[0] = process_primary_gid;
cbc61fcb33b370d049c16a3c44568b4deb4e2b33Timo Sirainen gid_count = 1;
cbc61fcb33b370d049c16a3c44568b4deb4e2b33Timo Sirainen add_primary_gid = FALSE;
cbc61fcb33b370d049c16a3c44568b4deb4e2b33Timo Sirainen }
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen
cbc61fcb33b370d049c16a3c44568b4deb4e2b33Timo Sirainen if (*tmp != NULL || add_primary_gid) {
cbc61fcb33b370d049c16a3c44568b4deb4e2b33Timo Sirainen /* @UNSAFE: add extra groups and/or primary GID to gids list */
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen gid_list2 = t_new(gid_t, gid_count + str_array_length(tmp) + 1);
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen memcpy(gid_list2, gid_list, gid_count * sizeof(gid_t));
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen for (; *tmp != NULL; tmp++) {
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen gid = get_group_id(*tmp);
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen if (gid != process_primary_gid)
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen gid_list2[gid_count++] = gid;
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen }
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen if (add_primary_gid)
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen gid_list2[gid_count++] = process_primary_gid;
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen gid_list = gid_list2;
cbc61fcb33b370d049c16a3c44568b4deb4e2b33Timo Sirainen }
cbc61fcb33b370d049c16a3c44568b4deb4e2b33Timo Sirainen
8e371a3ce32bd64288786855b8ce0cb63f19f7d1Timo Sirainen if (setgroups(gid_count, gid_list) < 0) {
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen if (errno == EINVAL) {
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen i_fatal("setgroups(%s) failed: Too many extra groups",
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen set->extra_groups == NULL ? "" :
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen set->extra_groups);
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen } else {
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen i_fatal("setgroups() failed: %m");
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen }
8e371a3ce32bd64288786855b8ce0cb63f19f7d1Timo Sirainen }
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen}
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainenvoid restrict_access(const struct restrict_access_settings *set,
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen const char *home, bool disallow_root)
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen{
cbc61fcb33b370d049c16a3c44568b4deb4e2b33Timo Sirainen bool is_root, have_root_group, preserve_groups = FALSE;
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen bool allow_root_gid;
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen is_root = geteuid() == 0;
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen
8e371a3ce32bd64288786855b8ce0cb63f19f7d1Timo Sirainen /* set the primary/privileged group */
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen process_primary_gid = set->gid;
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen process_privileged_gid = set->privileged_gid;
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen have_root_group = process_primary_gid == 0;
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen if (process_primary_gid != (gid_t)-1 ||
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen process_privileged_gid != (gid_t)-1) {
8e371a3ce32bd64288786855b8ce0cb63f19f7d1Timo Sirainen if (process_primary_gid == (gid_t)-1)
8e371a3ce32bd64288786855b8ce0cb63f19f7d1Timo Sirainen process_primary_gid = getegid();
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen restrict_init_groups(process_primary_gid,
cbc61fcb33b370d049c16a3c44568b4deb4e2b33Timo Sirainen process_privileged_gid);
cbc61fcb33b370d049c16a3c44568b4deb4e2b33Timo Sirainen } else {
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen if (process_primary_gid == (gid_t)-1)
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen process_primary_gid = getegid();
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen }
cbc61fcb33b370d049c16a3c44568b4deb4e2b33Timo Sirainen
cbc61fcb33b370d049c16a3c44568b4deb4e2b33Timo Sirainen /* set system user's groups */
cbc61fcb33b370d049c16a3c44568b4deb4e2b33Timo Sirainen if (set->system_groups_user != NULL && is_root) {
cbc61fcb33b370d049c16a3c44568b4deb4e2b33Timo Sirainen if (initgroups(set->system_groups_user,
cbc61fcb33b370d049c16a3c44568b4deb4e2b33Timo Sirainen process_primary_gid) < 0) {
cbc61fcb33b370d049c16a3c44568b4deb4e2b33Timo Sirainen i_fatal("initgroups(%s, %s) failed: %m",
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen set->system_groups_user,
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen get_gid_str(process_primary_gid));
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen }
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen preserve_groups = TRUE;
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen }
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen
8e371a3ce32bd64288786855b8ce0cb63f19f7d1Timo Sirainen /* add extra groups. if we set system user's groups, drop the
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen restricted groups at the same time. */
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen if (is_root) T_BEGIN {
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen fix_groups_list(set, preserve_groups,
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen &have_root_group);
8e371a3ce32bd64288786855b8ce0cb63f19f7d1Timo Sirainen } T_END;
8e371a3ce32bd64288786855b8ce0cb63f19f7d1Timo Sirainen
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen /* chrooting */
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen if (set->chroot_dir != NULL) {
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen /* kludge: localtime() must be called before chroot(),
8e371a3ce32bd64288786855b8ce0cb63f19f7d1Timo Sirainen or the timezone isn't known */
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen time_t t = 0;
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen (void)localtime(&t);
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen if (chroot(set->chroot_dir) != 0)
cbc61fcb33b370d049c16a3c44568b4deb4e2b33Timo Sirainen i_fatal("chroot(%s) failed: %m", set->chroot_dir);
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen chroot_dir = i_strdup(set->chroot_dir);
cbc61fcb33b370d049c16a3c44568b4deb4e2b33Timo Sirainen
cbc61fcb33b370d049c16a3c44568b4deb4e2b33Timo Sirainen if (home != NULL) {
cbc61fcb33b370d049c16a3c44568b4deb4e2b33Timo Sirainen if (chdir(home) < 0) {
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen i_error("chdir(%s) failed: %m", home);
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen home = NULL;
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen }
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen }
cbc61fcb33b370d049c16a3c44568b4deb4e2b33Timo Sirainen if (home == NULL) {
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen if (chdir("/") != 0)
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen i_fatal("chdir(/) failed: %m");
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen }
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen }
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen /* uid last */
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen if (set->uid != (uid_t)-1) {
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen if (setuid(set->uid) != 0) {
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen i_fatal("setuid(%s) failed with euid=%s: %m "
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen "(This binary should probably be called with "
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen "process user set to %s instead of %s)",
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen get_uid_str(set->uid), get_uid_str(geteuid()),
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen get_uid_str(set->uid), get_uid_str(geteuid()));
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen }
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen }
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen /* verify that we actually dropped the privileges */
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen if ((set->uid != (uid_t)-1 && set->uid != 0) || disallow_root) {
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen if (setuid(0) == 0) {
if (disallow_root &&
(set->uid == 0 || set->uid == (uid_t)-1))
i_fatal("This process must not be run as root");
i_fatal("We couldn't drop root privileges");
}
}
if (set->first_valid_gid != 0)
allow_root_gid = FALSE;
else if (process_primary_gid == 0 || process_privileged_gid == 0)
allow_root_gid = TRUE;
else
allow_root_gid = FALSE;
if (!allow_root_gid && set->uid != 0 &&
(set->uid != (uid_t)-1 || !is_root)) {
if (getgid() == 0 || getegid() == 0 || setgid(0) == 0) {
if (process_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)",
get_gid_str(process_primary_gid),
get_gid_str(getgid()), get_gid_str(getegid()));
}
}
}
void restrict_access_set_env(const struct restrict_access_settings *set)
{
if (set->system_groups_user != NULL &&
*set->system_groups_user != '\0') {
env_put(t_strconcat("RESTRICT_USER=",
set->system_groups_user, NULL));
}
if (set->chroot_dir != NULL && *set->chroot_dir != '\0')
env_put(t_strconcat("RESTRICT_CHROOT=", set->chroot_dir, NULL));
if (set->uid != (uid_t)-1) {
env_put(t_strdup_printf("RESTRICT_SETUID=%s",
dec2str(set->uid)));
}
if (set->gid != (gid_t)-1) {
env_put(t_strdup_printf("RESTRICT_SETGID=%s",
dec2str(set->gid)));
}
if (set->privileged_gid != (gid_t)-1) {
env_put(t_strdup_printf("RESTRICT_SETGID_PRIV=%s",
dec2str(set->privileged_gid)));
}
if (set->extra_groups != NULL && *set->extra_groups != '\0') {
env_put(t_strconcat("RESTRICT_SETEXTRAGROUPS=",
set->extra_groups, NULL));
}
if (set->first_valid_gid != 0) {
env_put(t_strdup_printf("RESTRICT_GID_FIRST=%s",
dec2str(set->first_valid_gid)));
}
if (set->last_valid_gid != 0) {
env_put(t_strdup_printf("RESTRICT_GID_LAST=%s",
dec2str(set->last_valid_gid)));
}
}
static const char *null_if_empty(const char *str)
{
return str == NULL || *str == '\0' ? NULL : str;
}
void restrict_access_get_env(struct restrict_access_settings *set_r)
{
const char *value;
restrict_access_init(set_r);
if ((value = getenv("RESTRICT_SETUID")) != NULL) {
if (str_to_uid(value, &set_r->uid) < 0)
i_fatal("Invalid uid: %s", value);
}
if ((value = getenv("RESTRICT_SETGID")) != NULL) {
if (str_to_gid(value, &set_r->gid) < 0)
i_fatal("Invalid gid: %s", value);
}
if ((value = getenv("RESTRICT_SETGID_PRIV")) != NULL) {
if (str_to_gid(value, &set_r->privileged_gid) < 0)
i_fatal("Invalid privileged_gid: %s", value);
}
if ((value = getenv("RESTRICT_GID_FIRST")) != NULL) {
if (str_to_gid(value, &set_r->first_valid_gid) < 0)
i_fatal("Invalid first_valid_gid: %s", value);
}
if ((value = getenv("RESTRICT_GID_LAST")) != NULL) {
if (str_to_gid(value, &set_r->last_valid_gid) < 0)
i_fatal("Invalid last_value_gid: %s", value);
}
set_r->extra_groups = null_if_empty(getenv("RESTRICT_SETEXTRAGROUPS"));
set_r->system_groups_user = null_if_empty(getenv("RESTRICT_USER"));
set_r->chroot_dir = null_if_empty(getenv("RESTRICT_CHROOT"));
}
void restrict_access_by_env(const char *home, bool disallow_root)
{
struct restrict_access_settings set;
restrict_access_get_env(&set);
restrict_access(&set, home, disallow_root);
/* clear the environment, so we don't fail if we get back here */
env_remove("RESTRICT_SETUID");
if (process_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_remove("RESTRICT_SETGID");
env_remove("RESTRICT_SETGID_PRIV");
}
env_remove("RESTRICT_GID_FIRST");
env_remove("RESTRICT_GID_LAST");
env_remove("RESTRICT_SETEXTRAGROUPS");
env_remove("RESTRICT_USER");
env_remove("RESTRICT_CHROOT");
}
const char *restrict_access_get_current_chroot(void)
{
return chroot_dir;
}
void restrict_access_allow_coredumps(bool allow ATTR_UNUSED)
{
#ifdef HAVE_PR_SET_DUMPABLE
(void)prctl(PR_SET_DUMPABLE, allow, 0, 0, 0);
#endif
}
int restrict_access_use_priv_gid(void)
{
i_assert(!process_using_priv_gid);
if (process_privileged_gid == (gid_t)-1)
return 0;
if (setegid(process_privileged_gid) < 0) {
i_error("setegid(privileged) failed: %m");
return -1;
}
process_using_priv_gid = TRUE;
return 0;
}
void restrict_access_drop_priv_gid(void)
{
if (!process_using_priv_gid)
return;
if (setegid(process_primary_gid) < 0)
i_fatal("setegid(primary) failed: %m");
process_using_priv_gid = FALSE;
}
bool restrict_access_have_priv_gid(void)
{
return process_privileged_gid != (gid_t)-1;
}