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