]> git.ipfire.org Git - thirdparty/util-linux.git/blame - login-utils/su-common.c
su: clean up groups initialization
[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>
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>
c74a7af1 61#include <utmp.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
82#define _PATH_LOGINDEFS_SU "/etc/defaults/su"
83#define _PATH_LOGINDEFS_RUNUSER "/etc/defaults/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
KZ
128
129static struct passwd *
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
b0332112 152log_syslog(struct passwd const *pw, 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. */
d10028a7
KZ
164 struct passwd *pwd = current_getpwuid();
165 old_user = pwd ? pwd->pw_name : "";
cf1a99da 166 }
bbc5a5ea 167
9b5dc4cb 168 if (get_terminal_name(STDERR_FILENO, 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 */
183static void log_btmp(struct passwd const *pw)
184{
185 struct utmp ut;
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
507341f8 195 get_terminal_name(STDERR_FILENO, 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
201#if defined(_HAVE_UT_TV) /* in <utmpbits.h> included by <utmp.h> */
202 gettimeofday(&tv, NULL);
203 ut.ut_tv.tv_sec = tv.tv_sec;
204 ut.ut_tv.tv_usec = tv.tv_usec;
205#else
206 {
207 time_t t;
208 time(&t);
209 ut.ut_time = t; /* ut_time is not always a time_t */
210 }
211#endif
212 ut.ut_type = LOGIN_PROCESS; /* XXX doesn't matter */
213 ut.ut_pid = getpid();
214
215 updwtmp(_PATH_BTMP, &ut);
216}
217
fb4edda7
KZ
218
219static int su_pam_conv(int num_msg, const struct pam_message **msg,
220 struct pam_response **resp, void *appdata_ptr)
221{
222 if (suppress_pam_info
223 && num_msg == 1
224 && msg
225 && msg[0]->msg_style == PAM_TEXT_INFO)
226 return PAM_SUCCESS;
fe2c9909 227#ifdef HAVE_SECURITY_PAM_MISC_H
fb4edda7 228 return misc_conv(num_msg, msg, resp, appdata_ptr);
fe2c9909
WJ
229#elif defined(HAVE_SECURITY_OPENPAM_H)
230 return openpam_ttyconv(num_msg, msg, resp, appdata_ptr);
231#endif
fb4edda7
KZ
232}
233
cf1a99da
KZ
234static struct pam_conv conv =
235{
fb4edda7
KZ
236 su_pam_conv,
237 NULL
cf1a99da
KZ
238};
239
240static void
241cleanup_pam (int retcode)
242{
243 int saved_errno = errno;
244
245 if (_pam_session_opened)
246 pam_close_session (pamh, 0);
247
248 if (_pam_cred_established)
249 pam_setcred (pamh, PAM_DELETE_CRED | PAM_SILENT);
250
251 pam_end(pamh, retcode);
252
253 errno = saved_errno;
254}
255
256/* Signal handler for parent process. */
257static void
8960f3ae 258su_catch_sig (int sig)
cf1a99da 259{
8960f3ae 260 caught_signal = sig;
cf1a99da
KZ
261}
262
263/* Export env variables declared by PAM modules. */
264static void
265export_pamenv (void)
266{
267 char **env;
268
269 /* This is a copy but don't care to free as we exec later anyways. */
270 env = pam_getenvlist (pamh);
271 while (env && *env)
272 {
273 if (putenv (*env) != 0)
274 err (EXIT_FAILURE, NULL);
275 env++;
276 }
277}
278
279static void
280create_watching_parent (void)
281{
282 pid_t child;
283 sigset_t ourset;
8960f3ae 284 struct sigaction oldact[3];
cf1a99da
KZ
285 int status = 0;
286 int retval;
287
288 retval = pam_open_session (pamh, 0);
289 if (is_pam_failure(retval))
290 {
291 cleanup_pam (retval);
61cebf77 292 errx (EXIT_FAILURE, _("cannot open session: %s"),
cf1a99da
KZ
293 pam_strerror (pamh, retval));
294 }
295 else
296 _pam_session_opened = 1;
297
0e8edc9f
KZ
298 memset(oldact, 0, sizeof(oldact));
299
cf1a99da
KZ
300 child = fork ();
301 if (child == (pid_t) -1)
302 {
303 cleanup_pam (PAM_ABORT);
304 err (EXIT_FAILURE, _("cannot create child process"));
305 }
306
307 /* the child proceeds to run the shell */
308 if (child == 0)
309 return;
310
311 /* In the parent watch the child. */
312
313 /* su without pam support does not have a helper that keeps
314 sitting on any directory so let's go to /. */
315 if (chdir ("/") != 0)
316 warn (_("cannot change directory to %s"), "/");
317
318 sigfillset (&ourset);
319 if (sigprocmask (SIG_BLOCK, &ourset, NULL))
320 {
321 warn (_("cannot block signals"));
322 caught_signal = true;
323 }
324 if (!caught_signal)
325 {
326 struct sigaction action;
327 action.sa_handler = su_catch_sig;
328 sigemptyset (&action.sa_mask);
329 action.sa_flags = 0;
330 sigemptyset (&ourset);
331 if (!same_session)
332 {
333 if (sigaddset(&ourset, SIGINT) || sigaddset(&ourset, SIGQUIT))
334 {
335 warn (_("cannot set signal handler"));
336 caught_signal = true;
337 }
338 }
339 if (!caught_signal && (sigaddset(&ourset, SIGTERM)
340 || sigaddset(&ourset, SIGALRM)
8960f3ae 341 || sigaction(SIGTERM, &action, &oldact[0])
cf1a99da
KZ
342 || sigprocmask(SIG_UNBLOCK, &ourset, NULL))) {
343 warn (_("cannot set signal handler"));
344 caught_signal = true;
345 }
8960f3ae
OO
346 if (!caught_signal && !same_session && (sigaction(SIGINT, &action, &oldact[1])
347 || sigaction(SIGQUIT, &action, &oldact[2])))
cf1a99da
KZ
348 {
349 warn (_("cannot set signal handler"));
350 caught_signal = true;
351 }
352 }
353 if (!caught_signal)
354 {
355 pid_t pid;
356 for (;;)
357 {
358 pid = waitpid (child, &status, WUNTRACED);
359
360 if (pid != (pid_t)-1 && WIFSTOPPED (status))
361 {
362 kill (getpid (), SIGSTOP);
363 /* once we get here, we must have resumed */
364 kill (pid, SIGCONT);
365 }
366 else
367 break;
368 }
369 if (pid != (pid_t)-1)
8960f3ae
OO
370 {
371 if (WIFSIGNALED (status))
372 {
1d966345 373 fprintf (stderr, "%s%s\n", strsignal (WTERMSIG (status)),
374 WCOREDUMP (status) ? _(" (core dumped)") : "");
8960f3ae 375 status = WTERMSIG (status) + 128;
8960f3ae
OO
376 }
377 else
378 status = WEXITSTATUS (status);
379 }
380 else if (caught_signal)
381 status = caught_signal + 128;
cf1a99da 382 else
8960f3ae 383 status = 1;
cf1a99da
KZ
384 }
385 else
386 status = 1;
387
388 if (caught_signal)
389 {
390 fprintf (stderr, _("\nSession terminated, killing shell..."));
391 kill (child, SIGTERM);
392 }
393
394 cleanup_pam (PAM_SUCCESS);
395
396 if (caught_signal)
397 {
398 sleep (2);
399 kill (child, SIGKILL);
400 fprintf (stderr, _(" ...killed.\n"));
8960f3ae
OO
401
402 /* Let's terminate itself with the received signal.
403 *
404 * It seems that shells use WIFSIGNALED() rather than our exit status
405 * value to detect situations when is necessary to cleanup (reset)
406 * terminal settings (kzak -- Jun 2013).
407 */
408 switch (caught_signal) {
409 case SIGTERM:
410 sigaction(SIGTERM, &oldact[0], NULL);
411 break;
412 case SIGINT:
413 sigaction(SIGINT, &oldact[1], NULL);
414 break;
415 case SIGQUIT:
416 sigaction(SIGQUIT, &oldact[2], NULL);
417 break;
418 default:
0e8edc9f
KZ
419 /* just in case that signal stuff initialization failed and
420 * caught_signal = true */
421 caught_signal = SIGKILL;
8960f3ae
OO
422 break;
423 }
270ab78a 424 kill(getpid(), caught_signal);
cf1a99da
KZ
425 }
426 exit (status);
427}
428
429static void
430authenticate (const struct passwd *pw)
431{
c74a7af1 432 const struct passwd *lpw = NULL;
7ec6adb1 433 const char *cp, *srvname = NULL;
cf1a99da
KZ
434 int retval;
435
7ec6adb1
KZ
436 switch (su_mode) {
437 case SU_MODE:
438 srvname = simulate_login ? PAM_SRVNAME_SU_L : PAM_SRVNAME_SU;
439 break;
440 case RUNUSER_MODE:
441 srvname = simulate_login ? PAM_SRVNAME_RUNUSER_L : PAM_SRVNAME_RUNUSER;
442 break;
82455ad5
KZ
443 default:
444 abort();
445 break;
7ec6adb1
KZ
446 }
447
448 retval = pam_start (srvname, pw->pw_name, &conv, &pamh);
cf1a99da
KZ
449 if (is_pam_failure(retval))
450 goto done;
451
452 if (isatty (0) && (cp = ttyname (0)) != NULL)
453 {
454 const char *tty;
455
456 if (strncmp (cp, "/dev/", 5) == 0)
457 tty = cp + 5;
458 else
459 tty = cp;
460 retval = pam_set_item (pamh, PAM_TTY, tty);
461 if (is_pam_failure(retval))
462 goto done;
463 }
464
d10028a7 465 lpw = current_getpwuid ();
cf1a99da
KZ
466 if (lpw && lpw->pw_name)
467 {
468 retval = pam_set_item (pamh, PAM_RUSER, (const void *) lpw->pw_name);
469 if (is_pam_failure(retval))
470 goto done;
471 }
472
7ec6adb1
KZ
473 if (su_mode == RUNUSER_MODE)
474 {
475 /*
476 * This is the only difference between runuser(1) and su(1). The command
477 * runuser(1) does not required authentication, because user is root.
478 */
479 if (restricted)
480 errx(EXIT_FAILURE, _("may not be used by non-root users"));
481 return;
482 }
483
cf1a99da
KZ
484 retval = pam_authenticate (pamh, 0);
485 if (is_pam_failure(retval))
486 goto done;
487
488 retval = pam_acct_mgmt (pamh, 0);
489 if (retval == PAM_NEW_AUTHTOK_REQD)
490 {
491 /* Password has expired. Offer option to change it. */
492 retval = pam_chauthtok (pamh, PAM_CHANGE_EXPIRED_AUTHTOK);
493 }
494
495done:
496
b0332112 497 log_syslog(pw, !is_pam_failure(retval));
cf1a99da
KZ
498
499 if (is_pam_failure(retval))
500 {
c74a7af1
KZ
501 const char *msg;
502
503 log_btmp(pw);
504
505 msg = pam_strerror(pamh, retval);
cf1a99da
KZ
506 pam_end(pamh, retval);
507 sleep (getlogindefs_num ("FAIL_DELAY", 1));
508 errx (EXIT_FAILURE, "%s", msg?msg:_("incorrect password"));
509 }
510}
511
cf1a99da
KZ
512static void
513set_path(const struct passwd* pw)
514{
515 int r;
516 if (pw->pw_uid)
517 r = logindefs_setenv("PATH", "ENV_PATH", _PATH_DEFPATH);
518
519 else if ((r = logindefs_setenv("PATH", "ENV_ROOTPATH", NULL)) != 0)
520 r = logindefs_setenv("PATH", "ENV_SUPATH", _PATH_DEFPATH_ROOT);
521
522 if (r != 0)
523 err (EXIT_FAILURE, _("failed to set PATH"));
524}
525
526/* Update `environ' for the new shell based on PW, with SHELL being
527 the value for the SHELL environment variable. */
528
529static void
530modify_environment (const struct passwd *pw, const char *shell)
531{
532 if (simulate_login)
533 {
534 /* Leave TERM unchanged. Set HOME, SHELL, USER, LOGNAME, PATH.
535 Unset all other environment variables. */
101c5838 536 char *term = getenv ("TERM");
cf1a99da
KZ
537 if (term)
538 term = xstrdup (term);
539 environ = xmalloc ((6 + !!term) * sizeof (char *));
540 environ[0] = NULL;
101c5838 541 if (term) {
cf1a99da 542 xsetenv ("TERM", term, 1);
101c5838
KZ
543 free(term);
544 }
cf1a99da 545 xsetenv ("HOME", pw->pw_dir, 1);
7a7f9d38
KZ
546 if (shell)
547 xsetenv ("SHELL", shell, 1);
cf1a99da
KZ
548 xsetenv ("USER", pw->pw_name, 1);
549 xsetenv ("LOGNAME", pw->pw_name, 1);
550 set_path(pw);
551 }
552 else
553 {
bb3e5808 554 /* Set HOME, SHELL, and (if not becoming a superuser)
cf1a99da
KZ
555 USER and LOGNAME. */
556 if (change_environment)
557 {
558 xsetenv ("HOME", pw->pw_dir, 1);
7a7f9d38
KZ
559 if (shell)
560 xsetenv ("SHELL", shell, 1);
cf1a99da
KZ
561 if (getlogindefs_bool ("ALWAYS_SET_PATH", 0))
562 set_path(pw);
705ee62f 563
cf1a99da
KZ
564 if (pw->pw_uid)
565 {
566 xsetenv ("USER", pw->pw_name, 1);
567 xsetenv ("LOGNAME", pw->pw_name, 1);
568 }
569 }
570 }
571
572 export_pamenv ();
573}
574
575/* Become the user and group(s) specified by PW. */
576
577static void
c619d3d1 578init_groups (const struct passwd *pw, gid_t *groups, size_t num_groups)
cf1a99da
KZ
579{
580 int retval;
581
582 errno = 0;
583
584 if (num_groups)
585 retval = setgroups (num_groups, groups);
586 else
587 retval = initgroups (pw->pw_name, pw->pw_gid);
588
589 if (retval == -1)
590 {
591 cleanup_pam (PAM_ABORT);
592 err (EXIT_FAILURE, _("cannot set groups"));
593 }
594 endgrent ();
595
596 retval = pam_setcred (pamh, PAM_ESTABLISH_CRED);
597 if (is_pam_failure(retval))
598 errx (EXIT_FAILURE, "%s", pam_strerror (pamh, retval));
599 else
600 _pam_cred_established = 1;
601}
602
603static void
604change_identity (const struct passwd *pw)
605{
606 if (setgid (pw->pw_gid))
607 err (EXIT_FAILURE, _("cannot set group id"));
608 if (setuid (pw->pw_uid))
609 err (EXIT_FAILURE, _("cannot set user id"));
610}
611
612/* Run SHELL, or DEFAULT_SHELL if SHELL is empty.
613 If COMMAND is nonzero, pass it to the shell with the -c option.
614 Pass ADDITIONAL_ARGS to the shell as more arguments; there
615 are N_ADDITIONAL_ARGS extra arguments. */
616
617static void
618run_shell (char const *shell, char const *command, char **additional_args,
619 size_t n_additional_args)
620{
621 size_t n_args = 1 + fast_startup + 2 * !!command + n_additional_args + 1;
622 char const **args = xcalloc (n_args, sizeof *args);
623 size_t argno = 1;
624
625 if (simulate_login)
626 {
627 char *arg0;
628 char *shell_basename;
629
630 shell_basename = basename (shell);
631 arg0 = xmalloc (strlen (shell_basename) + 2);
632 arg0[0] = '-';
633 strcpy (arg0 + 1, shell_basename);
634 args[0] = arg0;
635 }
636 else
637 args[0] = basename (shell);
638 if (fast_startup)
639 args[argno++] = "-f";
640 if (command)
641 {
642 args[argno++] = "-c";
643 args[argno++] = command;
644 }
645 memcpy (args + argno, additional_args, n_additional_args * sizeof *args);
646 args[argno + n_additional_args] = NULL;
647 execv (shell, (char **) args);
648
649 {
650 int exit_status = (errno == ENOENT ? EXIT_ENOENT : EXIT_CANNOT_INVOKE);
07ff972e 651 warn (_("failed to execute %s"), shell);
cf1a99da
KZ
652 exit (exit_status);
653 }
654}
655
656/* Return true if SHELL is a restricted shell (one not returned by
657 getusershell), else false, meaning it is a standard shell. */
658
659static bool
660restricted_shell (const char *shell)
661{
662 char *line;
663
664 setusershell ();
665 while ((line = getusershell ()) != NULL)
666 {
667 if (*line != '#' && !strcmp (line, shell))
668 {
669 endusershell ();
670 return false;
671 }
672 }
673 endusershell ();
674 return true;
675}
676
677static void __attribute__((__noreturn__))
678usage (int status)
679{
7a7f9d38
KZ
680 if (su_mode == RUNUSER_MODE) {
681 fputs(USAGE_HEADER, stdout);
0bd34150
BS
682 printf (_(" %s [options] -u <user> <command>\n"), program_invocation_short_name);
683 printf (_(" %s [options] [-] [<user> [<argument>...]]\n"), program_invocation_short_name);
7a7f9d38 684 fputs (_("\n"
0bd34150
BS
685 "Run <command> with the effective user ID and group ID of <user>. If -u is\n"
686 "not given, fall back to su(1)-compatible semantics and execute standard shell.\n"
1b435f38 687 "The options -c, -f, -l, and -s are mutually exclusive with -u.\n"), stdout);
7a7f9d38
KZ
688
689 fputs(USAGE_OPTIONS, stdout);
690
0bd34150 691 fputs (_(" -u, --user <user> username\n"), stdout);
7a7f9d38
KZ
692
693 } else {
694 fputs(USAGE_HEADER, stdout);
0bd34150 695 printf (_(" %s [options] [-] [<user> [<argument>...]]\n"), program_invocation_short_name);
7a7f9d38 696 fputs (_("\n"
0bd34150
BS
697 "Change the effective user ID and group ID to that of <user>.\n"
698 "A mere - implies -l. If <user> is not given, root is assumed.\n"), stdout);
7a7f9d38
KZ
699
700 fputs(USAGE_OPTIONS, stdout);
701 }
702
09f5902e
SK
703 fputs (_(" -m, -p, --preserve-environment do not reset environment variables\n"), stdout);
704 fputs (_(" -g, --group <group> specify the primary group\n"), stdout);
705 fputs (_(" -G, --supp-group <group> specify a supplemental group\n\n"), stdout);
706
707 fputs (_(" -, -l, --login make the shell a login shell\n"), stdout);
708 fputs (_(" -c, --command <command> pass a single command to the shell with -c\n"), stdout);
0bd34150
BS
709 fputs (_(" --session-command <command> pass a single command to the shell with -c\n"
710 " and do not create a new session\n"), stdout);
09f5902e 711 fputs (_(" -f, --fast pass -f to the shell (for csh or tcsh)\n"), stdout);
0bd34150 712 fputs (_(" -s, --shell <shell> run <shell> if /etc/shells allows it\n"), stdout);
7a7f9d38
KZ
713
714 fputs(USAGE_SEPARATOR, stdout);
715 fputs(USAGE_HELP, stdout);
716 fputs(USAGE_VERSION, stdout);
717 printf(USAGE_MAN_TAIL(su_mode == SU_MODE ? "su(1)" : "runuser(1)"));
cf1a99da
KZ
718 exit (status);
719}
720
721static
722void load_config(void)
723{
7ec6adb1
KZ
724 switch (su_mode) {
725 case SU_MODE:
726 logindefs_load_file(_PATH_LOGINDEFS_SU);
727 break;
728 case RUNUSER_MODE:
729 logindefs_load_file(_PATH_LOGINDEFS_RUNUSER);
730 break;
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{
742 uid_t ruid = getuid();
743 uid_t euid = geteuid();
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;
cf1a99da
KZ
776 char *command = NULL;
777 int request_same_session = 0;
778 char *shell = NULL;
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 */
bda67d64
KZ
798 {"help", no_argument, 0, 'h'},
799 {"version", no_argument, 0, 'V'},
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:
867 usage (EXIT_FAILURE);
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 */
902 case SU_MODE:
903 if (optind < argc)
904 new_user = argv[optind++];
905 break;
7a7f9d38 906 }
cf1a99da 907
c619d3d1 908 if ((use_supp || use_gid) && restricted)
cf1a99da
KZ
909 errx(EXIT_FAILURE, _("only root can specify alternative groups"));
910
911 logindefs_load_defaults = load_config;
912
913 pw = getpwnam (new_user);
914 if (! (pw && pw->pw_name && pw->pw_name[0] && pw->pw_dir && pw->pw_dir[0]
915 && pw->pw_passwd))
916 errx (EXIT_FAILURE, _("user %s does not exist"), new_user);
917
918 /* Make a copy of the password information and point pw at the local
919 copy instead. Otherwise, some systems (e.g. Linux) would clobber
920 the static data through the getlogin call from log_su.
921 Also, make sure pw->pw_shell is a nonempty string.
922 It may be NULL when NEW_USER is a username that is retrieved via NIS (YP),
923 but that doesn't have a default shell listed. */
924 pw_copy = *pw;
925 pw = &pw_copy;
926 pw->pw_name = xstrdup (pw->pw_name);
927 pw->pw_passwd = xstrdup (pw->pw_passwd);
928 pw->pw_dir = xstrdup (pw->pw_dir);
929 pw->pw_shell = xstrdup (pw->pw_shell && pw->pw_shell[0]
930 ? pw->pw_shell
931 : DEFAULT_SHELL);
932 endpwent ();
933
c619d3d1 934 if (use_supp && !use_gid)
cf1a99da 935 pw->pw_gid = groups[0];
c619d3d1
KZ
936 else if (use_gid)
937 pw->pw_gid = gid;
cf1a99da
KZ
938
939 authenticate (pw);
940
941 if (request_same_session || !command || !pw->pw_uid)
942 same_session = 1;
943
7a7f9d38
KZ
944 /* initialize shell variable only if "-u <user>" not specified */
945 if (runuser_user) {
946 shell = NULL;
947 } else {
948 if (!shell && !change_environment)
949 shell = getenv ("SHELL");
950 if (shell && getuid () != 0 && restricted_shell (pw->pw_shell))
951 {
952 /* The user being su'd to has a nonstandard shell, and so is
953 probably a uucp account or has restricted access. Don't
954 compromise the account by allowing access with a standard
955 shell. */
956 warnx (_("using restricted shell %s"), pw->pw_shell);
957 shell = NULL;
958 }
959 shell = xstrdup (shell ? shell : pw->pw_shell);
960 }
cf1a99da 961
c619d3d1 962 init_groups (pw, groups, ngroups);
cf1a99da 963
fb4edda7
KZ
964 if (!simulate_login || command)
965 suppress_pam_info = 1; /* don't print PAM info messages */
966
cf1a99da
KZ
967 create_watching_parent ();
968 /* Now we're in the child. */
969
970 change_identity (pw);
971 if (!same_session)
972 setsid ();
973
974 /* Set environment after pam_open_session, which may put KRB5CCNAME
975 into the pam_env, etc. */
976
977 modify_environment (pw, shell);
978
979 if (simulate_login && chdir (pw->pw_dir) != 0)
980 warn (_("warning: cannot change directory to %s"), pw->pw_dir);
981
7a7f9d38
KZ
982 if (shell)
983 run_shell (shell, command, argv + optind, max (0, argc - optind));
984 else {
985 execvp(argv[optind], &argv[optind]);
07ff972e 986 err(EXIT_FAILURE, _("failed to execute %s"), argv[optind]);
7a7f9d38 987 }
cf1a99da
KZ
988}
989
990// vim: sw=2 cinoptions=>4,n-2,{2,^-2,\:2,=2,g0,h2,p5,t0,+2,(0,u0,w1,m1