6c9f98bf34
* Added prefixes to all log messages. Prefix should describe source of message, like "config", "http", "ssl", "server", etc... This should give users more info to figure out what went wrong or what is going on. Prefixes also make automatic processing easier. * Usage is not displayed by default when given command line options are incorrect. This way it is easier to notice actual error.
445 lines
11 KiB
C
445 lines
11 KiB
C
// privileges.c -- Manage process privileges
|
|
// Copyright (C) 2008-2010 Markus Gutschke <markus@shellinabox.com>
|
|
//
|
|
// This program is free software; you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License version 2 as
|
|
// published by the Free Software Foundation.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License along
|
|
// with this program; if not, write to the Free Software Foundation, Inc.,
|
|
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
//
|
|
// In addition to these license terms, the author grants the following
|
|
// additional rights:
|
|
//
|
|
// If you modify this program, or any covered work, by linking or
|
|
// combining it with the OpenSSL project's OpenSSL library (or a
|
|
// modified version of that library), containing parts covered by the
|
|
// terms of the OpenSSL or SSLeay licenses, the author
|
|
// grants you additional permission to convey the resulting work.
|
|
// Corresponding Source for a non-source form of such a combination
|
|
// shall include the source code for the parts of OpenSSL used as well
|
|
// as that of the covered work.
|
|
//
|
|
// You may at your option choose to remove this additional permission from
|
|
// the work, or from any part of it.
|
|
//
|
|
// It is possible to build this program in a way that it loads OpenSSL
|
|
// libraries at run-time. If doing so, the following notices are required
|
|
// by the OpenSSL and SSLeay licenses:
|
|
//
|
|
// This product includes software developed by the OpenSSL Project
|
|
// for use in the OpenSSL Toolkit. (http://www.openssl.org/)
|
|
//
|
|
// This product includes cryptographic software written by Eric Young
|
|
// (eay@cryptsoft.com)
|
|
//
|
|
//
|
|
// The most up-to-date version of this program is always available from
|
|
// http://shellinabox.com
|
|
|
|
#define _GNU_SOURCE
|
|
#include "config.h"
|
|
|
|
#include <errno.h>
|
|
#include <grp.h>
|
|
#include <limits.h>
|
|
#include <pwd.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/types.h>
|
|
#include <unistd.h>
|
|
|
|
#include "shellinabox/privileges.h"
|
|
#include "logging/logging.h"
|
|
|
|
#define UNUSED_RETURN(x) do { (void)((x)+1); } while (0)
|
|
|
|
int runAsUser = -1;
|
|
int runAsGroup = -1;
|
|
|
|
#ifndef HAVE_GETRESUID
|
|
int getresuid(uid_t *ruid, uid_t *euid, uid_t *suid) {
|
|
*ruid = getuid();
|
|
*euid = geteuid();
|
|
*suid = -1;
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
#ifndef HAVE_GETRESGID
|
|
int getresgid(gid_t *rgid, gid_t *egid, gid_t *sgid) {
|
|
*rgid = getgid();
|
|
*egid = getegid();
|
|
*sgid = -1;
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
#ifndef HAVE_SETRESUID
|
|
int setresuid(uid_t ruid, uid_t euid, uid_t suid) {
|
|
return setreuid(ruid, euid);
|
|
}
|
|
#endif
|
|
|
|
#ifndef HAVE_SETRESGID
|
|
int setresgid(gid_t rgid, gid_t egid, gid_t sgid) {
|
|
return setregid(rgid, egid);
|
|
}
|
|
#endif
|
|
|
|
static void removeGroupPrivileges(int showError) {
|
|
gid_t rg, eg, sg;
|
|
check(!getresgid(&rg, &eg, &sg));
|
|
|
|
// Remove all supplementary groups. Allow this command to fail. That could
|
|
// happen if we run as an unprivileged user.
|
|
setgroups(0, (gid_t *)"");
|
|
|
|
if (runAsGroup >= 0) {
|
|
uid_t ru, eu, su;
|
|
check(!getresuid(&ru, &eu, &su));
|
|
|
|
// Try to switch the user-provided group.
|
|
if ((ru && runAsGroup != (int)rg) ||
|
|
setresgid(runAsGroup, runAsGroup, runAsGroup)) {
|
|
if (showError) {
|
|
fatal("[server] Only privileged users can change their group membership!");
|
|
} else {
|
|
_exit(1);
|
|
}
|
|
}
|
|
} else {
|
|
if (rg) {
|
|
// If we were started as a set-gid binary, drop these permissions, now.
|
|
check(!setresgid(rg, rg, rg));
|
|
} else {
|
|
// If we are running as root, switch to "nogroup"
|
|
gid_t ng = getGroupId("nogroup");
|
|
check(!setresgid(ng, ng, ng));
|
|
}
|
|
}
|
|
}
|
|
|
|
void lowerPrivileges(void) {
|
|
uid_t r, e, g;
|
|
check(!getresuid(&r, &e, &g));
|
|
|
|
// Permanently lower all group permissions. We do not actually need these,
|
|
// as we still have "root" user privileges in our saved-uid.
|
|
removeGroupPrivileges(0);
|
|
|
|
// Temporarily lower user privileges. If we used to have "root" privileges,
|
|
// we can later still regain them.
|
|
UNUSED_RETURN(setresuid(-1, -1, 0));
|
|
|
|
if (runAsUser >= 0) {
|
|
// Try to switch to the user-provided user id.
|
|
if (r && runAsUser != (int)r) {
|
|
fatal("[server] Only privileged users can change their user id!");
|
|
}
|
|
check(!setresuid(runAsUser, runAsUser, -1));
|
|
} else {
|
|
if (r) {
|
|
// If we were started as a set-uid binary, temporarily lower these
|
|
// permissions.
|
|
check(!setresuid(r, r, -1));
|
|
} else {
|
|
// If we are running as "root", temporarily switch to "nobody".
|
|
uid_t n = getUserId("nobody");
|
|
check(!setresuid(n, n, -1));
|
|
}
|
|
}
|
|
}
|
|
|
|
void dropPrivileges(void) {
|
|
uid_t r, e, s;
|
|
check(!getresuid(&r, &e, &s));
|
|
|
|
// Drop all group privileges.
|
|
removeGroupPrivileges(1);
|
|
|
|
if (runAsUser >= 0) {
|
|
// Try to switch to the user-provided user id.
|
|
if ((r && runAsUser != (int)r) ||
|
|
setresuid(runAsUser, runAsUser, runAsUser)) {
|
|
fatal("[server] Only privileged users can change their user id!");
|
|
}
|
|
} else {
|
|
if (r) {
|
|
// If we were started as a set-uid binary, permanently drop these
|
|
// permissions.
|
|
check(!setresuid(r, r, r));
|
|
} else {
|
|
// If we are running as "root", permanently switch to "nobody".
|
|
uid_t n = getUserId("nobody");
|
|
check(!setresuid(n, n, n));
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifndef HAVE_GETPWUID_R
|
|
// This is a not-thread-safe replacement for getpwuid_r()
|
|
#define getpwuid_r x_getpwuid_r
|
|
static int getpwuid_r(uid_t uid, struct passwd *pwd, char *buf, size_t buflen,
|
|
struct passwd **result) {
|
|
if (result) {
|
|
*result = NULL;
|
|
}
|
|
if (!pwd) {
|
|
return -1;
|
|
}
|
|
errno = 0;
|
|
struct passwd *p = getpwuid(uid);
|
|
if (!p) {
|
|
return errno ? -1 : 0;
|
|
}
|
|
*pwd = *p;
|
|
if (result) {
|
|
*result = pwd;
|
|
}
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
const char *getUserName(uid_t uid) {
|
|
struct passwd pwbuf, *pw;
|
|
char *buf;
|
|
#ifdef _SC_GETPW_R_SIZE_MAX
|
|
int len = sysconf(_SC_GETPW_R_SIZE_MAX);
|
|
if (len <= 0) {
|
|
len = 4096;
|
|
}
|
|
#else
|
|
int len = 4096;
|
|
#endif
|
|
check(buf = malloc(len));
|
|
char *user;
|
|
if (getpwuid_r(uid, &pwbuf, buf, len, &pw) || !pw) {
|
|
check(user = malloc(32));
|
|
snprintf(user, 32, "%d", uid);
|
|
} else {
|
|
check(user = strdup(pw->pw_name));
|
|
}
|
|
free(buf);
|
|
return user;
|
|
}
|
|
|
|
#ifndef HAVE_GETPWNAM_R
|
|
// This is a not-thread-safe replacement for getpwnam_r()
|
|
#define getpwnam_r x_getpwnam_r
|
|
static int getpwnam_r(const char *name, struct passwd *pwd, char *buf,
|
|
size_t buflen, struct passwd **result) {
|
|
if (result) {
|
|
*result = NULL;
|
|
}
|
|
if (!pwd) {
|
|
return -1;
|
|
}
|
|
errno = 0;
|
|
struct passwd *p = getpwnam(name);
|
|
if (!p) {
|
|
return errno ? -1 : 0;
|
|
}
|
|
*pwd = *p;
|
|
if (result) {
|
|
*result = pwd;
|
|
}
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
uid_t getUserId(const char *name) {
|
|
struct passwd pwbuf, *pw;
|
|
char *buf;
|
|
#ifdef _SC_GETPW_R_SIZE_MAX
|
|
int len = sysconf(_SC_GETPW_R_SIZE_MAX);
|
|
if (len <= 0) {
|
|
len = 4096;
|
|
}
|
|
#else
|
|
int len = 4096;
|
|
#endif
|
|
check(buf = malloc(len));
|
|
if (getpwnam_r(name, &pwbuf, buf, len, &pw) || !pw) {
|
|
fatal("[server] Cannot look up user id \"%s\"!", name);
|
|
}
|
|
uid_t uid = pw->pw_uid;
|
|
free(buf);
|
|
return uid;
|
|
}
|
|
|
|
uid_t parseUserArg(const char *arg, const char **name) {
|
|
char *end;
|
|
errno = 0;
|
|
unsigned long l = strtoul(arg, &end, 10);
|
|
if (errno || l > INT_MAX || *end) {
|
|
if (name) {
|
|
check(*name = strdup(arg));
|
|
}
|
|
return getUserId(arg);
|
|
} else {
|
|
if (name) {
|
|
*name = getUserName((uid_t)l);
|
|
}
|
|
return (uid_t)l;
|
|
}
|
|
}
|
|
|
|
#ifndef HAVE_GETGRGID_R
|
|
// This is a not-thread-safe replacement for getgrgid_r()
|
|
#define getgrgid_r x_getgrgid_r
|
|
static int getgrgid_r(gid_t gid, struct group *grp, char *buf, size_t buflen,
|
|
struct group **result) {
|
|
if (result) {
|
|
*result = NULL;
|
|
}
|
|
if (!grp) {
|
|
return -1;
|
|
}
|
|
errno = 0;
|
|
struct group *g = getgrgid(gid);
|
|
if (!g) {
|
|
return errno ? -1 : 0;
|
|
}
|
|
*grp = *g;
|
|
if (result) {
|
|
*result = grp;
|
|
}
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
const char *getGroupName(gid_t gid) {
|
|
struct group grbuf, *gr;
|
|
char *buf;
|
|
#ifdef _SC_GETGR_R_SIZE_MAX
|
|
int len = sysconf(_SC_GETGR_R_SIZE_MAX);
|
|
if (len <= 0) {
|
|
len = 4096;
|
|
}
|
|
#else
|
|
int len = 4096;
|
|
#endif
|
|
check(buf = malloc(len));
|
|
char *group;
|
|
if (getgrgid_r(gid, &grbuf, buf, len, &gr) || !gr) {
|
|
check(group = malloc(32));
|
|
snprintf(group, 32, "%d", gid);
|
|
} else {
|
|
check(group = strdup(gr->gr_name));
|
|
}
|
|
free(buf);
|
|
return group;
|
|
}
|
|
|
|
#ifndef HAVE_GETGRNAM_R
|
|
// This is a not-thread-safe replacement for getgrnam_r()
|
|
#define getgrnam_r x_getgrnam_r
|
|
static int getgrnam_r(const char *name, struct group *grp, char *buf,
|
|
size_t buflen, struct group **result) {
|
|
if (result) {
|
|
*result = NULL;
|
|
}
|
|
if (!grp) {
|
|
return -1;
|
|
}
|
|
errno = 0;
|
|
struct group *g = getgrnam(name);
|
|
if (!g) {
|
|
return errno ? -1 : 0;
|
|
}
|
|
*grp = *g;
|
|
if (result) {
|
|
*result = grp;
|
|
}
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
gid_t getGroupId(const char *name) {
|
|
static const long gr_max = 64 * 1024;
|
|
struct group grbuf, *gr;
|
|
char *temp;
|
|
char *buf;
|
|
int ret;
|
|
int gr_baselen;
|
|
#ifdef _SC_GETGR_R_SIZE_MAX
|
|
int gr_len = sysconf(_SC_GETGR_R_SIZE_MAX);
|
|
if (gr_len <= 0) {
|
|
gr_len = 4096;
|
|
}
|
|
#else
|
|
int gr_len = 4096;
|
|
#endif
|
|
gr_baselen = gr_len;
|
|
check(buf = malloc(gr_len));
|
|
for(;;) {
|
|
errno = 0;
|
|
ret = getgrnam_r(name, &grbuf, buf, gr_len, &gr);
|
|
if(!ret) {
|
|
if(gr) {
|
|
break;
|
|
} else if(!strcmp(name, "nogroup")) {
|
|
// Maybe, this system does not have a "nogroup" group. Substitute the
|
|
// group of the "nobody" user.
|
|
struct passwd pwbuf, *pw;
|
|
#ifdef _SC_GETPW_R_SIZE_MAX
|
|
int pw_len = sysconf(_SC_GETPW_R_SIZE_MAX);
|
|
if (pw_len <= 0) {
|
|
pw_len = 4096;
|
|
}
|
|
#else
|
|
int pw_len = 4096;
|
|
#endif
|
|
if (pw_len > gr_len) {
|
|
check(buf = realloc(buf, pw_len));
|
|
}
|
|
if (!getpwnam_r("nobody", &pwbuf, buf, pw_len, &pw) && pw) {
|
|
debug("[server] Substituting \"nobody\"'s primary group for \"nogroup\"");
|
|
gid_t gid = pw->pw_gid;
|
|
free(buf);
|
|
return gid;
|
|
}
|
|
}
|
|
}
|
|
if (ret && errno == ERANGE) {
|
|
if ((gr_len + gr_baselen) < gr_len || (gr_len + gr_baselen) > gr_max) {
|
|
fatal("[server] Cannot look up group \"%s\"! [buffer limit reached]", name);
|
|
break;
|
|
}
|
|
// grow the buffer by 'gr_baselen' each time getgrnam_r fails
|
|
gr_len += gr_baselen;
|
|
check(temp = realloc (buf, gr_len));
|
|
buf = temp;
|
|
continue;
|
|
}
|
|
fatal("[server] Cannot look up group \"%s\"", name);
|
|
}
|
|
gid_t gid = gr->gr_gid;
|
|
free(buf);
|
|
return gid;
|
|
}
|
|
|
|
gid_t parseGroupArg(const char *arg, const char **name) {
|
|
char *end;
|
|
errno = 0;
|
|
unsigned long l = strtoul(arg, &end, 10);
|
|
if (errno || l > INT_MAX || *end) {
|
|
if (name) {
|
|
check(*name = strdup(arg));
|
|
}
|
|
return getGroupId(arg);
|
|
} else {
|
|
if (name) {
|
|
*name = getGroupName((uid_t)l);
|
|
}
|
|
return (uid_t)l;
|
|
}
|
|
}
|