2 * Copyright (c) 2002, 2005 Networks Associates Technologies, Inc.
5 * Portions of this software were developed for the FreeBSD Project by
6 * ThinkSec AS and NAI Labs, the Security Research Division of Network
7 * Associates, Inc. under DARPA/SPAWAR contract N66001-01-C-8035
8 * ("CBOSS"), as part of the DARPA CHATS research program.
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
19 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32 * Copyright (c) 1988, 1993, 1994
33 * The Regents of the University of California. All rights reserved.
35 * Redistribution and use in source and binary forms, with or without
36 * modification, are permitted provided that the following conditions
38 * 1. Redistributions of source code must retain the above copyright
39 * notice, this list of conditions and the following disclaimer.
40 * 2. Redistributions in binary form must reproduce the above copyright
41 * notice, this list of conditions and the following disclaimer in the
42 * documentation and/or other materials provided with the distribution.
43 * 3. Neither the name of the University nor the names of its contributors
44 * may be used to endorse or promote products derived from this software
45 * without specific prior written permission.
47 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
48 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
49 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
50 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
51 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
52 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
53 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
54 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
55 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
56 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
59 * @(#)su.c 8.3 (Berkeley) 4/2/94
60 * $FreeBSD: src/usr.bin/su/su.c,v 1.88 2008/06/04 19:16:54 dwmalone Exp $
61 * $DragonFly: src/usr.bin/su/su.c,v 1.9 2006/01/12 13:43:11 corecode Exp $
64 #include <sys/param.h>
66 #include <sys/resource.h>
70 #include <bsm/libbsm.h>
71 #include <bsm/audit_uevents.h>
77 #include <login_cap.h>
88 #include <security/pam_appl.h>
89 #include <security/openpam.h>
91 #define PAM_END() do { \
94 local_ret = pam_setcred(pamh, PAM_DELETE_CRED); \
95 if (local_ret != PAM_SUCCESS) \
96 syslog(LOG_ERR, "pam_setcred: %s", \
97 pam_strerror(pamh, local_ret)); \
99 local_ret = pam_close_session(pamh, 0); \
100 if (local_ret != PAM_SUCCESS) \
101 syslog(LOG_ERR, "pam_close_session: %s",\
102 pam_strerror(pamh, local_ret)); \
104 local_ret = pam_end(pamh, local_ret); \
105 if (local_ret != PAM_SUCCESS) \
106 syslog(LOG_ERR, "pam_end: %s", \
107 pam_strerror(pamh, local_ret)); \
112 #define PAM_SET_ITEM(what, item) do { \
114 local_ret = pam_set_item(pamh, what, item); \
115 if (local_ret != PAM_SUCCESS) { \
116 syslog(LOG_ERR, "pam_set_item(" #what "): %s", \
117 pam_strerror(pamh, local_ret)); \
118 errx(1, "pam_set_item(" #what "): %s", \
119 pam_strerror(pamh, local_ret)); \
124 enum tristate { UNSET, YES, NO };
126 static pam_handle_t *pamh = NULL;
127 static char **environ_pam;
129 static char *ontty(void);
130 static int chshell(const char *);
131 static void usage(void) __dead2;
132 static void export_pam_environment(void);
133 static int ok_to_export(const char *);
135 extern char **environ;
138 main(int argc, char **argv)
140 static char *cleanenv;
142 struct pam_conv conv = { openpam_ttyconv, NULL };
150 pid_t child_pid, child_pgrp, pid;
151 int asme, ch, asthem, fastlogin, prio, i, retcode,
154 char *username, *class, shellbuf[MAXPATHLEN];
155 const char *p = p, *user, *shell, *mytty, **nargv;
157 struct sigaction sa, sa_int, sa_quit, sa_pipe;
164 shell = class = cleanenv = NULL;
165 asme = asthem = fastlogin = statusp = 0;
169 while ((ch = getopt(argc, argv, "-flmc:")) != -1)
192 if (optind < argc && strcmp(argv[optind], "-") == 0) {
199 user = argv[optind++];
206 * Try to provide more helpful debugging output if su(1) is running
207 * non-setuid, or was run from a file system not mounted setuid.
210 errx(1, "not running setuid");
213 if (getauid(&auid) < 0 && errno != ENOSYS) {
214 syslog(LOG_AUTH | LOG_ERR, "getauid: %s", strerror(errno));
215 errx(1, "Permission denied");
218 if (strlen(user) > MAXLOGNAME - 1) {
220 if (audit_submit(AUE_su, auid,
221 1, EPERM, "username too long: '%s'", user))
222 errx(1, "Permission denied");
224 errx(1, "username too long");
227 nargv = malloc(sizeof(char *) * (size_t)(argc + 4));
229 errx(1, "malloc failure");
231 nargv[argc + 3] = NULL;
232 for (i = argc; i >= optind; i--)
233 nargv[i + 3] = argv[i];
234 np.a = &nargv[i + 3];
239 prio = getpriority(PRIO_PROCESS, 0);
243 setpriority(PRIO_PROCESS, 0, -2);
244 openlog("su", LOG_CONS, LOG_AUTH);
246 /* get current login name, real uid and shell */
248 username = getlogin();
249 pwd = getpwnam(username);
250 if (username == NULL || pwd == NULL || pwd->pw_uid != ruid)
251 pwd = getpwuid(ruid);
254 if (audit_submit(AUE_su, auid, 1, EPERM,
255 "unable to determine invoking subject: '%s'", username))
256 errx(1, "Permission denied");
258 errx(1, "who are you?");
261 username = strdup(pwd->pw_name);
262 if (username == NULL)
263 err(1, "strdup failure");
266 if (pwd->pw_shell != NULL && *pwd->pw_shell != '\0') {
267 /* must copy - pwd memory is recycled */
268 shell = strncpy(shellbuf, pwd->pw_shell,
270 shellbuf[sizeof(shellbuf) - 1] = '\0';
273 shell = _PATH_BSHELL;
278 /* Do the whole PAM startup thing */
279 retcode = pam_start("su", user, &conv, &pamh);
280 if (retcode != PAM_SUCCESS) {
281 syslog(LOG_ERR, "pam_start: %s", pam_strerror(pamh, retcode));
282 errx(1, "pam_start: %s", pam_strerror(pamh, retcode));
285 PAM_SET_ITEM(PAM_RUSER, username);
287 mytty = ttyname(STDERR_FILENO);
290 PAM_SET_ITEM(PAM_TTY, mytty);
292 retcode = pam_authenticate(pamh, 0);
293 if (retcode != PAM_SUCCESS) {
295 if (audit_submit(AUE_su, auid, 1, EPERM, "bad su %s to %s on %s",
296 username, user, mytty))
297 errx(1, "Permission denied");
299 syslog(LOG_AUTH|LOG_WARNING, "BAD SU %s to %s on %s",
300 username, user, mytty);
304 if (audit_submit(AUE_su, auid, 0, 0, "successful authentication"))
305 errx(1, "Permission denied");
307 retcode = pam_get_item(pamh, PAM_USER, &v);
308 if (retcode == PAM_SUCCESS)
311 syslog(LOG_ERR, "pam_get_item(PAM_USER): %s",
312 pam_strerror(pamh, retcode));
313 pwd = getpwnam(user);
316 if (audit_submit(AUE_su, auid, 1, EPERM,
317 "unknown subject: %s", user))
318 errx(1, "Permission denied");
320 errx(1, "unknown login: %s", user);
323 retcode = pam_acct_mgmt(pamh, 0);
324 if (retcode == PAM_NEW_AUTHTOK_REQD) {
325 retcode = pam_chauthtok(pamh,
326 PAM_CHANGE_EXPIRED_AUTHTOK);
327 if (retcode != PAM_SUCCESS) {
329 aerr = pam_strerror(pamh, retcode);
331 aerr = "Unknown PAM error";
332 if (audit_submit(AUE_su, auid, 1, EPERM,
333 "pam_chauthtok: %s", aerr))
334 errx(1, "Permission denied");
336 syslog(LOG_ERR, "pam_chauthtok: %s",
337 pam_strerror(pamh, retcode));
341 if (retcode != PAM_SUCCESS) {
343 if (audit_submit(AUE_su, auid, 1, EPERM, "pam_acct_mgmt: %s",
344 pam_strerror(pamh, retcode)))
345 errx(1, "Permission denied");
347 syslog(LOG_ERR, "pam_acct_mgmt: %s",
348 pam_strerror(pamh, retcode));
352 /* get target login information */
354 lc = login_getpwclass(pwd);
358 if (audit_submit(AUE_su, auid, 1, EPERM,
359 "only root may use -c"))
360 errx(1, "Permission denied");
362 errx(1, "only root may use -c");
364 lc = login_getclass(class);
366 errx(1, "unknown class: %s", class);
369 /* if asme and non-standard target shell, must be root */
371 if (ruid != 0 && !chshell(pwd->pw_shell))
372 errx(1, "permission denied (shell)");
374 else if (pwd->pw_shell && *pwd->pw_shell) {
375 shell = pwd->pw_shell;
379 shell = _PATH_BSHELL;
383 /* if we're forking a csh, we want to slightly muck the args */
384 if (iscsh == UNSET) {
385 p = strrchr(shell, '/');
390 iscsh = strcmp(p, "csh") ? (strcmp(p, "tcsh") ? NO : YES) : YES;
392 setpriority(PRIO_PROCESS, 0, prio);
395 * PAM modules might add supplementary groups in pam_setcred(), so
396 * initialize them first.
398 if (setusercontext(lc, pwd, pwd->pw_uid, LOGIN_SETGROUP) < 0)
399 err(1, "setusercontext");
401 retcode = pam_setcred(pamh, PAM_ESTABLISH_CRED);
402 if (retcode != PAM_SUCCESS) {
403 syslog(LOG_ERR, "pam_setcred: %s",
404 pam_strerror(pamh, retcode));
405 errx(1, "failed to establish credentials.");
408 retcode = pam_open_session(pamh, 0);
409 if (retcode != PAM_SUCCESS) {
410 syslog(LOG_ERR, "pam_open_session: %s",
411 pam_strerror(pamh, retcode));
412 errx(1, "failed to open session.");
417 * We must fork() before setuid() because we need to call
418 * pam_setcred(pamh, PAM_DELETE_CRED) as root.
420 sa.sa_flags = SA_RESTART;
421 sa.sa_handler = SIG_IGN;
422 sigemptyset(&sa.sa_mask);
423 sigaction(SIGINT, &sa, &sa_int);
424 sigaction(SIGQUIT, &sa, &sa_quit);
425 sigaction(SIGPIPE, &sa, &sa_pipe);
426 sa.sa_handler = SIG_DFL;
427 sigaction(SIGTSTP, &sa, NULL);
429 if (pipe(fds) == -1) {
436 sa.sa_handler = SIG_IGN;
437 sigaction(SIGTTOU, &sa, NULL);
439 setpgid(child_pid, child_pid);
440 if (tcgetpgrp(STDERR_FILENO) == getpgrp())
441 tcsetpgrp(STDERR_FILENO, child_pid);
443 sigaction(SIGPIPE, &sa_pipe, NULL);
444 while ((pid = waitpid(child_pid, &statusp, WUNTRACED)) != -1) {
445 if (WIFSTOPPED(statusp)) {
446 child_pgrp = getpgid(child_pid);
447 if (tcgetpgrp(STDERR_FILENO) == child_pgrp)
448 tcsetpgrp(STDERR_FILENO, getpgrp());
449 kill(getpid(), SIGSTOP);
450 if (tcgetpgrp(STDERR_FILENO) == getpgrp()) {
451 child_pgrp = getpgid(child_pid);
452 tcsetpgrp(STDERR_FILENO, child_pgrp);
454 kill(child_pid, SIGCONT);
460 tcsetpgrp(STDERR_FILENO, getpgrp());
464 exit(WEXITSTATUS(statusp));
470 read(fds[0], &temp, 1);
472 sigaction(SIGPIPE, &sa_pipe, NULL);
473 sigaction(SIGINT, &sa_int, NULL);
474 sigaction(SIGQUIT, &sa_quit, NULL);
477 * Set all user context except for: Environmental variables
478 * Umask Login records (wtmp, etc) Path
479 * XXX Missing LOGIN_SETMAC
481 setwhat = LOGIN_SETALL & ~(LOGIN_SETENV | LOGIN_SETUMASK |
482 LOGIN_SETLOGIN | LOGIN_SETPATH | LOGIN_SETGROUP);
485 * If -s is present, also set the MAC label.
488 setwhat |= LOGIN_SETMAC;
491 * Don't touch resource/priority settings if -m has been used
492 * or -l and -c hasn't, and we're not su'ing to root.
494 if ((asme || (!asthem && class == NULL)) && pwd->pw_uid)
495 setwhat &= ~(LOGIN_SETPRIORITY | LOGIN_SETRESOURCES);
496 if (setusercontext(lc, pwd, pwd->pw_uid, setwhat) < 0)
497 err(1, "setusercontext");
505 if (asthem || pwd->pw_uid) {
506 if (setenv("USER", pwd->pw_name, 1) == -1) {
507 err(1, "setenv: cannot set USER=%s",
511 if (setenv("HOME", pwd->pw_dir, 1) == -1) {
512 err(1, "setenv: cannot set HOME=%s",
515 if (setenv("SHELL", shell, 1) == -1)
516 err(1, "setenv: cannot set SHELL=%s", shell);
520 * Add any environmental variables that the
521 * PAM modules may have set.
523 environ_pam = pam_getenvlist(pamh);
525 export_pam_environment();
527 /* set the su'd user's environment & umask */
528 setusercontext(lc, pwd, pwd->pw_uid,
529 LOGIN_SETPATH | LOGIN_SETUMASK |
532 if (setenv("TERM", p, 1) == -1) {
534 "setenv: cannot set TERM=%s",
539 p = pam_getenv(pamh, "HOME");
540 if (chdir(p ? p : pwd->pw_dir) < 0)
541 errx(1, "no directory");
552 /* csh strips the first character... */
553 *np.a = asthem ? "-su" : iscsh == YES ? "_su" : "su";
556 syslog(LOG_NOTICE, "%s to %s%s", username, user,
565 export_pam_environment(void)
570 for (pp = environ_pam; *pp != NULL; pp++) {
571 if (ok_to_export(*pp)) {
572 p = strchr(*pp, '=');
574 if (setenv(*pp, p + 1, 1) == -1)
575 err(1, "setenv: cannot set %s=%s", *pp, p + 1);
582 * Sanity checks on PAM environmental variables:
583 * - Make sure there is an '=' in the string.
584 * - Make sure the string doesn't run on too long.
585 * - Do not export certain variables. This list was taken from the
586 * Solaris pam_putenv(3) man page.
587 * Note that if the user is chrooted, PAM may have a better idea than we
588 * do of where her home directory is.
591 ok_to_export(const char *s)
593 static const char *noexport[] = {
594 "SHELL", /* "HOME", */ "LOGNAME", "MAIL", "CDPATH",
600 if (strlen(s) > 1024 || strchr(s, '=') == NULL)
602 if (strncmp(s, "LD_", 3) == 0)
604 for (pp = noexport; *pp != NULL; pp++) {
606 if (s[n] == '=' && strncmp(s, *pp, n) == 0)
616 fprintf(stderr, "usage: su [-] [-flm] [-c class] [login [args]]\n");
622 chshell(const char *sh)
629 while ((cp = getusershell()) != NULL && !r)
630 r = (strcmp(cp, sh) == 0);
639 static char buf[MAXPATHLEN + 4];
642 p = ttyname(STDERR_FILENO);
644 snprintf(buf, sizeof(buf), " on %s", p);