]> git.ipfire.org Git - thirdparty/util-linux.git/blob - login-utils/su-common.c
su: use switch() to split after fork
[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 errsv = 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 = errsv;
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 rc;
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 rc = pam_start(srvname, su->pwd->pw_name, &su->conv, &su->pamh);
225 if (is_pam_failure(rc))
226 goto done;
227
228 if (su->tty_name) {
229 rc = pam_set_item(su->pamh, PAM_TTY, su->tty_name);
230 if (is_pam_failure(rc))
231 goto done;
232 }
233 if (su->old_user) {
234 rc = pam_set_item(su->pamh, PAM_RUSER, (const void *) su->old_user);
235 if (is_pam_failure(rc))
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 rc = pam_authenticate(su->pamh, 0);
249 if (is_pam_failure(rc))
250 goto done;
251
252 /* Check password expiration and offer option to change it. */
253 rc = pam_acct_mgmt(su->pamh, 0);
254 if (rc == PAM_NEW_AUTHTOK_REQD)
255 rc = pam_chauthtok(su->pamh, PAM_CHANGE_EXPIRED_AUTHTOK);
256 done:
257 log_syslog(su, !is_pam_failure(rc));
258
259 if (is_pam_failure(rc)) {
260 const char *msg;
261
262 log_btmp(su);
263
264 msg = pam_strerror(su->pamh, rc);
265 pam_end(su->pamh, rc);
266 sleep(getlogindefs_num("FAIL_DELAY", 1));
267 errx(EXIT_FAILURE, "%s", msg ? msg : _("incorrect password"));
268 }
269 }
270
271 static void supam_open_session(struct su_context *su)
272 {
273 int rc = pam_open_session(su->pamh, 0);
274
275 if (is_pam_failure(rc)) {
276 supam_cleanup(su, rc);
277 errx(EXIT_FAILURE, _("cannot open session: %s"),
278 pam_strerror(su->pamh, rc));
279 } else
280 su->pam_has_session = 1;
281 }
282
283
284 static void create_watching_parent(struct su_context *su)
285 {
286 pid_t child;
287 sigset_t ourset;
288 struct sigaction oldact[3];
289 int status = 0;
290
291 switch ((int) (child = fork())) {
292 case -1: /* error */
293 supam_cleanup(su, PAM_ABORT);
294 err(EXIT_FAILURE, _("cannot create child process"));
295 break;
296
297 case 0: /* child */
298 return;
299
300 default: /* parent */
301 break;
302 }
303
304 memset(oldact, 0, sizeof(oldact));
305
306
307 /* In the parent watch the child. */
308
309 /* su without pam support does not have a helper that keeps
310 sitting on any directory so let's go to /. */
311 if (chdir("/") != 0)
312 warn(_("cannot change directory to %s"), "/");
313
314 sigfillset(&ourset);
315 if (sigprocmask(SIG_BLOCK, &ourset, NULL)) {
316 warn(_("cannot block signals"));
317 caught_signal = true;
318 }
319 if (!caught_signal) {
320 struct sigaction action;
321 action.sa_handler = su_catch_sig;
322 sigemptyset(&action.sa_mask);
323 action.sa_flags = 0;
324 sigemptyset(&ourset);
325 if (!su->same_session) {
326 if (sigaddset(&ourset, SIGINT)
327 || sigaddset(&ourset, SIGQUIT)) {
328 warn(_("cannot set signal handler"));
329 caught_signal = true;
330 }
331 }
332 if (!caught_signal && (sigaddset(&ourset, SIGTERM)
333 || sigaddset(&ourset, SIGALRM)
334 || sigaction(SIGTERM, &action,
335 &oldact[0])
336 || sigprocmask(SIG_UNBLOCK, &ourset,
337 NULL))) {
338 warn(_("cannot set signal handler"));
339 caught_signal = true;
340 }
341 if (!caught_signal && !su->same_session
342 && (sigaction(SIGINT, &action, &oldact[1])
343 || sigaction(SIGQUIT, &action, &oldact[2]))) {
344 warn(_("cannot set signal handler"));
345 caught_signal = true;
346 }
347 }
348 if (!caught_signal) {
349 pid_t pid;
350 for (;;) {
351 pid = waitpid(child, &status, WUNTRACED);
352
353 if (pid != (pid_t) - 1 && WIFSTOPPED(status)) {
354 kill(getpid(), SIGSTOP);
355 /* once we get here, we must have resumed */
356 kill(pid, SIGCONT);
357 } else
358 break;
359 }
360 if (pid != (pid_t) - 1) {
361 if (WIFSIGNALED(status)) {
362 fprintf(stderr, "%s%s\n",
363 strsignal(WTERMSIG(status)),
364 WCOREDUMP(status) ? _(" (core dumped)")
365 : "");
366 status = WTERMSIG(status) + 128;
367 } else
368 status = WEXITSTATUS(status);
369 } else if (caught_signal)
370 status = caught_signal + 128;
371 else
372 status = 1;
373 } else
374 status = 1;
375
376 if (caught_signal) {
377 fprintf(stderr, _("\nSession terminated, killing shell..."));
378 kill(child, SIGTERM);
379 }
380
381 supam_cleanup(su, PAM_SUCCESS);
382
383 if (caught_signal) {
384 sleep(2);
385 kill(child, SIGKILL);
386 fprintf(stderr, _(" ...killed.\n"));
387
388 /* Let's terminate itself with the received signal.
389 *
390 * It seems that shells use WIFSIGNALED() rather than our exit status
391 * value to detect situations when is necessary to cleanup (reset)
392 * terminal settings (kzak -- Jun 2013).
393 */
394 switch (caught_signal) {
395 case SIGTERM:
396 sigaction(SIGTERM, &oldact[0], NULL);
397 break;
398 case SIGINT:
399 sigaction(SIGINT, &oldact[1], NULL);
400 break;
401 case SIGQUIT:
402 sigaction(SIGQUIT, &oldact[2], NULL);
403 break;
404 default:
405 /* just in case that signal stuff initialization failed and
406 * caught_signal = true */
407 caught_signal = SIGKILL;
408 break;
409 }
410 kill(getpid(), caught_signal);
411 }
412 exit(status);
413 }
414
415 static void setenv_path(const struct passwd *pw)
416 {
417 int rc;
418
419 if (pw->pw_uid)
420 rc = logindefs_setenv("PATH", "ENV_PATH", _PATH_DEFPATH);
421
422 else if ((rc = logindefs_setenv("PATH", "ENV_ROOTPATH", NULL)) != 0)
423 rc = logindefs_setenv("PATH", "ENV_SUPATH", _PATH_DEFPATH_ROOT);
424
425 if (rc)
426 err(EXIT_FAILURE, _("failed to set the PATH environment variable"));
427 }
428
429 static void modify_environment(struct su_context *su, const char *shell)
430 {
431 const struct passwd *pw = su->pwd;
432
433 /* Leave TERM unchanged. Set HOME, SHELL, USER, LOGNAME, PATH.
434 * Unset all other environment variables.
435 */
436 if (su->simulate_login) {
437 char *term = getenv("TERM");
438 if (term)
439 term = xstrdup(term);
440
441 environ = xmalloc((6 + ! !term) * sizeof(char *));
442 environ[0] = NULL;
443 if (term) {
444 xsetenv("TERM", term, 1);
445 free(term);
446 }
447
448 xsetenv("HOME", pw->pw_dir, 1);
449 if (shell)
450 xsetenv("SHELL", shell, 1);
451 xsetenv("USER", pw->pw_name, 1);
452 xsetenv("LOGNAME", pw->pw_name, 1);
453 setenv_path(pw);
454
455 /* Set HOME, SHELL, and (if not becoming a superuser) USER and LOGNAME.
456 */
457 } else if (su->change_environment) {
458 xsetenv("HOME", pw->pw_dir, 1);
459 if (shell)
460 xsetenv("SHELL", shell, 1);
461
462 if (getlogindefs_bool("ALWAYS_SET_PATH", 0))
463 setenv_path(pw);
464
465 if (pw->pw_uid) {
466 xsetenv("USER", pw->pw_name, 1);
467 xsetenv("LOGNAME", pw->pw_name, 1);
468 }
469 }
470
471 supam_export_environment(su);
472 }
473
474 static void init_groups(struct su_context *su, gid_t *groups, size_t ngroups)
475 {
476 int rc;
477
478 errno = 0;
479 if (ngroups)
480 rc = setgroups(ngroups, groups);
481 else
482 rc = initgroups(su->pwd->pw_name, su->pwd->pw_gid);
483
484 if (rc == -1) {
485 supam_cleanup(su, PAM_ABORT);
486 err(EXIT_FAILURE, _("cannot set groups"));
487 }
488 endgrent();
489
490 rc = pam_setcred(su->pamh, PAM_ESTABLISH_CRED);
491 if (is_pam_failure(rc))
492 errx(EXIT_FAILURE, _("failed to user credentials: %s"),
493 pam_strerror(su->pamh, rc));
494 su->pam_has_cred = 1;
495 }
496
497 static void change_identity(const struct passwd *pw)
498 {
499 if (setgid(pw->pw_gid))
500 err(EXIT_FAILURE, _("cannot set group id"));
501 if (setuid(pw->pw_uid))
502 err(EXIT_FAILURE, _("cannot set user id"));
503 }
504
505 /* Run SHELL, if COMMAND is nonzero, pass it to the shell with the -c option.
506 * Pass ADDITIONAL_ARGS to the shell as more arguments; there are
507 * N_ADDITIONAL_ARGS extra arguments.
508 */
509 static void run_shell(
510 struct su_context *su,
511 char const *shell, char const *command, char **additional_args,
512 size_t n_additional_args)
513 {
514 size_t n_args = 1 + su->fast_startup + 2 * ! !command + n_additional_args + 1;
515 char const **args = xcalloc(n_args, sizeof *args);
516 size_t argno = 1;
517 int rc;
518
519 if (su->simulate_login) {
520 char *arg0;
521 char *shell_basename;
522
523 shell_basename = basename(shell);
524 arg0 = xmalloc(strlen(shell_basename) + 2);
525 arg0[0] = '-';
526 strcpy(arg0 + 1, shell_basename);
527 args[0] = arg0;
528 } else
529 args[0] = basename(shell);
530
531 if (su->fast_startup)
532 args[argno++] = "-f";
533 if (command) {
534 args[argno++] = "-c";
535 args[argno++] = command;
536 }
537
538 memcpy(args + argno, additional_args, n_additional_args * sizeof *args);
539 args[argno + n_additional_args] = NULL;
540 execv(shell, (char **)args);
541
542 rc = errno == ENOENT ? EXIT_ENOENT : EXIT_CANNOT_INVOKE;
543 err(rc, _("failed to execute %s"), shell);
544 }
545
546 /* Return true if SHELL is a restricted shell (one not returned by
547 * getusershell), else false, meaning it is a standard shell.
548 */
549 static bool is_restricted_shell(const char *shell)
550 {
551 char *line;
552
553 setusershell();
554 while ((line = getusershell()) != NULL) {
555 if (*line != '#' && !strcmp(line, shell)) {
556 endusershell();
557 return false;
558 }
559 }
560 endusershell();
561 return true;
562 }
563
564 static void usage_common(void)
565 {
566 fputs(_(" -m, -p, --preserve-environment do not reset environment variables\n"), stdout);
567 fputs(_(" -g, --group <group> specify the primary group\n"), stdout);
568 fputs(_(" -G, --supp-group <group> specify a supplemental group\n"), stdout);
569 fputs(USAGE_SEPARATOR, stdout);
570
571 fputs(_(" -, -l, --login make the shell a login shell\n"), stdout);
572 fputs(_(" -c, --command <command> pass a single command to the shell with -c\n"), stdout);
573 fputs(_(" --session-command <command> pass a single command to the shell with -c\n"
574 " and do not create a new session\n"), stdout);
575 fputs(_(" -f, --fast pass -f to the shell (for csh or tcsh)\n"), stdout);
576 fputs(_(" -s, --shell <shell> run <shell> if /etc/shells allows it\n"), stdout);
577
578 fputs(USAGE_SEPARATOR, stdout);
579 printf(USAGE_HELP_OPTIONS(33));
580
581 }
582
583 static void __attribute__ ((__noreturn__)) usage_runuser(void)
584 {
585 fputs(USAGE_HEADER, stdout);
586 fprintf(stdout,
587 _(" %1$s [options] -u <user> [[--] <command>]\n"
588 " %1$s [options] [-] [<user> [<argument>...]]\n"),
589 program_invocation_short_name);
590
591 fputs(USAGE_SEPARATOR, stdout);
592 fputs(_("Run <command> with the effective user ID and group ID of <user>. If -u is\n"
593 "not given, fall back to su(1)-compatible semantics and execute standard shell.\n"
594 "The options -c, -f, -l, and -s are mutually exclusive with -u.\n"), stdout);
595
596 fputs(USAGE_OPTIONS, stdout);
597 fputs(_(" -u, --user <user> username\n"), stdout);
598 usage_common();
599 fputs(USAGE_SEPARATOR, stdout);
600
601 fprintf(stdout, USAGE_MAN_TAIL("runuser(1)"));
602 exit(EXIT_SUCCESS);
603 }
604
605 static void __attribute__ ((__noreturn__)) usage_su(void)
606 {
607 fputs(USAGE_HEADER, stdout);
608 fprintf(stdout,
609 _(" %s [options] [-] [<user> [<argument>...]]\n"),
610 program_invocation_short_name);
611
612 fputs(USAGE_SEPARATOR, stdout);
613 fputs(_("Change the effective user ID and group ID to that of <user>.\n"
614 "A mere - implies -l. If <user> is not given, root is assumed.\n"), stdout);
615
616 fputs(USAGE_OPTIONS, stdout);
617 usage_common();
618
619 fprintf(stdout, USAGE_MAN_TAIL("su(1)"));
620 exit(EXIT_SUCCESS);
621 }
622
623 static void usage(int mode)
624 {
625 if (mode == SU_MODE)
626 usage_su();
627 else
628 usage_runuser();
629 }
630
631 static void load_config(void *data)
632 {
633 struct su_context *su = (struct su_context *) data;
634
635 logindefs_load_file(su->runuser ? _PATH_LOGINDEFS_RUNUSER : _PATH_LOGINDEFS_SU);
636 logindefs_load_file(_PATH_LOGINDEFS);
637 }
638
639 /*
640 * Returns 1 if the current user is not root
641 */
642 static int is_not_root(void)
643 {
644 const uid_t ruid = getuid();
645 const uid_t euid = geteuid();
646
647 /* if we're really root and aren't running setuid */
648 return (uid_t) 0 == ruid && ruid == euid ? 0 : 1;
649 }
650
651 static gid_t add_supp_group(const char *name, gid_t **groups, size_t *ngroups)
652 {
653 struct group *gr;
654
655 if (*ngroups >= NGROUPS_MAX)
656 errx(EXIT_FAILURE,
657 P_("specifying more than %d supplemental group is not possible",
658 "specifying more than %d supplemental groups is not possible",
659 NGROUPS_MAX - 1), NGROUPS_MAX - 1);
660
661 gr = getgrnam(name);
662 if (!gr)
663 errx(EXIT_FAILURE, _("group %s does not exist"), name);
664
665 *groups = xrealloc(*groups, sizeof(gid_t) * (*ngroups + 1));
666 (*groups)[*ngroups] = gr->gr_gid;
667 (*ngroups)++;
668
669 return gr->gr_gid;
670 }
671
672 int su_main(int argc, char **argv, int mode)
673 {
674 struct su_context _su = {
675 .conv = { supam_conv, NULL },
676 .runuser = (mode == RUNUSER_MODE ? 1 : 0),
677 .change_environment = 1,
678 .new_user = DEFAULT_USER
679 }, *su = &_su;
680
681 int optc;
682 char *command = NULL;
683 int request_same_session = 0;
684 char *shell = NULL;
685
686 gid_t *groups = NULL;
687 size_t ngroups = 0;
688 bool use_supp = false;
689 bool use_gid = false;
690 gid_t gid = 0;
691
692 static const struct option longopts[] = {
693 {"command", required_argument, NULL, 'c'},
694 {"session-command", required_argument, NULL, 'C'},
695 {"fast", no_argument, NULL, 'f'},
696 {"login", no_argument, NULL, 'l'},
697 {"preserve-environment", no_argument, NULL, 'p'},
698 {"shell", required_argument, NULL, 's'},
699 {"group", required_argument, NULL, 'g'},
700 {"supp-group", required_argument, NULL, 'G'},
701 {"user", required_argument, NULL, 'u'}, /* runuser only */
702 {"help", no_argument, 0, 'h'},
703 {"version", no_argument, 0, 'V'},
704 {NULL, 0, NULL, 0}
705 };
706
707 setlocale(LC_ALL, "");
708 bindtextdomain(PACKAGE, LOCALEDIR);
709 textdomain(PACKAGE);
710 atexit(close_stdout);
711
712 su->conv.appdata_ptr = (void *) su;
713
714 while ((optc =
715 getopt_long(argc, argv, "c:fg:G:lmps:u:hV", longopts,
716 NULL)) != -1) {
717 switch (optc) {
718 case 'c':
719 command = optarg;
720 break;
721
722 case 'C':
723 command = optarg;
724 request_same_session = 1;
725 break;
726
727 case 'f':
728 su->fast_startup = true;
729 break;
730
731 case 'g':
732 use_gid = true;
733 gid = add_supp_group(optarg, &groups, &ngroups);
734 break;
735
736 case 'G':
737 use_supp = true;
738 add_supp_group(optarg, &groups, &ngroups);
739 break;
740
741 case 'l':
742 su->simulate_login = true;
743 break;
744
745 case 'm':
746 case 'p':
747 su->change_environment = false;
748 break;
749
750 case 's':
751 shell = optarg;
752 break;
753
754 case 'u':
755 if (!su->runuser)
756 errtryhelp(EXIT_FAILURE);
757 su->runuser_uopt = 1;
758 su->new_user = optarg;
759 break;
760
761 case 'h':
762 usage(mode);
763
764 case 'V':
765 printf(UTIL_LINUX_VERSION);
766 exit(EXIT_SUCCESS);
767
768 default:
769 errtryhelp(EXIT_FAILURE);
770 }
771 }
772
773 su->restricted = is_not_root();
774
775 if (optind < argc && !strcmp(argv[optind], "-")) {
776 su->simulate_login = true;
777 ++optind;
778 }
779
780 if (su->simulate_login && !su->change_environment) {
781 warnx(_
782 ("ignoring --preserve-environment, it's mutually exclusive with --login"));
783 su->change_environment = true;
784 }
785
786 switch (mode) {
787 case RUNUSER_MODE:
788 /* runuser -u <user> <command> */
789 if (su->runuser_uopt) {
790 if (shell || su->fast_startup || command || su->simulate_login)
791 errx(EXIT_FAILURE,
792 _("options --{shell,fast,command,session-command,login} and "
793 "--user are mutually exclusive"));
794 if (optind == argc)
795 errx(EXIT_FAILURE, _("no command was specified"));
796 break;
797 }
798 /* fallthrough if -u <user> is not specified, then follow
799 * traditional su(1) behavior
800 */
801 case SU_MODE:
802 if (optind < argc)
803 su->new_user = argv[optind++];
804 break;
805 }
806
807 if ((use_supp || use_gid) && su->restricted)
808 errx(EXIT_FAILURE,
809 _("only root can specify alternative groups"));
810
811 logindefs_set_loader(load_config, (void *) su);
812 init_tty(su);
813
814 su->pwd = xgetpwnam(su->new_user, &su->pwdbuf);
815 if (!su->pwd
816 || !su->pwd->pw_passwd
817 || !su->pwd->pw_name || !*su->pwd->pw_name
818 || !su->pwd->pw_dir || !*su->pwd->pw_dir)
819 errx(EXIT_FAILURE, _("user %s does not exist"), su->new_user);
820
821 su->new_user = su->pwd->pw_name;
822 su->old_user = xgetlogin();
823
824 if (!su->pwd->pw_shell || !*su->pwd->pw_shell)
825 su->pwd->pw_shell = DEFAULT_SHELL;
826
827 if (use_supp && !use_gid)
828 su->pwd->pw_gid = groups[0];
829 else if (use_gid)
830 su->pwd->pw_gid = gid;
831
832 supam_authenticate(su);
833
834 if (request_same_session || !command || !su->pwd->pw_uid)
835 su->same_session = 1;
836
837 /* initialize shell variable only if "-u <user>" not specified */
838 if (su->runuser_uopt) {
839 shell = NULL;
840 } else {
841 if (!shell && !su->change_environment)
842 shell = getenv("SHELL");
843
844 if (shell
845 && getuid() != 0
846 && is_restricted_shell(su->pwd->pw_shell)) {
847 /* The user being su'd to has a nonstandard shell, and
848 * so is probably a uucp account or has restricted
849 * access. Don't compromise the account by allowing
850 * access with a standard shell.
851 */
852 warnx(_("using restricted shell %s"), su->pwd->pw_shell);
853 shell = NULL;
854 }
855 shell = xstrdup(shell ? shell : su->pwd->pw_shell);
856 }
857
858 init_groups(su, groups, ngroups);
859
860 if (!su->simulate_login || command)
861 su->suppress_pam_info = 1; /* don't print PAM info messages */
862
863 supam_open_session(su);
864
865 create_watching_parent(su);
866 /* Now we're in the child. */
867
868 change_identity(su->pwd);
869 if (!su->same_session)
870 setsid();
871
872 /* Set environment after pam_open_session, which may put KRB5CCNAME
873 into the pam_env, etc. */
874
875 modify_environment(su, shell);
876
877 if (su->simulate_login && chdir(su->pwd->pw_dir) != 0)
878 warn(_("warning: cannot change directory to %s"), su->pwd->pw_dir);
879
880 if (shell)
881 run_shell(su, shell, command, argv + optind, max(0, argc - optind));
882
883 execvp(argv[optind], &argv[optind]);
884 err(EXIT_FAILURE, _("failed to execute %s"), argv[optind]);
885 }