]> git.ipfire.org Git - thirdparty/util-linux.git/blame - login-utils/su-common.c
Merge branch 'fix-exit-codes' of https://github.com/rudimeier/util-linux
[thirdparty/util-linux.git] / login-utils / su-common.c
CommitLineData
cf1a99da
KZ
1/* su for Linux. Run a shell with substitute user and group IDs.
2 Copyright (C) 1992-2006 Free Software Foundation, Inc.
3 Copyright (C) 2012 SUSE Linux Products GmbH, Nuernberg
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2, or (at your option)
8 any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software Foundation,
17 Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
18
19/* Run a shell with the real and effective UID and GID and groups
20 of USER, default `root'.
21
22 The shell run is taken from USER's password entry, /bin/sh if
23 none is specified there. If the account has a password, su
24 prompts for a password unless run by a user with real UID 0.
25
26 Does not change the current directory.
27 Sets `HOME' and `SHELL' from the password entry for USER, and if
28 USER is not root, sets `USER' and `LOGNAME' to USER.
29 The subshell is not a login shell.
30
31 If one or more ARGs are given, they are passed as additional
32 arguments to the subshell.
33
34 Does not handle /bin/sh or other shells specially
35 (setting argv[0] to "-su", passing -c only to certain shells, etc.).
36 I don't see the point in doing that, and it's ugly.
37
9e930041 38 Based on an implementation by David MacKenzie <djm@gnu.ai.mit.edu>. */
cf1a99da
KZ
39
40enum
41{
42 EXIT_CANNOT_INVOKE = 126,
43 EXIT_ENOENT = 127
44};
45
46#include <config.h>
47#include <stdio.h>
48#include <getopt.h>
49#include <sys/types.h>
50#include <pwd.h>
51#include <grp.h>
52#include <security/pam_appl.h>
fe2c9909
WJ
53#ifdef HAVE_SECURITY_PAM_MISC_H
54# include <security/pam_misc.h>
55#elif defined(HAVE_SECURITY_OPENPAM_H)
56# include <security/openpam.h>
57#endif
cf1a99da
KZ
58#include <signal.h>
59#include <sys/wait.h>
60#include <syslog.h>
b4b919fe 61#include <utmpx.h>
cf1a99da
KZ
62
63#include "err.h"
64
65#include <stdbool.h>
66#include "c.h"
67#include "xalloc.h"
68#include "nls.h"
69#include "pathnames.h"
70#include "env.h"
4e183497 71#include "closestream.h"
c74a7af1 72#include "strutils.h"
bbc5a5ea 73#include "ttyutils.h"
cf1a99da
KZ
74
75/* name of the pam configuration files. separate configs for su and su - */
7ec6adb1
KZ
76#define PAM_SRVNAME_SU "su"
77#define PAM_SRVNAME_SU_L "su-l"
78
79#define PAM_SRVNAME_RUNUSER "runuser"
80#define PAM_SRVNAME_RUNUSER_L "runuser-l"
81
37410713
KZ
82#define _PATH_LOGINDEFS_SU "/etc/default/su"
83#define _PATH_LOGINDEFS_RUNUSER "/etc/default/runuser"
cf1a99da
KZ
84
85#define is_pam_failure(_rc) ((_rc) != PAM_SUCCESS)
86
87#include "logindefs.h"
88#include "su-common.h"
89
90/* The shell to run if none is given in the user's passwd entry. */
91#define DEFAULT_SHELL "/bin/sh"
92
93/* The user to become if none is specified. */
94#define DEFAULT_USER "root"
95
96#ifndef HAVE_ENVIRON_DECL
97extern char **environ;
98#endif
99
100static void run_shell (char const *, char const *, char **, size_t)
101 __attribute__ ((__noreturn__));
102
103/* If true, pass the `-f' option to the subshell. */
104static bool fast_startup;
105
106/* If true, simulate a login instead of just starting a shell. */
107static bool simulate_login;
108
109/* If true, change some environment vars to indicate the user su'd to. */
110static bool change_environment;
111
112/* If true, then don't call setsid() with a command. */
113static int same_session = 0;
114
115/* SU_MODE_{RUNUSER,SU} */
116static int su_mode;
117
fb4edda7
KZ
118/* Don't print PAM info messages (Last login, etc.). */
119static int suppress_pam_info;
120
cf1a99da
KZ
121static bool _pam_session_opened;
122static bool _pam_cred_established;
123static sig_atomic_t volatile caught_signal = false;
124static pam_handle_t *pamh = NULL;
125
7ec6adb1
KZ
126static int restricted = 1; /* zero for root user */
127
d10028a7 128
71f207a5 129static const struct passwd *
d10028a7
KZ
130current_getpwuid(void)
131{
132 uid_t ruid;
133
134 /* GNU Hurd implementation has an extension where a process can exist in a
135 * non-conforming environment, and thus be outside the realms of POSIX
136 * process identifiers; on this platform, getuid() fails with a status of
137 * (uid_t)(-1) and sets errno if a program is run from a non-conforming
138 * environment.
139 *
140 * http://austingroupbugs.net/view.php?id=511
141 */
142 errno = 0;
143 ruid = getuid ();
144
145 return errno == 0 ? getpwuid (ruid) : NULL;
146}
147
cf1a99da
KZ
148/* Log the fact that someone has run su to the user given by PW;
149 if SUCCESSFUL is true, they gave the correct password, etc. */
150
151static void
71f207a5 152log_syslog(struct passwd const * const pw, const bool successful)
cf1a99da
KZ
153{
154 const char *new_user, *old_user, *tty;
155
156 new_user = pw->pw_name;
157 /* The utmp entry (via getlogin) is probably the best way to identify
158 the user, especially if someone su's from a su-shell. */
159 old_user = getlogin ();
160 if (!old_user)
161 {
162 /* getlogin can fail -- usually due to lack of utmp entry.
163 Resort to getpwuid. */
71f207a5 164 const struct passwd *pwd = current_getpwuid();
d10028a7 165 old_user = pwd ? pwd->pw_name : "";
cf1a99da 166 }
bbc5a5ea 167
285c1f3a 168 if (get_terminal_name(NULL, &tty, NULL) != 0 || !tty)
cf1a99da
KZ
169 tty = "none";
170
171 openlog (program_invocation_short_name, 0 , LOG_AUTH);
172 syslog (LOG_NOTICE, "%s(to %s) %s on %s",
7ec6adb1
KZ
173 successful ? "" :
174 su_mode == RUNUSER_MODE ? "FAILED RUNUSER " : "FAILED SU ",
cf1a99da
KZ
175 new_user, old_user, tty);
176 closelog ();
177}
178
c74a7af1
KZ
179
180/*
181 * Log failed login attempts in _PATH_BTMP if that exists.
182 */
71f207a5 183static void log_btmp(struct passwd const * const pw)
c74a7af1 184{
b4b919fe 185 struct utmpx ut;
c74a7af1
KZ
186 struct timeval tv;
187 const char *tty_name, *tty_num;
188
189 memset(&ut, 0, sizeof(ut));
190
191 strncpy(ut.ut_user,
192 pw && pw->pw_name ? pw->pw_name : "(unknown)",
193 sizeof(ut.ut_user));
194
285c1f3a 195 get_terminal_name(NULL, &tty_name, &tty_num);
c74a7af1
KZ
196 if (tty_num)
197 xstrncpy(ut.ut_id, tty_num, sizeof(ut.ut_id));
198 if (tty_name)
199 xstrncpy(ut.ut_line, tty_name, sizeof(ut.ut_line));
200
c74a7af1
KZ
201 gettimeofday(&tv, NULL);
202 ut.ut_tv.tv_sec = tv.tv_sec;
203 ut.ut_tv.tv_usec = tv.tv_usec;
c74a7af1
KZ
204 ut.ut_type = LOGIN_PROCESS; /* XXX doesn't matter */
205 ut.ut_pid = getpid();
206
b4b919fe 207 updwtmpx(_PATH_BTMP, &ut);
c74a7af1
KZ
208}
209
fb4edda7
KZ
210
211static int su_pam_conv(int num_msg, const struct pam_message **msg,
212 struct pam_response **resp, void *appdata_ptr)
213{
214 if (suppress_pam_info
215 && num_msg == 1
216 && msg
217 && msg[0]->msg_style == PAM_TEXT_INFO)
218 return PAM_SUCCESS;
fe2c9909 219#ifdef HAVE_SECURITY_PAM_MISC_H
fb4edda7 220 return misc_conv(num_msg, msg, resp, appdata_ptr);
fe2c9909
WJ
221#elif defined(HAVE_SECURITY_OPENPAM_H)
222 return openpam_ttyconv(num_msg, msg, resp, appdata_ptr);
223#endif
fb4edda7
KZ
224}
225
cf1a99da
KZ
226static struct pam_conv conv =
227{
fb4edda7
KZ
228 su_pam_conv,
229 NULL
cf1a99da
KZ
230};
231
232static void
71f207a5 233cleanup_pam (const int retcode)
cf1a99da 234{
71f207a5 235 const int saved_errno = errno;
cf1a99da
KZ
236
237 if (_pam_session_opened)
238 pam_close_session (pamh, 0);
239
240 if (_pam_cred_established)
241 pam_setcred (pamh, PAM_DELETE_CRED | PAM_SILENT);
242
243 pam_end(pamh, retcode);
244
245 errno = saved_errno;
246}
247
248/* Signal handler for parent process. */
249static void
8960f3ae 250su_catch_sig (int sig)
cf1a99da 251{
8960f3ae 252 caught_signal = sig;
cf1a99da
KZ
253}
254
255/* Export env variables declared by PAM modules. */
256static void
257export_pamenv (void)
258{
259 char **env;
260
261 /* This is a copy but don't care to free as we exec later anyways. */
262 env = pam_getenvlist (pamh);
263 while (env && *env)
264 {
265 if (putenv (*env) != 0)
266 err (EXIT_FAILURE, NULL);
267 env++;
268 }
269}
270
271static void
272create_watching_parent (void)
273{
274 pid_t child;
275 sigset_t ourset;
8960f3ae 276 struct sigaction oldact[3];
cf1a99da 277 int status = 0;
71f207a5 278 const int retval = pam_open_session (pamh, 0);
cf1a99da 279
cf1a99da
KZ
280 if (is_pam_failure(retval))
281 {
282 cleanup_pam (retval);
61cebf77 283 errx (EXIT_FAILURE, _("cannot open session: %s"),
cf1a99da
KZ
284 pam_strerror (pamh, retval));
285 }
286 else
287 _pam_session_opened = 1;
288
0e8edc9f
KZ
289 memset(oldact, 0, sizeof(oldact));
290
cf1a99da
KZ
291 child = fork ();
292 if (child == (pid_t) -1)
293 {
294 cleanup_pam (PAM_ABORT);
295 err (EXIT_FAILURE, _("cannot create child process"));
296 }
297
298 /* the child proceeds to run the shell */
299 if (child == 0)
300 return;
301
302 /* In the parent watch the child. */
303
304 /* su without pam support does not have a helper that keeps
305 sitting on any directory so let's go to /. */
306 if (chdir ("/") != 0)
307 warn (_("cannot change directory to %s"), "/");
308
309 sigfillset (&ourset);
310 if (sigprocmask (SIG_BLOCK, &ourset, NULL))
311 {
312 warn (_("cannot block signals"));
313 caught_signal = true;
314 }
315 if (!caught_signal)
316 {
317 struct sigaction action;
318 action.sa_handler = su_catch_sig;
319 sigemptyset (&action.sa_mask);
320 action.sa_flags = 0;
321 sigemptyset (&ourset);
322 if (!same_session)
323 {
324 if (sigaddset(&ourset, SIGINT) || sigaddset(&ourset, SIGQUIT))
325 {
326 warn (_("cannot set signal handler"));
327 caught_signal = true;
328 }
329 }
330 if (!caught_signal && (sigaddset(&ourset, SIGTERM)
331 || sigaddset(&ourset, SIGALRM)
8960f3ae 332 || sigaction(SIGTERM, &action, &oldact[0])
cf1a99da
KZ
333 || sigprocmask(SIG_UNBLOCK, &ourset, NULL))) {
334 warn (_("cannot set signal handler"));
335 caught_signal = true;
336 }
8960f3ae
OO
337 if (!caught_signal && !same_session && (sigaction(SIGINT, &action, &oldact[1])
338 || sigaction(SIGQUIT, &action, &oldact[2])))
cf1a99da
KZ
339 {
340 warn (_("cannot set signal handler"));
341 caught_signal = true;
342 }
343 }
344 if (!caught_signal)
345 {
346 pid_t pid;
347 for (;;)
348 {
349 pid = waitpid (child, &status, WUNTRACED);
350
351 if (pid != (pid_t)-1 && WIFSTOPPED (status))
352 {
353 kill (getpid (), SIGSTOP);
354 /* once we get here, we must have resumed */
355 kill (pid, SIGCONT);
356 }
357 else
358 break;
359 }
360 if (pid != (pid_t)-1)
8960f3ae
OO
361 {
362 if (WIFSIGNALED (status))
363 {
1d966345 364 fprintf (stderr, "%s%s\n", strsignal (WTERMSIG (status)),
365 WCOREDUMP (status) ? _(" (core dumped)") : "");
8960f3ae 366 status = WTERMSIG (status) + 128;
8960f3ae
OO
367 }
368 else
369 status = WEXITSTATUS (status);
dffab154
KZ
370
371 /* child is gone, don't use the PID anymore */
372 child = (pid_t) -1;
8960f3ae
OO
373 }
374 else if (caught_signal)
375 status = caught_signal + 128;
cf1a99da 376 else
8960f3ae 377 status = 1;
cf1a99da
KZ
378 }
379 else
380 status = 1;
381
dffab154 382 if (caught_signal && child != (pid_t)-1)
cf1a99da
KZ
383 {
384 fprintf (stderr, _("\nSession terminated, killing shell..."));
385 kill (child, SIGTERM);
386 }
387
388 cleanup_pam (PAM_SUCCESS);
389
390 if (caught_signal)
391 {
dffab154
KZ
392 if (child != (pid_t)-1)
393 {
394 sleep (2);
395 kill (child, SIGKILL);
396 fprintf (stderr, _(" ...killed.\n"));
397 }
8960f3ae
OO
398
399 /* Let's terminate itself with the received signal.
400 *
401 * It seems that shells use WIFSIGNALED() rather than our exit status
402 * value to detect situations when is necessary to cleanup (reset)
403 * terminal settings (kzak -- Jun 2013).
404 */
405 switch (caught_signal) {
406 case SIGTERM:
407 sigaction(SIGTERM, &oldact[0], NULL);
408 break;
409 case SIGINT:
410 sigaction(SIGINT, &oldact[1], NULL);
411 break;
412 case SIGQUIT:
413 sigaction(SIGQUIT, &oldact[2], NULL);
414 break;
415 default:
0e8edc9f
KZ
416 /* just in case that signal stuff initialization failed and
417 * caught_signal = true */
418 caught_signal = SIGKILL;
8960f3ae
OO
419 break;
420 }
270ab78a 421 kill(getpid(), caught_signal);
cf1a99da
KZ
422 }
423 exit (status);
424}
425
426static void
71f207a5 427authenticate (const struct passwd * const pw)
cf1a99da 428{
c74a7af1 429 const struct passwd *lpw = NULL;
7ec6adb1 430 const char *cp, *srvname = NULL;
cf1a99da
KZ
431 int retval;
432
7ec6adb1
KZ
433 switch (su_mode) {
434 case SU_MODE:
435 srvname = simulate_login ? PAM_SRVNAME_SU_L : PAM_SRVNAME_SU;
436 break;
437 case RUNUSER_MODE:
438 srvname = simulate_login ? PAM_SRVNAME_RUNUSER_L : PAM_SRVNAME_RUNUSER;
439 break;
82455ad5
KZ
440 default:
441 abort();
442 break;
7ec6adb1
KZ
443 }
444
445 retval = pam_start (srvname, pw->pw_name, &conv, &pamh);
cf1a99da
KZ
446 if (is_pam_failure(retval))
447 goto done;
448
449 if (isatty (0) && (cp = ttyname (0)) != NULL)
450 {
451 const char *tty;
452
453 if (strncmp (cp, "/dev/", 5) == 0)
454 tty = cp + 5;
455 else
456 tty = cp;
457 retval = pam_set_item (pamh, PAM_TTY, tty);
458 if (is_pam_failure(retval))
459 goto done;
460 }
461
d10028a7 462 lpw = current_getpwuid ();
cf1a99da
KZ
463 if (lpw && lpw->pw_name)
464 {
465 retval = pam_set_item (pamh, PAM_RUSER, (const void *) lpw->pw_name);
466 if (is_pam_failure(retval))
467 goto done;
468 }
469
7ec6adb1
KZ
470 if (su_mode == RUNUSER_MODE)
471 {
472 /*
473 * This is the only difference between runuser(1) and su(1). The command
474 * runuser(1) does not required authentication, because user is root.
475 */
476 if (restricted)
477 errx(EXIT_FAILURE, _("may not be used by non-root users"));
478 return;
479 }
480
cf1a99da
KZ
481 retval = pam_authenticate (pamh, 0);
482 if (is_pam_failure(retval))
483 goto done;
484
485 retval = pam_acct_mgmt (pamh, 0);
486 if (retval == PAM_NEW_AUTHTOK_REQD)
487 {
488 /* Password has expired. Offer option to change it. */
489 retval = pam_chauthtok (pamh, PAM_CHANGE_EXPIRED_AUTHTOK);
490 }
491
492done:
493
b0332112 494 log_syslog(pw, !is_pam_failure(retval));
cf1a99da
KZ
495
496 if (is_pam_failure(retval))
497 {
c74a7af1
KZ
498 const char *msg;
499
500 log_btmp(pw);
501
502 msg = pam_strerror(pamh, retval);
cf1a99da
KZ
503 pam_end(pamh, retval);
504 sleep (getlogindefs_num ("FAIL_DELAY", 1));
505 errx (EXIT_FAILURE, "%s", msg?msg:_("incorrect password"));
506 }
507}
508
cf1a99da 509static void
71f207a5 510set_path(const struct passwd * const pw)
cf1a99da
KZ
511{
512 int r;
513 if (pw->pw_uid)
514 r = logindefs_setenv("PATH", "ENV_PATH", _PATH_DEFPATH);
515
516 else if ((r = logindefs_setenv("PATH", "ENV_ROOTPATH", NULL)) != 0)
517 r = logindefs_setenv("PATH", "ENV_SUPATH", _PATH_DEFPATH_ROOT);
518
519 if (r != 0)
984a6096 520 err (EXIT_FAILURE, _("failed to set the %s environment variable"), "PATH");
cf1a99da
KZ
521}
522
523/* Update `environ' for the new shell based on PW, with SHELL being
524 the value for the SHELL environment variable. */
525
526static void
71f207a5 527modify_environment (const struct passwd * const pw, const char * const shell)
cf1a99da
KZ
528{
529 if (simulate_login)
530 {
531 /* Leave TERM unchanged. Set HOME, SHELL, USER, LOGNAME, PATH.
532 Unset all other environment variables. */
101c5838 533 char *term = getenv ("TERM");
cf1a99da
KZ
534 if (term)
535 term = xstrdup (term);
536 environ = xmalloc ((6 + !!term) * sizeof (char *));
537 environ[0] = NULL;
101c5838 538 if (term) {
cf1a99da 539 xsetenv ("TERM", term, 1);
101c5838
KZ
540 free(term);
541 }
cf1a99da 542 xsetenv ("HOME", pw->pw_dir, 1);
7a7f9d38
KZ
543 if (shell)
544 xsetenv ("SHELL", shell, 1);
cf1a99da
KZ
545 xsetenv ("USER", pw->pw_name, 1);
546 xsetenv ("LOGNAME", pw->pw_name, 1);
547 set_path(pw);
548 }
549 else
550 {
bb3e5808 551 /* Set HOME, SHELL, and (if not becoming a superuser)
cf1a99da
KZ
552 USER and LOGNAME. */
553 if (change_environment)
554 {
555 xsetenv ("HOME", pw->pw_dir, 1);
7a7f9d38
KZ
556 if (shell)
557 xsetenv ("SHELL", shell, 1);
cf1a99da
KZ
558 if (getlogindefs_bool ("ALWAYS_SET_PATH", 0))
559 set_path(pw);
705ee62f 560
cf1a99da
KZ
561 if (pw->pw_uid)
562 {
563 xsetenv ("USER", pw->pw_name, 1);
564 xsetenv ("LOGNAME", pw->pw_name, 1);
565 }
566 }
567 }
568
569 export_pamenv ();
570}
571
572/* Become the user and group(s) specified by PW. */
573
574static void
71f207a5 575init_groups (const struct passwd * const pw, const gid_t * const groups, const size_t num_groups)
cf1a99da
KZ
576{
577 int retval;
578
579 errno = 0;
580
581 if (num_groups)
582 retval = setgroups (num_groups, groups);
583 else
584 retval = initgroups (pw->pw_name, pw->pw_gid);
585
586 if (retval == -1)
587 {
588 cleanup_pam (PAM_ABORT);
589 err (EXIT_FAILURE, _("cannot set groups"));
590 }
591 endgrent ();
592
593 retval = pam_setcred (pamh, PAM_ESTABLISH_CRED);
594 if (is_pam_failure(retval))
595 errx (EXIT_FAILURE, "%s", pam_strerror (pamh, retval));
596 else
597 _pam_cred_established = 1;
598}
599
600static void
71f207a5 601change_identity (const struct passwd * const pw)
cf1a99da
KZ
602{
603 if (setgid (pw->pw_gid))
604 err (EXIT_FAILURE, _("cannot set group id"));
605 if (setuid (pw->pw_uid))
606 err (EXIT_FAILURE, _("cannot set user id"));
607}
608
609/* Run SHELL, or DEFAULT_SHELL if SHELL is empty.
610 If COMMAND is nonzero, pass it to the shell with the -c option.
611 Pass ADDITIONAL_ARGS to the shell as more arguments; there
612 are N_ADDITIONAL_ARGS extra arguments. */
613
614static void
71f207a5
SK
615run_shell (char const * const shell, char const * const command, char ** const additional_args,
616 const size_t n_additional_args)
cf1a99da 617{
71f207a5 618 const size_t n_args = 1 + fast_startup + 2 * !!command + n_additional_args + 1;
90936faf 619 const char **args = xcalloc (n_args, sizeof *args);
cf1a99da
KZ
620 size_t argno = 1;
621
622 if (simulate_login)
623 {
624 char *arg0;
71f207a5 625 const char *shell_basename;
cf1a99da
KZ
626
627 shell_basename = basename (shell);
628 arg0 = xmalloc (strlen (shell_basename) + 2);
629 arg0[0] = '-';
630 strcpy (arg0 + 1, shell_basename);
631 args[0] = arg0;
632 }
633 else
634 args[0] = basename (shell);
635 if (fast_startup)
636 args[argno++] = "-f";
637 if (command)
638 {
639 args[argno++] = "-c";
640 args[argno++] = command;
641 }
642 memcpy (args + argno, additional_args, n_additional_args * sizeof *args);
643 args[argno + n_additional_args] = NULL;
644 execv (shell, (char **) args);
645
646 {
647 int exit_status = (errno == ENOENT ? EXIT_ENOENT : EXIT_CANNOT_INVOKE);
07ff972e 648 warn (_("failed to execute %s"), shell);
cf1a99da
KZ
649 exit (exit_status);
650 }
651}
652
653/* Return true if SHELL is a restricted shell (one not returned by
654 getusershell), else false, meaning it is a standard shell. */
655
656static bool
71f207a5 657restricted_shell (const char * const shell)
cf1a99da
KZ
658{
659 char *line;
660
661 setusershell ();
662 while ((line = getusershell ()) != NULL)
663 {
664 if (*line != '#' && !strcmp (line, shell))
665 {
666 endusershell ();
667 return false;
668 }
669 }
670 endusershell ();
671 return true;
672}
673
674static void __attribute__((__noreturn__))
71f207a5 675usage (const int status)
cf1a99da 676{
7a7f9d38
KZ
677 if (su_mode == RUNUSER_MODE) {
678 fputs(USAGE_HEADER, stdout);
281873b8 679 printf (_(" %s [options] -u <user> [[--] <command>]\n"), program_invocation_short_name);
0bd34150 680 printf (_(" %s [options] [-] [<user> [<argument>...]]\n"), program_invocation_short_name);
7a7f9d38 681 fputs (_("\n"
0bd34150
BS
682 "Run <command> with the effective user ID and group ID of <user>. If -u is\n"
683 "not given, fall back to su(1)-compatible semantics and execute standard shell.\n"
1b435f38 684 "The options -c, -f, -l, and -s are mutually exclusive with -u.\n"), stdout);
7a7f9d38
KZ
685
686 fputs(USAGE_OPTIONS, stdout);
687
0bd34150 688 fputs (_(" -u, --user <user> username\n"), stdout);
7a7f9d38
KZ
689
690 } else {
691 fputs(USAGE_HEADER, stdout);
0bd34150 692 printf (_(" %s [options] [-] [<user> [<argument>...]]\n"), program_invocation_short_name);
7a7f9d38 693 fputs (_("\n"
0bd34150
BS
694 "Change the effective user ID and group ID to that of <user>.\n"
695 "A mere - implies -l. If <user> is not given, root is assumed.\n"), stdout);
7a7f9d38
KZ
696
697 fputs(USAGE_OPTIONS, stdout);
698 }
699
09f5902e
SK
700 fputs (_(" -m, -p, --preserve-environment do not reset environment variables\n"), stdout);
701 fputs (_(" -g, --group <group> specify the primary group\n"), stdout);
702 fputs (_(" -G, --supp-group <group> specify a supplemental group\n\n"), stdout);
703
704 fputs (_(" -, -l, --login make the shell a login shell\n"), stdout);
705 fputs (_(" -c, --command <command> pass a single command to the shell with -c\n"), stdout);
0bd34150
BS
706 fputs (_(" --session-command <command> pass a single command to the shell with -c\n"
707 " and do not create a new session\n"), stdout);
09f5902e 708 fputs (_(" -f, --fast pass -f to the shell (for csh or tcsh)\n"), stdout);
0bd34150 709 fputs (_(" -s, --shell <shell> run <shell> if /etc/shells allows it\n"), stdout);
7a7f9d38
KZ
710
711 fputs(USAGE_SEPARATOR, stdout);
712 fputs(USAGE_HELP, stdout);
713 fputs(USAGE_VERSION, stdout);
714 printf(USAGE_MAN_TAIL(su_mode == SU_MODE ? "su(1)" : "runuser(1)"));
cf1a99da
KZ
715 exit (status);
716}
717
718static
719void load_config(void)
720{
7ec6adb1
KZ
721 switch (su_mode) {
722 case SU_MODE:
723 logindefs_load_file(_PATH_LOGINDEFS_SU);
724 break;
725 case RUNUSER_MODE:
726 logindefs_load_file(_PATH_LOGINDEFS_RUNUSER);
727 break;
71f207a5
SK
728 default:
729 abort();
730 break;
7ec6adb1
KZ
731 }
732
cf1a99da
KZ
733 logindefs_load_file(_PATH_LOGINDEFS);
734}
735
736/*
737 * Returns 1 if the current user is not root
738 */
739static int
740evaluate_uid(void)
741{
71f207a5
SK
742 const uid_t ruid = getuid();
743 const uid_t euid = geteuid();
cf1a99da
KZ
744
745 /* if we're really root and aren't running setuid */
746 return (uid_t) 0 == ruid && ruid == euid ? 0 : 1;
747}
748
c619d3d1
KZ
749static gid_t
750add_supp_group(const char *name, gid_t **groups, size_t *ngroups)
751{
752 struct group *gr;
753
754 if (*ngroups >= NGROUPS_MAX)
755 errx(EXIT_FAILURE,
756 P_("specifying more than %d supplemental group is not possible",
757 "specifying more than %d supplemental groups is not possible",
758 NGROUPS_MAX - 1), NGROUPS_MAX - 1);
759
760 gr = getgrnam(name);
761 if (!gr)
762 errx(EXIT_FAILURE, _("group %s does not exist"), name);
763
764 *groups = xrealloc(*groups, sizeof(gid_t) * (*ngroups + 1));
765 (*groups)[*ngroups] = gr->gr_gid;
766 (*ngroups)++;
767
768 return gr->gr_gid;
769}
770
cf1a99da
KZ
771int
772su_main (int argc, char **argv, int mode)
773{
774 int optc;
7a7f9d38 775 const char *new_user = DEFAULT_USER, *runuser_user = NULL;
71f207a5 776 const char *command = NULL;
cf1a99da 777 int request_same_session = 0;
71f207a5 778 const char *shell = NULL;
cf1a99da
KZ
779 struct passwd *pw;
780 struct passwd pw_copy;
c619d3d1
KZ
781
782 gid_t *groups = NULL;
783 size_t ngroups = 0;
784 bool use_supp = false;
785 bool use_gid = false;
786 gid_t gid = 0;
cf1a99da 787
bda67d64
KZ
788 static const struct option longopts[] = {
789 {"command", required_argument, NULL, 'c'},
790 {"session-command", required_argument, NULL, 'C'},
791 {"fast", no_argument, NULL, 'f'},
792 {"login", no_argument, NULL, 'l'},
793 {"preserve-environment", no_argument, NULL, 'p'},
794 {"shell", required_argument, NULL, 's'},
795 {"group", required_argument, NULL, 'g'},
796 {"supp-group", required_argument, NULL, 'G'},
7a7f9d38 797 {"user", required_argument, NULL, 'u'}, /* runuser only */
87918040
SK
798 {"help", no_argument, NULL, 'h'},
799 {"version", no_argument, NULL, 'V'},
bda67d64
KZ
800 {NULL, 0, NULL, 0}
801 };
802
cf1a99da
KZ
803 setlocale (LC_ALL, "");
804 bindtextdomain (PACKAGE, LOCALEDIR);
805 textdomain (PACKAGE);
4e183497 806 atexit(close_stdout);
cf1a99da
KZ
807
808 su_mode = mode;
809 fast_startup = false;
810 simulate_login = false;
811 change_environment = true;
812
49932ad8 813 while ((optc = getopt_long (argc, argv, "c:fg:G:lmps:u:hV", longopts, NULL)) != -1)
cf1a99da
KZ
814 {
815 switch (optc)
816 {
817 case 'c':
818 command = optarg;
819 break;
820
821 case 'C':
822 command = optarg;
823 request_same_session = 1;
824 break;
825
826 case 'f':
827 fast_startup = true;
828 break;
829
830 case 'g':
c619d3d1
KZ
831 use_gid = true;
832 gid = add_supp_group(optarg, &groups, &ngroups);
cf1a99da
KZ
833 break;
834
835 case 'G':
c619d3d1
KZ
836 use_supp = true;
837 add_supp_group(optarg, &groups, &ngroups);
cf1a99da
KZ
838 break;
839
840 case 'l':
841 simulate_login = true;
842 break;
843
844 case 'm':
845 case 'p':
846 change_environment = false;
847 break;
848
849 case 's':
850 shell = optarg;
851 break;
852
7a7f9d38
KZ
853 case 'u':
854 if (su_mode != RUNUSER_MODE)
855 usage (EXIT_FAILURE);
856 runuser_user = optarg;
857 break;
858
cf1a99da
KZ
859 case 'h':
860 usage(0);
861
862 case 'V':
863 printf(UTIL_LINUX_VERSION);
864 exit(EXIT_SUCCESS);
865
866 default:
677ec86c 867 errtryhelp(EXIT_FAILURE);
cf1a99da
KZ
868 }
869 }
870
871 restricted = evaluate_uid ();
872
873 if (optind < argc && !strcmp (argv[optind], "-"))
874 {
875 simulate_login = true;
876 ++optind;
877 }
7a7f9d38 878
3e5c0a2d 879 if (simulate_login && !change_environment) {
09af3db4 880 warnx(_("ignoring --preserve-environment, it's mutually exclusive with --login"));
3e5c0a2d
KZ
881 change_environment = true;
882 }
883
360e0887
KZ
884 switch (su_mode) {
885 case RUNUSER_MODE:
886 if (runuser_user) {
887 /* runuser -u <user> <command> */
888 new_user = runuser_user;
889 if (shell || fast_startup || command || simulate_login) {
890 errx(EXIT_FAILURE,
7a7f9d38 891 _("options --{shell,fast,command,session-command,login} and "
09af3db4 892 "--user are mutually exclusive"));
360e0887
KZ
893 }
894 if (optind == argc)
09af3db4 895 errx(EXIT_FAILURE, _("no command was specified"));
360e0887
KZ
896
897 break;
7a7f9d38 898 }
360e0887
KZ
899 /* fallthrough if -u <user> is not specified, then follow
900 * traditional su(1) behavior
901 */
b1557fe9 902 /* fallthrough */
360e0887
KZ
903 case SU_MODE:
904 if (optind < argc)
905 new_user = argv[optind++];
906 break;
71f207a5
SK
907 default:
908 abort();
909 break;
7a7f9d38 910 }
cf1a99da 911
c619d3d1 912 if ((use_supp || use_gid) && restricted)
cf1a99da
KZ
913 errx(EXIT_FAILURE, _("only root can specify alternative groups"));
914
915 logindefs_load_defaults = load_config;
916
917 pw = getpwnam (new_user);
918 if (! (pw && pw->pw_name && pw->pw_name[0] && pw->pw_dir && pw->pw_dir[0]
919 && pw->pw_passwd))
920 errx (EXIT_FAILURE, _("user %s does not exist"), new_user);
921
922 /* Make a copy of the password information and point pw at the local
923 copy instead. Otherwise, some systems (e.g. Linux) would clobber
924 the static data through the getlogin call from log_su.
925 Also, make sure pw->pw_shell is a nonempty string.
926 It may be NULL when NEW_USER is a username that is retrieved via NIS (YP),
927 but that doesn't have a default shell listed. */
928 pw_copy = *pw;
929 pw = &pw_copy;
930 pw->pw_name = xstrdup (pw->pw_name);
931 pw->pw_passwd = xstrdup (pw->pw_passwd);
932 pw->pw_dir = xstrdup (pw->pw_dir);
933 pw->pw_shell = xstrdup (pw->pw_shell && pw->pw_shell[0]
934 ? pw->pw_shell
935 : DEFAULT_SHELL);
936 endpwent ();
937
c619d3d1 938 if (use_supp && !use_gid)
cf1a99da 939 pw->pw_gid = groups[0];
c619d3d1
KZ
940 else if (use_gid)
941 pw->pw_gid = gid;
cf1a99da
KZ
942
943 authenticate (pw);
944
945 if (request_same_session || !command || !pw->pw_uid)
946 same_session = 1;
947
7a7f9d38
KZ
948 /* initialize shell variable only if "-u <user>" not specified */
949 if (runuser_user) {
950 shell = NULL;
951 } else {
952 if (!shell && !change_environment)
953 shell = getenv ("SHELL");
954 if (shell && getuid () != 0 && restricted_shell (pw->pw_shell))
955 {
956 /* The user being su'd to has a nonstandard shell, and so is
957 probably a uucp account or has restricted access. Don't
958 compromise the account by allowing access with a standard
959 shell. */
960 warnx (_("using restricted shell %s"), pw->pw_shell);
961 shell = NULL;
962 }
963 shell = xstrdup (shell ? shell : pw->pw_shell);
964 }
cf1a99da 965
c619d3d1 966 init_groups (pw, groups, ngroups);
cf1a99da 967
fb4edda7
KZ
968 if (!simulate_login || command)
969 suppress_pam_info = 1; /* don't print PAM info messages */
970
cf1a99da
KZ
971 create_watching_parent ();
972 /* Now we're in the child. */
973
974 change_identity (pw);
975 if (!same_session)
976 setsid ();
977
978 /* Set environment after pam_open_session, which may put KRB5CCNAME
979 into the pam_env, etc. */
980
981 modify_environment (pw, shell);
982
983 if (simulate_login && chdir (pw->pw_dir) != 0)
984 warn (_("warning: cannot change directory to %s"), pw->pw_dir);
985
7a7f9d38
KZ
986 if (shell)
987 run_shell (shell, command, argv + optind, max (0, argc - optind));
988 else {
989 execvp(argv[optind], &argv[optind]);
07ff972e 990 err(EXIT_FAILURE, _("failed to execute %s"), argv[optind]);
7a7f9d38 991 }
cf1a99da
KZ
992}
993
994// vim: sw=2 cinoptions=>4,n-2,{2,^-2,\:2,=2,g0,h2,p5,t0,+2,(0,u0,w1,m1