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