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