]> git.ipfire.org Git - thirdparty/util-linux.git/blame - login-utils/su-common.c
suL fix use after free on error
[thirdparty/util-linux.git] / login-utils / su-common.c
CommitLineData
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
76UL_DEBUG_DEFINE_MASK(su);
77UL_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
116extern char **environ;
117#endif
118
665f36be
KZ
119enum {
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 */
130struct 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 169static sig_atomic_t volatile caught_signal = 0;
cf1a99da 170
b9a92282
KZ
171/* Signal handler for parent process. */
172static void
173su_catch_sig(int sig)
174{
175 caught_signal = sig;
176}
177
2260e493
KZ
178static 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
183static 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
195static 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
258static 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
265static 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 289static 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 305static 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
331static 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 352static 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 367static 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 383static 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 442static 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 458static 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
528static 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 */
669static 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
682static 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 */
699static 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 716static 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 732static 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 792static 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 817static 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 */
831static 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 */
875static 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 888static 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 911static 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 932static 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 949static 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 959static 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 973static 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
983static 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 1011static 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 1034int 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}