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