]> git.ipfire.org Git - thirdparty/util-linux.git/blob - login-utils/su-common.c
Merge branch 'master' of https://github.com/pali/util-linux
[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/default/su"
83 #define _PATH_LOGINDEFS_RUNUSER "/etc/default/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 const 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 * const pw, const 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 const 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 * 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 (const int retcode)
234 {
235 const 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 const int retval = pam_open_session (pamh, 0);
279
280 if (is_pam_failure(retval))
281 {
282 cleanup_pam (retval);
283 errx (EXIT_FAILURE, _("cannot open session: %s"),
284 pam_strerror (pamh, retval));
285 }
286 else
287 _pam_session_opened = 1;
288
289 memset(oldact, 0, sizeof(oldact));
290
291 child = fork ();
292 if (child == (pid_t) -1)
293 {
294 cleanup_pam (PAM_ABORT);
295 err (EXIT_FAILURE, _("cannot create child process"));
296 }
297
298 /* the child proceeds to run the shell */
299 if (child == 0)
300 return;
301
302 /* In the parent watch the child. */
303
304 /* su without pam support does not have a helper that keeps
305 sitting on any directory so let's go to /. */
306 if (chdir ("/") != 0)
307 warn (_("cannot change directory to %s"), "/");
308
309 sigfillset (&ourset);
310 if (sigprocmask (SIG_BLOCK, &ourset, NULL))
311 {
312 warn (_("cannot block signals"));
313 caught_signal = true;
314 }
315 if (!caught_signal)
316 {
317 struct sigaction action;
318 action.sa_handler = su_catch_sig;
319 sigemptyset (&action.sa_mask);
320 action.sa_flags = 0;
321 sigemptyset (&ourset);
322 if (!same_session)
323 {
324 if (sigaddset(&ourset, SIGINT) || sigaddset(&ourset, SIGQUIT))
325 {
326 warn (_("cannot set signal handler"));
327 caught_signal = true;
328 }
329 }
330 if (!caught_signal && (sigaddset(&ourset, SIGTERM)
331 || sigaddset(&ourset, SIGALRM)
332 || sigaction(SIGTERM, &action, &oldact[0])
333 || sigprocmask(SIG_UNBLOCK, &ourset, NULL))) {
334 warn (_("cannot set signal handler"));
335 caught_signal = true;
336 }
337 if (!caught_signal && !same_session && (sigaction(SIGINT, &action, &oldact[1])
338 || sigaction(SIGQUIT, &action, &oldact[2])))
339 {
340 warn (_("cannot set signal handler"));
341 caught_signal = true;
342 }
343 }
344 if (!caught_signal)
345 {
346 pid_t pid;
347 for (;;)
348 {
349 pid = waitpid (child, &status, WUNTRACED);
350
351 if (pid != (pid_t)-1 && WIFSTOPPED (status))
352 {
353 kill (getpid (), SIGSTOP);
354 /* once we get here, we must have resumed */
355 kill (pid, SIGCONT);
356 }
357 else
358 break;
359 }
360 if (pid != (pid_t)-1)
361 {
362 if (WIFSIGNALED (status))
363 {
364 fprintf (stderr, "%s%s\n", strsignal (WTERMSIG (status)),
365 WCOREDUMP (status) ? _(" (core dumped)") : "");
366 status = WTERMSIG (status) + 128;
367 }
368 else
369 status = WEXITSTATUS (status);
370
371 /* child is gone, don't use the PID anymore */
372 child = (pid_t) -1;
373 }
374 else if (caught_signal)
375 status = caught_signal + 128;
376 else
377 status = 1;
378 }
379 else
380 status = 1;
381
382 if (caught_signal && child != (pid_t)-1)
383 {
384 fprintf (stderr, _("\nSession terminated, killing shell..."));
385 kill (child, SIGTERM);
386 }
387
388 cleanup_pam (PAM_SUCCESS);
389
390 if (caught_signal)
391 {
392 if (child != (pid_t)-1)
393 {
394 sleep (2);
395 kill (child, SIGKILL);
396 fprintf (stderr, _(" ...killed.\n"));
397 }
398
399 /* Let's terminate itself with the received signal.
400 *
401 * It seems that shells use WIFSIGNALED() rather than our exit status
402 * value to detect situations when is necessary to cleanup (reset)
403 * terminal settings (kzak -- Jun 2013).
404 */
405 switch (caught_signal) {
406 case SIGTERM:
407 sigaction(SIGTERM, &oldact[0], NULL);
408 break;
409 case SIGINT:
410 sigaction(SIGINT, &oldact[1], NULL);
411 break;
412 case SIGQUIT:
413 sigaction(SIGQUIT, &oldact[2], NULL);
414 break;
415 default:
416 /* just in case that signal stuff initialization failed and
417 * caught_signal = true */
418 caught_signal = SIGKILL;
419 break;
420 }
421 kill(getpid(), caught_signal);
422 }
423 exit (status);
424 }
425
426 static void
427 authenticate (const struct passwd * const pw)
428 {
429 const struct passwd *lpw = NULL;
430 const char *cp, *srvname = NULL;
431 int retval;
432
433 switch (su_mode) {
434 case SU_MODE:
435 srvname = simulate_login ? PAM_SRVNAME_SU_L : PAM_SRVNAME_SU;
436 break;
437 case RUNUSER_MODE:
438 srvname = simulate_login ? PAM_SRVNAME_RUNUSER_L : PAM_SRVNAME_RUNUSER;
439 break;
440 default:
441 abort();
442 break;
443 }
444
445 retval = pam_start (srvname, pw->pw_name, &conv, &pamh);
446 if (is_pam_failure(retval))
447 goto done;
448
449 if (isatty (0) && (cp = ttyname (0)) != NULL)
450 {
451 const char *tty;
452
453 if (strncmp (cp, "/dev/", 5) == 0)
454 tty = cp + 5;
455 else
456 tty = cp;
457 retval = pam_set_item (pamh, PAM_TTY, tty);
458 if (is_pam_failure(retval))
459 goto done;
460 }
461
462 lpw = current_getpwuid ();
463 if (lpw && lpw->pw_name)
464 {
465 retval = pam_set_item (pamh, PAM_RUSER, (const void *) lpw->pw_name);
466 if (is_pam_failure(retval))
467 goto done;
468 }
469
470 if (su_mode == RUNUSER_MODE)
471 {
472 /*
473 * This is the only difference between runuser(1) and su(1). The command
474 * runuser(1) does not required authentication, because user is root.
475 */
476 if (restricted)
477 errx(EXIT_FAILURE, _("may not be used by non-root users"));
478 return;
479 }
480
481 retval = pam_authenticate (pamh, 0);
482 if (is_pam_failure(retval))
483 goto done;
484
485 retval = pam_acct_mgmt (pamh, 0);
486 if (retval == PAM_NEW_AUTHTOK_REQD)
487 {
488 /* Password has expired. Offer option to change it. */
489 retval = pam_chauthtok (pamh, PAM_CHANGE_EXPIRED_AUTHTOK);
490 }
491
492 done:
493
494 log_syslog(pw, !is_pam_failure(retval));
495
496 if (is_pam_failure(retval))
497 {
498 const char *msg;
499
500 log_btmp(pw);
501
502 msg = pam_strerror(pamh, retval);
503 pam_end(pamh, retval);
504 sleep (getlogindefs_num ("FAIL_DELAY", 1));
505 errx (EXIT_FAILURE, "%s", msg?msg:_("incorrect password"));
506 }
507 }
508
509 static void
510 set_path(const struct passwd * const pw)
511 {
512 int r;
513 if (pw->pw_uid)
514 r = logindefs_setenv("PATH", "ENV_PATH", _PATH_DEFPATH);
515
516 else if ((r = logindefs_setenv("PATH", "ENV_ROOTPATH", NULL)) != 0)
517 r = logindefs_setenv("PATH", "ENV_SUPATH", _PATH_DEFPATH_ROOT);
518
519 if (r != 0)
520 err (EXIT_FAILURE, _("failed to set the %s environment variable"), "PATH");
521 }
522
523 /* Update `environ' for the new shell based on PW, with SHELL being
524 the value for the SHELL environment variable. */
525
526 static void
527 modify_environment (const struct passwd * const pw, const char * const shell)
528 {
529 if (simulate_login)
530 {
531 /* Leave TERM unchanged. Set HOME, SHELL, USER, LOGNAME, PATH.
532 Unset all other environment variables. */
533 char *term = getenv ("TERM");
534 if (term)
535 term = xstrdup (term);
536 environ = xmalloc ((6 + !!term) * sizeof (char *));
537 environ[0] = NULL;
538 if (term) {
539 xsetenv ("TERM", term, 1);
540 free(term);
541 }
542 xsetenv ("HOME", pw->pw_dir, 1);
543 if (shell)
544 xsetenv ("SHELL", shell, 1);
545 xsetenv ("USER", pw->pw_name, 1);
546 xsetenv ("LOGNAME", pw->pw_name, 1);
547 set_path(pw);
548 }
549 else
550 {
551 /* Set HOME, SHELL, and (if not becoming a superuser)
552 USER and LOGNAME. */
553 if (change_environment)
554 {
555 xsetenv ("HOME", pw->pw_dir, 1);
556 if (shell)
557 xsetenv ("SHELL", shell, 1);
558 if (getlogindefs_bool ("ALWAYS_SET_PATH", 0))
559 set_path(pw);
560
561 if (pw->pw_uid)
562 {
563 xsetenv ("USER", pw->pw_name, 1);
564 xsetenv ("LOGNAME", pw->pw_name, 1);
565 }
566 }
567 }
568
569 export_pamenv ();
570 }
571
572 /* Become the user and group(s) specified by PW. */
573
574 static void
575 init_groups (const struct passwd * const pw, const gid_t * const groups, const size_t num_groups)
576 {
577 int retval;
578
579 errno = 0;
580
581 if (num_groups)
582 retval = setgroups (num_groups, groups);
583 else
584 retval = initgroups (pw->pw_name, pw->pw_gid);
585
586 if (retval == -1)
587 {
588 cleanup_pam (PAM_ABORT);
589 err (EXIT_FAILURE, _("cannot set groups"));
590 }
591 endgrent ();
592
593 retval = pam_setcred (pamh, PAM_ESTABLISH_CRED);
594 if (is_pam_failure(retval))
595 errx (EXIT_FAILURE, "%s", pam_strerror (pamh, retval));
596 else
597 _pam_cred_established = 1;
598 }
599
600 static void
601 change_identity (const struct passwd * const pw)
602 {
603 if (setgid (pw->pw_gid))
604 err (EXIT_FAILURE, _("cannot set group id"));
605 if (setuid (pw->pw_uid))
606 err (EXIT_FAILURE, _("cannot set user id"));
607 }
608
609 /* Run SHELL, or DEFAULT_SHELL if SHELL is empty.
610 If COMMAND is nonzero, pass it to the shell with the -c option.
611 Pass ADDITIONAL_ARGS to the shell as more arguments; there
612 are N_ADDITIONAL_ARGS extra arguments. */
613
614 static void
615 run_shell (char const * const shell, char const * const command, char ** const additional_args,
616 const size_t n_additional_args)
617 {
618 const size_t n_args = 1 + fast_startup + 2 * !!command + n_additional_args + 1;
619 const char **args = xcalloc (n_args, sizeof *args);
620 size_t argno = 1;
621
622 if (simulate_login)
623 {
624 char *arg0;
625 const char *shell_basename;
626
627 shell_basename = basename (shell);
628 arg0 = xmalloc (strlen (shell_basename) + 2);
629 arg0[0] = '-';
630 strcpy (arg0 + 1, shell_basename);
631 args[0] = arg0;
632 }
633 else
634 args[0] = basename (shell);
635 if (fast_startup)
636 args[argno++] = "-f";
637 if (command)
638 {
639 args[argno++] = "-c";
640 args[argno++] = command;
641 }
642 memcpy (args + argno, additional_args, n_additional_args * sizeof *args);
643 args[argno + n_additional_args] = NULL;
644 execv (shell, (char **) args);
645
646 {
647 int exit_status = (errno == ENOENT ? EXIT_ENOENT : EXIT_CANNOT_INVOKE);
648 warn (_("failed to execute %s"), shell);
649 exit (exit_status);
650 }
651 }
652
653 /* Return true if SHELL is a restricted shell (one not returned by
654 getusershell), else false, meaning it is a standard shell. */
655
656 static bool
657 restricted_shell (const char * const shell)
658 {
659 char *line;
660
661 setusershell ();
662 while ((line = getusershell ()) != NULL)
663 {
664 if (*line != '#' && !strcmp (line, shell))
665 {
666 endusershell ();
667 return false;
668 }
669 }
670 endusershell ();
671 return true;
672 }
673
674 static void __attribute__((__noreturn__))
675 usage(void)
676 {
677 if (su_mode == RUNUSER_MODE) {
678 fputs(USAGE_HEADER, stdout);
679 printf (_(" %s [options] -u <user> [[--] <command>]\n"), program_invocation_short_name);
680 printf (_(" %s [options] [-] [<user> [<argument>...]]\n"), program_invocation_short_name);
681 fputs (_("\n"
682 "Run <command> with the effective user ID and group ID of <user>. If -u is\n"
683 "not given, fall back to su(1)-compatible semantics and execute standard shell.\n"
684 "The options -c, -f, -l, and -s are mutually exclusive with -u.\n"), stdout);
685
686 fputs(USAGE_OPTIONS, stdout);
687
688 fputs (_(" -u, --user <user> username\n"), stdout);
689
690 } else {
691 fputs(USAGE_HEADER, stdout);
692 printf (_(" %s [options] [-] [<user> [<argument>...]]\n"), program_invocation_short_name);
693 fputs (_("\n"
694 "Change the effective user ID and group ID to that of <user>.\n"
695 "A mere - implies -l. If <user> is not given, root is assumed.\n"), stdout);
696
697 fputs(USAGE_OPTIONS, stdout);
698 }
699
700 fputs (_(" -m, -p, --preserve-environment do not reset environment variables\n"), stdout);
701 fputs (_(" -g, --group <group> specify the primary group\n"), stdout);
702 fputs (_(" -G, --supp-group <group> specify a supplemental group\n\n"), stdout);
703
704 fputs (_(" -, -l, --login make the shell a login shell\n"), stdout);
705 fputs (_(" -c, --command <command> pass a single command to the shell with -c\n"), stdout);
706 fputs (_(" --session-command <command> pass a single command to the shell with -c\n"
707 " and do not create a new session\n"), stdout);
708 fputs (_(" -f, --fast pass -f to the shell (for csh or tcsh)\n"), stdout);
709 fputs (_(" -s, --shell <shell> run <shell> if /etc/shells allows it\n"), stdout);
710
711 fputs(USAGE_SEPARATOR, stdout);
712 printf(USAGE_HELP_OPTIONS(33));
713 printf(USAGE_MAN_TAIL(su_mode == SU_MODE ? "su(1)" : "runuser(1)"));
714 exit(EXIT_SUCCESS);
715 }
716
717 static
718 void load_config(void)
719 {
720 switch (su_mode) {
721 case SU_MODE:
722 logindefs_load_file(_PATH_LOGINDEFS_SU);
723 break;
724 case RUNUSER_MODE:
725 logindefs_load_file(_PATH_LOGINDEFS_RUNUSER);
726 break;
727 default:
728 abort();
729 break;
730 }
731
732 logindefs_load_file(_PATH_LOGINDEFS);
733 }
734
735 /*
736 * Returns 1 if the current user is not root
737 */
738 static int
739 evaluate_uid(void)
740 {
741 const uid_t ruid = getuid();
742 const uid_t euid = geteuid();
743
744 /* if we're really root and aren't running setuid */
745 return (uid_t) 0 == ruid && ruid == euid ? 0 : 1;
746 }
747
748 static gid_t
749 add_supp_group(const char *name, gid_t **groups, size_t *ngroups)
750 {
751 struct group *gr;
752
753 if (*ngroups >= NGROUPS_MAX)
754 errx(EXIT_FAILURE,
755 P_("specifying more than %d supplemental group is not possible",
756 "specifying more than %d supplemental groups is not possible",
757 NGROUPS_MAX - 1), NGROUPS_MAX - 1);
758
759 gr = getgrnam(name);
760 if (!gr)
761 errx(EXIT_FAILURE, _("group %s does not exist"), name);
762
763 *groups = xrealloc(*groups, sizeof(gid_t) * (*ngroups + 1));
764 (*groups)[*ngroups] = gr->gr_gid;
765 (*ngroups)++;
766
767 return gr->gr_gid;
768 }
769
770 int
771 su_main (int argc, char **argv, int mode)
772 {
773 int optc;
774 const char *new_user = DEFAULT_USER, *runuser_user = NULL;
775 const char *command = NULL;
776 int request_same_session = 0;
777 const char *shell = NULL;
778 struct passwd *pw;
779 struct passwd pw_copy;
780
781 gid_t *groups = NULL;
782 size_t ngroups = 0;
783 bool use_supp = false;
784 bool use_gid = false;
785 gid_t gid = 0;
786
787 static const struct option longopts[] = {
788 {"command", required_argument, NULL, 'c'},
789 {"session-command", required_argument, NULL, 'C'},
790 {"fast", no_argument, NULL, 'f'},
791 {"login", no_argument, NULL, 'l'},
792 {"preserve-environment", no_argument, NULL, 'p'},
793 {"shell", required_argument, NULL, 's'},
794 {"group", required_argument, NULL, 'g'},
795 {"supp-group", required_argument, NULL, 'G'},
796 {"user", required_argument, NULL, 'u'}, /* runuser only */
797 {"help", no_argument, NULL, 'h'},
798 {"version", no_argument, NULL, 'V'},
799 {NULL, 0, NULL, 0}
800 };
801
802 setlocale (LC_ALL, "");
803 bindtextdomain (PACKAGE, LOCALEDIR);
804 textdomain (PACKAGE);
805 atexit(close_stdout);
806
807 su_mode = mode;
808 fast_startup = false;
809 simulate_login = false;
810 change_environment = true;
811
812 while ((optc = getopt_long (argc, argv, "c:fg:G:lmps:u:hV", longopts, NULL)) != -1)
813 {
814 switch (optc)
815 {
816 case 'c':
817 command = optarg;
818 break;
819
820 case 'C':
821 command = optarg;
822 request_same_session = 1;
823 break;
824
825 case 'f':
826 fast_startup = true;
827 break;
828
829 case 'g':
830 use_gid = true;
831 gid = add_supp_group(optarg, &groups, &ngroups);
832 break;
833
834 case 'G':
835 use_supp = true;
836 add_supp_group(optarg, &groups, &ngroups);
837 break;
838
839 case 'l':
840 simulate_login = true;
841 break;
842
843 case 'm':
844 case 'p':
845 change_environment = false;
846 break;
847
848 case 's':
849 shell = optarg;
850 break;
851
852 case 'u':
853 if (su_mode != RUNUSER_MODE) {
854 warnx(_("invalid option -- 'u'"));
855 errtryhelp(EXIT_FAILURE);
856 }
857 runuser_user = optarg;
858 break;
859
860 case 'h':
861 usage();
862
863 case 'V':
864 printf(UTIL_LINUX_VERSION);
865 exit(EXIT_SUCCESS);
866
867 default:
868 errtryhelp(EXIT_FAILURE);
869 }
870 }
871
872 restricted = evaluate_uid ();
873
874 if (optind < argc && !strcmp (argv[optind], "-"))
875 {
876 simulate_login = true;
877 ++optind;
878 }
879
880 if (simulate_login && !change_environment) {
881 warnx(_("ignoring --preserve-environment, it's mutually exclusive with --login"));
882 change_environment = true;
883 }
884
885 switch (su_mode) {
886 case RUNUSER_MODE:
887 if (runuser_user) {
888 /* runuser -u <user> <command> */
889 new_user = runuser_user;
890 if (shell || fast_startup || command || simulate_login) {
891 errx(EXIT_FAILURE,
892 _("options --{shell,fast,command,session-command,login} and "
893 "--user are mutually exclusive"));
894 }
895 if (optind == argc)
896 errx(EXIT_FAILURE, _("no command was specified"));
897
898 break;
899 }
900 /* fallthrough if -u <user> is not specified, then follow
901 * traditional su(1) behavior
902 */
903 /* fallthrough */
904 case SU_MODE:
905 if (optind < argc)
906 new_user = argv[optind++];
907 break;
908 default:
909 abort();
910 break;
911 }
912
913 if ((use_supp || use_gid) && restricted)
914 errx(EXIT_FAILURE, _("only root can specify alternative groups"));
915
916 logindefs_load_defaults = load_config;
917
918 pw = getpwnam (new_user);
919 if (! (pw && pw->pw_name && pw->pw_name[0] && pw->pw_dir && pw->pw_dir[0]
920 && pw->pw_passwd))
921 errx (EXIT_FAILURE, _("user %s does not exist"), new_user);
922
923 /* Make a copy of the password information and point pw at the local
924 copy instead. Otherwise, some systems (e.g. Linux) would clobber
925 the static data through the getlogin call from log_su.
926 Also, make sure pw->pw_shell is a nonempty string.
927 It may be NULL when NEW_USER is a username that is retrieved via NIS (YP),
928 but that doesn't have a default shell listed. */
929 pw_copy = *pw;
930 pw = &pw_copy;
931 pw->pw_name = xstrdup (pw->pw_name);
932 pw->pw_passwd = xstrdup (pw->pw_passwd);
933 pw->pw_dir = xstrdup (pw->pw_dir);
934 pw->pw_shell = xstrdup (pw->pw_shell && pw->pw_shell[0]
935 ? pw->pw_shell
936 : DEFAULT_SHELL);
937 endpwent ();
938
939 if (use_supp && !use_gid)
940 pw->pw_gid = groups[0];
941 else if (use_gid)
942 pw->pw_gid = gid;
943
944 authenticate (pw);
945
946 if (request_same_session || !command || !pw->pw_uid)
947 same_session = 1;
948
949 /* initialize shell variable only if "-u <user>" not specified */
950 if (runuser_user) {
951 shell = NULL;
952 } else {
953 if (!shell && !change_environment)
954 shell = getenv ("SHELL");
955 if (shell && getuid () != 0 && restricted_shell (pw->pw_shell))
956 {
957 /* The user being su'd to has a nonstandard shell, and so is
958 probably a uucp account or has restricted access. Don't
959 compromise the account by allowing access with a standard
960 shell. */
961 warnx (_("using restricted shell %s"), pw->pw_shell);
962 shell = NULL;
963 }
964 shell = xstrdup (shell ? shell : pw->pw_shell);
965 }
966
967 init_groups (pw, groups, ngroups);
968
969 if (!simulate_login || command)
970 suppress_pam_info = 1; /* don't print PAM info messages */
971
972 create_watching_parent ();
973 /* Now we're in the child. */
974
975 change_identity (pw);
976 if (!same_session)
977 setsid ();
978
979 /* Set environment after pam_open_session, which may put KRB5CCNAME
980 into the pam_env, etc. */
981
982 modify_environment (pw, shell);
983
984 if (simulate_login && chdir (pw->pw_dir) != 0)
985 warn (_("warning: cannot change directory to %s"), pw->pw_dir);
986
987 if (shell)
988 run_shell (shell, command, argv + optind, max (0, argc - optind));
989 else {
990 execvp(argv[optind], &argv[optind]);
991 err(EXIT_FAILURE, _("failed to execute %s"), argv[optind]);
992 }
993 }
994
995 // vim: sw=2 cinoptions=>4,n-2,{2,^-2,\:2,=2,g0,h2,p5,t0,+2,(0,u0,w1,m1