shellinabox/shellinabox/privileges.c
KLuka 6c9f98bf34 Logging and debuging
* 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.
2015-08-23 19:25:36 +02:00

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;
}
}