]>
Commit | Line | Data |
---|---|---|
cf1a99da KZ |
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 implemenation 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 | #include <security/pam_misc.h> | |
54 | #include <signal.h> | |
55 | #include <sys/wait.h> | |
56 | #include <syslog.h> | |
c74a7af1 | 57 | #include <utmp.h> |
cf1a99da KZ |
58 | |
59 | #include "err.h" | |
60 | ||
61 | #include <stdbool.h> | |
62 | #include "c.h" | |
63 | #include "xalloc.h" | |
64 | #include "nls.h" | |
65 | #include "pathnames.h" | |
66 | #include "env.h" | |
4e183497 | 67 | #include "closestream.h" |
c74a7af1 | 68 | #include "strutils.h" |
bbc5a5ea | 69 | #include "ttyutils.h" |
cf1a99da KZ |
70 | |
71 | /* name of the pam configuration files. separate configs for su and su - */ | |
7ec6adb1 KZ |
72 | #define PAM_SRVNAME_SU "su" |
73 | #define PAM_SRVNAME_SU_L "su-l" | |
74 | ||
75 | #define PAM_SRVNAME_RUNUSER "runuser" | |
76 | #define PAM_SRVNAME_RUNUSER_L "runuser-l" | |
77 | ||
78 | #define _PATH_LOGINDEFS_SU "/etc/defaults/su" | |
79 | #define _PATH_LOGINDEFS_RUNUSER "/etc/defaults/runuser" | |
cf1a99da KZ |
80 | |
81 | #define is_pam_failure(_rc) ((_rc) != PAM_SUCCESS) | |
82 | ||
83 | #include "logindefs.h" | |
84 | #include "su-common.h" | |
85 | ||
86 | /* The shell to run if none is given in the user's passwd entry. */ | |
87 | #define DEFAULT_SHELL "/bin/sh" | |
88 | ||
89 | /* The user to become if none is specified. */ | |
90 | #define DEFAULT_USER "root" | |
91 | ||
92 | #ifndef HAVE_ENVIRON_DECL | |
93 | extern char **environ; | |
94 | #endif | |
95 | ||
96 | static void run_shell (char const *, char const *, char **, size_t) | |
97 | __attribute__ ((__noreturn__)); | |
98 | ||
99 | /* If true, pass the `-f' option to the subshell. */ | |
100 | static bool fast_startup; | |
101 | ||
102 | /* If true, simulate a login instead of just starting a shell. */ | |
103 | static bool simulate_login; | |
104 | ||
105 | /* If true, change some environment vars to indicate the user su'd to. */ | |
106 | static bool change_environment; | |
107 | ||
108 | /* If true, then don't call setsid() with a command. */ | |
109 | static int same_session = 0; | |
110 | ||
111 | /* SU_MODE_{RUNUSER,SU} */ | |
112 | static int su_mode; | |
113 | ||
114 | static bool _pam_session_opened; | |
115 | static bool _pam_cred_established; | |
116 | static sig_atomic_t volatile caught_signal = false; | |
117 | static pam_handle_t *pamh = NULL; | |
118 | ||
7ec6adb1 KZ |
119 | static int restricted = 1; /* zero for root user */ |
120 | ||
d10028a7 KZ |
121 | |
122 | static struct passwd * | |
123 | current_getpwuid(void) | |
124 | { | |
125 | uid_t ruid; | |
126 | ||
127 | /* GNU Hurd implementation has an extension where a process can exist in a | |
128 | * non-conforming environment, and thus be outside the realms of POSIX | |
129 | * process identifiers; on this platform, getuid() fails with a status of | |
130 | * (uid_t)(-1) and sets errno if a program is run from a non-conforming | |
131 | * environment. | |
132 | * | |
133 | * http://austingroupbugs.net/view.php?id=511 | |
134 | */ | |
135 | errno = 0; | |
136 | ruid = getuid (); | |
137 | ||
138 | return errno == 0 ? getpwuid (ruid) : NULL; | |
139 | } | |
140 | ||
cf1a99da KZ |
141 | /* Log the fact that someone has run su to the user given by PW; |
142 | if SUCCESSFUL is true, they gave the correct password, etc. */ | |
143 | ||
144 | static void | |
b0332112 | 145 | log_syslog(struct passwd const *pw, bool successful) |
cf1a99da KZ |
146 | { |
147 | const char *new_user, *old_user, *tty; | |
148 | ||
149 | new_user = pw->pw_name; | |
150 | /* The utmp entry (via getlogin) is probably the best way to identify | |
151 | the user, especially if someone su's from a su-shell. */ | |
152 | old_user = getlogin (); | |
153 | if (!old_user) | |
154 | { | |
155 | /* getlogin can fail -- usually due to lack of utmp entry. | |
156 | Resort to getpwuid. */ | |
d10028a7 KZ |
157 | struct passwd *pwd = current_getpwuid(); |
158 | old_user = pwd ? pwd->pw_name : ""; | |
cf1a99da | 159 | } |
bbc5a5ea KZ |
160 | |
161 | if (get_terminal_name(NULL, &tty, NULL) == 0 && tty) | |
cf1a99da KZ |
162 | tty = "none"; |
163 | ||
164 | openlog (program_invocation_short_name, 0 , LOG_AUTH); | |
165 | syslog (LOG_NOTICE, "%s(to %s) %s on %s", | |
7ec6adb1 KZ |
166 | successful ? "" : |
167 | su_mode == RUNUSER_MODE ? "FAILED RUNUSER " : "FAILED SU ", | |
cf1a99da KZ |
168 | new_user, old_user, tty); |
169 | closelog (); | |
170 | } | |
171 | ||
c74a7af1 KZ |
172 | |
173 | /* | |
174 | * Log failed login attempts in _PATH_BTMP if that exists. | |
175 | */ | |
176 | static void log_btmp(struct passwd const *pw) | |
177 | { | |
178 | struct utmp ut; | |
179 | struct timeval tv; | |
180 | const char *tty_name, *tty_num; | |
181 | ||
182 | memset(&ut, 0, sizeof(ut)); | |
183 | ||
184 | strncpy(ut.ut_user, | |
185 | pw && pw->pw_name ? pw->pw_name : "(unknown)", | |
186 | sizeof(ut.ut_user)); | |
187 | ||
188 | get_terminal_name(NULL, &tty_name, &tty_num); | |
189 | if (tty_num) | |
190 | xstrncpy(ut.ut_id, tty_num, sizeof(ut.ut_id)); | |
191 | if (tty_name) | |
192 | xstrncpy(ut.ut_line, tty_name, sizeof(ut.ut_line)); | |
193 | ||
194 | #if defined(_HAVE_UT_TV) /* in <utmpbits.h> included by <utmp.h> */ | |
195 | gettimeofday(&tv, NULL); | |
196 | ut.ut_tv.tv_sec = tv.tv_sec; | |
197 | ut.ut_tv.tv_usec = tv.tv_usec; | |
198 | #else | |
199 | { | |
200 | time_t t; | |
201 | time(&t); | |
202 | ut.ut_time = t; /* ut_time is not always a time_t */ | |
203 | } | |
204 | #endif | |
205 | ut.ut_type = LOGIN_PROCESS; /* XXX doesn't matter */ | |
206 | ut.ut_pid = getpid(); | |
207 | ||
208 | updwtmp(_PATH_BTMP, &ut); | |
209 | } | |
210 | ||
cf1a99da KZ |
211 | static struct pam_conv conv = |
212 | { | |
213 | misc_conv, | |
214 | NULL | |
215 | }; | |
216 | ||
217 | static void | |
218 | cleanup_pam (int retcode) | |
219 | { | |
220 | int saved_errno = errno; | |
221 | ||
222 | if (_pam_session_opened) | |
223 | pam_close_session (pamh, 0); | |
224 | ||
225 | if (_pam_cred_established) | |
226 | pam_setcred (pamh, PAM_DELETE_CRED | PAM_SILENT); | |
227 | ||
228 | pam_end(pamh, retcode); | |
229 | ||
230 | errno = saved_errno; | |
231 | } | |
232 | ||
233 | /* Signal handler for parent process. */ | |
234 | static void | |
235 | su_catch_sig (int sig __attribute__((__unused__))) | |
236 | { | |
237 | caught_signal = true; | |
238 | } | |
239 | ||
240 | /* Export env variables declared by PAM modules. */ | |
241 | static void | |
242 | export_pamenv (void) | |
243 | { | |
244 | char **env; | |
245 | ||
246 | /* This is a copy but don't care to free as we exec later anyways. */ | |
247 | env = pam_getenvlist (pamh); | |
248 | while (env && *env) | |
249 | { | |
250 | if (putenv (*env) != 0) | |
251 | err (EXIT_FAILURE, NULL); | |
252 | env++; | |
253 | } | |
254 | } | |
255 | ||
256 | static void | |
257 | create_watching_parent (void) | |
258 | { | |
259 | pid_t child; | |
260 | sigset_t ourset; | |
261 | int status = 0; | |
262 | int retval; | |
263 | ||
264 | retval = pam_open_session (pamh, 0); | |
265 | if (is_pam_failure(retval)) | |
266 | { | |
267 | cleanup_pam (retval); | |
61cebf77 | 268 | errx (EXIT_FAILURE, _("cannot open session: %s"), |
cf1a99da KZ |
269 | pam_strerror (pamh, retval)); |
270 | } | |
271 | else | |
272 | _pam_session_opened = 1; | |
273 | ||
274 | child = fork (); | |
275 | if (child == (pid_t) -1) | |
276 | { | |
277 | cleanup_pam (PAM_ABORT); | |
278 | err (EXIT_FAILURE, _("cannot create child process")); | |
279 | } | |
280 | ||
281 | /* the child proceeds to run the shell */ | |
282 | if (child == 0) | |
283 | return; | |
284 | ||
285 | /* In the parent watch the child. */ | |
286 | ||
287 | /* su without pam support does not have a helper that keeps | |
288 | sitting on any directory so let's go to /. */ | |
289 | if (chdir ("/") != 0) | |
290 | warn (_("cannot change directory to %s"), "/"); | |
291 | ||
292 | sigfillset (&ourset); | |
293 | if (sigprocmask (SIG_BLOCK, &ourset, NULL)) | |
294 | { | |
295 | warn (_("cannot block signals")); | |
296 | caught_signal = true; | |
297 | } | |
298 | if (!caught_signal) | |
299 | { | |
300 | struct sigaction action; | |
301 | action.sa_handler = su_catch_sig; | |
302 | sigemptyset (&action.sa_mask); | |
303 | action.sa_flags = 0; | |
304 | sigemptyset (&ourset); | |
305 | if (!same_session) | |
306 | { | |
307 | if (sigaddset(&ourset, SIGINT) || sigaddset(&ourset, SIGQUIT)) | |
308 | { | |
309 | warn (_("cannot set signal handler")); | |
310 | caught_signal = true; | |
311 | } | |
312 | } | |
313 | if (!caught_signal && (sigaddset(&ourset, SIGTERM) | |
314 | || sigaddset(&ourset, SIGALRM) | |
315 | || sigaction(SIGTERM, &action, NULL) | |
316 | || sigprocmask(SIG_UNBLOCK, &ourset, NULL))) { | |
317 | warn (_("cannot set signal handler")); | |
318 | caught_signal = true; | |
319 | } | |
320 | if (!caught_signal && !same_session && (sigaction(SIGINT, &action, NULL) | |
321 | || sigaction(SIGQUIT, &action, NULL))) | |
322 | { | |
323 | warn (_("cannot set signal handler")); | |
324 | caught_signal = true; | |
325 | } | |
326 | } | |
327 | if (!caught_signal) | |
328 | { | |
329 | pid_t pid; | |
330 | for (;;) | |
331 | { | |
332 | pid = waitpid (child, &status, WUNTRACED); | |
333 | ||
334 | if (pid != (pid_t)-1 && WIFSTOPPED (status)) | |
335 | { | |
336 | kill (getpid (), SIGSTOP); | |
337 | /* once we get here, we must have resumed */ | |
338 | kill (pid, SIGCONT); | |
339 | } | |
340 | else | |
341 | break; | |
342 | } | |
343 | if (pid != (pid_t)-1) | |
344 | if (WIFSIGNALED (status)) | |
ea8a1039 OO |
345 | { |
346 | status = WTERMSIG (status) + 128; | |
347 | if (WCOREDUMP (status)) | |
348 | fprintf (stderr, _("%s (core dumped)\n"), | |
349 | strsignal (WTERMSIG (status))); | |
350 | } | |
cf1a99da KZ |
351 | else |
352 | status = WEXITSTATUS (status); | |
353 | else | |
354 | status = 1; | |
355 | } | |
356 | else | |
357 | status = 1; | |
358 | ||
359 | if (caught_signal) | |
360 | { | |
361 | fprintf (stderr, _("\nSession terminated, killing shell...")); | |
362 | kill (child, SIGTERM); | |
363 | } | |
364 | ||
365 | cleanup_pam (PAM_SUCCESS); | |
366 | ||
367 | if (caught_signal) | |
368 | { | |
369 | sleep (2); | |
370 | kill (child, SIGKILL); | |
371 | fprintf (stderr, _(" ...killed.\n")); | |
372 | } | |
373 | exit (status); | |
374 | } | |
375 | ||
376 | static void | |
377 | authenticate (const struct passwd *pw) | |
378 | { | |
c74a7af1 | 379 | const struct passwd *lpw = NULL; |
7ec6adb1 | 380 | const char *cp, *srvname = NULL; |
cf1a99da KZ |
381 | int retval; |
382 | ||
7ec6adb1 KZ |
383 | switch (su_mode) { |
384 | case SU_MODE: | |
385 | srvname = simulate_login ? PAM_SRVNAME_SU_L : PAM_SRVNAME_SU; | |
386 | break; | |
387 | case RUNUSER_MODE: | |
388 | srvname = simulate_login ? PAM_SRVNAME_RUNUSER_L : PAM_SRVNAME_RUNUSER; | |
389 | break; | |
82455ad5 KZ |
390 | default: |
391 | abort(); | |
392 | break; | |
7ec6adb1 KZ |
393 | } |
394 | ||
395 | retval = pam_start (srvname, pw->pw_name, &conv, &pamh); | |
cf1a99da KZ |
396 | if (is_pam_failure(retval)) |
397 | goto done; | |
398 | ||
399 | if (isatty (0) && (cp = ttyname (0)) != NULL) | |
400 | { | |
401 | const char *tty; | |
402 | ||
403 | if (strncmp (cp, "/dev/", 5) == 0) | |
404 | tty = cp + 5; | |
405 | else | |
406 | tty = cp; | |
407 | retval = pam_set_item (pamh, PAM_TTY, tty); | |
408 | if (is_pam_failure(retval)) | |
409 | goto done; | |
410 | } | |
411 | ||
d10028a7 | 412 | lpw = current_getpwuid (); |
cf1a99da KZ |
413 | if (lpw && lpw->pw_name) |
414 | { | |
415 | retval = pam_set_item (pamh, PAM_RUSER, (const void *) lpw->pw_name); | |
416 | if (is_pam_failure(retval)) | |
417 | goto done; | |
418 | } | |
419 | ||
7ec6adb1 KZ |
420 | if (su_mode == RUNUSER_MODE) |
421 | { | |
422 | /* | |
423 | * This is the only difference between runuser(1) and su(1). The command | |
424 | * runuser(1) does not required authentication, because user is root. | |
425 | */ | |
426 | if (restricted) | |
427 | errx(EXIT_FAILURE, _("may not be used by non-root users")); | |
428 | return; | |
429 | } | |
430 | ||
cf1a99da KZ |
431 | retval = pam_authenticate (pamh, 0); |
432 | if (is_pam_failure(retval)) | |
433 | goto done; | |
434 | ||
435 | retval = pam_acct_mgmt (pamh, 0); | |
436 | if (retval == PAM_NEW_AUTHTOK_REQD) | |
437 | { | |
438 | /* Password has expired. Offer option to change it. */ | |
439 | retval = pam_chauthtok (pamh, PAM_CHANGE_EXPIRED_AUTHTOK); | |
440 | } | |
441 | ||
442 | done: | |
443 | ||
c74a7af1 KZ |
444 | if (lpw && lpw->pw_name) |
445 | pw = lpw; | |
446 | ||
b0332112 | 447 | log_syslog(pw, !is_pam_failure(retval)); |
cf1a99da KZ |
448 | |
449 | if (is_pam_failure(retval)) | |
450 | { | |
c74a7af1 KZ |
451 | const char *msg; |
452 | ||
453 | log_btmp(pw); | |
454 | ||
455 | msg = pam_strerror(pamh, retval); | |
cf1a99da KZ |
456 | pam_end(pamh, retval); |
457 | sleep (getlogindefs_num ("FAIL_DELAY", 1)); | |
458 | errx (EXIT_FAILURE, "%s", msg?msg:_("incorrect password")); | |
459 | } | |
460 | } | |
461 | ||
cf1a99da KZ |
462 | static void |
463 | set_path(const struct passwd* pw) | |
464 | { | |
465 | int r; | |
466 | if (pw->pw_uid) | |
467 | r = logindefs_setenv("PATH", "ENV_PATH", _PATH_DEFPATH); | |
468 | ||
469 | else if ((r = logindefs_setenv("PATH", "ENV_ROOTPATH", NULL)) != 0) | |
470 | r = logindefs_setenv("PATH", "ENV_SUPATH", _PATH_DEFPATH_ROOT); | |
471 | ||
472 | if (r != 0) | |
473 | err (EXIT_FAILURE, _("failed to set PATH")); | |
474 | } | |
475 | ||
476 | /* Update `environ' for the new shell based on PW, with SHELL being | |
477 | the value for the SHELL environment variable. */ | |
478 | ||
479 | static void | |
480 | modify_environment (const struct passwd *pw, const char *shell) | |
481 | { | |
482 | if (simulate_login) | |
483 | { | |
484 | /* Leave TERM unchanged. Set HOME, SHELL, USER, LOGNAME, PATH. | |
485 | Unset all other environment variables. */ | |
486 | char const *term = getenv ("TERM"); | |
487 | if (term) | |
488 | term = xstrdup (term); | |
489 | environ = xmalloc ((6 + !!term) * sizeof (char *)); | |
490 | environ[0] = NULL; | |
491 | if (term) | |
492 | xsetenv ("TERM", term, 1); | |
493 | xsetenv ("HOME", pw->pw_dir, 1); | |
7a7f9d38 KZ |
494 | if (shell) |
495 | xsetenv ("SHELL", shell, 1); | |
cf1a99da KZ |
496 | xsetenv ("USER", pw->pw_name, 1); |
497 | xsetenv ("LOGNAME", pw->pw_name, 1); | |
498 | set_path(pw); | |
499 | } | |
500 | else | |
501 | { | |
502 | /* Set HOME, SHELL, and if not becoming a super-user, | |
503 | USER and LOGNAME. */ | |
504 | if (change_environment) | |
505 | { | |
506 | xsetenv ("HOME", pw->pw_dir, 1); | |
7a7f9d38 KZ |
507 | if (shell) |
508 | xsetenv ("SHELL", shell, 1); | |
cf1a99da KZ |
509 | if (getlogindefs_bool ("ALWAYS_SET_PATH", 0)) |
510 | set_path(pw); | |
705ee62f | 511 | |
cf1a99da KZ |
512 | if (pw->pw_uid) |
513 | { | |
514 | xsetenv ("USER", pw->pw_name, 1); | |
515 | xsetenv ("LOGNAME", pw->pw_name, 1); | |
516 | } | |
517 | } | |
518 | } | |
519 | ||
520 | export_pamenv (); | |
521 | } | |
522 | ||
523 | /* Become the user and group(s) specified by PW. */ | |
524 | ||
525 | static void | |
526 | init_groups (const struct passwd *pw, gid_t *groups, int num_groups) | |
527 | { | |
528 | int retval; | |
529 | ||
530 | errno = 0; | |
531 | ||
532 | if (num_groups) | |
533 | retval = setgroups (num_groups, groups); | |
534 | else | |
535 | retval = initgroups (pw->pw_name, pw->pw_gid); | |
536 | ||
537 | if (retval == -1) | |
538 | { | |
539 | cleanup_pam (PAM_ABORT); | |
540 | err (EXIT_FAILURE, _("cannot set groups")); | |
541 | } | |
542 | endgrent (); | |
543 | ||
544 | retval = pam_setcred (pamh, PAM_ESTABLISH_CRED); | |
545 | if (is_pam_failure(retval)) | |
546 | errx (EXIT_FAILURE, "%s", pam_strerror (pamh, retval)); | |
547 | else | |
548 | _pam_cred_established = 1; | |
549 | } | |
550 | ||
551 | static void | |
552 | change_identity (const struct passwd *pw) | |
553 | { | |
554 | if (setgid (pw->pw_gid)) | |
555 | err (EXIT_FAILURE, _("cannot set group id")); | |
556 | if (setuid (pw->pw_uid)) | |
557 | err (EXIT_FAILURE, _("cannot set user id")); | |
558 | } | |
559 | ||
560 | /* Run SHELL, or DEFAULT_SHELL if SHELL is empty. | |
561 | If COMMAND is nonzero, pass it to the shell with the -c option. | |
562 | Pass ADDITIONAL_ARGS to the shell as more arguments; there | |
563 | are N_ADDITIONAL_ARGS extra arguments. */ | |
564 | ||
565 | static void | |
566 | run_shell (char const *shell, char const *command, char **additional_args, | |
567 | size_t n_additional_args) | |
568 | { | |
569 | size_t n_args = 1 + fast_startup + 2 * !!command + n_additional_args + 1; | |
570 | char const **args = xcalloc (n_args, sizeof *args); | |
571 | size_t argno = 1; | |
572 | ||
573 | if (simulate_login) | |
574 | { | |
575 | char *arg0; | |
576 | char *shell_basename; | |
577 | ||
578 | shell_basename = basename (shell); | |
579 | arg0 = xmalloc (strlen (shell_basename) + 2); | |
580 | arg0[0] = '-'; | |
581 | strcpy (arg0 + 1, shell_basename); | |
582 | args[0] = arg0; | |
583 | } | |
584 | else | |
585 | args[0] = basename (shell); | |
586 | if (fast_startup) | |
587 | args[argno++] = "-f"; | |
588 | if (command) | |
589 | { | |
590 | args[argno++] = "-c"; | |
591 | args[argno++] = command; | |
592 | } | |
593 | memcpy (args + argno, additional_args, n_additional_args * sizeof *args); | |
594 | args[argno + n_additional_args] = NULL; | |
595 | execv (shell, (char **) args); | |
596 | ||
597 | { | |
598 | int exit_status = (errno == ENOENT ? EXIT_ENOENT : EXIT_CANNOT_INVOKE); | |
07ff972e | 599 | warn (_("failed to execute %s"), shell); |
cf1a99da KZ |
600 | exit (exit_status); |
601 | } | |
602 | } | |
603 | ||
604 | /* Return true if SHELL is a restricted shell (one not returned by | |
605 | getusershell), else false, meaning it is a standard shell. */ | |
606 | ||
607 | static bool | |
608 | restricted_shell (const char *shell) | |
609 | { | |
610 | char *line; | |
611 | ||
612 | setusershell (); | |
613 | while ((line = getusershell ()) != NULL) | |
614 | { | |
615 | if (*line != '#' && !strcmp (line, shell)) | |
616 | { | |
617 | endusershell (); | |
618 | return false; | |
619 | } | |
620 | } | |
621 | endusershell (); | |
622 | return true; | |
623 | } | |
624 | ||
625 | static void __attribute__((__noreturn__)) | |
626 | usage (int status) | |
627 | { | |
7a7f9d38 KZ |
628 | if (su_mode == RUNUSER_MODE) { |
629 | fputs(USAGE_HEADER, stdout); | |
630 | printf (_(" %s [options] -u <USER> COMMAND\n"), program_invocation_short_name); | |
631 | printf (_(" %s [options] [-] [USER [arg]...]\n"), program_invocation_short_name); | |
632 | fputs (_("\n" | |
633 | "Run COMMAND with the effective <user> id and group id. If -u not\n" | |
634 | "given, fallback to su(1) compatible semantic and shell is executed.\n" | |
635 | "The options -l, -c, -f, -s are mutually exclusive to -u.\n"), stdout); | |
636 | ||
637 | fputs(USAGE_OPTIONS, stdout); | |
638 | ||
639 | fputs (_( | |
640 | " -u, --user <user> username\n"), stdout); | |
641 | ||
642 | } else { | |
643 | fputs(USAGE_HEADER, stdout); | |
644 | printf (_(" %s [options] [-] [USER [arg]...]\n"), program_invocation_short_name); | |
645 | fputs (_("\n" | |
646 | "Change the effective user id and group id to that of USER.\n" | |
647 | "A mere - implies -l. If USER not given, assume root.\n"), stdout); | |
648 | ||
649 | fputs(USAGE_OPTIONS, stdout); | |
650 | } | |
651 | ||
09f5902e SK |
652 | fputs (_(" -m, -p, --preserve-environment do not reset environment variables\n"), stdout); |
653 | fputs (_(" -g, --group <group> specify the primary group\n"), stdout); | |
654 | fputs (_(" -G, --supp-group <group> specify a supplemental group\n\n"), stdout); | |
655 | ||
656 | fputs (_(" -, -l, --login make the shell a login shell\n"), stdout); | |
657 | fputs (_(" -c, --command <command> pass a single command to the shell with -c\n"), stdout); | |
658 | fputs (_(" --session-command <command> pass a single command to the shell with -c\n"), stdout); | |
659 | fputs (_(" and do not create a new session\n"), stdout); | |
660 | fputs (_(" -f, --fast pass -f to the shell (for csh or tcsh)\n"), stdout); | |
661 | fputs (_(" -s, --shell <shell> run shell if /etc/shells allows it\n"), stdout); | |
7a7f9d38 KZ |
662 | |
663 | fputs(USAGE_SEPARATOR, stdout); | |
664 | fputs(USAGE_HELP, stdout); | |
665 | fputs(USAGE_VERSION, stdout); | |
666 | printf(USAGE_MAN_TAIL(su_mode == SU_MODE ? "su(1)" : "runuser(1)")); | |
cf1a99da KZ |
667 | exit (status); |
668 | } | |
669 | ||
670 | static | |
671 | void load_config(void) | |
672 | { | |
7ec6adb1 KZ |
673 | switch (su_mode) { |
674 | case SU_MODE: | |
675 | logindefs_load_file(_PATH_LOGINDEFS_SU); | |
676 | break; | |
677 | case RUNUSER_MODE: | |
678 | logindefs_load_file(_PATH_LOGINDEFS_RUNUSER); | |
679 | break; | |
680 | } | |
681 | ||
cf1a99da KZ |
682 | logindefs_load_file(_PATH_LOGINDEFS); |
683 | } | |
684 | ||
685 | /* | |
686 | * Returns 1 if the current user is not root | |
687 | */ | |
688 | static int | |
689 | evaluate_uid(void) | |
690 | { | |
691 | uid_t ruid = getuid(); | |
692 | uid_t euid = geteuid(); | |
693 | ||
694 | /* if we're really root and aren't running setuid */ | |
695 | return (uid_t) 0 == ruid && ruid == euid ? 0 : 1; | |
696 | } | |
697 | ||
698 | int | |
699 | su_main (int argc, char **argv, int mode) | |
700 | { | |
701 | int optc; | |
7a7f9d38 | 702 | const char *new_user = DEFAULT_USER, *runuser_user = NULL; |
cf1a99da KZ |
703 | char *command = NULL; |
704 | int request_same_session = 0; | |
705 | char *shell = NULL; | |
706 | struct passwd *pw; | |
707 | struct passwd pw_copy; | |
708 | struct group *gr; | |
709 | gid_t groups[NGROUPS_MAX]; | |
710 | int num_supp_groups = 0; | |
711 | int use_gid = 0; | |
cf1a99da | 712 | |
bda67d64 KZ |
713 | static const struct option longopts[] = { |
714 | {"command", required_argument, NULL, 'c'}, | |
715 | {"session-command", required_argument, NULL, 'C'}, | |
716 | {"fast", no_argument, NULL, 'f'}, | |
717 | {"login", no_argument, NULL, 'l'}, | |
718 | {"preserve-environment", no_argument, NULL, 'p'}, | |
719 | {"shell", required_argument, NULL, 's'}, | |
720 | {"group", required_argument, NULL, 'g'}, | |
721 | {"supp-group", required_argument, NULL, 'G'}, | |
7a7f9d38 | 722 | {"user", required_argument, NULL, 'u'}, /* runuser only */ |
bda67d64 KZ |
723 | {"help", no_argument, 0, 'h'}, |
724 | {"version", no_argument, 0, 'V'}, | |
725 | {NULL, 0, NULL, 0} | |
726 | }; | |
727 | ||
cf1a99da KZ |
728 | setlocale (LC_ALL, ""); |
729 | bindtextdomain (PACKAGE, LOCALEDIR); | |
730 | textdomain (PACKAGE); | |
4e183497 | 731 | atexit(close_stdout); |
cf1a99da KZ |
732 | |
733 | su_mode = mode; | |
734 | fast_startup = false; | |
735 | simulate_login = false; | |
736 | change_environment = true; | |
737 | ||
7a7f9d38 | 738 | while ((optc = getopt_long (argc, argv, "+c:fg:G:lmps:u:hV", longopts, NULL)) != -1) |
cf1a99da KZ |
739 | { |
740 | switch (optc) | |
741 | { | |
742 | case 'c': | |
743 | command = optarg; | |
744 | break; | |
745 | ||
746 | case 'C': | |
747 | command = optarg; | |
748 | request_same_session = 1; | |
749 | break; | |
750 | ||
751 | case 'f': | |
752 | fast_startup = true; | |
753 | break; | |
754 | ||
755 | case 'g': | |
756 | gr = getgrnam(optarg); | |
757 | if (!gr) | |
758 | errx(EXIT_FAILURE, _("group %s does not exist"), optarg); | |
759 | use_gid = 1; | |
760 | groups[0] = gr->gr_gid; | |
761 | break; | |
762 | ||
763 | case 'G': | |
764 | num_supp_groups++; | |
765 | if (num_supp_groups >= NGROUPS_MAX) | |
766 | errx(EXIT_FAILURE, | |
767 | _("can't specify more than %d supplemental groups"), | |
768 | NGROUPS_MAX - 1); | |
769 | gr = getgrnam(optarg); | |
770 | if (!gr) | |
771 | errx(EXIT_FAILURE, _("group %s does not exist"), optarg); | |
772 | groups[num_supp_groups] = gr->gr_gid; | |
773 | break; | |
774 | ||
775 | case 'l': | |
776 | simulate_login = true; | |
777 | break; | |
778 | ||
779 | case 'm': | |
780 | case 'p': | |
781 | change_environment = false; | |
782 | break; | |
783 | ||
784 | case 's': | |
785 | shell = optarg; | |
786 | break; | |
787 | ||
7a7f9d38 KZ |
788 | case 'u': |
789 | if (su_mode != RUNUSER_MODE) | |
790 | usage (EXIT_FAILURE); | |
791 | runuser_user = optarg; | |
792 | break; | |
793 | ||
cf1a99da KZ |
794 | case 'h': |
795 | usage(0); | |
796 | ||
797 | case 'V': | |
798 | printf(UTIL_LINUX_VERSION); | |
799 | exit(EXIT_SUCCESS); | |
800 | ||
801 | default: | |
802 | usage (EXIT_FAILURE); | |
803 | } | |
804 | } | |
805 | ||
806 | restricted = evaluate_uid (); | |
807 | ||
808 | if (optind < argc && !strcmp (argv[optind], "-")) | |
809 | { | |
810 | simulate_login = true; | |
811 | ++optind; | |
812 | } | |
7a7f9d38 | 813 | |
360e0887 KZ |
814 | switch (su_mode) { |
815 | case RUNUSER_MODE: | |
816 | if (runuser_user) { | |
817 | /* runuser -u <user> <command> */ | |
818 | new_user = runuser_user; | |
819 | if (shell || fast_startup || command || simulate_login) { | |
820 | errx(EXIT_FAILURE, | |
7a7f9d38 KZ |
821 | _("options --{shell,fast,command,session-command,login} and " |
822 | "--user are mutually exclusive.")); | |
360e0887 KZ |
823 | } |
824 | if (optind == argc) | |
825 | errx(EXIT_FAILURE, _("COMMAND not specified.")); | |
826 | ||
827 | break; | |
7a7f9d38 | 828 | } |
360e0887 KZ |
829 | /* fallthrough if -u <user> is not specified, then follow |
830 | * traditional su(1) behavior | |
831 | */ | |
832 | case SU_MODE: | |
833 | if (optind < argc) | |
834 | new_user = argv[optind++]; | |
835 | break; | |
7a7f9d38 | 836 | } |
cf1a99da KZ |
837 | |
838 | if ((num_supp_groups || use_gid) && restricted) | |
839 | errx(EXIT_FAILURE, _("only root can specify alternative groups")); | |
840 | ||
841 | logindefs_load_defaults = load_config; | |
842 | ||
843 | pw = getpwnam (new_user); | |
844 | if (! (pw && pw->pw_name && pw->pw_name[0] && pw->pw_dir && pw->pw_dir[0] | |
845 | && pw->pw_passwd)) | |
846 | errx (EXIT_FAILURE, _("user %s does not exist"), new_user); | |
847 | ||
848 | /* Make a copy of the password information and point pw at the local | |
849 | copy instead. Otherwise, some systems (e.g. Linux) would clobber | |
850 | the static data through the getlogin call from log_su. | |
851 | Also, make sure pw->pw_shell is a nonempty string. | |
852 | It may be NULL when NEW_USER is a username that is retrieved via NIS (YP), | |
853 | but that doesn't have a default shell listed. */ | |
854 | pw_copy = *pw; | |
855 | pw = &pw_copy; | |
856 | pw->pw_name = xstrdup (pw->pw_name); | |
857 | pw->pw_passwd = xstrdup (pw->pw_passwd); | |
858 | pw->pw_dir = xstrdup (pw->pw_dir); | |
859 | pw->pw_shell = xstrdup (pw->pw_shell && pw->pw_shell[0] | |
860 | ? pw->pw_shell | |
861 | : DEFAULT_SHELL); | |
862 | endpwent (); | |
863 | ||
864 | if (num_supp_groups && !use_gid) | |
865 | { | |
866 | pw->pw_gid = groups[1]; | |
867 | memmove (groups, groups + 1, sizeof(gid_t) * num_supp_groups); | |
868 | } | |
869 | else if (use_gid) | |
870 | { | |
871 | pw->pw_gid = groups[0]; | |
872 | num_supp_groups++; | |
873 | } | |
874 | ||
875 | authenticate (pw); | |
876 | ||
877 | if (request_same_session || !command || !pw->pw_uid) | |
878 | same_session = 1; | |
879 | ||
7a7f9d38 KZ |
880 | /* initialize shell variable only if "-u <user>" not specified */ |
881 | if (runuser_user) { | |
882 | shell = NULL; | |
883 | } else { | |
884 | if (!shell && !change_environment) | |
885 | shell = getenv ("SHELL"); | |
886 | if (shell && getuid () != 0 && restricted_shell (pw->pw_shell)) | |
887 | { | |
888 | /* The user being su'd to has a nonstandard shell, and so is | |
889 | probably a uucp account or has restricted access. Don't | |
890 | compromise the account by allowing access with a standard | |
891 | shell. */ | |
892 | warnx (_("using restricted shell %s"), pw->pw_shell); | |
893 | shell = NULL; | |
894 | } | |
895 | shell = xstrdup (shell ? shell : pw->pw_shell); | |
896 | } | |
cf1a99da KZ |
897 | |
898 | init_groups (pw, groups, num_supp_groups); | |
899 | ||
900 | create_watching_parent (); | |
901 | /* Now we're in the child. */ | |
902 | ||
903 | change_identity (pw); | |
904 | if (!same_session) | |
905 | setsid (); | |
906 | ||
907 | /* Set environment after pam_open_session, which may put KRB5CCNAME | |
908 | into the pam_env, etc. */ | |
909 | ||
910 | modify_environment (pw, shell); | |
911 | ||
912 | if (simulate_login && chdir (pw->pw_dir) != 0) | |
913 | warn (_("warning: cannot change directory to %s"), pw->pw_dir); | |
914 | ||
7a7f9d38 KZ |
915 | if (shell) |
916 | run_shell (shell, command, argv + optind, max (0, argc - optind)); | |
917 | else { | |
918 | execvp(argv[optind], &argv[optind]); | |
07ff972e | 919 | err(EXIT_FAILURE, _("failed to execute %s"), argv[optind]); |
7a7f9d38 | 920 | } |
cf1a99da KZ |
921 | } |
922 | ||
923 | // vim: sw=2 cinoptions=>4,n-2,{2,^-2,\:2,=2,g0,h2,p5,t0,+2,(0,u0,w1,m1 |