]> git.ipfire.org Git - thirdparty/util-linux.git/blob - login-utils/su-common.c
Use --help suggestion on invalid option
[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 <utmpx.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(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 utmpx 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(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 gettimeofday(&tv, NULL);
202 ut.ut_tv.tv_sec = tv.tv_sec;
203 ut.ut_tv.tv_usec = tv.tv_usec;
204 ut.ut_type = LOGIN_PROCESS; /* XXX doesn't matter */
205 ut.ut_pid = getpid();
206
207 updwtmpx(_PATH_BTMP, &ut);
208 }
209
210
211 static int su_pam_conv(int num_msg, const struct pam_message **msg,
212 struct pam_response **resp, void *appdata_ptr)
213 {
214 if (suppress_pam_info
215 && num_msg == 1
216 && msg
217 && msg[0]->msg_style == PAM_TEXT_INFO)
218 return PAM_SUCCESS;
219 #ifdef HAVE_SECURITY_PAM_MISC_H
220 return misc_conv(num_msg, msg, resp, appdata_ptr);
221 #elif defined(HAVE_SECURITY_OPENPAM_H)
222 return openpam_ttyconv(num_msg, msg, resp, appdata_ptr);
223 #endif
224 }
225
226 static struct pam_conv conv =
227 {
228 su_pam_conv,
229 NULL
230 };
231
232 static void
233 cleanup_pam (int retcode)
234 {
235 int saved_errno = errno;
236
237 if (_pam_session_opened)
238 pam_close_session (pamh, 0);
239
240 if (_pam_cred_established)
241 pam_setcred (pamh, PAM_DELETE_CRED | PAM_SILENT);
242
243 pam_end(pamh, retcode);
244
245 errno = saved_errno;
246 }
247
248 /* Signal handler for parent process. */
249 static void
250 su_catch_sig (int sig)
251 {
252 caught_signal = sig;
253 }
254
255 /* Export env variables declared by PAM modules. */
256 static void
257 export_pamenv (void)
258 {
259 char **env;
260
261 /* This is a copy but don't care to free as we exec later anyways. */
262 env = pam_getenvlist (pamh);
263 while (env && *env)
264 {
265 if (putenv (*env) != 0)
266 err (EXIT_FAILURE, NULL);
267 env++;
268 }
269 }
270
271 static void
272 create_watching_parent (void)
273 {
274 pid_t child;
275 sigset_t ourset;
276 struct sigaction oldact[3];
277 int status = 0;
278 int retval;
279
280 retval = pam_open_session (pamh, 0);
281 if (is_pam_failure(retval))
282 {
283 cleanup_pam (retval);
284 errx (EXIT_FAILURE, _("cannot open session: %s"),
285 pam_strerror (pamh, retval));
286 }
287 else
288 _pam_session_opened = 1;
289
290 memset(oldact, 0, sizeof(oldact));
291
292 child = fork ();
293 if (child == (pid_t) -1)
294 {
295 cleanup_pam (PAM_ABORT);
296 err (EXIT_FAILURE, _("cannot create child process"));
297 }
298
299 /* the child proceeds to run the shell */
300 if (child == 0)
301 return;
302
303 /* In the parent watch the child. */
304
305 /* su without pam support does not have a helper that keeps
306 sitting on any directory so let's go to /. */
307 if (chdir ("/") != 0)
308 warn (_("cannot change directory to %s"), "/");
309
310 sigfillset (&ourset);
311 if (sigprocmask (SIG_BLOCK, &ourset, NULL))
312 {
313 warn (_("cannot block signals"));
314 caught_signal = true;
315 }
316 if (!caught_signal)
317 {
318 struct sigaction action;
319 action.sa_handler = su_catch_sig;
320 sigemptyset (&action.sa_mask);
321 action.sa_flags = 0;
322 sigemptyset (&ourset);
323 if (!same_session)
324 {
325 if (sigaddset(&ourset, SIGINT) || sigaddset(&ourset, SIGQUIT))
326 {
327 warn (_("cannot set signal handler"));
328 caught_signal = true;
329 }
330 }
331 if (!caught_signal && (sigaddset(&ourset, SIGTERM)
332 || sigaddset(&ourset, SIGALRM)
333 || sigaction(SIGTERM, &action, &oldact[0])
334 || sigprocmask(SIG_UNBLOCK, &ourset, NULL))) {
335 warn (_("cannot set signal handler"));
336 caught_signal = true;
337 }
338 if (!caught_signal && !same_session && (sigaction(SIGINT, &action, &oldact[1])
339 || sigaction(SIGQUIT, &action, &oldact[2])))
340 {
341 warn (_("cannot set signal handler"));
342 caught_signal = true;
343 }
344 }
345 if (!caught_signal)
346 {
347 pid_t pid;
348 for (;;)
349 {
350 pid = waitpid (child, &status, WUNTRACED);
351
352 if (pid != (pid_t)-1 && WIFSTOPPED (status))
353 {
354 kill (getpid (), SIGSTOP);
355 /* once we get here, we must have resumed */
356 kill (pid, SIGCONT);
357 }
358 else
359 break;
360 }
361 if (pid != (pid_t)-1)
362 {
363 if (WIFSIGNALED (status))
364 {
365 fprintf (stderr, "%s%s\n", strsignal (WTERMSIG (status)),
366 WCOREDUMP (status) ? _(" (core dumped)") : "");
367 status = WTERMSIG (status) + 128;
368 }
369 else
370 status = WEXITSTATUS (status);
371 }
372 else if (caught_signal)
373 status = caught_signal + 128;
374 else
375 status = 1;
376 }
377 else
378 status = 1;
379
380 if (caught_signal)
381 {
382 fprintf (stderr, _("\nSession terminated, killing shell..."));
383 kill (child, SIGTERM);
384 }
385
386 cleanup_pam (PAM_SUCCESS);
387
388 if (caught_signal)
389 {
390 sleep (2);
391 kill (child, SIGKILL);
392 fprintf (stderr, _(" ...killed.\n"));
393
394 /* Let's terminate itself with the received signal.
395 *
396 * It seems that shells use WIFSIGNALED() rather than our exit status
397 * value to detect situations when is necessary to cleanup (reset)
398 * terminal settings (kzak -- Jun 2013).
399 */
400 switch (caught_signal) {
401 case SIGTERM:
402 sigaction(SIGTERM, &oldact[0], NULL);
403 break;
404 case SIGINT:
405 sigaction(SIGINT, &oldact[1], NULL);
406 break;
407 case SIGQUIT:
408 sigaction(SIGQUIT, &oldact[2], NULL);
409 break;
410 default:
411 /* just in case that signal stuff initialization failed and
412 * caught_signal = true */
413 caught_signal = SIGKILL;
414 break;
415 }
416 kill(getpid(), caught_signal);
417 }
418 exit (status);
419 }
420
421 static void
422 authenticate (const struct passwd *pw)
423 {
424 const struct passwd *lpw = NULL;
425 const char *cp, *srvname = NULL;
426 int retval;
427
428 switch (su_mode) {
429 case SU_MODE:
430 srvname = simulate_login ? PAM_SRVNAME_SU_L : PAM_SRVNAME_SU;
431 break;
432 case RUNUSER_MODE:
433 srvname = simulate_login ? PAM_SRVNAME_RUNUSER_L : PAM_SRVNAME_RUNUSER;
434 break;
435 default:
436 abort();
437 break;
438 }
439
440 retval = pam_start (srvname, pw->pw_name, &conv, &pamh);
441 if (is_pam_failure(retval))
442 goto done;
443
444 if (isatty (0) && (cp = ttyname (0)) != NULL)
445 {
446 const char *tty;
447
448 if (strncmp (cp, "/dev/", 5) == 0)
449 tty = cp + 5;
450 else
451 tty = cp;
452 retval = pam_set_item (pamh, PAM_TTY, tty);
453 if (is_pam_failure(retval))
454 goto done;
455 }
456
457 lpw = current_getpwuid ();
458 if (lpw && lpw->pw_name)
459 {
460 retval = pam_set_item (pamh, PAM_RUSER, (const void *) lpw->pw_name);
461 if (is_pam_failure(retval))
462 goto done;
463 }
464
465 if (su_mode == RUNUSER_MODE)
466 {
467 /*
468 * This is the only difference between runuser(1) and su(1). The command
469 * runuser(1) does not required authentication, because user is root.
470 */
471 if (restricted)
472 errx(EXIT_FAILURE, _("may not be used by non-root users"));
473 return;
474 }
475
476 retval = pam_authenticate (pamh, 0);
477 if (is_pam_failure(retval))
478 goto done;
479
480 retval = pam_acct_mgmt (pamh, 0);
481 if (retval == PAM_NEW_AUTHTOK_REQD)
482 {
483 /* Password has expired. Offer option to change it. */
484 retval = pam_chauthtok (pamh, PAM_CHANGE_EXPIRED_AUTHTOK);
485 }
486
487 done:
488
489 log_syslog(pw, !is_pam_failure(retval));
490
491 if (is_pam_failure(retval))
492 {
493 const char *msg;
494
495 log_btmp(pw);
496
497 msg = pam_strerror(pamh, retval);
498 pam_end(pamh, retval);
499 sleep (getlogindefs_num ("FAIL_DELAY", 1));
500 errx (EXIT_FAILURE, "%s", msg?msg:_("incorrect password"));
501 }
502 }
503
504 static void
505 set_path(const struct passwd* pw)
506 {
507 int r;
508 if (pw->pw_uid)
509 r = logindefs_setenv("PATH", "ENV_PATH", _PATH_DEFPATH);
510
511 else if ((r = logindefs_setenv("PATH", "ENV_ROOTPATH", NULL)) != 0)
512 r = logindefs_setenv("PATH", "ENV_SUPATH", _PATH_DEFPATH_ROOT);
513
514 if (r != 0)
515 err (EXIT_FAILURE, _("failed to set the %s environment variable"), "PATH");
516 }
517
518 /* Update `environ' for the new shell based on PW, with SHELL being
519 the value for the SHELL environment variable. */
520
521 static void
522 modify_environment (const struct passwd *pw, const char *shell)
523 {
524 if (simulate_login)
525 {
526 /* Leave TERM unchanged. Set HOME, SHELL, USER, LOGNAME, PATH.
527 Unset all other environment variables. */
528 char *term = getenv ("TERM");
529 if (term)
530 term = xstrdup (term);
531 environ = xmalloc ((6 + !!term) * sizeof (char *));
532 environ[0] = NULL;
533 if (term) {
534 xsetenv ("TERM", term, 1);
535 free(term);
536 }
537 xsetenv ("HOME", pw->pw_dir, 1);
538 if (shell)
539 xsetenv ("SHELL", shell, 1);
540 xsetenv ("USER", pw->pw_name, 1);
541 xsetenv ("LOGNAME", pw->pw_name, 1);
542 set_path(pw);
543 }
544 else
545 {
546 /* Set HOME, SHELL, and (if not becoming a superuser)
547 USER and LOGNAME. */
548 if (change_environment)
549 {
550 xsetenv ("HOME", pw->pw_dir, 1);
551 if (shell)
552 xsetenv ("SHELL", shell, 1);
553 if (getlogindefs_bool ("ALWAYS_SET_PATH", 0))
554 set_path(pw);
555
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
569 static void
570 init_groups (const struct passwd *pw, gid_t *groups, size_t 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
595 static void
596 change_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
609 static void
610 run_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);
643 warn (_("failed to execute %s"), shell);
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
651 static bool
652 restricted_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
669 static void __attribute__((__noreturn__))
670 usage (int status)
671 {
672 if (su_mode == RUNUSER_MODE) {
673 fputs(USAGE_HEADER, stdout);
674 printf (_(" %s [options] -u <user> <command>\n"), program_invocation_short_name);
675 printf (_(" %s [options] [-] [<user> [<argument>...]]\n"), program_invocation_short_name);
676 fputs (_("\n"
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"
679 "The options -c, -f, -l, and -s are mutually exclusive with -u.\n"), stdout);
680
681 fputs(USAGE_OPTIONS, stdout);
682
683 fputs (_(" -u, --user <user> username\n"), stdout);
684
685 } else {
686 fputs(USAGE_HEADER, stdout);
687 printf (_(" %s [options] [-] [<user> [<argument>...]]\n"), program_invocation_short_name);
688 fputs (_("\n"
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);
691
692 fputs(USAGE_OPTIONS, stdout);
693 }
694
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);
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);
703 fputs (_(" -f, --fast pass -f to the shell (for csh or tcsh)\n"), stdout);
704 fputs (_(" -s, --shell <shell> run <shell> if /etc/shells allows it\n"), stdout);
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)"));
710 exit (status);
711 }
712
713 static
714 void load_config(void)
715 {
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
725 logindefs_load_file(_PATH_LOGINDEFS);
726 }
727
728 /*
729 * Returns 1 if the current user is not root
730 */
731 static int
732 evaluate_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
741 static gid_t
742 add_supp_group(const char *name, gid_t **groups, size_t *ngroups)
743 {
744 struct group *gr;
745
746 if (*ngroups >= NGROUPS_MAX)
747 errx(EXIT_FAILURE,
748 P_("specifying more than %d supplemental group is not possible",
749 "specifying more than %d supplemental groups is not possible",
750 NGROUPS_MAX - 1), NGROUPS_MAX - 1);
751
752 gr = getgrnam(name);
753 if (!gr)
754 errx(EXIT_FAILURE, _("group %s does not exist"), name);
755
756 *groups = xrealloc(*groups, sizeof(gid_t) * (*ngroups + 1));
757 (*groups)[*ngroups] = gr->gr_gid;
758 (*ngroups)++;
759
760 return gr->gr_gid;
761 }
762
763 int
764 su_main (int argc, char **argv, int mode)
765 {
766 int optc;
767 const char *new_user = DEFAULT_USER, *runuser_user = NULL;
768 char *command = NULL;
769 int request_same_session = 0;
770 char *shell = NULL;
771 struct passwd *pw;
772 struct passwd pw_copy;
773
774 gid_t *groups = NULL;
775 size_t ngroups = 0;
776 bool use_supp = false;
777 bool use_gid = false;
778 gid_t gid = 0;
779
780 static const struct option longopts[] = {
781 {"command", required_argument, NULL, 'c'},
782 {"session-command", required_argument, NULL, 'C'},
783 {"fast", no_argument, NULL, 'f'},
784 {"login", no_argument, NULL, 'l'},
785 {"preserve-environment", no_argument, NULL, 'p'},
786 {"shell", required_argument, NULL, 's'},
787 {"group", required_argument, NULL, 'g'},
788 {"supp-group", required_argument, NULL, 'G'},
789 {"user", required_argument, NULL, 'u'}, /* runuser only */
790 {"help", no_argument, 0, 'h'},
791 {"version", no_argument, 0, 'V'},
792 {NULL, 0, NULL, 0}
793 };
794
795 setlocale (LC_ALL, "");
796 bindtextdomain (PACKAGE, LOCALEDIR);
797 textdomain (PACKAGE);
798 atexit(close_stdout);
799
800 su_mode = mode;
801 fast_startup = false;
802 simulate_login = false;
803 change_environment = true;
804
805 while ((optc = getopt_long (argc, argv, "c:fg:G:lmps:u:hV", longopts, NULL)) != -1)
806 {
807 switch (optc)
808 {
809 case 'c':
810 command = optarg;
811 break;
812
813 case 'C':
814 command = optarg;
815 request_same_session = 1;
816 break;
817
818 case 'f':
819 fast_startup = true;
820 break;
821
822 case 'g':
823 use_gid = true;
824 gid = add_supp_group(optarg, &groups, &ngroups);
825 break;
826
827 case 'G':
828 use_supp = true;
829 add_supp_group(optarg, &groups, &ngroups);
830 break;
831
832 case 'l':
833 simulate_login = true;
834 break;
835
836 case 'm':
837 case 'p':
838 change_environment = false;
839 break;
840
841 case 's':
842 shell = optarg;
843 break;
844
845 case 'u':
846 if (su_mode != RUNUSER_MODE)
847 usage (EXIT_FAILURE);
848 runuser_user = optarg;
849 break;
850
851 case 'h':
852 usage(0);
853
854 case 'V':
855 printf(UTIL_LINUX_VERSION);
856 exit(EXIT_SUCCESS);
857
858 default:
859 errtryhelp(EXIT_FAILURE);
860 }
861 }
862
863 restricted = evaluate_uid ();
864
865 if (optind < argc && !strcmp (argv[optind], "-"))
866 {
867 simulate_login = true;
868 ++optind;
869 }
870
871 if (simulate_login && !change_environment) {
872 warnx(_("ignoring --preserve-environment, it's mutually exclusive with --login"));
873 change_environment = true;
874 }
875
876 switch (su_mode) {
877 case RUNUSER_MODE:
878 if (runuser_user) {
879 /* runuser -u <user> <command> */
880 new_user = runuser_user;
881 if (shell || fast_startup || command || simulate_login) {
882 errx(EXIT_FAILURE,
883 _("options --{shell,fast,command,session-command,login} and "
884 "--user are mutually exclusive"));
885 }
886 if (optind == argc)
887 errx(EXIT_FAILURE, _("no command was specified"));
888
889 break;
890 }
891 /* fallthrough if -u <user> is not specified, then follow
892 * traditional su(1) behavior
893 */
894 case SU_MODE:
895 if (optind < argc)
896 new_user = argv[optind++];
897 break;
898 }
899
900 if ((use_supp || use_gid) && restricted)
901 errx(EXIT_FAILURE, _("only root can specify alternative groups"));
902
903 logindefs_load_defaults = load_config;
904
905 pw = getpwnam (new_user);
906 if (! (pw && pw->pw_name && pw->pw_name[0] && pw->pw_dir && pw->pw_dir[0]
907 && pw->pw_passwd))
908 errx (EXIT_FAILURE, _("user %s does not exist"), new_user);
909
910 /* Make a copy of the password information and point pw at the local
911 copy instead. Otherwise, some systems (e.g. Linux) would clobber
912 the static data through the getlogin call from log_su.
913 Also, make sure pw->pw_shell is a nonempty string.
914 It may be NULL when NEW_USER is a username that is retrieved via NIS (YP),
915 but that doesn't have a default shell listed. */
916 pw_copy = *pw;
917 pw = &pw_copy;
918 pw->pw_name = xstrdup (pw->pw_name);
919 pw->pw_passwd = xstrdup (pw->pw_passwd);
920 pw->pw_dir = xstrdup (pw->pw_dir);
921 pw->pw_shell = xstrdup (pw->pw_shell && pw->pw_shell[0]
922 ? pw->pw_shell
923 : DEFAULT_SHELL);
924 endpwent ();
925
926 if (use_supp && !use_gid)
927 pw->pw_gid = groups[0];
928 else if (use_gid)
929 pw->pw_gid = gid;
930
931 authenticate (pw);
932
933 if (request_same_session || !command || !pw->pw_uid)
934 same_session = 1;
935
936 /* initialize shell variable only if "-u <user>" not specified */
937 if (runuser_user) {
938 shell = NULL;
939 } else {
940 if (!shell && !change_environment)
941 shell = getenv ("SHELL");
942 if (shell && getuid () != 0 && restricted_shell (pw->pw_shell))
943 {
944 /* The user being su'd to has a nonstandard shell, and so is
945 probably a uucp account or has restricted access. Don't
946 compromise the account by allowing access with a standard
947 shell. */
948 warnx (_("using restricted shell %s"), pw->pw_shell);
949 shell = NULL;
950 }
951 shell = xstrdup (shell ? shell : pw->pw_shell);
952 }
953
954 init_groups (pw, groups, ngroups);
955
956 if (!simulate_login || command)
957 suppress_pam_info = 1; /* don't print PAM info messages */
958
959 create_watching_parent ();
960 /* Now we're in the child. */
961
962 change_identity (pw);
963 if (!same_session)
964 setsid ();
965
966 /* Set environment after pam_open_session, which may put KRB5CCNAME
967 into the pam_env, etc. */
968
969 modify_environment (pw, shell);
970
971 if (simulate_login && chdir (pw->pw_dir) != 0)
972 warn (_("warning: cannot change directory to %s"), pw->pw_dir);
973
974 if (shell)
975 run_shell (shell, command, argv + optind, max (0, argc - optind));
976 else {
977 execvp(argv[optind], &argv[optind]);
978 err(EXIT_FAILURE, _("failed to execute %s"), argv[optind]);
979 }
980 }
981
982 // vim: sw=2 cinoptions=>4,n-2,{2,^-2,\:2,=2,g0,h2,p5,t0,+2,(0,u0,w1,m1