1 // privileges.c -- Manage process privileges
2 // Copyright (C) 2008-2009 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 static void removeGroupPrivileges(int showError) {
68 check(!getresgid(&rg, &eg, &sg));
70 // Remove all supplementary groups. Allow this command to fail. That could
71 // happen if we run as an unprivileged user.
72 setgroups(0, (gid_t *)"");
74 if (runAsGroup >= 0) {
76 getresuid(&ru, &eu, &su);
78 // Try to switch the user-provided group.
79 if ((ru && runAsGroup != rg) ||
80 setresgid(runAsGroup, runAsGroup, runAsGroup)) {
82 fatal("Only privileged users can change their group memberships");
89 // If we were started as a set-gid binary, drop these permissions, now.
90 check(!setresgid(rg, rg, rg));
92 // If we are running as root, switch to "nogroup"
93 gid_t ng = getGroupId("nogroup");
94 check(!setresgid(ng, ng, ng));
99 void lowerPrivileges(void) {
101 check(!getresuid(&r, &e, &g));
103 // Permanently lower all group permissions. We do not actually need these,
104 // as we still have "root" user privileges in our saved-uid.
105 removeGroupPrivileges(0);
107 // Temporarily lower user privileges. If we used to have "root" privileges,
108 // we can later still regain them.
109 setresuid(-1, -1, 0);
111 if (runAsUser >= 0) {
112 // Try to switch to the user-provided user id.
113 if (r && runAsUser != r) {
114 fatal("Only privileged users can change their user id");
116 check(!setresuid(runAsUser, runAsUser, -1));
119 // If we were started as a set-uid binary, temporarily lower these
121 check(!setresuid(r, r, -1));
123 // If we are running as "root", temporarily switch to "nobody".
124 uid_t n = getUserId("nobody");
125 check(!setresuid(n, n, -1));
130 void dropPrivileges(void) {
132 check(!getresuid(&r, &e, &s));
134 // Drop all group privileges.
135 removeGroupPrivileges(1);
137 if (runAsUser >= 0) {
138 // Try to switch to the user-provided user id.
139 if ((r && runAsUser != r) ||
140 setresuid(runAsUser, runAsUser, runAsUser)) {
141 fatal("Only privileged users can change their user id.");
145 // If we were started as a set-uid binary, permanently drop these
147 check(!setresuid(r, r, r));
149 // If we are running as "root", permanently switch to "nobody".
150 uid_t n = getUserId("nobody");
151 check(!setresuid(n, n, n));
156 #ifndef HAVE_GETPWUID_R
157 // This is a not-thread-safe replacement for getpwuid_r()
158 #define getpwuid_r x_getpwuid_r
159 static int getpwuid_r(uid_t uid, struct passwd *pwd, char *buf, size_t buflen,
160 struct passwd **result) {
168 struct passwd *p = getpwuid(uid);
170 return errno ? -1 : 0;
180 const char *getUserName(uid_t uid) {
181 struct passwd pwbuf, *pw;
183 #ifdef _SC_GETPW_R_SIZE_MAX
184 int len = sysconf(_SC_GETPW_R_SIZE_MAX);
191 check(buf = malloc(len));
193 if (getpwuid_r(uid, &pwbuf, buf, len, &pw) || !pw) {
194 check(user = malloc(32));
195 snprintf(user, 32, "%d", uid);
197 check(user = strdup(pw->pw_name));
203 #ifndef HAVE_GETPWNAM_R
204 // This is a not-thread-safe replacement for getpwnam_r()
205 #define getpwnam_r x_getpwnam_r
206 static int getpwnam_r(const char *name, struct passwd *pwd, char *buf,
207 size_t buflen, struct passwd **result) {
215 struct passwd *p = getpwnam(name);
217 return errno ? -1 : 0;
227 uid_t getUserId(const char *name) {
228 struct passwd pwbuf, *pw;
230 #ifdef _SC_GETPW_R_SIZE_MAX
231 int len = sysconf(_SC_GETPW_R_SIZE_MAX);
238 check(buf = malloc(len));
239 if (getpwnam_r(name, &pwbuf, buf, len, &pw) || !pw) {
240 fatal("Cannot look up user id \"%s\"", name);
242 uid_t uid = pw->pw_uid;
247 uid_t parseUser(const char *arg, const char **name) {
250 unsigned long l = strtoul(arg, &end, 10);
251 if (errno || l > INT_MAX || *end) {
253 check(*name = strdup(arg));
255 return getUserId(arg);
258 *name = getUserName((uid_t)l);
264 #ifndef HAVE_GETGRGID_R
265 // This is a not-thread-safe replacement for getgrgid_r()
266 #define getgrgid_r x_getgrgid_r
267 static int getgrgid_r(gid_t gid, struct group *grp, char *buf, size_t buflen,
268 struct group **result) {
276 struct group *g = getgrgid(gid);
278 return errno ? -1 : 0;
288 const char *getGroupName(gid_t gid) {
289 struct group grbuf, *gr;
291 #ifdef _SC_GETGR_R_SIZE_MAX
292 int len = sysconf(_SC_GETGR_R_SIZE_MAX);
299 check(buf = malloc(len));
301 if (getgrgid_r(gid, &grbuf, buf, len, &gr) || !gr) {
302 check(group = malloc(32));
303 snprintf(group, 32, "%d", gid);
305 check(group = strdup(gr->gr_name));
311 #ifndef HAVE_GETGRNAM_R
312 // This is a not-thread-safe replacement for getgrnam_r()
313 #define getgrnam_r x_getgrnam_r
314 static int getgrnam_r(const char *name, struct group *grp, char *buf,
315 size_t buflen, struct group **result) {
323 struct group *g = getgrnam(name);
325 return errno ? -1 : 0;
335 gid_t getGroupId(const char *name) {
336 struct group grbuf, *gr;
338 #ifdef _SC_GETGR_R_SIZE_MAX
339 int gr_len = sysconf(_SC_GETGR_R_SIZE_MAX);
346 check(buf = malloc(gr_len));
347 if (getgrnam_r(name, &grbuf, buf, gr_len, &gr) || !gr) {
348 // Maybe, this system does not have a "nogroup" group. Substitute the
349 // group of the "nobody" user.
350 if (!strcmp(name, "nogroup")) {
351 struct passwd pwbuf, *pw;
352 #ifdef _SC_GETPW_R_SIZE_MAX
353 int pw_len = sysconf(_SC_GETPW_R_SIZE_MAX);
360 if (pw_len > gr_len) {
361 check(buf = realloc(buf, pw_len));
363 if (!getpwnam_r("nobody", &pwbuf, buf, pw_len, &pw) && pw) {
364 debug("Substituting \"nobody's\" primary group for \"nogroup\"");
365 gid_t gid = pw->pw_gid;
370 fatal("Cannot look up group \"%s\"", name);
372 gid_t gid = gr->gr_gid;
377 gid_t parseGroup(const char *arg, const char **name) {
380 unsigned long l = strtoul(arg, &end, 10);
381 if (errno || l > INT_MAX || *end) {
383 check(*name = strdup(arg));
385 return getGroupId(arg);
388 *name = getGroupName((uid_t)l);