]>
Commit | Line | Data |
---|---|---|
7460295f | 1 | // privileges.c -- Manage process privileges |
bc83b450 | 2 | // Copyright (C) 2008-2010 Markus Gutschke <markus@shellinabox.com> |
7460295f MG |
3 | // |
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. | |
7 | // | |
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. | |
12 | // | |
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. | |
16 | // | |
17 | // In addition to these license terms, the author grants the following | |
18 | // additional rights: | |
19 | // | |
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. | |
28 | // | |
29 | // You may at your option choose to remove this additional permission from | |
30 | // the work, or from any part of it. | |
31 | // | |
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: | |
35 | // | |
36 | // This product includes software developed by the OpenSSL Project | |
37 | // for use in the OpenSSL Toolkit. (http://www.openssl.org/) | |
38 | // | |
39 | // This product includes cryptographic software written by Eric Young | |
40 | // (eay@cryptsoft.com) | |
41 | // | |
42 | // | |
43 | // The most up-to-date version of this program is always available from | |
44 | // http://shellinabox.com | |
45 | ||
46 | #define _GNU_SOURCE | |
bdd01e84 | 47 | #include "config.h" |
7460295f MG |
48 | |
49 | #include <errno.h> | |
50 | #include <grp.h> | |
51 | #include <limits.h> | |
52 | #include <pwd.h> | |
53 | #include <stdio.h> | |
54 | #include <stdlib.h> | |
55 | #include <string.h> | |
56 | #include <sys/types.h> | |
57 | #include <unistd.h> | |
58 | ||
59 | #include "shellinabox/privileges.h" | |
60 | #include "logging/logging.h" | |
61 | ||
62 | int runAsUser = -1; | |
63 | int runAsGroup = -1; | |
7460295f MG |
64 | |
65 | ||
242e6c5b MG |
66 | #ifndef HAVE_GETRESUID |
67 | int getresuid(uid_t *ruid, uid_t *euid, uid_t *suid) { | |
68 | *ruid = getuid(); | |
69 | *euid = geteuid(); | |
70 | *suid = -1; | |
71 | return 0; | |
72 | } | |
73 | #endif | |
74 | ||
75 | #ifndef HAVE_GETRESGID | |
76 | int getresgid(gid_t *rgid, gid_t *egid, gid_t *sgid) { | |
77 | *rgid = getgid(); | |
78 | *egid = getegid(); | |
79 | *sgid = -1; | |
80 | return 0; | |
81 | } | |
82 | #endif | |
83 | ||
84 | #ifndef HAVE_SETRESUID | |
85 | int setresuid(uid_t ruid, uid_t euid, uid_t suid) { | |
86 | return setreuid(ruid, euid); | |
87 | } | |
88 | #endif | |
89 | ||
90 | #ifndef HAVE_SETRESGID | |
91 | int setresgid(gid_t rgid, gid_t egid, gid_t sgid) { | |
92 | return setregid(rgid, egid); | |
93 | } | |
94 | #endif | |
95 | ||
d1edcc0e MG |
96 | static void removeGroupPrivileges(int showError) { |
97 | gid_t rg, eg, sg; | |
98 | check(!getresgid(&rg, &eg, &sg)); | |
99 | ||
7460295f MG |
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 *)""); | |
d1edcc0e | 103 | |
7460295f | 104 | if (runAsGroup >= 0) { |
d1edcc0e MG |
105 | uid_t ru, eu, su; |
106 | getresuid(&ru, &eu, &su); | |
107 | ||
7460295f | 108 | // Try to switch the user-provided group. |
bc83b450 | 109 | if ((ru && runAsGroup != (int)rg) || |
d1edcc0e MG |
110 | setresgid(runAsGroup, runAsGroup, runAsGroup)) { |
111 | if (showError) { | |
7460295f | 112 | fatal("Only privileged users can change their group memberships"); |
d1edcc0e MG |
113 | } else { |
114 | _exit(1); | |
7460295f MG |
115 | } |
116 | } | |
117 | } else { | |
d1edcc0e | 118 | if (rg) { |
7460295f | 119 | // If we were started as a set-gid binary, drop these permissions, now. |
d1edcc0e | 120 | check(!setresgid(rg, rg, rg)); |
7460295f MG |
121 | } else { |
122 | // If we are running as root, switch to "nogroup" | |
d1edcc0e MG |
123 | gid_t ng = getGroupId("nogroup"); |
124 | check(!setresgid(ng, ng, ng)); | |
7460295f MG |
125 | } |
126 | } | |
127 | } | |
128 | ||
129 | void lowerPrivileges(void) { | |
d1edcc0e MG |
130 | uid_t r, e, g; |
131 | check(!getresuid(&r, &e, &g)); | |
132 | ||
7460295f MG |
133 | // Permanently lower all group permissions. We do not actually need these, |
134 | // as we still have "root" user privileges in our saved-uid. | |
d1edcc0e | 135 | removeGroupPrivileges(0); |
7460295f MG |
136 | |
137 | // Temporarily lower user privileges. If we used to have "root" privileges, | |
138 | // we can later still regain them. | |
139 | setresuid(-1, -1, 0); | |
140 | ||
141 | if (runAsUser >= 0) { | |
142 | // Try to switch to the user-provided user id. | |
bc83b450 | 143 | if (r && runAsUser != (int)r) { |
d1edcc0e MG |
144 | fatal("Only privileged users can change their user id"); |
145 | } | |
7460295f MG |
146 | check(!setresuid(runAsUser, runAsUser, -1)); |
147 | } else { | |
7460295f MG |
148 | if (r) { |
149 | // If we were started as a set-uid binary, temporarily lower these | |
150 | // permissions. | |
151 | check(!setresuid(r, r, -1)); | |
152 | } else { | |
153 | // If we are running as "root", temporarily switch to "nobody". | |
154 | uid_t n = getUserId("nobody"); | |
155 | check(!setresuid(n, n, -1)); | |
156 | } | |
157 | } | |
158 | } | |
159 | ||
160 | void dropPrivileges(void) { | |
d1edcc0e MG |
161 | uid_t r, e, s; |
162 | check(!getresuid(&r, &e, &s)); | |
163 | ||
7460295f | 164 | // Drop all group privileges. |
d1edcc0e | 165 | removeGroupPrivileges(1); |
7460295f MG |
166 | |
167 | if (runAsUser >= 0) { | |
168 | // Try to switch to the user-provided user id. | |
bc83b450 | 169 | if ((r && runAsUser != (int)r) || |
d1edcc0e | 170 | setresuid(runAsUser, runAsUser, runAsUser)) { |
7460295f MG |
171 | fatal("Only privileged users can change their user id."); |
172 | } | |
173 | } else { | |
7460295f MG |
174 | if (r) { |
175 | // If we were started as a set-uid binary, permanently drop these | |
176 | // permissions. | |
177 | check(!setresuid(r, r, r)); | |
178 | } else { | |
179 | // If we are running as "root", permanently switch to "nobody". | |
180 | uid_t n = getUserId("nobody"); | |
181 | check(!setresuid(n, n, n)); | |
182 | } | |
183 | } | |
184 | } | |
185 | ||
572ac014 MG |
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) { | |
191 | if (result) { | |
192 | *result = NULL; | |
193 | } | |
194 | if (!pwd) { | |
195 | return -1; | |
196 | } | |
197 | errno = 0; | |
198 | struct passwd *p = getpwuid(uid); | |
199 | if (!p) { | |
200 | return errno ? -1 : 0; | |
201 | } | |
202 | *pwd = *p; | |
203 | if (result) { | |
204 | *result = pwd; | |
205 | } | |
206 | return 0; | |
207 | } | |
208 | #endif | |
209 | ||
7460295f MG |
210 | const char *getUserName(uid_t uid) { |
211 | struct passwd pwbuf, *pw; | |
212 | char *buf; | |
572ac014 | 213 | #ifdef _SC_GETPW_R_SIZE_MAX |
7460295f | 214 | int len = sysconf(_SC_GETPW_R_SIZE_MAX); |
29135474 MG |
215 | if (len <= 0) { |
216 | len = 4096; | |
217 | } | |
572ac014 MG |
218 | #else |
219 | int len = 4096; | |
220 | #endif | |
7460295f MG |
221 | check(buf = malloc(len)); |
222 | char *user; | |
223 | if (getpwuid_r(uid, &pwbuf, buf, len, &pw) || !pw) { | |
224 | check(user = malloc(32)); | |
225 | snprintf(user, 32, "%d", uid); | |
226 | } else { | |
227 | check(user = strdup(pw->pw_name)); | |
228 | } | |
229 | free(buf); | |
230 | return user; | |
231 | } | |
232 | ||
572ac014 MG |
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) { | |
238 | if (result) { | |
239 | *result = NULL; | |
240 | } | |
241 | if (!pwd) { | |
242 | return -1; | |
243 | } | |
244 | errno = 0; | |
245 | struct passwd *p = getpwnam(name); | |
246 | if (!p) { | |
247 | return errno ? -1 : 0; | |
248 | } | |
249 | *pwd = *p; | |
250 | if (result) { | |
251 | *result = pwd; | |
252 | } | |
253 | return 0; | |
254 | } | |
255 | #endif | |
256 | ||
7460295f MG |
257 | uid_t getUserId(const char *name) { |
258 | struct passwd pwbuf, *pw; | |
259 | char *buf; | |
572ac014 | 260 | #ifdef _SC_GETPW_R_SIZE_MAX |
29135474 MG |
261 | int len = sysconf(_SC_GETPW_R_SIZE_MAX); |
262 | if (len <= 0) { | |
263 | len = 4096; | |
264 | } | |
572ac014 MG |
265 | #else |
266 | int len = 4096; | |
267 | #endif | |
29135474 | 268 | check(buf = malloc(len)); |
7460295f MG |
269 | if (getpwnam_r(name, &pwbuf, buf, len, &pw) || !pw) { |
270 | fatal("Cannot look up user id \"%s\"", name); | |
271 | } | |
29135474 | 272 | uid_t uid = pw->pw_uid; |
7460295f MG |
273 | free(buf); |
274 | return uid; | |
275 | } | |
276 | ||
4aec9144 | 277 | uid_t parseUserArg(const char *arg, const char **name) { |
7460295f MG |
278 | char *end; |
279 | errno = 0; | |
280 | unsigned long l = strtoul(arg, &end, 10); | |
281 | if (errno || l > INT_MAX || *end) { | |
282 | if (name) { | |
283 | check(*name = strdup(arg)); | |
284 | } | |
285 | return getUserId(arg); | |
286 | } else { | |
287 | if (name) { | |
288 | *name = getUserName((uid_t)l); | |
289 | } | |
290 | return (uid_t)l; | |
291 | } | |
292 | } | |
293 | ||
572ac014 MG |
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) { | |
299 | if (result) { | |
300 | *result = NULL; | |
301 | } | |
302 | if (!grp) { | |
303 | return -1; | |
304 | } | |
305 | errno = 0; | |
306 | struct group *g = getgrgid(gid); | |
307 | if (!g) { | |
308 | return errno ? -1 : 0; | |
309 | } | |
310 | *grp = *g; | |
311 | if (result) { | |
312 | *result = grp; | |
313 | } | |
314 | return 0; | |
315 | } | |
316 | #endif | |
317 | ||
7460295f MG |
318 | const char *getGroupName(gid_t gid) { |
319 | struct group grbuf, *gr; | |
320 | char *buf; | |
572ac014 | 321 | #ifdef _SC_GETGR_R_SIZE_MAX |
7460295f | 322 | int len = sysconf(_SC_GETGR_R_SIZE_MAX); |
29135474 MG |
323 | if (len <= 0) { |
324 | len = 4096; | |
325 | } | |
572ac014 MG |
326 | #else |
327 | int len = 4096; | |
328 | #endif | |
7460295f MG |
329 | check(buf = malloc(len)); |
330 | char *group; | |
331 | if (getgrgid_r(gid, &grbuf, buf, len, &gr) || !gr) { | |
332 | check(group = malloc(32)); | |
333 | snprintf(group, 32, "%d", gid); | |
334 | } else { | |
335 | check(group = strdup(gr->gr_name)); | |
336 | } | |
337 | free(buf); | |
338 | return group; | |
339 | } | |
340 | ||
572ac014 MG |
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) { | |
346 | if (result) { | |
347 | *result = NULL; | |
348 | } | |
349 | if (!grp) { | |
350 | return -1; | |
351 | } | |
352 | errno = 0; | |
353 | struct group *g = getgrnam(name); | |
354 | if (!g) { | |
355 | return errno ? -1 : 0; | |
356 | } | |
357 | *grp = *g; | |
358 | if (result) { | |
359 | *result = grp; | |
360 | } | |
361 | return 0; | |
362 | } | |
363 | #endif | |
364 | ||
7460295f MG |
365 | gid_t getGroupId(const char *name) { |
366 | struct group grbuf, *gr; | |
367 | char *buf; | |
572ac014 | 368 | #ifdef _SC_GETGR_R_SIZE_MAX |
8bcba5ef MG |
369 | int gr_len = sysconf(_SC_GETGR_R_SIZE_MAX); |
370 | if (gr_len <= 0) { | |
371 | gr_len = 4096; | |
29135474 | 372 | } |
572ac014 MG |
373 | #else |
374 | int gr_len = 4096; | |
375 | #endif | |
8bcba5ef MG |
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; | |
572ac014 | 382 | #ifdef _SC_GETPW_R_SIZE_MAX |
8bcba5ef MG |
383 | int pw_len = sysconf(_SC_GETPW_R_SIZE_MAX); |
384 | if (pw_len <= 0) { | |
385 | pw_len = 4096; | |
386 | } | |
572ac014 MG |
387 | #else |
388 | int pw_len = 4096; | |
389 | #endif | |
8bcba5ef MG |
390 | if (pw_len > gr_len) { |
391 | check(buf = realloc(buf, pw_len)); | |
392 | } | |
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; | |
396 | free(buf); | |
397 | return gid; | |
398 | } | |
399 | } | |
7460295f MG |
400 | fatal("Cannot look up group \"%s\"", name); |
401 | } | |
402 | gid_t gid = gr->gr_gid; | |
403 | free(buf); | |
404 | return gid; | |
405 | } | |
406 | ||
4aec9144 | 407 | gid_t parseGroupArg(const char *arg, const char **name) { |
7460295f MG |
408 | char *end; |
409 | errno = 0; | |
410 | unsigned long l = strtoul(arg, &end, 10); | |
411 | if (errno || l > INT_MAX || *end) { | |
412 | if (name) { | |
413 | check(*name = strdup(arg)); | |
414 | } | |
415 | return getGroupId(arg); | |
416 | } else { | |
417 | if (name) { | |
418 | *name = getGroupName((uid_t)l); | |
419 | } | |
420 | return (uid_t)l; | |
421 | } | |
422 | } |