#include "orconfig.h"
#include "lib/process/setuid.h"
#if defined(HAVE_SYS_CAPABILITY_H) && defined(HAVE_CAP_SET_PROC)
#define HAVE_LINUX_CAPABILITIES
#endif
#include "lib/container/smartlist.h"
#include "lib/fs/userdb.h"
#include "lib/log/log.h"
#include "lib/log/util_bug.h"
#include "lib/malloc/malloc.h"
#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_GRP_H
#include <grp.h>
#endif
#ifdef HAVE_PWD_H
#include <pwd.h>
#endif
#ifdef HAVE_SYS_CAPABILITY_H
#include <sys/capability.h>
#endif
#ifdef HAVE_SYS_PRCTL_H
#include <sys/prctl.h>
#endif
#include <errno.h>
#include <string.h>
#ifndef _WIN32
static int
log_credential_status(void)
{
#define CREDENTIAL_LOG_LEVEL LOG_INFO
uid_t ruid, euid, suid;
gid_t rgid, egid, sgid;
gid_t *sup_gids = NULL;
int sup_gids_size;
int ngids;
#ifdef HAVE_GETRESUID
if (getresuid(&ruid, &euid, &suid) != 0) {
log_warn(LD_GENERAL, "Error getting changed UIDs: %s", strerror(errno));
return -1;
} else {
log_fn(CREDENTIAL_LOG_LEVEL, LD_GENERAL,
"UID is %u (real), %u (effective), %u (saved)",
(unsigned)ruid, (unsigned)euid, (unsigned)suid);
}
#else
ruid = getuid();
euid = geteuid();
(void)suid;
log_fn(CREDENTIAL_LOG_LEVEL, LD_GENERAL,
"UID is %u (real), %u (effective), unknown (saved)",
(unsigned)ruid, (unsigned)euid);
#endif
#ifdef HAVE_GETRESGID
if (getresgid(&rgid, &egid, &sgid) != 0) {
log_warn(LD_GENERAL, "Error getting changed GIDs: %s", strerror(errno));
return -1;
} else {
log_fn(CREDENTIAL_LOG_LEVEL, LD_GENERAL,
"GID is %u (real), %u (effective), %u (saved)",
(unsigned)rgid, (unsigned)egid, (unsigned)sgid);
}
#else
rgid = getgid();
egid = getegid();
(void)sgid;
log_fn(CREDENTIAL_LOG_LEVEL, LD_GENERAL,
"GID is %u (real), %u (effective), unknown (saved)",
(unsigned)rgid, (unsigned)egid);
#endif
sup_gids_size = 64;
sup_gids = tor_calloc(64, sizeof(gid_t));
while ((ngids = getgroups(sup_gids_size, sup_gids)) < 0 &&
errno == EINVAL &&
sup_gids_size < NGROUPS_MAX) {
sup_gids_size *= 2;
sup_gids = tor_reallocarray(sup_gids, sizeof(gid_t), sup_gids_size);
}
if (ngids < 0) {
log_warn(LD_GENERAL, "Error getting supplementary GIDs: %s",
strerror(errno));
tor_free(sup_gids);
return -1;
} else {
int i, retval = 0;
char *s = NULL;
smartlist_t *elts = smartlist_new();
for (i = 0; i<ngids; i++) {
smartlist_add_asprintf(elts, "%u", (unsigned)sup_gids[i]);
}
s = smartlist_join_strings(elts, " ", 0, NULL);
log_fn(CREDENTIAL_LOG_LEVEL, LD_GENERAL, "Supplementary groups are: %s",s);
tor_free(s);
SMARTLIST_FOREACH(elts, char *, cp, tor_free(cp));
smartlist_free(elts);
tor_free(sup_gids);
return retval;
}
return 0;
}
#endif
int
have_capability_support(void)
{
#ifdef HAVE_LINUX_CAPABILITIES
cap_t caps = cap_get_proc();
if (caps == NULL)
return 0;
cap_free(caps);
return 1;
#else
return 0;
#endif
}
#ifdef HAVE_LINUX_CAPABILITIES
static int
drop_capabilities(int pre_setuid)
{
const cap_value_t caplist[] = {
CAP_NET_BIND_SERVICE, CAP_SETUID, CAP_SETGID
};
const char *where = pre_setuid ? "pre-setuid" : "post-setuid";
const int n_effective = pre_setuid ? 3 : 1;
const int n_permitted = pre_setuid ? 3 : 1;
const int n_inheritable = 1;
const int keepcaps = pre_setuid ? 1 : 0;
if (prctl(PR_SET_KEEPCAPS, keepcaps) < 0) {
log_warn(LD_CONFIG, "Unable to call prctl() %s: %s",
where, strerror(errno));
return -1;
}
cap_t caps = cap_get_proc();
if (!caps) {
log_warn(LD_CONFIG, "Unable to call cap_get_proc() %s: %s",
where, strerror(errno));
return -1;
}
cap_clear(caps);
cap_set_flag(caps, CAP_EFFECTIVE, n_effective, caplist, CAP_SET);
cap_set_flag(caps, CAP_PERMITTED, n_permitted, caplist, CAP_SET);
cap_set_flag(caps, CAP_INHERITABLE, n_inheritable, caplist, CAP_SET);
int r = cap_set_proc(caps);
cap_free(caps);
if (r < 0) {
log_warn(LD_CONFIG, "No permission to set capabilities %s: %s",
where, strerror(errno));
return -1;
}
return 0;
}
#endif
int
switch_id(const char *user, const unsigned flags)
{
#ifndef _WIN32
const struct passwd *pw = NULL;
uid_t old_uid;
gid_t old_gid;
static int have_already_switched_id = 0;
const int keep_bindlow = !!(flags & SWITCH_ID_KEEP_BINDLOW);
const int warn_if_no_caps = !!(flags & SWITCH_ID_WARN_IF_NO_CAPS);
tor_assert(user);
if (have_already_switched_id)
return 0;
if (log_credential_status())
return -1;
log_fn(CREDENTIAL_LOG_LEVEL, LD_GENERAL, "Changing user and groups");
old_uid = getuid();
old_gid = getgid();
pw = tor_getpwnam(user);
if (pw == NULL) {
log_warn(LD_CONFIG, "Error setting configured user: %s not found", user);
return -1;
}
#ifdef HAVE_LINUX_CAPABILITIES
(void) warn_if_no_caps;
if (keep_bindlow) {
if (drop_capabilities(1))
return -1;
}
#else
(void) keep_bindlow;
if (warn_if_no_caps) {
log_warn(LD_CONFIG, "KeepBindCapabilities set, but no capability support "
"on this system.");
}
#endif
if (setgroups(1, &pw->pw_gid)) {
log_warn(LD_GENERAL, "Error setting groups to gid %d: \"%s\".",
(int)pw->pw_gid, strerror(errno));
if (old_uid == pw->pw_uid) {
log_warn(LD_GENERAL, "Tor is already running as %s. You do not need "
"the \"User\" option if you are already running as the user "
"you want to be. (If you did not set the User option in your "
"torrc, check whether it was specified on the command line "
"by a startup script.)", user);
} else {
log_warn(LD_GENERAL, "If you set the \"User\" option, you must start Tor"
" as root.");
}
return -1;
}
if (setegid(pw->pw_gid)) {
log_warn(LD_GENERAL, "Error setting egid to %d: %s",
(int)pw->pw_gid, strerror(errno));
return -1;
}
if (setgid(pw->pw_gid)) {
log_warn(LD_GENERAL, "Error setting gid to %d: %s",
(int)pw->pw_gid, strerror(errno));
return -1;
}
if (setuid(pw->pw_uid)) {
log_warn(LD_GENERAL, "Error setting configured uid to %s (%d): %s",
user, (int)pw->pw_uid, strerror(errno));
return -1;
}
if (seteuid(pw->pw_uid)) {
log_warn(LD_GENERAL, "Error setting configured euid to %s (%d): %s",
user, (int)pw->pw_uid, strerror(errno));
return -1;
}
#ifdef HAVE_LINUX_CAPABILITIES
if (keep_bindlow) {
if (drop_capabilities(0))
return -1;
}
#endif
#if !defined(CYGWIN) && !defined(__CYGWIN__)
if (pw->pw_uid) {
if (pw->pw_gid != old_gid &&
(setgid(old_gid) != -1 || setegid(old_gid) != -1)) {
log_warn(LD_GENERAL, "Was able to restore group credentials even after "
"switching GID: this means that the setgid code didn't work.");
return -1;
}
if (pw->pw_uid != old_uid &&
(setuid(old_uid) != -1 || seteuid(old_uid) != -1)) {
log_warn(LD_GENERAL, "Was able to restore user credentials even after "
"switching UID: this means that the setuid code didn't work.");
return -1;
}
}
#endif
if (log_credential_status()) {
return -1;
}
have_already_switched_id = 1;
#if defined(__linux__) && defined(HAVE_SYS_PRCTL_H) && \
defined(HAVE_PRCTL) && defined(PR_SET_DUMPABLE)
if (pw->pw_uid) {
log_info(LD_CONFIG, "Re-enabling coredumps");
if (prctl(PR_SET_DUMPABLE, 1)) {
log_warn(LD_CONFIG, "Unable to re-enable coredumps: %s",strerror(errno));
}
}
#endif
return 0;
#else
(void)user;
(void)flags;
log_warn(LD_CONFIG, "Switching users is unsupported on your OS.");
return -1;
#endif
}