]> git.ipfire.org Git - thirdparty/util-linux.git/blob - login-utils/su-common.c
su: cleanup setenv 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 setenv_path(const struct passwd *pw)
413 {
414 int r;
415
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 static void modify_environment(struct su_context *su, const char *shell)
428 {
429 const struct passwd *pw = su->pwd;
430
431 /* Leave TERM unchanged. Set HOME, SHELL, USER, LOGNAME, PATH.
432 * Unset all other environment variables.
433 */
434 if (su->simulate_login) {
435 char *term = getenv("TERM");
436 if (term)
437 term = xstrdup(term);
438
439 environ = xmalloc((6 + ! !term) * sizeof(char *));
440 environ[0] = NULL;
441 if (term) {
442 xsetenv("TERM", term, 1);
443 free(term);
444 }
445
446 xsetenv("HOME", pw->pw_dir, 1);
447 if (shell)
448 xsetenv("SHELL", shell, 1);
449 xsetenv("USER", pw->pw_name, 1);
450 xsetenv("LOGNAME", pw->pw_name, 1);
451 setenv_path(pw);
452
453 /* Set HOME, SHELL, and (if not becoming a superuser) USER and LOGNAME.
454 */
455 } else if (su->change_environment) {
456 xsetenv("HOME", pw->pw_dir, 1);
457 if (shell)
458 xsetenv("SHELL", shell, 1);
459
460 if (getlogindefs_bool("ALWAYS_SET_PATH", 0))
461 setenv_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 supam_export_environment(su);
470 }
471
472 /* Become the user and group(s) specified by PW. */
473
474 static void
475 init_groups(struct su_context *su, gid_t * groups, size_t num_groups)
476 {
477 int retval;
478
479 errno = 0;
480
481 if (num_groups)
482 retval = setgroups(num_groups, groups);
483 else
484 retval = initgroups(su->pwd->pw_name, su->pwd->pw_gid);
485
486 if (retval == -1) {
487 supam_cleanup(su, PAM_ABORT);
488 err(EXIT_FAILURE, _("cannot set groups"));
489 }
490 endgrent();
491
492 retval = pam_setcred(su->pamh, PAM_ESTABLISH_CRED);
493 if (is_pam_failure(retval))
494 errx(EXIT_FAILURE, "%s", pam_strerror(su->pamh, retval));
495 else
496 su->pam_has_cred = 1;
497 }
498
499 static void
500 change_identity (const struct passwd * const pw)
501 {
502 if (setgid(pw->pw_gid))
503 err(EXIT_FAILURE, _("cannot set group id"));
504 if (setuid(pw->pw_uid))
505 err(EXIT_FAILURE, _("cannot set user id"));
506 }
507
508 /* Run SHELL, if COMMAND is nonzero, pass it to the shell with the -c option.
509 * Pass ADDITIONAL_ARGS to the shell as more arguments; there are
510 * N_ADDITIONAL_ARGS extra arguments.
511 */
512 static void run_shell(
513 struct su_context *su,
514 char const *shell, char const *command, char **additional_args,
515 size_t n_additional_args)
516 {
517 size_t n_args = 1 + su->fast_startup + 2 * ! !command + n_additional_args + 1;
518 char const **args = xcalloc(n_args, sizeof *args);
519 size_t argno = 1;
520 int rc;
521
522 if (su->simulate_login) {
523 char *arg0;
524 char *shell_basename;
525
526 shell_basename = basename(shell);
527 arg0 = xmalloc(strlen(shell_basename) + 2);
528 arg0[0] = '-';
529 strcpy(arg0 + 1, shell_basename);
530 args[0] = arg0;
531 } else
532 args[0] = basename(shell);
533
534 if (su->fast_startup)
535 args[argno++] = "-f";
536 if (command) {
537 args[argno++] = "-c";
538 args[argno++] = command;
539 }
540
541 memcpy(args + argno, additional_args, n_additional_args * sizeof *args);
542 args[argno + n_additional_args] = NULL;
543 execv(shell, (char **)args);
544
545 rc = errno == ENOENT ? EXIT_ENOENT : EXIT_CANNOT_INVOKE;
546 err(rc, _("failed to execute %s"), shell);
547 }
548
549 /* Return true if SHELL is a restricted shell (one not returned by
550 * getusershell), else false, meaning it is a standard shell.
551 */
552 static bool is_restricted_shell(const char *shell)
553 {
554 char *line;
555
556 setusershell();
557 while ((line = getusershell()) != NULL) {
558 if (*line != '#' && !strcmp(line, shell)) {
559 endusershell();
560 return false;
561 }
562 }
563 endusershell();
564 return true;
565 }
566
567 static void usage_common(void)
568 {
569 fputs(_(" -m, -p, --preserve-environment do not reset environment variables\n"), stdout);
570 fputs(_(" -g, --group <group> specify the primary group\n"), stdout);
571 fputs(_(" -G, --supp-group <group> specify a supplemental group\n"), stdout);
572 fputs(USAGE_SEPARATOR, stdout);
573
574 fputs(_(" -, -l, --login make the shell a login shell\n"), stdout);
575 fputs(_(" -c, --command <command> pass a single command to the shell with -c\n"), stdout);
576 fputs(_(" --session-command <command> pass a single command to the shell with -c\n"
577 " and do not create a new session\n"), stdout);
578 fputs(_(" -f, --fast pass -f to the shell (for csh or tcsh)\n"), stdout);
579 fputs(_(" -s, --shell <shell> run <shell> if /etc/shells allows it\n"), stdout);
580
581 fputs(USAGE_SEPARATOR, stdout);
582 printf(USAGE_HELP_OPTIONS(33));
583
584 }
585
586 static void __attribute__ ((__noreturn__)) usage_runuser(void)
587 {
588 fputs(USAGE_HEADER, stdout);
589 fprintf(stdout,
590 _(" %1$s [options] -u <user> [[--] <command>]\n"
591 " %1$s [options] [-] [<user> [<argument>...]]\n"),
592 program_invocation_short_name);
593
594 fputs(USAGE_SEPARATOR, stdout);
595 fputs(_("Run <command> with the effective user ID and group ID of <user>. If -u is\n"
596 "not given, fall back to su(1)-compatible semantics and execute standard shell.\n"
597 "The options -c, -f, -l, and -s are mutually exclusive with -u.\n"), stdout);
598
599 fputs(USAGE_OPTIONS, stdout);
600 fputs(_(" -u, --user <user> username\n"), stdout);
601 usage_common();
602 fputs(USAGE_SEPARATOR, stdout);
603
604 fprintf(stdout, USAGE_MAN_TAIL("runuser(1)"));
605 exit(EXIT_SUCCESS);
606 }
607
608 static void __attribute__ ((__noreturn__)) usage_su(void)
609 {
610 fputs(USAGE_HEADER, stdout);
611 fprintf(stdout,
612 _(" %s [options] [-] [<user> [<argument>...]]\n"),
613 program_invocation_short_name);
614
615 fputs(USAGE_SEPARATOR, stdout);
616 fputs(_("Change the effective user ID and group ID to that of <user>.\n"
617 "A mere - implies -l. If <user> is not given, root is assumed.\n"), stdout);
618
619 fputs(USAGE_OPTIONS, stdout);
620 usage_common();
621
622 fprintf(stdout, USAGE_MAN_TAIL("su(1)"));
623 exit(EXIT_SUCCESS);
624 }
625
626 static void usage(int mode)
627 {
628 if (mode == SU_MODE)
629 usage_su();
630 else
631 usage_runuser();
632 }
633
634 static void load_config(void *data)
635 {
636 struct su_context *su = (struct su_context *) data;
637
638 logindefs_load_file(su->runuser ? _PATH_LOGINDEFS_RUNUSER : _PATH_LOGINDEFS_SU);
639 logindefs_load_file(_PATH_LOGINDEFS);
640 }
641
642 /*
643 * Returns 1 if the current user is not root
644 */
645 static int
646 evaluate_uid(void)
647 {
648 const uid_t ruid = getuid();
649 const uid_t euid = geteuid();
650
651 /* if we're really root and aren't running setuid */
652 return (uid_t) 0 == ruid && ruid == euid ? 0 : 1;
653 }
654
655 static gid_t
656 add_supp_group(const char *name, gid_t ** groups, size_t * ngroups)
657 {
658 struct group *gr;
659
660 if (*ngroups >= NGROUPS_MAX)
661 errx(EXIT_FAILURE,
662 P_
663 ("specifying more than %d supplemental group is not possible",
664 "specifying more than %d supplemental groups is not possible",
665 NGROUPS_MAX - 1), NGROUPS_MAX - 1);
666
667 gr = getgrnam(name);
668 if (!gr)
669 errx(EXIT_FAILURE, _("group %s does not exist"), name);
670
671 *groups = xrealloc(*groups, sizeof(gid_t) * (*ngroups + 1));
672 (*groups)[*ngroups] = gr->gr_gid;
673 (*ngroups)++;
674
675 return gr->gr_gid;
676 }
677
678 int
679 su_main(int argc, char **argv, int mode)
680 {
681 struct su_context _su = {
682 .conv = { supam_conv, NULL },
683 .runuser = (mode == RUNUSER_MODE ? 1 : 0),
684 .change_environment = 1,
685 .new_user = DEFAULT_USER
686 }, *su = &_su;
687
688 int optc;
689 char *command = NULL;
690 int request_same_session = 0;
691 char *shell = NULL;
692
693 gid_t *groups = NULL;
694 size_t ngroups = 0;
695 bool use_supp = false;
696 bool use_gid = false;
697 gid_t gid = 0;
698
699 static const struct option longopts[] = {
700 {"command", required_argument, NULL, 'c'},
701 {"session-command", required_argument, NULL, 'C'},
702 {"fast", no_argument, NULL, 'f'},
703 {"login", no_argument, NULL, 'l'},
704 {"preserve-environment", no_argument, NULL, 'p'},
705 {"shell", required_argument, NULL, 's'},
706 {"group", required_argument, NULL, 'g'},
707 {"supp-group", required_argument, NULL, 'G'},
708 {"user", required_argument, NULL, 'u'}, /* runuser only */
709 {"help", no_argument, 0, 'h'},
710 {"version", no_argument, 0, 'V'},
711 {NULL, 0, NULL, 0}
712 };
713
714 setlocale(LC_ALL, "");
715 bindtextdomain(PACKAGE, LOCALEDIR);
716 textdomain(PACKAGE);
717 atexit(close_stdout);
718
719 su->conv.appdata_ptr = (void *) su;
720
721 while ((optc =
722 getopt_long(argc, argv, "c:fg:G:lmps:u:hV", longopts,
723 NULL)) != -1) {
724 switch (optc) {
725 case 'c':
726 command = optarg;
727 break;
728
729 case 'C':
730 command = optarg;
731 request_same_session = 1;
732 break;
733
734 case 'f':
735 su->fast_startup = true;
736 break;
737
738 case 'g':
739 use_gid = true;
740 gid = add_supp_group(optarg, &groups, &ngroups);
741 break;
742
743 case 'G':
744 use_supp = true;
745 add_supp_group(optarg, &groups, &ngroups);
746 break;
747
748 case 'l':
749 su->simulate_login = true;
750 break;
751
752 case 'm':
753 case 'p':
754 su->change_environment = false;
755 break;
756
757 case 's':
758 shell = optarg;
759 break;
760
761 case 'u':
762 if (!su->runuser)
763 errtryhelp(EXIT_FAILURE);
764 su->runuser_uopt = 1;
765 su->new_user = optarg;
766 break;
767
768 case 'h':
769 usage(mode);
770
771 case 'V':
772 printf(UTIL_LINUX_VERSION);
773 exit(EXIT_SUCCESS);
774
775 default:
776 errtryhelp(EXIT_FAILURE);
777 }
778 }
779
780 su->restricted = evaluate_uid();
781
782 if (optind < argc && !strcmp(argv[optind], "-")) {
783 su->simulate_login = true;
784 ++optind;
785 }
786
787 if (su->simulate_login && !su->change_environment) {
788 warnx(_
789 ("ignoring --preserve-environment, it's mutually exclusive with --login"));
790 su->change_environment = true;
791 }
792
793 switch (mode) {
794 case RUNUSER_MODE:
795 /* runuser -u <user> <command> */
796 if (su->runuser_uopt) {
797 if (shell || su->fast_startup || command || su->simulate_login)
798 errx(EXIT_FAILURE,
799 _("options --{shell,fast,command,session-command,login} and "
800 "--user are mutually exclusive"));
801 if (optind == argc)
802 errx(EXIT_FAILURE, _("no command was specified"));
803 break;
804 }
805 /* fallthrough if -u <user> is not specified, then follow
806 * traditional su(1) behavior
807 */
808 case SU_MODE:
809 if (optind < argc)
810 su->new_user = argv[optind++];
811 break;
812 }
813
814 if ((use_supp || use_gid) && su->restricted)
815 errx(EXIT_FAILURE,
816 _("only root can specify alternative groups"));
817
818 logindefs_set_loader(load_config, (void *) su);
819 init_tty(su);
820
821 su->pwd = xgetpwnam(su->new_user, &su->pwdbuf);
822 if (!su->pwd
823 || !su->pwd->pw_passwd
824 || !su->pwd->pw_name || !*su->pwd->pw_name
825 || !su->pwd->pw_dir || !*su->pwd->pw_dir)
826 errx(EXIT_FAILURE, _("user %s does not exist"), su->new_user);
827
828 su->new_user = su->pwd->pw_name;
829 su->old_user = xgetlogin();
830
831 if (!su->pwd->pw_shell || !*su->pwd->pw_shell)
832 su->pwd->pw_shell = DEFAULT_SHELL;
833
834 if (use_supp && !use_gid)
835 su->pwd->pw_gid = groups[0];
836 else if (use_gid)
837 su->pwd->pw_gid = gid;
838
839 supam_authenticate(su);
840
841 if (request_same_session || !command || !su->pwd->pw_uid)
842 su->same_session = 1;
843
844 /* initialize shell variable only if "-u <user>" not specified */
845 if (su->runuser_uopt) {
846 shell = NULL;
847 } else {
848 if (!shell && !su->change_environment)
849 shell = getenv("SHELL");
850
851 if (shell
852 && getuid() != 0
853 && is_restricted_shell(su->pwd->pw_shell)) {
854 /* The user being su'd to has a nonstandard shell, and
855 * so is probably a uucp account or has restricted
856 * access. Don't compromise the account by allowing
857 * access with a standard shell.
858 */
859 warnx(_("using restricted shell %s"), su->pwd->pw_shell);
860 shell = NULL;
861 }
862 shell = xstrdup(shell ? shell : su->pwd->pw_shell);
863 }
864
865 init_groups(su, groups, ngroups);
866
867 if (!su->simulate_login || command)
868 su->suppress_pam_info = 1; /* don't print PAM info messages */
869
870 create_watching_parent(su);
871 /* Now we're in the child. */
872
873 change_identity(su->pwd);
874 if (!su->same_session)
875 setsid();
876
877 /* Set environment after pam_open_session, which may put KRB5CCNAME
878 into the pam_env, etc. */
879
880 modify_environment(su, shell);
881
882 if (su->simulate_login && chdir(su->pwd->pw_dir) != 0)
883 warn(_("warning: cannot change directory to %s"), su->pwd->pw_dir);
884
885 if (shell)
886 run_shell(su, shell, command, argv + optind, max(0, argc - optind));
887
888 execvp(argv[optind], &argv[optind]);
889 err(EXIT_FAILURE, _("failed to execute %s"), argv[optind]);
890 }