1 // privileges.c -- Manage process privileges
2 // Copyright (C) 2008-2010 Markus Gutschke <markus@shellinabox.com>
4 // This program is free software; you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License version 2 as
6 // published by the Free Software Foundation.
8 // This program is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 // GNU General Public License for more details.
13 // You should have received a copy of the GNU General Public License along
14 // with this program; if not, write to the Free Software Foundation, Inc.,
15 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17 // In addition to these license terms, the author grants the following
20 // If you modify this program, or any covered work, by linking or
21 // combining it with the OpenSSL project's OpenSSL library (or a
22 // modified version of that library), containing parts covered by the
23 // terms of the OpenSSL or SSLeay licenses, the author
24 // grants you additional permission to convey the resulting work.
25 // Corresponding Source for a non-source form of such a combination
26 // shall include the source code for the parts of OpenSSL used as well
27 // as that of the covered work.
29 // You may at your option choose to remove this additional permission from
30 // the work, or from any part of it.
32 // It is possible to build this program in a way that it loads OpenSSL
33 // libraries at run-time. If doing so, the following notices are required
34 // by the OpenSSL and SSLeay licenses:
36 // This product includes software developed by the OpenSSL Project
37 // for use in the OpenSSL Toolkit. (http://www.openssl.org/)
39 // This product includes cryptographic software written by Eric Young
40 // (eay@cryptsoft.com)
43 // The most up-to-date version of this program is always available from
44 // http://shellinabox.com
56 #include <sys/types.h>
59 #include "shellinabox/privileges.h"
60 #include "logging/logging.h"
66 #ifndef HAVE_GETRESUID
67 int getresuid(uid_t *ruid, uid_t *euid, uid_t *suid) {
75 #ifndef HAVE_GETRESGID
76 int getresgid(gid_t *rgid, gid_t *egid, gid_t *sgid) {
84 #ifndef HAVE_SETRESUID
85 int setresuid(uid_t ruid, uid_t euid, uid_t suid) {
86 return setreuid(ruid, euid);
90 #ifndef HAVE_SETRESGID
91 int setresgid(gid_t rgid, gid_t egid, gid_t sgid) {
92 return setregid(rgid, egid);
96 static void removeGroupPrivileges(int showError) {
98 check(!getresgid(&rg, &eg, &sg));
100 // Remove all supplementary groups. Allow this command to fail. That could
101 // happen if we run as an unprivileged user.
102 setgroups(0, (gid_t *)"");
104 if (runAsGroup >= 0) {
106 getresuid(&ru, &eu, &su);
108 // Try to switch the user-provided group.
109 if ((ru && runAsGroup != (int)rg) ||
110 setresgid(runAsGroup, runAsGroup, runAsGroup)) {
112 fatal("Only privileged users can change their group memberships");
119 // If we were started as a set-gid binary, drop these permissions, now.
120 check(!setresgid(rg, rg, rg));
122 // If we are running as root, switch to "nogroup"
123 gid_t ng = getGroupId("nogroup");
124 check(!setresgid(ng, ng, ng));
129 void lowerPrivileges(void) {
131 check(!getresuid(&r, &e, &g));
133 // Permanently lower all group permissions. We do not actually need these,
134 // as we still have "root" user privileges in our saved-uid.
135 removeGroupPrivileges(0);
137 // Temporarily lower user privileges. If we used to have "root" privileges,
138 // we can later still regain them.
139 setresuid(-1, -1, 0);
141 if (runAsUser >= 0) {
142 // Try to switch to the user-provided user id.
143 if (r && runAsUser != (int)r) {
144 fatal("Only privileged users can change their user id");
146 check(!setresuid(runAsUser, runAsUser, -1));
149 // If we were started as a set-uid binary, temporarily lower these
151 check(!setresuid(r, r, -1));
153 // If we are running as "root", temporarily switch to "nobody".
154 uid_t n = getUserId("nobody");
155 check(!setresuid(n, n, -1));
160 void dropPrivileges(void) {
162 check(!getresuid(&r, &e, &s));
164 // Drop all group privileges.
165 removeGroupPrivileges(1);
167 if (runAsUser >= 0) {
168 // Try to switch to the user-provided user id.
169 if ((r && runAsUser != (int)r) ||
170 setresuid(runAsUser, runAsUser, runAsUser)) {
171 fatal("Only privileged users can change their user id.");
175 // If we were started as a set-uid binary, permanently drop these
177 check(!setresuid(r, r, r));
179 // If we are running as "root", permanently switch to "nobody".
180 uid_t n = getUserId("nobody");
181 check(!setresuid(n, n, n));
186 #ifndef HAVE_GETPWUID_R
187 // This is a not-thread-safe replacement for getpwuid_r()
188 #define getpwuid_r x_getpwuid_r
189 static int getpwuid_r(uid_t uid, struct passwd *pwd, char *buf, size_t buflen,
190 struct passwd **result) {
198 struct passwd *p = getpwuid(uid);
200 return errno ? -1 : 0;
210 const char *getUserName(uid_t uid) {
211 struct passwd pwbuf, *pw;
213 #ifdef _SC_GETPW_R_SIZE_MAX
214 int len = sysconf(_SC_GETPW_R_SIZE_MAX);
221 check(buf = malloc(len));
223 if (getpwuid_r(uid, &pwbuf, buf, len, &pw) || !pw) {
224 check(user = malloc(32));
225 snprintf(user, 32, "%d", uid);
227 check(user = strdup(pw->pw_name));
233 #ifndef HAVE_GETPWNAM_R
234 // This is a not-thread-safe replacement for getpwnam_r()
235 #define getpwnam_r x_getpwnam_r
236 static int getpwnam_r(const char *name, struct passwd *pwd, char *buf,
237 size_t buflen, struct passwd **result) {
245 struct passwd *p = getpwnam(name);
247 return errno ? -1 : 0;
257 uid_t getUserId(const char *name) {
258 struct passwd pwbuf, *pw;
260 #ifdef _SC_GETPW_R_SIZE_MAX
261 int len = sysconf(_SC_GETPW_R_SIZE_MAX);
268 check(buf = malloc(len));
269 if (getpwnam_r(name, &pwbuf, buf, len, &pw) || !pw) {
270 fatal("Cannot look up user id \"%s\"", name);
272 uid_t uid = pw->pw_uid;
277 uid_t parseUserArg(const char *arg, const char **name) {
280 unsigned long l = strtoul(arg, &end, 10);
281 if (errno || l > INT_MAX || *end) {
283 check(*name = strdup(arg));
285 return getUserId(arg);
288 *name = getUserName((uid_t)l);
294 #ifndef HAVE_GETGRGID_R
295 // This is a not-thread-safe replacement for getgrgid_r()
296 #define getgrgid_r x_getgrgid_r
297 static int getgrgid_r(gid_t gid, struct group *grp, char *buf, size_t buflen,
298 struct group **result) {
306 struct group *g = getgrgid(gid);
308 return errno ? -1 : 0;
318 const char *getGroupName(gid_t gid) {
319 struct group grbuf, *gr;
321 #ifdef _SC_GETGR_R_SIZE_MAX
322 int len = sysconf(_SC_GETGR_R_SIZE_MAX);
329 check(buf = malloc(len));
331 if (getgrgid_r(gid, &grbuf, buf, len, &gr) || !gr) {
332 check(group = malloc(32));
333 snprintf(group, 32, "%d", gid);
335 check(group = strdup(gr->gr_name));
341 #ifndef HAVE_GETGRNAM_R
342 // This is a not-thread-safe replacement for getgrnam_r()
343 #define getgrnam_r x_getgrnam_r
344 static int getgrnam_r(const char *name, struct group *grp, char *buf,
345 size_t buflen, struct group **result) {
353 struct group *g = getgrnam(name);
355 return errno ? -1 : 0;
365 gid_t getGroupId(const char *name) {
366 struct group grbuf, *gr;
368 #ifdef _SC_GETGR_R_SIZE_MAX
369 int gr_len = sysconf(_SC_GETGR_R_SIZE_MAX);
376 check(buf = malloc(gr_len));
377 if (getgrnam_r(name, &grbuf, buf, gr_len, &gr) || !gr) {
378 // Maybe, this system does not have a "nogroup" group. Substitute the
379 // group of the "nobody" user.
380 if (!strcmp(name, "nogroup")) {
381 struct passwd pwbuf, *pw;
382 #ifdef _SC_GETPW_R_SIZE_MAX
383 int pw_len = sysconf(_SC_GETPW_R_SIZE_MAX);
390 if (pw_len > gr_len) {
391 check(buf = realloc(buf, pw_len));
393 if (!getpwnam_r("nobody", &pwbuf, buf, pw_len, &pw) && pw) {
394 debug("Substituting \"nobody's\" primary group for \"nogroup\"");
395 gid_t gid = pw->pw_gid;
400 fatal("Cannot look up group \"%s\"", name);
402 gid_t gid = gr->gr_gid;
407 gid_t parseGroupArg(const char *arg, const char **name) {
410 unsigned long l = strtoul(arg, &end, 10);
411 if (errno || l > INT_MAX || *end) {
413 check(*name = strdup(arg));
415 return getGroupId(arg);
418 *name = getGroupName((uid_t)l);