]>
Commit | Line | Data |
---|---|---|
b1195aa0 KZ |
1 | /* |
2 | * su(1) for Linux. Run a shell with substitute user and group IDs. | |
3 | * | |
4 | * Copyright (C) 1992-2006 Free Software Foundation, Inc. | |
5 | * Copyright (C) 2012 SUSE Linux Products GmbH, Nuernberg | |
eb7d0ad0 | 6 | * Copyright (C) 2016-2017 Karel Zak <kzak@redhat.com> |
b1195aa0 KZ |
7 | * |
8 | * This program is free software; you can redistribute it and/or modify it | |
9 | * under the terms of the GNU General Public License as published by the Free | |
10 | * Software Foundation; either version 2, or (at your option) any later | |
11 | * version. | |
12 | * | |
13 | * This program is distributed in the hope that it will be useful, but WITHOUT | |
14 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
15 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | |
16 | * more details. You should have received a copy of the GNU General Public | |
17 | * License along with this program; if not, write to the Free Software | |
18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, | |
19 | * USA. | |
20 | * | |
21 | * | |
22 | * Based on an implementation by David MacKenzie <djm@gnu.ai.mit.edu>. | |
23 | */ | |
cf1a99da KZ |
24 | #include <stdio.h> |
25 | #include <getopt.h> | |
26 | #include <sys/types.h> | |
27 | #include <pwd.h> | |
28 | #include <grp.h> | |
77454e58 | 29 | #include <libgen.h> |
cf1a99da | 30 | #include <security/pam_appl.h> |
fe2c9909 | 31 | #ifdef HAVE_SECURITY_PAM_MISC_H |
b1195aa0 | 32 | # include <security/pam_misc.h> |
fe2c9909 | 33 | #elif defined(HAVE_SECURITY_OPENPAM_H) |
b1195aa0 | 34 | # include <security/openpam.h> |
fe2c9909 | 35 | #endif |
cf1a99da KZ |
36 | #include <signal.h> |
37 | #include <sys/wait.h> | |
38 | #include <syslog.h> | |
b4b919fe | 39 | #include <utmpx.h> |
e5496191 KZ |
40 | #include <sys/time.h> |
41 | ||
42 | #ifdef HAVE_SYS_RESOURCE_H | |
43 | # include <sys/resource.h> | |
44 | #endif | |
cf1a99da | 45 | |
b923bdbe | 46 | #ifdef HAVE_PTY |
eb7d0ad0 KZ |
47 | # include <pty.h> |
48 | # include <poll.h> | |
49 | # include <sys/signalfd.h> | |
b923bdbe | 50 | # include "pty-session.h" |
eb7d0ad0 KZ |
51 | # define USE_PTY |
52 | #endif | |
53 | ||
cf1a99da KZ |
54 | #include "err.h" |
55 | ||
56 | #include <stdbool.h> | |
b1195aa0 | 57 | |
cf1a99da KZ |
58 | #include "c.h" |
59 | #include "xalloc.h" | |
60 | #include "nls.h" | |
61 | #include "pathnames.h" | |
62 | #include "env.h" | |
4e183497 | 63 | #include "closestream.h" |
75efef98 | 64 | #include "strv.h" |
c74a7af1 | 65 | #include "strutils.h" |
bbc5a5ea | 66 | #include "ttyutils.h" |
032d759a | 67 | #include "pwdutils.h" |
75efef98 | 68 | #include "optutils.h" |
cf1a99da | 69 | |
b1195aa0 KZ |
70 | #include "logindefs.h" |
71 | #include "su-common.h" | |
825feef8 | 72 | #include "shells.h" |
b1195aa0 | 73 | |
2260e493 KZ |
74 | #include "debug.h" |
75 | ||
76 | UL_DEBUG_DEFINE_MASK(su); | |
77 | UL_DEBUG_DEFINE_MASKNAMES(su) = UL_DEBUG_EMPTY_MASKNAMES; | |
78 | ||
79 | #define SU_DEBUG_INIT (1 << 1) | |
80 | #define SU_DEBUG_PAM (1 << 2) | |
81 | #define SU_DEBUG_PARENT (1 << 3) | |
82 | #define SU_DEBUG_TTY (1 << 4) | |
83 | #define SU_DEBUG_LOG (1 << 5) | |
84 | #define SU_DEBUG_MISC (1 << 6) | |
85 | #define SU_DEBUG_SIG (1 << 7) | |
eb7d0ad0 | 86 | #define SU_DEBUG_PTY (1 << 8) |
2260e493 KZ |
87 | #define SU_DEBUG_ALL 0xFFFF |
88 | ||
89 | #define DBG(m, x) __UL_DBG(su, SU_DEBUG_, m, x) | |
90 | #define ON_DBG(m, x) __UL_DBG_CALL(su, SU_DEBUG_, m, x) | |
91 | ||
cf1a99da | 92 | /* name of the pam configuration files. separate configs for su and su - */ |
7ec6adb1 KZ |
93 | #define PAM_SRVNAME_SU "su" |
94 | #define PAM_SRVNAME_SU_L "su-l" | |
95 | ||
96 | #define PAM_SRVNAME_RUNUSER "runuser" | |
97 | #define PAM_SRVNAME_RUNUSER_L "runuser-l" | |
98 | ||
9e584ff3 TK |
99 | #ifdef HAVE_LIBECONF |
100 | #define _PATH_LOGINDEFS_SU "default/su" | |
101 | #define _PATH_LOGINDEFS_RUNUSER "default/runuser" | |
102 | #else | |
37410713 KZ |
103 | #define _PATH_LOGINDEFS_SU "/etc/default/su" |
104 | #define _PATH_LOGINDEFS_RUNUSER "/etc/default/runuser" | |
9e584ff3 | 105 | #endif |
cf1a99da KZ |
106 | |
107 | #define is_pam_failure(_rc) ((_rc) != PAM_SUCCESS) | |
108 | ||
cf1a99da KZ |
109 | /* The shell to run if none is given in the user's passwd entry. */ |
110 | #define DEFAULT_SHELL "/bin/sh" | |
111 | ||
112 | /* The user to become if none is specified. */ | |
113 | #define DEFAULT_USER "root" | |
114 | ||
115 | #ifndef HAVE_ENVIRON_DECL | |
116 | extern char **environ; | |
117 | #endif | |
118 | ||
665f36be KZ |
119 | enum { |
120 | SIGTERM_IDX = 0, | |
121 | SIGINT_IDX, | |
122 | SIGQUIT_IDX, | |
123 | ||
124 | SIGNALS_IDX_COUNT | |
125 | }; | |
126 | ||
832f5cd5 KZ |
127 | /* |
128 | * su/runuser control struct | |
129 | */ | |
130 | struct su_context { | |
131 | pam_handle_t *pamh; /* PAM handler */ | |
132 | struct pam_conv conv; /* PAM conversation */ | |
133 | ||
032d759a KZ |
134 | struct passwd *pwd; /* new user info */ |
135 | char *pwdbuf; /* pwd strings */ | |
136 | ||
61653767 | 137 | const char *tty_path; /* tty device path */ |
302b7b65 KZ |
138 | const char *tty_name; /* tty_path without /dev prefix */ |
139 | const char *tty_number; /* end of the tty_path */ | |
140 | ||
94c6730b | 141 | char *new_user; /* wanted user */ |
fcf841f8 | 142 | char *old_user; /* original user */ |
94c6730b | 143 | |
44f36ad1 | 144 | pid_t child; /* fork() baby */ |
13ee2f4d | 145 | int childstatus; /* wait() status */ |
44f36ad1 | 146 | |
75efef98 KZ |
147 | char **env_whitelist_names; /* environment whitelist */ |
148 | char **env_whitelist_vals; | |
149 | ||
665f36be | 150 | struct sigaction oldact[SIGNALS_IDX_COUNT]; /* original sigactions indexed by SIG*_IDX */ |
eb7d0ad0 | 151 | #ifdef USE_PTY |
b923bdbe | 152 | struct ul_pty *pty; /* pseudo terminal handler (for --pty) */ |
eb7d0ad0 | 153 | #endif |
832f5cd5 | 154 | unsigned int runuser :1, /* flase=su, true=runuser */ |
94c6730b | 155 | runuser_uopt :1, /* runuser -u specified */ |
302b7b65 | 156 | isterm :1, /* is stdin terminal? */ |
832f5cd5 KZ |
157 | fast_startup :1, /* pass the `-f' option to the subshell. */ |
158 | simulate_login :1, /* simulate a login instead of just starting a shell. */ | |
159 | change_environment :1, /* change some environment vars to indicate the user su'd to.*/ | |
160 | same_session :1, /* don't call setsid() with a command. */ | |
161 | suppress_pam_info:1, /* don't print PAM info messages (Last login, etc.). */ | |
162 | pam_has_session :1, /* PAM session opened */ | |
163 | pam_has_cred :1, /* PAM cred established */ | |
b923bdbe | 164 | force_pty :1, /* create pseudo-terminal */ |
832f5cd5 KZ |
165 | restricted :1; /* false for root user */ |
166 | }; | |
cf1a99da | 167 | |
cf1a99da | 168 | |
3649a3db | 169 | static sig_atomic_t volatile caught_signal = 0; |
cf1a99da | 170 | |
b9a92282 KZ |
171 | /* Signal handler for parent process. */ |
172 | static void | |
173 | su_catch_sig(int sig) | |
174 | { | |
175 | caught_signal = sig; | |
176 | } | |
177 | ||
2260e493 KZ |
178 | static void su_init_debug(void) |
179 | { | |
a15dca2f | 180 | __UL_INIT_DEBUG_FROM_ENV(su, SU_DEBUG_, 0, SU_DEBUG); |
2260e493 KZ |
181 | } |
182 | ||
302b7b65 KZ |
183 | static void init_tty(struct su_context *su) |
184 | { | |
185 | su->isterm = isatty(STDIN_FILENO) ? 1 : 0; | |
73afd3f8 | 186 | DBG(TTY, ul_debug("initialize [is-term=%s]", su->isterm ? "true" : "false")); |
302b7b65 | 187 | if (su->isterm) |
61653767 | 188 | get_terminal_name(&su->tty_path, &su->tty_name, &su->tty_number); |
302b7b65 KZ |
189 | } |
190 | ||
13ee2f4d KZ |
191 | /* |
192 | * Note, this function has to be possible call more than once. If the child is | |
193 | * already dead than it returns saved result from the previous call. | |
194 | */ | |
eb7d0ad0 KZ |
195 | static int wait_for_child(struct su_context *su) |
196 | { | |
bdd43357 | 197 | pid_t pid = (pid_t) -1; |
eb7d0ad0 KZ |
198 | int status = 0; |
199 | ||
13ee2f4d KZ |
200 | if (su->child == (pid_t) -1) |
201 | return su->childstatus; | |
202 | ||
eb7d0ad0 | 203 | if (su->child != (pid_t) -1) { |
e9fde3e9 KZ |
204 | /* |
205 | * The "su" parent process spends all time here in waitpid(), | |
206 | * but "su --pty" uses pty_proxy_master() and waitpid() is only | |
207 | * called to pick up child status or to react to SIGSTOP. | |
208 | */ | |
eb7d0ad0 KZ |
209 | DBG(SIG, ul_debug("waiting for child [%d]...", su->child)); |
210 | for (;;) { | |
211 | pid = waitpid(su->child, &status, WUNTRACED); | |
212 | ||
213 | if (pid != (pid_t) - 1 && WIFSTOPPED(status)) { | |
e9fde3e9 | 214 | DBG(SIG, ul_debug(" child got SIGSTOP -- stop all session")); |
eb7d0ad0 KZ |
215 | kill(getpid(), SIGSTOP); |
216 | /* once we get here, we must have resumed */ | |
217 | kill(pid, SIGCONT); | |
e9fde3e9 KZ |
218 | DBG(SIG, ul_debug(" session resumed -- continue")); |
219 | #ifdef USE_PTY | |
220 | /* Let's go back to pty_proxy_master() */ | |
b923bdbe | 221 | if (su->force_pty && ul_pty_is_running(su->pty)) { |
e9fde3e9 KZ |
222 | DBG(SIG, ul_debug(" leaving on child SIGSTOP")); |
223 | return 0; | |
224 | } | |
225 | #endif | |
eb7d0ad0 KZ |
226 | } else |
227 | break; | |
228 | } | |
229 | } | |
230 | if (pid != (pid_t) -1) { | |
231 | if (WIFSIGNALED(status)) { | |
232 | fprintf(stderr, "%s%s\n", | |
233 | strsignal(WTERMSIG(status)), | |
234 | WCOREDUMP(status) ? _(" (core dumped)") | |
235 | : ""); | |
236 | status = WTERMSIG(status) + 128; | |
237 | } else | |
238 | status = WEXITSTATUS(status); | |
239 | ||
240 | DBG(SIG, ul_debug("child %d is dead", su->child)); | |
241 | su->child = (pid_t) -1; /* Don't use the PID anymore! */ | |
13ee2f4d | 242 | su->childstatus = status; |
b923bdbe KZ |
243 | #ifdef USE_PTY |
244 | /* inform pty suff that we have no child anymore */ | |
245 | if (su->force_pty) | |
246 | ul_pty_set_child(su->pty, (pid_t) -1); | |
247 | #endif | |
eb7d0ad0 KZ |
248 | } else if (caught_signal) |
249 | status = caught_signal + 128; | |
250 | else | |
251 | status = 1; | |
252 | ||
13ee2f4d | 253 | DBG(SIG, ul_debug("child status=%d", status)); |
eb7d0ad0 KZ |
254 | return status; |
255 | } | |
256 | ||
eb7d0ad0 | 257 | #ifdef USE_PTY |
bdd43357 KZ |
258 | static void wait_for_child_cb( |
259 | void *data, | |
260 | pid_t child __attribute__((__unused__))) | |
eb7d0ad0 | 261 | { |
b923bdbe | 262 | wait_for_child((struct su_context *) data); |
eb7d0ad0 | 263 | } |
17d5b264 KZ |
264 | |
265 | static void chownmod_pty(struct su_context *su) | |
266 | { | |
267 | gid_t gid = su->pwd->pw_gid; | |
268 | mode_t mode = (mode_t) getlogindefs_num("TTYPERM", TTY_MODE); | |
269 | const char *grname = getlogindefs_str("TTYGROUP", TTYGRPNAME); | |
270 | ||
271 | if (grname && *grname) { | |
272 | struct group *gr = getgrnam(grname); | |
273 | if (gr) /* group by name */ | |
274 | gid = gr->gr_gid; | |
275 | else /* group by ID */ | |
276 | gid = (gid_t) getlogindefs_num("TTYGROUP", gid); | |
277 | } | |
278 | ||
279 | if (ul_pty_chownmod_slave(su->pty, | |
280 | su->pwd->pw_uid, | |
281 | gid, mode)) | |
282 | warn(_("change owner or mode for pseudo-terminal failed")); | |
283 | } | |
b923bdbe | 284 | #endif |
eb7d0ad0 | 285 | |
cf1a99da KZ |
286 | /* Log the fact that someone has run su to the user given by PW; |
287 | if SUCCESSFUL is true, they gave the correct password, etc. */ | |
288 | ||
032d759a | 289 | static void log_syslog(struct su_context *su, bool successful) |
cf1a99da | 290 | { |
2260e493 KZ |
291 | DBG(LOG, ul_debug("syslog logging")); |
292 | ||
677a3168 | 293 | openlog(su->runuser ? "runuser" : "su", LOG_PID, LOG_AUTH); |
983652ab KZ |
294 | syslog(LOG_NOTICE, "%s(to %s) %s on %s", |
295 | successful ? "" : | |
832f5cd5 | 296 | su->runuser ? "FAILED RUNUSER " : "FAILED SU ", |
94c6730b | 297 | su->new_user, su->old_user ? : "", |
302b7b65 | 298 | su->tty_name ? : "none"); |
983652ab KZ |
299 | closelog(); |
300 | } | |
c74a7af1 KZ |
301 | |
302 | /* | |
303 | * Log failed login attempts in _PATH_BTMP if that exists. | |
304 | */ | |
032d759a | 305 | static void log_btmp(struct su_context *su) |
c74a7af1 | 306 | { |
b4b919fe | 307 | struct utmpx ut; |
c74a7af1 | 308 | struct timeval tv; |
c74a7af1 | 309 | |
2260e493 KZ |
310 | DBG(LOG, ul_debug("btmp logging")); |
311 | ||
c74a7af1 | 312 | memset(&ut, 0, sizeof(ut)); |
7f76bc8a | 313 | str2memcpy(ut.ut_user, |
032d759a | 314 | su->pwd && su->pwd->pw_name ? su->pwd->pw_name : "(unknown)", |
c74a7af1 KZ |
315 | sizeof(ut.ut_user)); |
316 | ||
302b7b65 | 317 | if (su->tty_number) |
7f76bc8a | 318 | str2memcpy(ut.ut_id, su->tty_number, sizeof(ut.ut_id)); |
302b7b65 | 319 | if (su->tty_name) |
7f76bc8a | 320 | str2memcpy(ut.ut_line, su->tty_name, sizeof(ut.ut_line)); |
c74a7af1 | 321 | |
c74a7af1 KZ |
322 | gettimeofday(&tv, NULL); |
323 | ut.ut_tv.tv_sec = tv.tv_sec; | |
324 | ut.ut_tv.tv_usec = tv.tv_usec; | |
c74a7af1 KZ |
325 | ut.ut_type = LOGIN_PROCESS; /* XXX doesn't matter */ |
326 | ut.ut_pid = getpid(); | |
327 | ||
b4b919fe | 328 | updwtmpx(_PATH_BTMP, &ut); |
c74a7af1 KZ |
329 | } |
330 | ||
b9a92282 KZ |
331 | static int supam_conv( int num_msg, |
332 | const struct pam_message **msg, | |
333 | struct pam_response **resp, | |
334 | void *data) | |
fb4edda7 | 335 | { |
b9a92282 | 336 | struct su_context *su = (struct su_context *) data; |
832f5cd5 KZ |
337 | |
338 | if (su->suppress_pam_info | |
b9a92282 KZ |
339 | && num_msg == 1 |
340 | && msg && msg[0]->msg_style == PAM_TEXT_INFO) | |
fb4edda7 | 341 | return PAM_SUCCESS; |
832f5cd5 | 342 | |
fe2c9909 | 343 | #ifdef HAVE_SECURITY_PAM_MISC_H |
b9a92282 | 344 | return misc_conv(num_msg, msg, resp, data); |
fe2c9909 | 345 | #elif defined(HAVE_SECURITY_OPENPAM_H) |
b9a92282 | 346 | return openpam_ttyconv(num_msg, msg, resp, data); |
3fe8061d | 347 | #else |
1ade162b | 348 | return PAM_CONV_ERR; |
3fe8061d | 349 | #endif |
fb4edda7 KZ |
350 | } |
351 | ||
b9a92282 | 352 | static void supam_cleanup(struct su_context *su, int retcode) |
cf1a99da | 353 | { |
e402d137 | 354 | const int errsv = errno; |
cf1a99da | 355 | |
2260e493 KZ |
356 | DBG(PAM, ul_debug("cleanup")); |
357 | ||
832f5cd5 KZ |
358 | if (su->pam_has_session) |
359 | pam_close_session(su->pamh, 0); | |
832f5cd5 KZ |
360 | if (su->pam_has_cred) |
361 | pam_setcred(su->pamh, PAM_DELETE_CRED | PAM_SILENT); | |
832f5cd5 | 362 | pam_end(su->pamh, retcode); |
e402d137 | 363 | errno = errsv; |
cf1a99da KZ |
364 | } |
365 | ||
cf1a99da | 366 | |
b9a92282 | 367 | static void supam_export_environment(struct su_context *su) |
cf1a99da | 368 | { |
2260e493 KZ |
369 | char **env; |
370 | ||
371 | DBG(PAM, ul_debug("init environ[]")); | |
372 | ||
983652ab | 373 | /* This is a copy but don't care to free as we exec later anyways. */ |
2260e493 | 374 | env = pam_getenvlist(su->pamh); |
b9a92282 | 375 | |
983652ab KZ |
376 | while (env && *env) { |
377 | if (putenv(*env) != 0) | |
b9a92282 | 378 | err(EXIT_FAILURE, _("failed to modify environment")); |
983652ab KZ |
379 | env++; |
380 | } | |
cf1a99da KZ |
381 | } |
382 | ||
032d759a | 383 | static void supam_authenticate(struct su_context *su) |
dc5bfb71 | 384 | { |
302b7b65 | 385 | const char *srvname = NULL; |
e402d137 | 386 | int rc; |
dc5bfb71 KZ |
387 | |
388 | srvname = su->runuser ? | |
389 | (su->simulate_login ? PAM_SRVNAME_RUNUSER_L : PAM_SRVNAME_RUNUSER) : | |
390 | (su->simulate_login ? PAM_SRVNAME_SU_L : PAM_SRVNAME_SU); | |
391 | ||
2260e493 KZ |
392 | DBG(PAM, ul_debug("start [name: %s]", srvname)); |
393 | ||
e402d137 KZ |
394 | rc = pam_start(srvname, su->pwd->pw_name, &su->conv, &su->pamh); |
395 | if (is_pam_failure(rc)) | |
dc5bfb71 KZ |
396 | goto done; |
397 | ||
61653767 KZ |
398 | if (su->tty_path) { |
399 | rc = pam_set_item(su->pamh, PAM_TTY, su->tty_path); | |
e402d137 | 400 | if (is_pam_failure(rc)) |
dc5bfb71 KZ |
401 | goto done; |
402 | } | |
94c6730b | 403 | if (su->old_user) { |
e402d137 KZ |
404 | rc = pam_set_item(su->pamh, PAM_RUSER, (const void *) su->old_user); |
405 | if (is_pam_failure(rc)) | |
dc5bfb71 KZ |
406 | goto done; |
407 | } | |
dc5bfb71 KZ |
408 | if (su->runuser) { |
409 | /* | |
410 | * This is the only difference between runuser(1) and su(1). The command | |
411 | * runuser(1) does not required authentication, because user is root. | |
412 | */ | |
413 | if (su->restricted) | |
414 | errx(EXIT_FAILURE, _("may not be used by non-root users")); | |
415 | return; | |
416 | } | |
417 | ||
e402d137 KZ |
418 | rc = pam_authenticate(su->pamh, 0); |
419 | if (is_pam_failure(rc)) | |
dc5bfb71 KZ |
420 | goto done; |
421 | ||
e402d137 KZ |
422 | /* Check password expiration and offer option to change it. */ |
423 | rc = pam_acct_mgmt(su->pamh, 0); | |
424 | if (rc == PAM_NEW_AUTHTOK_REQD) | |
425 | rc = pam_chauthtok(su->pamh, PAM_CHANGE_EXPIRED_AUTHTOK); | |
dc5bfb71 | 426 | done: |
e402d137 | 427 | log_syslog(su, !is_pam_failure(rc)); |
dc5bfb71 | 428 | |
e402d137 | 429 | if (is_pam_failure(rc)) { |
dc5bfb71 KZ |
430 | const char *msg; |
431 | ||
2260e493 | 432 | DBG(PAM, ul_debug("authentication failed")); |
032d759a | 433 | log_btmp(su); |
dc5bfb71 | 434 | |
e402d137 KZ |
435 | msg = pam_strerror(su->pamh, rc); |
436 | pam_end(su->pamh, rc); | |
dc5bfb71 | 437 | sleep(getlogindefs_num("FAIL_DELAY", 1)); |
99c1ebee | 438 | errx(EXIT_FAILURE, "%s", msg ? msg : _("authentication failed")); |
dc5bfb71 KZ |
439 | } |
440 | } | |
441 | ||
a4440cd1 | 442 | static void supam_open_session(struct su_context *su) |
cf1a99da | 443 | { |
2260e493 KZ |
444 | int rc; |
445 | ||
446 | DBG(PAM, ul_debug("opening session")); | |
983652ab | 447 | |
2260e493 | 448 | rc = pam_open_session(su->pamh, 0); |
e402d137 | 449 | if (is_pam_failure(rc)) { |
3b013748 KZ |
450 | const char *msg = pam_strerror(su->pamh, rc); |
451 | ||
e402d137 | 452 | supam_cleanup(su, rc); |
3b013748 | 453 | errx(EXIT_FAILURE, _("cannot open session: %s"), msg); |
983652ab | 454 | } else |
832f5cd5 | 455 | su->pam_has_session = 1; |
a4440cd1 KZ |
456 | } |
457 | ||
305ef556 | 458 | static void parent_setup_signals(struct su_context *su) |
a4440cd1 | 459 | { |
a4440cd1 | 460 | sigset_t ourset; |
983652ab | 461 | |
5fc211d2 | 462 | /* |
8ce9c386 KZ |
463 | * Signals setup |
464 | * | |
5fc211d2 | 465 | * 1) block all signals |
5fc211d2 | 466 | */ |
b09e7ea8 | 467 | DBG(SIG, ul_debug("initialize signals")); |
b09e7ea8 | 468 | |
983652ab | 469 | sigfillset(&ourset); |
0214f438 | 470 | if (sigprocmask(SIG_BLOCK, &ourset, NULL)) { |
983652ab KZ |
471 | warn(_("cannot block signals")); |
472 | caught_signal = true; | |
cf1a99da | 473 | } |
5fc211d2 | 474 | |
983652ab KZ |
475 | if (!caught_signal) { |
476 | struct sigaction action; | |
477 | action.sa_handler = su_catch_sig; | |
478 | sigemptyset(&action.sa_mask); | |
479 | action.sa_flags = 0; | |
8ce9c386 | 480 | |
983652ab | 481 | sigemptyset(&ourset); |
5fc211d2 | 482 | |
8ce9c386 | 483 | /* 2a) add wanted signals to the mask (for session) */ |
5fc211d2 KZ |
484 | if (!su->same_session |
485 | && (sigaddset(&ourset, SIGINT) | |
486 | || sigaddset(&ourset, SIGQUIT))) { | |
487 | ||
8ce9c386 | 488 | warn(_("cannot initialize signal mask for session")); |
5fc211d2 | 489 | caught_signal = true; |
983652ab | 490 | } |
8ce9c386 | 491 | /* 2b) add wanted generic signals to the mask */ |
5fc211d2 KZ |
492 | if (!caught_signal |
493 | && (sigaddset(&ourset, SIGTERM) | |
8ce9c386 | 494 | || sigaddset(&ourset, SIGALRM))) { |
5fc211d2 | 495 | |
8ce9c386 | 496 | warn(_("cannot initialize signal mask")); |
983652ab KZ |
497 | caught_signal = true; |
498 | } | |
8ce9c386 KZ |
499 | |
500 | /* 3a) set signal handlers (for session) */ | |
5fc211d2 KZ |
501 | if (!caught_signal |
502 | && !su->same_session | |
665f36be KZ |
503 | && (sigaction(SIGINT, &action, &su->oldact[SIGINT_IDX]) |
504 | || sigaction(SIGQUIT, &action, &su->oldact[SIGQUIT_IDX]))) { | |
5fc211d2 | 505 | |
8ce9c386 KZ |
506 | warn(_("cannot set signal handler for session")); |
507 | caught_signal = true; | |
508 | } | |
509 | ||
510 | /* 3b) set signal handlers */ | |
511 | if (!caught_signal | |
665f36be | 512 | && sigaction(SIGTERM, &action, &su->oldact[SIGTERM_IDX])) { |
8ce9c386 | 513 | |
983652ab KZ |
514 | warn(_("cannot set signal handler")); |
515 | caught_signal = true; | |
516 | } | |
8ce9c386 KZ |
517 | |
518 | /* 4) unblock wanted signals */ | |
519 | if (!caught_signal | |
520 | && sigprocmask(SIG_UNBLOCK, &ourset, NULL)) { | |
521 | ||
522 | warn(_("cannot set signal mask")); | |
523 | caught_signal = true; | |
524 | } | |
983652ab | 525 | } |
305ef556 KZ |
526 | } |
527 | ||
305ef556 KZ |
528 | static void create_watching_parent(struct su_context *su) |
529 | { | |
312b64d9 | 530 | struct sigaction action; |
305ef556 KZ |
531 | int status; |
532 | ||
533 | DBG(MISC, ul_debug("forking...")); | |
ae6e2537 | 534 | #ifdef USE_PTY |
b923bdbe KZ |
535 | if (su->force_pty) { |
536 | struct ul_pty_callbacks *cb; | |
537 | ||
b923bdbe KZ |
538 | /* set callbacks */ |
539 | ul_pty_set_callback_data(su->pty, (void *) su); | |
0214f438 | 540 | |
b923bdbe KZ |
541 | cb = ul_pty_get_callbacks(su->pty); |
542 | cb->child_wait = wait_for_child_cb; | |
543 | cb->child_sigstop = wait_for_child_cb; | |
544 | ||
497c61f7 KZ |
545 | ul_pty_slave_echo(su->pty, 1); |
546 | ||
b923bdbe KZ |
547 | /* create pty */ |
548 | if (ul_pty_setup(su->pty)) | |
549 | err(EXIT_FAILURE, _("failed to create pseudo-terminal")); | |
4681d88b KZ |
550 | if (ul_pty_signals_setup(su->pty)) |
551 | err(EXIT_FAILURE, _("failed to initialize signals handler")); | |
b923bdbe | 552 | } |
ae6e2537 | 553 | #endif |
eb7d0ad0 KZ |
554 | fflush(stdout); /* ??? */ |
555 | ||
312b64d9 TS |
556 | /* set default handler for SIGCHLD */ |
557 | sigemptyset(&action.sa_mask); | |
558 | action.sa_flags = 0; | |
559 | action.sa_handler = SIG_DFL; | |
560 | if (sigaction(SIGCHLD, &action, NULL)) { | |
561 | supam_cleanup(su, PAM_ABORT); | |
562 | #ifdef USE_PTY | |
563 | if (su->force_pty) | |
564 | ul_pty_cleanup(su->pty); | |
565 | #endif | |
566 | err(EXIT_FAILURE, _("cannot set child signal handler")); | |
567 | } | |
568 | ||
305ef556 KZ |
569 | switch ((int) (su->child = fork())) { |
570 | case -1: /* error */ | |
571 | supam_cleanup(su, PAM_ABORT); | |
ae6e2537 | 572 | #ifdef USE_PTY |
b923bdbe KZ |
573 | if (su->force_pty) |
574 | ul_pty_cleanup(su->pty); | |
ae6e2537 | 575 | #endif |
305ef556 KZ |
576 | err(EXIT_FAILURE, _("cannot create child process")); |
577 | break; | |
578 | ||
579 | case 0: /* child */ | |
580 | return; | |
581 | ||
582 | default: /* parent */ | |
583 | DBG(MISC, ul_debug("child [pid=%d]", (int) su->child)); | |
584 | break; | |
585 | } | |
586 | ||
f4b03edb KZ |
587 | /* free unnecessary stuff */ |
588 | free_getlogindefs_data(); | |
305ef556 KZ |
589 | |
590 | /* In the parent watch the child. */ | |
591 | ||
592 | /* su without pam support does not have a helper that keeps | |
593 | sitting on any directory so let's go to /. */ | |
594 | if (chdir("/") != 0) | |
595 | warn(_("cannot change directory to %s"), "/"); | |
ae6e2537 | 596 | #ifdef USE_PTY |
b923bdbe KZ |
597 | if (su->force_pty) { |
598 | ul_pty_set_child(su->pty, su->child); | |
599 | ||
1e89f4fe | 600 | ul_pty_proxy_master(su->pty); |
b923bdbe KZ |
601 | |
602 | /* ul_pty_proxy_master() keeps classic signal handler are out of game */ | |
603 | caught_signal = ul_pty_get_delivered_signal(su->pty); | |
604 | ||
605 | ul_pty_cleanup(su->pty); | |
606 | } else | |
ae6e2537 | 607 | #endif |
eb7d0ad0 | 608 | parent_setup_signals(su); |
5fc211d2 KZ |
609 | |
610 | /* | |
611 | * Wait for child | |
612 | */ | |
b09e7ea8 | 613 | if (!caught_signal) |
44f36ad1 | 614 | status = wait_for_child(su); |
b09e7ea8 | 615 | else |
983652ab KZ |
616 | status = 1; |
617 | ||
13ee2f4d KZ |
618 | DBG(SIG, ul_debug("final child status=%d", status)); |
619 | ||
44f36ad1 | 620 | if (caught_signal && su->child != (pid_t)-1) { |
983652ab | 621 | fprintf(stderr, _("\nSession terminated, killing shell...")); |
44f36ad1 | 622 | kill(su->child, SIGTERM); |
dffab154 | 623 | } |
8960f3ae | 624 | |
b9a92282 | 625 | supam_cleanup(su, PAM_SUCCESS); |
983652ab KZ |
626 | |
627 | if (caught_signal) { | |
44f36ad1 | 628 | if (su->child != (pid_t)-1) { |
6b283282 KZ |
629 | DBG(SIG, ul_debug("killing child")); |
630 | sleep(2); | |
44f36ad1 | 631 | kill(su->child, SIGKILL); |
6b283282 KZ |
632 | fprintf(stderr, _(" ...killed.\n")); |
633 | } | |
983652ab KZ |
634 | |
635 | /* Let's terminate itself with the received signal. | |
636 | * | |
637 | * It seems that shells use WIFSIGNALED() rather than our exit status | |
638 | * value to detect situations when is necessary to cleanup (reset) | |
639 | * terminal settings (kzak -- Jun 2013). | |
640 | */ | |
2260e493 | 641 | DBG(SIG, ul_debug("restore signals setting")); |
983652ab KZ |
642 | switch (caught_signal) { |
643 | case SIGTERM: | |
665f36be | 644 | sigaction(SIGTERM, &su->oldact[SIGTERM_IDX], NULL); |
983652ab KZ |
645 | break; |
646 | case SIGINT: | |
665f36be | 647 | sigaction(SIGINT, &su->oldact[SIGINT_IDX], NULL); |
983652ab KZ |
648 | break; |
649 | case SIGQUIT: | |
665f36be | 650 | sigaction(SIGQUIT, &su->oldact[SIGQUIT_IDX], NULL); |
983652ab KZ |
651 | break; |
652 | default: | |
653 | /* just in case that signal stuff initialization failed and | |
654 | * caught_signal = true */ | |
655 | caught_signal = SIGKILL; | |
656 | break; | |
657 | } | |
2260e493 | 658 | DBG(SIG, ul_debug("self-send %d signal", caught_signal)); |
983652ab KZ |
659 | kill(getpid(), caught_signal); |
660 | } | |
2260e493 KZ |
661 | |
662 | DBG(MISC, ul_debug("exiting [rc=%d]", status)); | |
983652ab | 663 | exit(status); |
cf1a99da KZ |
664 | } |
665 | ||
75efef98 KZ |
666 | /* Adds @name from the current environment to the whitelist. If @name is not |
667 | * set then nothing is added to the whitelist and returns 1. | |
668 | */ | |
669 | static int env_whitelist_add(struct su_context *su, const char *name) | |
670 | { | |
671 | const char *env = getenv(name); | |
672 | ||
673 | if (!env) | |
674 | return 1; | |
675 | if (strv_extend(&su->env_whitelist_names, name)) | |
676 | err_oom(); | |
677 | if (strv_extend(&su->env_whitelist_vals, env)) | |
678 | err_oom(); | |
679 | return 0; | |
680 | } | |
681 | ||
682 | static int env_whitelist_setenv(struct su_context *su, int overwrite) | |
683 | { | |
684 | char **one; | |
685 | size_t i = 0; | |
686 | int rc; | |
687 | ||
688 | STRV_FOREACH(one, su->env_whitelist_names) { | |
689 | rc = setenv(*one, su->env_whitelist_vals[i], overwrite); | |
690 | if (rc) | |
691 | return rc; | |
692 | i++; | |
693 | } | |
694 | ||
695 | return 0; | |
696 | } | |
697 | ||
698 | /* Creates (add to) whitelist from comma delimited string */ | |
699 | static int env_whitelist_from_string(struct su_context *su, const char *str) | |
700 | { | |
701 | char **all = strv_split(str, ","); | |
702 | char **one; | |
703 | ||
704 | if (!all) { | |
705 | if (errno == ENOMEM) | |
706 | err_oom(); | |
707 | return -EINVAL; | |
708 | } | |
709 | ||
710 | STRV_FOREACH(one, all) | |
711 | env_whitelist_add(su, *one); | |
712 | strv_free(all); | |
713 | return 0; | |
714 | } | |
715 | ||
13396b10 | 716 | static void setenv_path(const struct passwd *pw) |
cf1a99da | 717 | { |
e402d137 | 718 | int rc; |
13396b10 | 719 | |
2260e493 KZ |
720 | DBG(MISC, ul_debug("setting PATH")); |
721 | ||
983652ab | 722 | if (pw->pw_uid) |
e402d137 | 723 | rc = logindefs_setenv("PATH", "ENV_PATH", _PATH_DEFPATH); |
cf1a99da | 724 | |
86f42e5a SB |
725 | else if ((rc = logindefs_setenv("PATH", "ENV_SUPATH", NULL)) != 0) |
726 | rc = logindefs_setenv("PATH", "ENV_ROOTPATH", _PATH_DEFPATH_ROOT); | |
cf1a99da | 727 | |
e402d137 KZ |
728 | if (rc) |
729 | err(EXIT_FAILURE, _("failed to set the PATH environment variable")); | |
cf1a99da KZ |
730 | } |
731 | ||
13396b10 | 732 | static void modify_environment(struct su_context *su, const char *shell) |
cf1a99da | 733 | { |
032d759a KZ |
734 | const struct passwd *pw = su->pwd; |
735 | ||
2260e493 KZ |
736 | |
737 | DBG(MISC, ul_debug("modify environ[]")); | |
738 | ||
13396b10 | 739 | /* Leave TERM unchanged. Set HOME, SHELL, USER, LOGNAME, PATH. |
75efef98 KZ |
740 | * |
741 | * Unset all other environment variables, but follow | |
742 | * --whitelist-environment if specified. | |
13396b10 | 743 | */ |
832f5cd5 | 744 | if (su->simulate_login) { |
75efef98 KZ |
745 | /* leave TERM unchanged */ |
746 | env_whitelist_add(su, "TERM"); | |
747 | ||
748 | /* Note that original su(1) has allocated environ[] by malloc | |
749 | * to the number of expected variables. This seems unnecessary | |
00749b05 | 750 | * optimization as libc later re-alloc(current_size+2) and for |
75efef98 KZ |
751 | * empty environ[] the curren_size is zero. It seems better to |
752 | * keep all logic around environment in glibc's hands. | |
753 | * --kzak [Aug 2018] | |
754 | */ | |
755 | #ifdef HAVE_CLEARENV | |
756 | clearenv(); | |
757 | #else | |
758 | environ = NULL; | |
759 | #endif | |
760 | /* always reset */ | |
983652ab KZ |
761 | if (shell) |
762 | xsetenv("SHELL", shell, 1); | |
75efef98 KZ |
763 | |
764 | setenv_path(pw); | |
765 | ||
766 | xsetenv("HOME", pw->pw_dir, 1); | |
983652ab KZ |
767 | xsetenv("USER", pw->pw_name, 1); |
768 | xsetenv("LOGNAME", pw->pw_name, 1); | |
75efef98 KZ |
769 | |
770 | /* apply all from whitelist, but no overwrite */ | |
771 | env_whitelist_setenv(su, 0); | |
13396b10 KZ |
772 | |
773 | /* Set HOME, SHELL, and (if not becoming a superuser) USER and LOGNAME. | |
774 | */ | |
775 | } else if (su->change_environment) { | |
776 | xsetenv("HOME", pw->pw_dir, 1); | |
777 | if (shell) | |
778 | xsetenv("SHELL", shell, 1); | |
779 | ||
780 | if (getlogindefs_bool("ALWAYS_SET_PATH", 0)) | |
781 | setenv_path(pw); | |
782 | ||
783 | if (pw->pw_uid) { | |
784 | xsetenv("USER", pw->pw_name, 1); | |
785 | xsetenv("LOGNAME", pw->pw_name, 1); | |
983652ab KZ |
786 | } |
787 | } | |
788 | ||
b9a92282 | 789 | supam_export_environment(su); |
cf1a99da KZ |
790 | } |
791 | ||
93031585 | 792 | static void init_groups(struct su_context *su, gid_t *groups, size_t ngroups) |
cf1a99da | 793 | { |
93031585 | 794 | int rc; |
983652ab | 795 | |
2260e493 KZ |
796 | DBG(MISC, ul_debug("initialize groups")); |
797 | ||
983652ab | 798 | errno = 0; |
93031585 KZ |
799 | if (ngroups) |
800 | rc = setgroups(ngroups, groups); | |
983652ab | 801 | else |
93031585 | 802 | rc = initgroups(su->pwd->pw_name, su->pwd->pw_gid); |
983652ab | 803 | |
93031585 | 804 | if (rc == -1) { |
b9a92282 | 805 | supam_cleanup(su, PAM_ABORT); |
983652ab KZ |
806 | err(EXIT_FAILURE, _("cannot set groups")); |
807 | } | |
808 | endgrent(); | |
809 | ||
93031585 KZ |
810 | rc = pam_setcred(su->pamh, PAM_ESTABLISH_CRED); |
811 | if (is_pam_failure(rc)) | |
bdc3cc65 | 812 | errx(EXIT_FAILURE, _("failed to establish user credentials: %s"), |
93031585 KZ |
813 | pam_strerror(su->pamh, rc)); |
814 | su->pam_has_cred = 1; | |
cf1a99da KZ |
815 | } |
816 | ||
93031585 | 817 | static void change_identity(const struct passwd *pw) |
cf1a99da | 818 | { |
2260e493 KZ |
819 | DBG(MISC, ul_debug("changing identity [GID=%d, UID=%d]", pw->pw_gid, pw->pw_uid)); |
820 | ||
983652ab KZ |
821 | if (setgid(pw->pw_gid)) |
822 | err(EXIT_FAILURE, _("cannot set group id")); | |
823 | if (setuid(pw->pw_uid)) | |
824 | err(EXIT_FAILURE, _("cannot set user id")); | |
cf1a99da KZ |
825 | } |
826 | ||
581ddd37 KZ |
827 | /* Run SHELL, if COMMAND is nonzero, pass it to the shell with the -c option. |
828 | * Pass ADDITIONAL_ARGS to the shell as more arguments; there are | |
829 | * N_ADDITIONAL_ARGS extra arguments. | |
830 | */ | |
831 | static void run_shell( | |
832 | struct su_context *su, | |
833 | char const *shell, char const *command, char **additional_args, | |
834 | size_t n_additional_args) | |
cf1a99da | 835 | { |
581ddd37 | 836 | size_t n_args = 1 + su->fast_startup + 2 * ! !command + n_additional_args + 1; |
feab5687 | 837 | const char **args = xcalloc(n_args, sizeof *args); |
983652ab | 838 | size_t argno = 1; |
4b2e6f50 | 839 | char *tmp; |
983652ab | 840 | |
242708de KZ |
841 | DBG(MISC, ul_debug("starting shell [shell=%s, command=\"%s\"%s%s]", |
842 | shell, command, | |
843 | su->simulate_login ? " login" : "", | |
844 | su->fast_startup ? " fast-start" : "")); | |
4b2e6f50 | 845 | tmp = xstrdup(shell); |
2260e493 | 846 | |
832f5cd5 | 847 | if (su->simulate_login) { |
983652ab KZ |
848 | char *arg0; |
849 | char *shell_basename; | |
850 | ||
77454e58 | 851 | shell_basename = basename(tmp); |
983652ab KZ |
852 | arg0 = xmalloc(strlen(shell_basename) + 2); |
853 | arg0[0] = '-'; | |
854 | strcpy(arg0 + 1, shell_basename); | |
855 | args[0] = arg0; | |
4b2e6f50 TY |
856 | } else |
857 | args[0] = basename(tmp); | |
581ddd37 | 858 | |
832f5cd5 | 859 | if (su->fast_startup) |
983652ab KZ |
860 | args[argno++] = "-f"; |
861 | if (command) { | |
862 | args[argno++] = "-c"; | |
863 | args[argno++] = command; | |
864 | } | |
581ddd37 | 865 | |
983652ab KZ |
866 | memcpy(args + argno, additional_args, n_additional_args * sizeof *args); |
867 | args[argno + n_additional_args] = NULL; | |
868 | execv(shell, (char **)args); | |
3c29b695 | 869 | errexec(shell); |
cf1a99da KZ |
870 | } |
871 | ||
872 | /* Return true if SHELL is a restricted shell (one not returned by | |
581ddd37 KZ |
873 | * getusershell), else false, meaning it is a standard shell. |
874 | */ | |
875 | static bool is_restricted_shell(const char *shell) | |
cf1a99da | 876 | { |
825feef8 SS |
877 | if (is_known_shell(shell)) { |
878 | return false; | |
cf1a99da | 879 | } |
825feef8 SS |
880 | #ifdef USE_VENDORDIR |
881 | DBG(MISC, ul_debug("%s is restricted shell (not in e.g. vendor shells file, /etc/shells, ...)", shell)); | |
882 | #else | |
e19db044 | 883 | DBG(MISC, ul_debug("%s is restricted shell (not in /etc/shells)", shell)); |
825feef8 | 884 | #endif |
983652ab | 885 | return true; |
cf1a99da KZ |
886 | } |
887 | ||
42be9bda | 888 | static void usage_common(void) |
cf1a99da | 889 | { |
75efef98 KZ |
890 | fputs(_(" -m, -p, --preserve-environment do not reset environment variables\n"), stdout); |
891 | fputs(_(" -w, --whitelist-environment <list> don't reset specified variables\n"), stdout); | |
892 | fputs(USAGE_SEPARATOR, stdout); | |
893 | ||
42be9bda KZ |
894 | fputs(_(" -g, --group <group> specify the primary group\n"), stdout); |
895 | fputs(_(" -G, --supp-group <group> specify a supplemental group\n"), stdout); | |
896 | fputs(USAGE_SEPARATOR, stdout); | |
983652ab | 897 | |
42be9bda KZ |
898 | fputs(_(" -, -l, --login make the shell a login shell\n"), stdout); |
899 | fputs(_(" -c, --command <command> pass a single command to the shell with -c\n"), stdout); | |
900 | fputs(_(" --session-command <command> pass a single command to the shell with -c\n" | |
901 | " and do not create a new session\n"), stdout); | |
902 | fputs(_(" -f, --fast pass -f to the shell (for csh or tcsh)\n"), stdout); | |
903 | fputs(_(" -s, --shell <shell> run <shell> if /etc/shells allows it\n"), stdout); | |
927ded6b | 904 | fputs(_(" -P, --pty create a new pseudo-terminal\n"), stdout); |
2bc9cd09 | 905 | fputs(_(" -T, --no-pty do not create a new pseudo-terminal (bad security!)\n"), stdout); |
42be9bda KZ |
906 | |
907 | fputs(USAGE_SEPARATOR, stdout); | |
908 | printf(USAGE_HELP_OPTIONS(33)); | |
42be9bda | 909 | } |
983652ab | 910 | |
e192de65 | 911 | static void usage_runuser(void) |
42be9bda KZ |
912 | { |
913 | fputs(USAGE_HEADER, stdout); | |
914 | fprintf(stdout, | |
915 | _(" %1$s [options] -u <user> [[--] <command>]\n" | |
916 | " %1$s [options] [-] [<user> [<argument>...]]\n"), | |
917 | program_invocation_short_name); | |
983652ab KZ |
918 | |
919 | fputs(USAGE_SEPARATOR, stdout); | |
42be9bda KZ |
920 | fputs(_("Run <command> with the effective user ID and group ID of <user>. If -u is\n" |
921 | "not given, fall back to su(1)-compatible semantics and execute standard shell.\n" | |
922 | "The options -c, -f, -l, and -s are mutually exclusive with -u.\n"), stdout); | |
923 | ||
924 | fputs(USAGE_OPTIONS, stdout); | |
925 | fputs(_(" -u, --user <user> username\n"), stdout); | |
926 | usage_common(); | |
927 | fputs(USAGE_SEPARATOR, stdout); | |
928 | ||
929 | fprintf(stdout, USAGE_MAN_TAIL("runuser(1)")); | |
42be9bda KZ |
930 | } |
931 | ||
e192de65 | 932 | static void usage_su(void) |
42be9bda KZ |
933 | { |
934 | fputs(USAGE_HEADER, stdout); | |
935 | fprintf(stdout, | |
936 | _(" %s [options] [-] [<user> [<argument>...]]\n"), | |
937 | program_invocation_short_name); | |
938 | ||
939 | fputs(USAGE_SEPARATOR, stdout); | |
940 | fputs(_("Change the effective user ID and group ID to that of <user>.\n" | |
941 | "A mere - implies -l. If <user> is not given, root is assumed.\n"), stdout); | |
942 | ||
943 | fputs(USAGE_OPTIONS, stdout); | |
944 | usage_common(); | |
945 | ||
946 | fprintf(stdout, USAGE_MAN_TAIL("su(1)")); | |
42be9bda KZ |
947 | } |
948 | ||
e192de65 | 949 | static void __attribute__((__noreturn__)) usage(int mode) |
42be9bda KZ |
950 | { |
951 | if (mode == SU_MODE) | |
952 | usage_su(); | |
953 | else | |
954 | usage_runuser(); | |
e192de65 KZ |
955 | |
956 | exit(EXIT_SUCCESS); | |
cf1a99da KZ |
957 | } |
958 | ||
832f5cd5 | 959 | static void load_config(void *data) |
cf1a99da | 960 | { |
832f5cd5 | 961 | struct su_context *su = (struct su_context *) data; |
983652ab | 962 | |
2260e493 | 963 | DBG(MISC, ul_debug("loading logindefs")); |
9e584ff3 | 964 | #ifndef HAVE_LIBECONF |
983652ab | 965 | logindefs_load_file(_PATH_LOGINDEFS); |
9e584ff3 | 966 | #endif |
15a191f6 | 967 | logindefs_load_file(su->runuser ? _PATH_LOGINDEFS_RUNUSER : _PATH_LOGINDEFS_SU); |
cf1a99da KZ |
968 | } |
969 | ||
970 | /* | |
971 | * Returns 1 if the current user is not root | |
972 | */ | |
13de9b21 | 973 | static int is_not_root(void) |
cf1a99da | 974 | { |
983652ab KZ |
975 | const uid_t ruid = getuid(); |
976 | const uid_t euid = geteuid(); | |
cf1a99da | 977 | |
983652ab KZ |
978 | /* if we're really root and aren't running setuid */ |
979 | return (uid_t) 0 == ruid && ruid == euid ? 0 : 1; | |
cf1a99da KZ |
980 | } |
981 | ||
08273c67 | 982 | /* Don't rely on PAM and reset the most important limits. */ |
e5496191 KZ |
983 | static void sanitize_prlimits(void) |
984 | { | |
985 | #ifdef HAVE_SYS_RESOURCE_H | |
986 | struct rlimit lm = { .rlim_cur = 0, .rlim_max = 0 }; | |
987 | ||
08273c67 | 988 | /* reset to zero */ |
0b7aacda | 989 | #ifdef RLIMIT_NICE |
e5496191 | 990 | setrlimit(RLIMIT_NICE, &lm); |
0b7aacda ST |
991 | #endif |
992 | #ifdef RLIMIT_RTPRIO | |
e5496191 | 993 | setrlimit(RLIMIT_RTPRIO, &lm); |
0b7aacda | 994 | #endif |
08273c67 KZ |
995 | |
996 | /* reset to unlimited */ | |
997 | lm.rlim_cur = RLIM_INFINITY; | |
998 | lm.rlim_max = RLIM_INFINITY; | |
999 | setrlimit(RLIMIT_FSIZE, &lm); | |
be3acd23 | 1000 | setrlimit(RLIMIT_AS, &lm); |
08273c67 KZ |
1001 | |
1002 | /* reset soft limit only */ | |
1003 | getrlimit(RLIMIT_NOFILE, &lm); | |
1004 | if (lm.rlim_cur != FD_SETSIZE) { | |
1005 | lm.rlim_cur = FD_SETSIZE; | |
1006 | setrlimit(RLIMIT_NOFILE, &lm); | |
1007 | } | |
e5496191 KZ |
1008 | #endif |
1009 | } | |
1010 | ||
e402d137 | 1011 | static gid_t add_supp_group(const char *name, gid_t **groups, size_t *ngroups) |
c619d3d1 | 1012 | { |
983652ab | 1013 | struct group *gr; |
c619d3d1 | 1014 | |
983652ab KZ |
1015 | if (*ngroups >= NGROUPS_MAX) |
1016 | errx(EXIT_FAILURE, | |
e402d137 KZ |
1017 | P_("specifying more than %d supplemental group is not possible", |
1018 | "specifying more than %d supplemental groups is not possible", | |
1019 | NGROUPS_MAX - 1), NGROUPS_MAX - 1); | |
c619d3d1 | 1020 | |
983652ab KZ |
1021 | gr = getgrnam(name); |
1022 | if (!gr) | |
1023 | errx(EXIT_FAILURE, _("group %s does not exist"), name); | |
c619d3d1 | 1024 | |
2260e493 KZ |
1025 | DBG(MISC, ul_debug("add %s group [name=%s, GID=%d]", name, gr->gr_name, (int) gr->gr_gid)); |
1026 | ||
64d6d400 | 1027 | *groups = xreallocarray(*groups, *ngroups + 1, sizeof(gid_t)); |
983652ab KZ |
1028 | (*groups)[*ngroups] = gr->gr_gid; |
1029 | (*ngroups)++; | |
c619d3d1 | 1030 | |
983652ab | 1031 | return gr->gr_gid; |
c619d3d1 KZ |
1032 | } |
1033 | ||
e402d137 | 1034 | int su_main(int argc, char **argv, int mode) |
cf1a99da | 1035 | { |
832f5cd5 | 1036 | struct su_context _su = { |
b9a92282 | 1037 | .conv = { supam_conv, NULL }, |
832f5cd5 | 1038 | .runuser = (mode == RUNUSER_MODE ? 1 : 0), |
94c6730b | 1039 | .change_environment = 1, |
b923bdbe | 1040 | .new_user = DEFAULT_USER |
832f5cd5 KZ |
1041 | }, *su = &_su; |
1042 | ||
983652ab | 1043 | int optc; |
983652ab KZ |
1044 | char *command = NULL; |
1045 | int request_same_session = 0; | |
1046 | char *shell = NULL; | |
983652ab KZ |
1047 | |
1048 | gid_t *groups = NULL; | |
1049 | size_t ngroups = 0; | |
1050 | bool use_supp = false; | |
1051 | bool use_gid = false; | |
1052 | gid_t gid = 0; | |
1053 | ||
1054 | static const struct option longopts[] = { | |
1055 | {"command", required_argument, NULL, 'c'}, | |
1056 | {"session-command", required_argument, NULL, 'C'}, | |
1057 | {"fast", no_argument, NULL, 'f'}, | |
1058 | {"login", no_argument, NULL, 'l'}, | |
1059 | {"preserve-environment", no_argument, NULL, 'p'}, | |
04845ec7 | 1060 | {"pty", no_argument, NULL, 'P'}, |
2bc9cd09 | 1061 | {"no-pty", no_argument, NULL, 'T'}, |
983652ab KZ |
1062 | {"shell", required_argument, NULL, 's'}, |
1063 | {"group", required_argument, NULL, 'g'}, | |
1064 | {"supp-group", required_argument, NULL, 'G'}, | |
1065 | {"user", required_argument, NULL, 'u'}, /* runuser only */ | |
75efef98 | 1066 | {"whitelist-environment", required_argument, NULL, 'w'}, |
983652ab KZ |
1067 | {"help", no_argument, 0, 'h'}, |
1068 | {"version", no_argument, 0, 'V'}, | |
1069 | {NULL, 0, NULL, 0} | |
1070 | }; | |
75efef98 KZ |
1071 | static const ul_excl_t excl[] = { /* rows and cols in ASCII order */ |
1072 | { 'm', 'w' }, /* preserve-environment, whitelist-environment */ | |
1073 | { 'p', 'w' }, /* preserve-environment, whitelist-environment */ | |
1074 | { 0 } | |
1075 | }; | |
1076 | int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT; | |
983652ab KZ |
1077 | |
1078 | setlocale(LC_ALL, ""); | |
1079 | bindtextdomain(PACKAGE, LOCALEDIR); | |
1080 | textdomain(PACKAGE); | |
2c308875 | 1081 | close_stdout_atexit(); |
983652ab | 1082 | |
2260e493 | 1083 | su_init_debug(); |
832f5cd5 | 1084 | su->conv.appdata_ptr = (void *) su; |
983652ab KZ |
1085 | |
1086 | while ((optc = | |
2bc9cd09 | 1087 | getopt_long(argc, argv, "c:fg:G:lmpPTs:u:hVw:", longopts, |
983652ab | 1088 | NULL)) != -1) { |
75efef98 KZ |
1089 | |
1090 | err_exclusive_options(optc, longopts, excl, excl_st); | |
1091 | ||
983652ab KZ |
1092 | switch (optc) { |
1093 | case 'c': | |
1094 | command = optarg; | |
1095 | break; | |
1096 | ||
1097 | case 'C': | |
1098 | command = optarg; | |
1099 | request_same_session = 1; | |
1100 | break; | |
1101 | ||
1102 | case 'f': | |
832f5cd5 | 1103 | su->fast_startup = true; |
983652ab KZ |
1104 | break; |
1105 | ||
1106 | case 'g': | |
1107 | use_gid = true; | |
1108 | gid = add_supp_group(optarg, &groups, &ngroups); | |
1109 | break; | |
1110 | ||
1111 | case 'G': | |
1112 | use_supp = true; | |
1113 | add_supp_group(optarg, &groups, &ngroups); | |
1114 | break; | |
1115 | ||
1116 | case 'l': | |
832f5cd5 | 1117 | su->simulate_login = true; |
983652ab KZ |
1118 | break; |
1119 | ||
1120 | case 'm': | |
1121 | case 'p': | |
832f5cd5 | 1122 | su->change_environment = false; |
983652ab KZ |
1123 | break; |
1124 | ||
75efef98 KZ |
1125 | case 'w': |
1126 | env_whitelist_from_string(su, optarg); | |
1127 | break; | |
1128 | ||
04845ec7 | 1129 | case 'P': |
eb7d0ad0 | 1130 | #ifdef USE_PTY |
b923bdbe | 1131 | su->force_pty = 1; |
eb7d0ad0 | 1132 | #else |
ae6e2537 | 1133 | errx(EXIT_FAILURE, _("--pty is not supported for your system")); |
eb7d0ad0 | 1134 | #endif |
04845ec7 KZ |
1135 | break; |
1136 | ||
2bc9cd09 SP |
1137 | case 'T': |
1138 | su->force_pty = 0; | |
1139 | break; | |
1140 | ||
983652ab KZ |
1141 | case 's': |
1142 | shell = optarg; | |
1143 | break; | |
1144 | ||
1145 | case 'u': | |
832f5cd5 | 1146 | if (!su->runuser) |
42be9bda | 1147 | errtryhelp(EXIT_FAILURE); |
94c6730b KZ |
1148 | su->runuser_uopt = 1; |
1149 | su->new_user = optarg; | |
983652ab KZ |
1150 | break; |
1151 | ||
1152 | case 'h': | |
42be9bda | 1153 | usage(mode); |
983652ab KZ |
1154 | |
1155 | case 'V': | |
2c308875 | 1156 | print_version(EXIT_SUCCESS); |
983652ab | 1157 | default: |
42be9bda | 1158 | errtryhelp(EXIT_FAILURE); |
983652ab KZ |
1159 | } |
1160 | } | |
cf1a99da | 1161 | |
13de9b21 | 1162 | su->restricted = is_not_root(); |
983652ab KZ |
1163 | |
1164 | if (optind < argc && !strcmp(argv[optind], "-")) { | |
832f5cd5 | 1165 | su->simulate_login = true; |
983652ab KZ |
1166 | ++optind; |
1167 | } | |
1168 | ||
832f5cd5 | 1169 | if (su->simulate_login && !su->change_environment) { |
983652ab KZ |
1170 | warnx(_ |
1171 | ("ignoring --preserve-environment, it's mutually exclusive with --login")); | |
832f5cd5 | 1172 | su->change_environment = true; |
983652ab KZ |
1173 | } |
1174 | ||
832f5cd5 | 1175 | switch (mode) { |
983652ab | 1176 | case RUNUSER_MODE: |
e192de65 KZ |
1177 | /* runuser -u <user> <command> |
1178 | * | |
1179 | * If -u <user> is not specified, then follow traditional su(1) behavior and | |
1180 | * fallthrough | |
1181 | */ | |
94c6730b KZ |
1182 | if (su->runuser_uopt) { |
1183 | if (shell || su->fast_startup || command || su->simulate_login) | |
983652ab | 1184 | errx(EXIT_FAILURE, |
94c6730b | 1185 | _("options --{shell,fast,command,session-command,login} and " |
983652ab | 1186 | "--user are mutually exclusive")); |
983652ab | 1187 | if (optind == argc) |
94c6730b | 1188 | errx(EXIT_FAILURE, _("no command was specified")); |
983652ab KZ |
1189 | break; |
1190 | } | |
e192de65 | 1191 | /* fallthrough */ |
983652ab KZ |
1192 | case SU_MODE: |
1193 | if (optind < argc) | |
94c6730b | 1194 | su->new_user = argv[optind++]; |
983652ab KZ |
1195 | break; |
1196 | } | |
1197 | ||
832f5cd5 | 1198 | if ((use_supp || use_gid) && su->restricted) |
983652ab KZ |
1199 | errx(EXIT_FAILURE, |
1200 | _("only root can specify alternative groups")); | |
1201 | ||
832f5cd5 | 1202 | logindefs_set_loader(load_config, (void *) su); |
302b7b65 | 1203 | init_tty(su); |
983652ab | 1204 | |
94c6730b | 1205 | su->pwd = xgetpwnam(su->new_user, &su->pwdbuf); |
032d759a KZ |
1206 | if (!su->pwd |
1207 | || !su->pwd->pw_passwd | |
1208 | || !su->pwd->pw_name || !*su->pwd->pw_name | |
1209 | || !su->pwd->pw_dir || !*su->pwd->pw_dir) | |
9a1a9822 JH |
1210 | errx(EXIT_FAILURE, |
1211 | _("user %s does not exist or the user entry does not " | |
1212 | "contain all the required fields"), su->new_user); | |
94c6730b KZ |
1213 | |
1214 | su->new_user = su->pwd->pw_name; | |
1215 | su->old_user = xgetlogin(); | |
983652ab | 1216 | |
032d759a KZ |
1217 | if (!su->pwd->pw_shell || !*su->pwd->pw_shell) |
1218 | su->pwd->pw_shell = DEFAULT_SHELL; | |
983652ab KZ |
1219 | |
1220 | if (use_supp && !use_gid) | |
032d759a | 1221 | su->pwd->pw_gid = groups[0]; |
983652ab | 1222 | else if (use_gid) |
032d759a | 1223 | su->pwd->pw_gid = gid; |
983652ab | 1224 | |
032d759a | 1225 | supam_authenticate(su); |
983652ab | 1226 | |
032d759a | 1227 | if (request_same_session || !command || !su->pwd->pw_uid) |
832f5cd5 | 1228 | su->same_session = 1; |
983652ab KZ |
1229 | |
1230 | /* initialize shell variable only if "-u <user>" not specified */ | |
94c6730b | 1231 | if (su->runuser_uopt) { |
983652ab KZ |
1232 | shell = NULL; |
1233 | } else { | |
832f5cd5 | 1234 | if (!shell && !su->change_environment) |
983652ab | 1235 | shell = getenv("SHELL"); |
581ddd37 KZ |
1236 | |
1237 | if (shell | |
ad296391 | 1238 | && strcmp(shell, su->pwd->pw_shell) != 0 |
581ddd37 KZ |
1239 | && getuid() != 0 |
1240 | && is_restricted_shell(su->pwd->pw_shell)) { | |
1241 | /* The user being su'd to has a nonstandard shell, and | |
1242 | * so is probably a uucp account or has restricted | |
1243 | * access. Don't compromise the account by allowing | |
1244 | * access with a standard shell. | |
1245 | */ | |
032d759a | 1246 | warnx(_("using restricted shell %s"), su->pwd->pw_shell); |
983652ab KZ |
1247 | shell = NULL; |
1248 | } | |
032d759a | 1249 | shell = xstrdup(shell ? shell : su->pwd->pw_shell); |
cf1a99da | 1250 | } |
cf1a99da | 1251 | |
032d759a | 1252 | init_groups(su, groups, ngroups); |
983652ab | 1253 | |
832f5cd5 KZ |
1254 | if (!su->simulate_login || command) |
1255 | su->suppress_pam_info = 1; /* don't print PAM info messages */ | |
983652ab | 1256 | |
e5496191 KZ |
1257 | sanitize_prlimits(); |
1258 | ||
a4440cd1 KZ |
1259 | supam_open_session(su); |
1260 | ||
b923bdbe KZ |
1261 | #ifdef USE_PTY |
1262 | if (su->force_pty) { | |
1263 | ON_DBG(PTY, ul_pty_init_debug(0xffff)); | |
1264 | ||
1265 | su->pty = ul_new_pty(su->isterm); | |
1266 | if (!su->pty) | |
1267 | err(EXIT_FAILURE, _("failed to allocate pty handler")); | |
1268 | } | |
1269 | #endif | |
832f5cd5 | 1270 | create_watching_parent(su); |
983652ab KZ |
1271 | /* Now we're in the child. */ |
1272 | ||
17d5b264 KZ |
1273 | #ifdef USE_PTY |
1274 | if (su->force_pty) | |
1275 | chownmod_pty(su); | |
1276 | #endif | |
032d759a | 1277 | change_identity(su->pwd); |
b923bdbe KZ |
1278 | if (!su->same_session) { |
1279 | /* note that on --pty we call setsid() in ul_pty_init_slave() */ | |
927ded6b | 1280 | DBG(MISC, ul_debug("call setsid()")); |
983652ab | 1281 | setsid(); |
927ded6b | 1282 | } |
4365c810 | 1283 | #ifdef USE_PTY |
b923bdbe KZ |
1284 | if (su->force_pty) |
1285 | ul_pty_init_slave(su->pty); | |
4365c810 | 1286 | #endif |
983652ab KZ |
1287 | /* Set environment after pam_open_session, which may put KRB5CCNAME |
1288 | into the pam_env, etc. */ | |
1289 | ||
032d759a | 1290 | modify_environment(su, shell); |
983652ab | 1291 | |
032d759a KZ |
1292 | if (su->simulate_login && chdir(su->pwd->pw_dir) != 0) |
1293 | warn(_("warning: cannot change directory to %s"), su->pwd->pw_dir); | |
983652ab | 1294 | |
4660286e AM |
1295 | /* http://www.linux-pam.org/Linux-PAM-html/adg-interface-by-app-expected.html#adg-pam_end */ |
1296 | (void) pam_end(su->pamh, PAM_SUCCESS|PAM_DATA_SILENT); | |
1297 | ||
983652ab | 1298 | if (shell) |
832f5cd5 KZ |
1299 | run_shell(su, shell, command, argv + optind, max(0, argc - optind)); |
1300 | ||
1301 | execvp(argv[optind], &argv[optind]); | |
1302 | err(EXIT_FAILURE, _("failed to execute %s"), argv[optind]); | |
983652ab | 1303 | } |