]> git.ipfire.org Git - thirdparty/util-linux.git/blob - login-utils/su-common.c
misc: Fix various typos
[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 {
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 #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
58 #include <signal.h>
59 #include <sys/wait.h>
60 #include <syslog.h>
61 #include <utmp.h>
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"
71 #include "closestream.h"
72 #include "strutils.h"
73 #include "ttyutils.h"
74
75 /* name of the pam configuration files. separate configs for su and su - */
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"
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
97 extern char **environ;
98 #endif
99
100 static void run_shell (char const *, char const *, char **, size_t)
101 __attribute__ ((__noreturn__));
102
103 /* If true, pass the `-f' option to the subshell. */
104 static bool fast_startup;
105
106 /* If true, simulate a login instead of just starting a shell. */
107 static bool simulate_login;
108
109 /* If true, change some environment vars to indicate the user su'd to. */
110 static bool change_environment;
111
112 /* If true, then don't call setsid() with a command. */
113 static int same_session = 0;
114
115 /* SU_MODE_{RUNUSER,SU} */
116 static int su_mode;
117
118 /* Don't print PAM info messages (Last login, etc.). */
119 static int suppress_pam_info;
120
121 static bool _pam_session_opened;
122 static bool _pam_cred_established;
123 static sig_atomic_t volatile caught_signal = false;
124 static pam_handle_t *pamh = NULL;
125
126 static int restricted = 1; /* zero for root user */
127
128
129 static struct passwd *
130 current_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
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
151 static void
152 log_syslog(struct passwd const *pw, bool successful)
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. */
164 struct passwd *pwd = current_getpwuid();
165 old_user = pwd ? pwd->pw_name : "";
166 }
167
168 if (get_terminal_name(STDERR_FILENO, NULL, &tty, NULL) != 0 || !tty)
169 tty = "none";
170
171 openlog (program_invocation_short_name, 0 , LOG_AUTH);
172 syslog (LOG_NOTICE, "%s(to %s) %s on %s",
173 successful ? "" :
174 su_mode == RUNUSER_MODE ? "FAILED RUNUSER " : "FAILED SU ",
175 new_user, old_user, tty);
176 closelog ();
177 }
178
179
180 /*
181 * Log failed login attempts in _PATH_BTMP if that exists.
182 */
183 static 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
195 get_terminal_name(STDERR_FILENO, NULL, &tty_name, &tty_num);
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
218
219 static 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;
227 #ifdef HAVE_SECURITY_PAM_MISC_H
228 return misc_conv(num_msg, msg, resp, appdata_ptr);
229 #elif defined(HAVE_SECURITY_OPENPAM_H)
230 return openpam_ttyconv(num_msg, msg, resp, appdata_ptr);
231 #endif
232 }
233
234 static struct pam_conv conv =
235 {
236 su_pam_conv,
237 NULL
238 };
239
240 static void
241 cleanup_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. */
257 static void
258 su_catch_sig (int sig)
259 {
260 caught_signal = sig;
261 }
262
263 /* Export env variables declared by PAM modules. */
264 static void
265 export_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
279 static void
280 create_watching_parent (void)
281 {
282 pid_t child;
283 sigset_t ourset;
284 struct sigaction oldact[3];
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);
292 errx (EXIT_FAILURE, _("cannot open session: %s"),
293 pam_strerror (pamh, retval));
294 }
295 else
296 _pam_session_opened = 1;
297
298 memset(oldact, 0, sizeof(oldact));
299
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)
341 || sigaction(SIGTERM, &action, &oldact[0])
342 || sigprocmask(SIG_UNBLOCK, &ourset, NULL))) {
343 warn (_("cannot set signal handler"));
344 caught_signal = true;
345 }
346 if (!caught_signal && !same_session && (sigaction(SIGINT, &action, &oldact[1])
347 || sigaction(SIGQUIT, &action, &oldact[2])))
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)
370 {
371 if (WIFSIGNALED (status))
372 {
373 fprintf (stderr, "%s%s\n", strsignal (WTERMSIG (status)),
374 WCOREDUMP (status) ? _(" (core dumped)") : "");
375 status = WTERMSIG (status) + 128;
376 }
377 else
378 status = WEXITSTATUS (status);
379 }
380 else if (caught_signal)
381 status = caught_signal + 128;
382 else
383 status = 1;
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"));
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:
419 /* just in case that signal stuff initialization failed and
420 * caught_signal = true */
421 caught_signal = SIGKILL;
422 break;
423 }
424 kill(getpid(), caught_signal);
425 }
426 exit (status);
427 }
428
429 static void
430 authenticate (const struct passwd *pw)
431 {
432 const struct passwd *lpw = NULL;
433 const char *cp, *srvname = NULL;
434 int retval;
435
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;
443 default:
444 abort();
445 break;
446 }
447
448 retval = pam_start (srvname, pw->pw_name, &conv, &pamh);
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
465 lpw = current_getpwuid ();
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
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
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
495 done:
496
497 log_syslog(pw, !is_pam_failure(retval));
498
499 if (is_pam_failure(retval))
500 {
501 const char *msg;
502
503 log_btmp(pw);
504
505 msg = pam_strerror(pamh, retval);
506 pam_end(pamh, retval);
507 sleep (getlogindefs_num ("FAIL_DELAY", 1));
508 errx (EXIT_FAILURE, "%s", msg?msg:_("incorrect password"));
509 }
510 }
511
512 static void
513 set_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
529 static void
530 modify_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. */
536 char *term = getenv ("TERM");
537 if (term)
538 term = xstrdup (term);
539 environ = xmalloc ((6 + !!term) * sizeof (char *));
540 environ[0] = NULL;
541 if (term) {
542 xsetenv ("TERM", term, 1);
543 free(term);
544 }
545 xsetenv ("HOME", pw->pw_dir, 1);
546 if (shell)
547 xsetenv ("SHELL", shell, 1);
548 xsetenv ("USER", pw->pw_name, 1);
549 xsetenv ("LOGNAME", pw->pw_name, 1);
550 set_path(pw);
551 }
552 else
553 {
554 /* Set HOME, SHELL, and (if not becoming a superuser)
555 USER and LOGNAME. */
556 if (change_environment)
557 {
558 xsetenv ("HOME", pw->pw_dir, 1);
559 if (shell)
560 xsetenv ("SHELL", shell, 1);
561 if (getlogindefs_bool ("ALWAYS_SET_PATH", 0))
562 set_path(pw);
563
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
577 static void
578 init_groups (const struct passwd *pw, gid_t *groups, size_t num_groups)
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
603 static void
604 change_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
617 static void
618 run_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);
651 warn (_("failed to execute %s"), shell);
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
659 static bool
660 restricted_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
677 static void __attribute__((__noreturn__))
678 usage (int status)
679 {
680 if (su_mode == RUNUSER_MODE) {
681 fputs(USAGE_HEADER, stdout);
682 printf (_(" %s [options] -u <user> <command>\n"), program_invocation_short_name);
683 printf (_(" %s [options] [-] [<user> [<argument>...]]\n"), program_invocation_short_name);
684 fputs (_("\n"
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"
687 "The options -c, -f, -l, and -s are mutually exclusive with -u.\n"), stdout);
688
689 fputs(USAGE_OPTIONS, stdout);
690
691 fputs (_(" -u, --user <user> username\n"), stdout);
692
693 } else {
694 fputs(USAGE_HEADER, stdout);
695 printf (_(" %s [options] [-] [<user> [<argument>...]]\n"), program_invocation_short_name);
696 fputs (_("\n"
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);
699
700 fputs(USAGE_OPTIONS, stdout);
701 }
702
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);
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);
711 fputs (_(" -f, --fast pass -f to the shell (for csh or tcsh)\n"), stdout);
712 fputs (_(" -s, --shell <shell> run <shell> if /etc/shells allows it\n"), stdout);
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)"));
718 exit (status);
719 }
720
721 static
722 void load_config(void)
723 {
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
733 logindefs_load_file(_PATH_LOGINDEFS);
734 }
735
736 /*
737 * Returns 1 if the current user is not root
738 */
739 static int
740 evaluate_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
749 static gid_t
750 add_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
771 int
772 su_main (int argc, char **argv, int mode)
773 {
774 int optc;
775 const char *new_user = DEFAULT_USER, *runuser_user = NULL;
776 char *command = NULL;
777 int request_same_session = 0;
778 char *shell = NULL;
779 struct passwd *pw;
780 struct passwd pw_copy;
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;
787
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'},
797 {"user", required_argument, NULL, 'u'}, /* runuser only */
798 {"help", no_argument, 0, 'h'},
799 {"version", no_argument, 0, 'V'},
800 {NULL, 0, NULL, 0}
801 };
802
803 setlocale (LC_ALL, "");
804 bindtextdomain (PACKAGE, LOCALEDIR);
805 textdomain (PACKAGE);
806 atexit(close_stdout);
807
808 su_mode = mode;
809 fast_startup = false;
810 simulate_login = false;
811 change_environment = true;
812
813 while ((optc = getopt_long (argc, argv, "c:fg:G:lmps:u:hV", longopts, NULL)) != -1)
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':
831 use_gid = true;
832 gid = add_supp_group(optarg, &groups, &ngroups);
833 break;
834
835 case 'G':
836 use_supp = true;
837 add_supp_group(optarg, &groups, &ngroups);
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
853 case 'u':
854 if (su_mode != RUNUSER_MODE)
855 usage (EXIT_FAILURE);
856 runuser_user = optarg;
857 break;
858
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 }
878
879 if (simulate_login && !change_environment) {
880 warnx(_("ignoring --preserve-environment, it's mutually exclusive with --login"));
881 change_environment = true;
882 }
883
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,
891 _("options --{shell,fast,command,session-command,login} and "
892 "--user are mutually exclusive"));
893 }
894 if (optind == argc)
895 errx(EXIT_FAILURE, _("no command was specified"));
896
897 break;
898 }
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;
906 }
907
908 if ((use_supp || use_gid) && restricted)
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
934 if (use_supp && !use_gid)
935 pw->pw_gid = groups[0];
936 else if (use_gid)
937 pw->pw_gid = gid;
938
939 authenticate (pw);
940
941 if (request_same_session || !command || !pw->pw_uid)
942 same_session = 1;
943
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 }
961
962 init_groups (pw, groups, ngroups);
963
964 if (!simulate_login || command)
965 suppress_pam_info = 1; /* don't print PAM info messages */
966
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
982 if (shell)
983 run_shell (shell, command, argv + optind, max (0, argc - optind));
984 else {
985 execvp(argv[optind], &argv[optind]);
986 err(EXIT_FAILURE, _("failed to execute %s"), argv[optind]);
987 }
988 }
989
990 // vim: sw=2 cinoptions=>4,n-2,{2,^-2,\:2,=2,g0,h2,p5,t0,+2,(0,u0,w1,m1