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