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 $
63 #include <sys/param.h>
65 #include <sys/resource.h>
69 #include <bsm/libbsm.h>
70 #include <bsm/audit_uevents.h>
76 #include <login_cap.h>
87 #include <security/pam_appl.h>
88 #include <security/openpam.h>
90 #define PAM_END() do { \
93 local_ret = pam_setcred(pamh, PAM_DELETE_CRED); \
94 if (local_ret != PAM_SUCCESS) \
95 syslog(LOG_ERR, "pam_setcred: %s", \
96 pam_strerror(pamh, local_ret)); \
98 local_ret = pam_close_session(pamh, 0); \
99 if (local_ret != PAM_SUCCESS) \
100 syslog(LOG_ERR, "pam_close_session: %s",\
101 pam_strerror(pamh, local_ret)); \
103 local_ret = pam_end(pamh, local_ret); \
104 if (local_ret != PAM_SUCCESS) \
105 syslog(LOG_ERR, "pam_end: %s", \
106 pam_strerror(pamh, local_ret)); \
111 #define PAM_SET_ITEM(what, item) do { \
113 local_ret = pam_set_item(pamh, what, item); \
114 if (local_ret != PAM_SUCCESS) { \
115 syslog(LOG_ERR, "pam_set_item(" #what "): %s", \
116 pam_strerror(pamh, local_ret)); \
117 errx(1, "pam_set_item(" #what "): %s", \
118 pam_strerror(pamh, local_ret)); \
123 enum tristate
{ UNSET
, YES
, NO
};
125 static pam_handle_t
*pamh
= NULL
;
126 static char **environ_pam
;
128 static char *ontty(void);
129 static int chshell(const char *);
130 static void usage(void) __dead2
;
131 static void export_pam_environment(void);
132 static int ok_to_export(const char *);
134 extern char **environ
;
137 main(int argc
, char **argv
)
139 static char *cleanenv
;
141 struct pam_conv conv
= { openpam_ttyconv
, NULL
};
149 pid_t child_pid
, child_pgrp
, pid
;
150 int asme
, ch
, asthem
, fastlogin
, prio
, i
, retcode
,
153 char *username
, *class, shellbuf
[MAXPATHLEN
];
154 const char *p
= NULL
, *user
, *shell
, *mytty
, **nargv
;
156 struct sigaction sa
, sa_int
, sa_quit
, sa_pipe
;
163 shell
= class = cleanenv
= NULL
;
164 asme
= asthem
= fastlogin
= statusp
= 0;
168 while ((ch
= getopt(argc
, argv
, "-flmc:")) != -1)
191 if (optind
< argc
&& strcmp(argv
[optind
], "-") == 0) {
198 user
= argv
[optind
++];
205 * Try to provide more helpful debugging output if su(1) is running
206 * non-setuid, or was run from a file system not mounted setuid.
209 errx(1, "not running setuid");
212 if (getauid(&auid
) < 0 && errno
!= ENOSYS
) {
213 syslog(LOG_AUTH
| LOG_ERR
, "getauid: %s", strerror(errno
));
214 errx(1, "Permission denied");
217 if (strlen(user
) > MAXLOGNAME
- 1) {
219 if (audit_submit(AUE_su
, auid
,
220 1, EPERM
, "username too long: '%s'", user
))
221 errx(1, "Permission denied");
223 errx(1, "username too long");
226 nargv
= malloc(sizeof(char *) * (size_t)(argc
+ 4));
228 errx(1, "malloc failure");
230 nargv
[argc
+ 3] = NULL
;
231 for (i
= argc
; i
>= optind
; i
--)
232 nargv
[i
+ 3] = argv
[i
];
233 np
.a
= &nargv
[i
+ 3];
238 prio
= getpriority(PRIO_PROCESS
, 0);
242 setpriority(PRIO_PROCESS
, 0, -2);
243 openlog("su", LOG_CONS
, LOG_AUTH
);
245 /* get current login name, real uid and shell */
247 username
= getlogin();
248 pwd
= getpwnam(username
);
249 if (username
== NULL
|| pwd
== NULL
|| pwd
->pw_uid
!= ruid
)
250 pwd
= getpwuid(ruid
);
253 if (audit_submit(AUE_su
, auid
, 1, EPERM
,
254 "unable to determine invoking subject: '%s'", username
))
255 errx(1, "Permission denied");
257 errx(1, "who are you?");
260 username
= strdup(pwd
->pw_name
);
261 if (username
== NULL
)
262 err(1, "strdup failure");
265 if (pwd
->pw_shell
!= NULL
&& *pwd
->pw_shell
!= '\0') {
266 /* must copy - pwd memory is recycled */
267 shell
= strncpy(shellbuf
, pwd
->pw_shell
,
269 shellbuf
[sizeof(shellbuf
) - 1] = '\0';
272 shell
= _PATH_BSHELL
;
277 /* Do the whole PAM startup thing */
278 retcode
= pam_start("su", user
, &conv
, &pamh
);
279 if (retcode
!= PAM_SUCCESS
) {
280 syslog(LOG_ERR
, "pam_start: %s", pam_strerror(pamh
, retcode
));
281 errx(1, "pam_start: %s", pam_strerror(pamh
, retcode
));
284 PAM_SET_ITEM(PAM_RUSER
, username
);
286 mytty
= ttyname(STDERR_FILENO
);
289 PAM_SET_ITEM(PAM_TTY
, mytty
);
291 retcode
= pam_authenticate(pamh
, 0);
292 if (retcode
!= PAM_SUCCESS
) {
294 if (audit_submit(AUE_su
, auid
, 1, EPERM
, "bad su %s to %s on %s",
295 username
, user
, mytty
))
296 errx(1, "Permission denied");
298 syslog(LOG_AUTH
|LOG_WARNING
, "BAD SU %s to %s on %s",
299 username
, user
, mytty
);
303 if (audit_submit(AUE_su
, auid
, 0, 0, "successful authentication"))
304 errx(1, "Permission denied");
306 retcode
= pam_get_item(pamh
, PAM_USER
, &v
);
307 if (retcode
== PAM_SUCCESS
)
310 syslog(LOG_ERR
, "pam_get_item(PAM_USER): %s",
311 pam_strerror(pamh
, retcode
));
312 pwd
= getpwnam(user
);
315 if (audit_submit(AUE_su
, auid
, 1, EPERM
,
316 "unknown subject: %s", user
))
317 errx(1, "Permission denied");
319 errx(1, "unknown login: %s", user
);
322 retcode
= pam_acct_mgmt(pamh
, 0);
323 if (retcode
== PAM_NEW_AUTHTOK_REQD
) {
324 retcode
= pam_chauthtok(pamh
,
325 PAM_CHANGE_EXPIRED_AUTHTOK
);
326 if (retcode
!= PAM_SUCCESS
) {
328 aerr
= pam_strerror(pamh
, retcode
);
330 aerr
= "Unknown PAM error";
331 if (audit_submit(AUE_su
, auid
, 1, EPERM
,
332 "pam_chauthtok: %s", aerr
))
333 errx(1, "Permission denied");
335 syslog(LOG_ERR
, "pam_chauthtok: %s",
336 pam_strerror(pamh
, retcode
));
340 if (retcode
!= PAM_SUCCESS
) {
342 if (audit_submit(AUE_su
, auid
, 1, EPERM
, "pam_acct_mgmt: %s",
343 pam_strerror(pamh
, retcode
)))
344 errx(1, "Permission denied");
346 syslog(LOG_ERR
, "pam_acct_mgmt: %s",
347 pam_strerror(pamh
, retcode
));
351 /* get target login information */
353 lc
= login_getpwclass(pwd
);
357 if (audit_submit(AUE_su
, auid
, 1, EPERM
,
358 "only root may use -c"))
359 errx(1, "Permission denied");
361 errx(1, "only root may use -c");
363 lc
= login_getclass(class);
365 errx(1, "unknown class: %s", class);
368 /* if asme and non-standard target shell, must be root */
370 if (ruid
!= 0 && !chshell(pwd
->pw_shell
))
371 errx(1, "permission denied (shell)");
373 else if (pwd
->pw_shell
&& *pwd
->pw_shell
) {
374 shell
= pwd
->pw_shell
;
378 shell
= _PATH_BSHELL
;
382 /* if we're forking a csh, we want to slightly muck the args */
383 if (iscsh
== UNSET
) {
384 p
= strrchr(shell
, '/');
389 iscsh
= strcmp(p
, "csh") ? (strcmp(p
, "tcsh") ? NO
: YES
) : YES
;
391 setpriority(PRIO_PROCESS
, 0, prio
);
394 * PAM modules might add supplementary groups in pam_setcred(), so
395 * initialize them first.
397 if (setusercontext(lc
, pwd
, pwd
->pw_uid
, LOGIN_SETGROUP
) < 0)
398 err(1, "setusercontext");
400 retcode
= pam_setcred(pamh
, PAM_ESTABLISH_CRED
);
401 if (retcode
!= PAM_SUCCESS
) {
402 syslog(LOG_ERR
, "pam_setcred: %s",
403 pam_strerror(pamh
, retcode
));
404 errx(1, "failed to establish credentials.");
407 retcode
= pam_open_session(pamh
, 0);
408 if (retcode
!= PAM_SUCCESS
) {
409 syslog(LOG_ERR
, "pam_open_session: %s",
410 pam_strerror(pamh
, retcode
));
411 errx(1, "failed to open session.");
416 * We must fork() before setuid() because we need to call
417 * pam_setcred(pamh, PAM_DELETE_CRED) as root.
419 sa
.sa_flags
= SA_RESTART
;
420 sa
.sa_handler
= SIG_IGN
;
421 sigemptyset(&sa
.sa_mask
);
422 sigaction(SIGINT
, &sa
, &sa_int
);
423 sigaction(SIGQUIT
, &sa
, &sa_quit
);
424 sigaction(SIGPIPE
, &sa
, &sa_pipe
);
425 sa
.sa_handler
= SIG_DFL
;
426 sigaction(SIGTSTP
, &sa
, NULL
);
428 if (pipe(fds
) == -1) {
435 sa
.sa_handler
= SIG_IGN
;
436 sigaction(SIGTTOU
, &sa
, NULL
);
438 setpgid(child_pid
, child_pid
);
439 if (tcgetpgrp(STDERR_FILENO
) == getpgrp())
440 tcsetpgrp(STDERR_FILENO
, child_pid
);
442 sigaction(SIGPIPE
, &sa_pipe
, NULL
);
443 while ((pid
= waitpid(child_pid
, &statusp
, WUNTRACED
)) != -1) {
444 if (WIFSTOPPED(statusp
)) {
445 child_pgrp
= getpgid(child_pid
);
446 if (tcgetpgrp(STDERR_FILENO
) == child_pgrp
)
447 tcsetpgrp(STDERR_FILENO
, getpgrp());
448 kill(getpid(), SIGSTOP
);
449 if (tcgetpgrp(STDERR_FILENO
) == getpgrp()) {
450 child_pgrp
= getpgid(child_pid
);
451 tcsetpgrp(STDERR_FILENO
, child_pgrp
);
453 kill(child_pid
, SIGCONT
);
459 tcsetpgrp(STDERR_FILENO
, getpgrp());
463 exit(WEXITSTATUS(statusp
));
469 read(fds
[0], &temp
, 1);
471 sigaction(SIGPIPE
, &sa_pipe
, NULL
);
472 sigaction(SIGINT
, &sa_int
, NULL
);
473 sigaction(SIGQUIT
, &sa_quit
, NULL
);
476 * Set all user context except for: Environmental variables
477 * Umask Login records (wtmp, etc) Path
478 * XXX Missing LOGIN_SETMAC
480 setwhat
= LOGIN_SETALL
& ~(LOGIN_SETENV
| LOGIN_SETUMASK
|
481 LOGIN_SETLOGIN
| LOGIN_SETPATH
| LOGIN_SETGROUP
);
484 * If -s is present, also set the MAC label.
487 setwhat
|= LOGIN_SETMAC
;
490 * Don't touch resource/priority settings if -m has been used
491 * or -l and -c hasn't, and we're not su'ing to root.
493 if ((asme
|| (!asthem
&& class == NULL
)) && pwd
->pw_uid
)
494 setwhat
&= ~(LOGIN_SETPRIORITY
| LOGIN_SETRESOURCES
);
495 if (setusercontext(lc
, pwd
, pwd
->pw_uid
, setwhat
) < 0)
496 err(1, "setusercontext");
504 if (asthem
|| pwd
->pw_uid
) {
505 if (setenv("USER", pwd
->pw_name
, 1) == -1) {
506 err(1, "setenv: cannot set USER=%s",
510 if (setenv("HOME", pwd
->pw_dir
, 1) == -1) {
511 err(1, "setenv: cannot set HOME=%s",
514 if (setenv("SHELL", shell
, 1) == -1)
515 err(1, "setenv: cannot set SHELL=%s", shell
);
519 * Add any environmental variables that the
520 * PAM modules may have set.
522 environ_pam
= pam_getenvlist(pamh
);
524 export_pam_environment();
526 /* set the su'd user's environment & umask */
527 setusercontext(lc
, pwd
, pwd
->pw_uid
,
528 LOGIN_SETPATH
| LOGIN_SETUMASK
|
531 if (setenv("TERM", p
, 1) == -1) {
533 "setenv: cannot set TERM=%s",
538 p
= pam_getenv(pamh
, "HOME");
539 if (chdir(p
? p
: pwd
->pw_dir
) < 0)
540 errx(1, "no directory");
551 /* csh strips the first character... */
552 *np
.a
= asthem
? "-su" : iscsh
== YES
? "_su" : "su";
555 syslog(LOG_NOTICE
, "%s to %s%s", username
, user
,
564 export_pam_environment(void)
569 for (pp
= environ_pam
; *pp
!= NULL
; pp
++) {
570 if (ok_to_export(*pp
)) {
571 p
= strchr(*pp
, '=');
573 if (setenv(*pp
, p
+ 1, 1) == -1)
574 err(1, "setenv: cannot set %s=%s", *pp
, p
+ 1);
581 * Sanity checks on PAM environmental variables:
582 * - Make sure there is an '=' in the string.
583 * - Make sure the string doesn't run on too long.
584 * - Do not export certain variables. This list was taken from the
585 * Solaris pam_putenv(3) man page.
586 * Note that if the user is chrooted, PAM may have a better idea than we
587 * do of where her home directory is.
590 ok_to_export(const char *s
)
592 static const char *noexport
[] = {
593 "SHELL", /* "HOME", */ "LOGNAME", "MAIL", "CDPATH",
599 if (strlen(s
) > 1024 || strchr(s
, '=') == NULL
)
601 if (strncmp(s
, "LD_", 3) == 0)
603 for (pp
= noexport
; *pp
!= NULL
; pp
++) {
605 if (s
[n
] == '=' && strncmp(s
, *pp
, n
) == 0)
615 fprintf(stderr
, "usage: su [-] [-flm] [-c class] [login [args]]\n");
621 chshell(const char *sh
)
628 while ((cp
= getusershell()) != NULL
&& !r
)
629 r
= (strcmp(cp
, sh
) == 0);
638 static char buf
[MAXPATHLEN
+ 4];
641 p
= ttyname(STDERR_FILENO
);
643 snprintf(buf
, sizeof(buf
), " on %s", p
);