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. All advertising materials mentioning features or use of this software
44 * must display the following acknowledgement:
45 * This product includes software developed by the University of
46 * California, Berkeley and its contributors.
47 * 4. Neither the name of the University nor the names of its contributors
48 * may be used to endorse or promote products derived from this software
49 * without specific prior written permission.
51 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
52 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
53 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
54 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
55 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
56 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
57 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
58 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
59 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
60 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
63 * @(#)su.c 8.3 (Berkeley) 4/2/94
64 * $FreeBSD: src/usr.bin/su/su.c,v 1.88 2008/06/04 19:16:54 dwmalone Exp $
65 * $DragonFly: src/usr.bin/su/su.c,v 1.9 2006/01/12 13:43:11 corecode Exp $
68 #include <sys/param.h>
70 #include <sys/resource.h>
74 #include <bsm/libbsm.h>
75 #include <bsm/audit_uevents.h>
81 #include <login_cap.h>
92 #include <security/pam_appl.h>
93 #include <security/openpam.h>
95 #define PAM_END() do { \
98 local_ret = pam_setcred(pamh, PAM_DELETE_CRED); \
99 if (local_ret != PAM_SUCCESS) \
100 syslog(LOG_ERR, "pam_setcred: %s", \
101 pam_strerror(pamh, local_ret)); \
103 local_ret = pam_close_session(pamh, 0); \
104 if (local_ret != PAM_SUCCESS) \
105 syslog(LOG_ERR, "pam_close_session: %s",\
106 pam_strerror(pamh, local_ret)); \
108 local_ret = pam_end(pamh, local_ret); \
109 if (local_ret != PAM_SUCCESS) \
110 syslog(LOG_ERR, "pam_end: %s", \
111 pam_strerror(pamh, local_ret)); \
116 #define PAM_SET_ITEM(what, item) do { \
118 local_ret = pam_set_item(pamh, what, item); \
119 if (local_ret != PAM_SUCCESS) { \
120 syslog(LOG_ERR, "pam_set_item(" #what "): %s", \
121 pam_strerror(pamh, local_ret)); \
122 errx(1, "pam_set_item(" #what "): %s", \
123 pam_strerror(pamh, local_ret)); \
128 enum tristate
{ UNSET
, YES
, NO
};
130 static pam_handle_t
*pamh
= NULL
;
131 static char **environ_pam
;
133 static char *ontty(void);
134 static int chshell(const char *);
135 static void usage(void) __dead2
;
136 static void export_pam_environment(void);
137 static int ok_to_export(const char *);
139 extern char **environ
;
142 main(int argc
, char **argv
)
144 static char *cleanenv
;
146 struct pam_conv conv
= { openpam_ttyconv
, NULL
};
154 pid_t child_pid
, child_pgrp
, pid
;
155 int asme
, ch
, asthem
, fastlogin
, prio
, i
, retcode
,
158 char *username
, *class, shellbuf
[MAXPATHLEN
];
159 const char *p
= p
, *user
, *shell
, *mytty
, **nargv
;
161 struct sigaction sa
, sa_int
, sa_quit
, sa_pipe
;
168 shell
= class = cleanenv
= NULL
;
169 asme
= asthem
= fastlogin
= statusp
= 0;
173 while ((ch
= getopt(argc
, argv
, "-flmc:")) != -1)
196 if (optind
< argc
&& strcmp(argv
[optind
], "-") == 0) {
203 user
= argv
[optind
++];
210 * Try to provide more helpful debugging output if su(1) is running
211 * non-setuid, or was run from a file system not mounted setuid.
214 errx(1, "not running setuid");
217 if (getauid(&auid
) < 0 && errno
!= ENOSYS
) {
218 syslog(LOG_AUTH
| LOG_ERR
, "getauid: %s", strerror(errno
));
219 errx(1, "Permission denied");
222 if (strlen(user
) > MAXLOGNAME
- 1) {
224 if (audit_submit(AUE_su
, auid
,
225 1, EPERM
, "username too long: '%s'", user
))
226 errx(1, "Permission denied");
228 errx(1, "username too long");
231 nargv
= malloc(sizeof(char *) * (size_t)(argc
+ 4));
233 errx(1, "malloc failure");
235 nargv
[argc
+ 3] = NULL
;
236 for (i
= argc
; i
>= optind
; i
--)
237 nargv
[i
+ 3] = argv
[i
];
238 np
.a
= &nargv
[i
+ 3];
243 prio
= getpriority(PRIO_PROCESS
, 0);
247 setpriority(PRIO_PROCESS
, 0, -2);
248 openlog("su", LOG_CONS
, LOG_AUTH
);
250 /* get current login name, real uid and shell */
252 username
= getlogin();
253 pwd
= getpwnam(username
);
254 if (username
== NULL
|| pwd
== NULL
|| pwd
->pw_uid
!= ruid
)
255 pwd
= getpwuid(ruid
);
258 if (audit_submit(AUE_su
, auid
, 1, EPERM
,
259 "unable to determine invoking subject: '%s'", username
))
260 errx(1, "Permission denied");
262 errx(1, "who are you?");
265 username
= strdup(pwd
->pw_name
);
266 if (username
== NULL
)
267 err(1, "strdup failure");
270 if (pwd
->pw_shell
!= NULL
&& *pwd
->pw_shell
!= '\0') {
271 /* must copy - pwd memory is recycled */
272 shell
= strncpy(shellbuf
, pwd
->pw_shell
,
274 shellbuf
[sizeof(shellbuf
) - 1] = '\0';
277 shell
= _PATH_BSHELL
;
282 /* Do the whole PAM startup thing */
283 retcode
= pam_start("su", user
, &conv
, &pamh
);
284 if (retcode
!= PAM_SUCCESS
) {
285 syslog(LOG_ERR
, "pam_start: %s", pam_strerror(pamh
, retcode
));
286 errx(1, "pam_start: %s", pam_strerror(pamh
, retcode
));
289 PAM_SET_ITEM(PAM_RUSER
, username
);
291 mytty
= ttyname(STDERR_FILENO
);
294 PAM_SET_ITEM(PAM_TTY
, mytty
);
296 retcode
= pam_authenticate(pamh
, 0);
297 if (retcode
!= PAM_SUCCESS
) {
299 if (audit_submit(AUE_su
, auid
, 1, EPERM
, "bad su %s to %s on %s",
300 username
, user
, mytty
))
301 errx(1, "Permission denied");
303 syslog(LOG_AUTH
|LOG_WARNING
, "BAD SU %s to %s on %s",
304 username
, user
, mytty
);
308 if (audit_submit(AUE_su
, auid
, 0, 0, "successful authentication"))
309 errx(1, "Permission denied");
311 retcode
= pam_get_item(pamh
, PAM_USER
, &v
);
312 if (retcode
== PAM_SUCCESS
)
315 syslog(LOG_ERR
, "pam_get_item(PAM_USER): %s",
316 pam_strerror(pamh
, retcode
));
317 pwd
= getpwnam(user
);
320 if (audit_submit(AUE_su
, auid
, 1, EPERM
,
321 "unknown subject: %s", user
))
322 errx(1, "Permission denied");
324 errx(1, "unknown login: %s", user
);
327 retcode
= pam_acct_mgmt(pamh
, 0);
328 if (retcode
== PAM_NEW_AUTHTOK_REQD
) {
329 retcode
= pam_chauthtok(pamh
,
330 PAM_CHANGE_EXPIRED_AUTHTOK
);
331 if (retcode
!= PAM_SUCCESS
) {
333 aerr
= pam_strerror(pamh
, retcode
);
335 aerr
= "Unknown PAM error";
336 if (audit_submit(AUE_su
, auid
, 1, EPERM
,
337 "pam_chauthtok: %s", aerr
))
338 errx(1, "Permission denied");
340 syslog(LOG_ERR
, "pam_chauthtok: %s",
341 pam_strerror(pamh
, retcode
));
345 if (retcode
!= PAM_SUCCESS
) {
347 if (audit_submit(AUE_su
, auid
, 1, EPERM
, "pam_acct_mgmt: %s",
348 pam_strerror(pamh
, retcode
)))
349 errx(1, "Permission denied");
351 syslog(LOG_ERR
, "pam_acct_mgmt: %s",
352 pam_strerror(pamh
, retcode
));
356 /* get target login information */
358 lc
= login_getpwclass(pwd
);
362 if (audit_submit(AUE_su
, auid
, 1, EPERM
,
363 "only root may use -c"))
364 errx(1, "Permission denied");
366 errx(1, "only root may use -c");
368 lc
= login_getclass(class);
370 errx(1, "unknown class: %s", class);
373 /* if asme and non-standard target shell, must be root */
375 if (ruid
!= 0 && !chshell(pwd
->pw_shell
))
376 errx(1, "permission denied (shell)");
378 else if (pwd
->pw_shell
&& *pwd
->pw_shell
) {
379 shell
= pwd
->pw_shell
;
383 shell
= _PATH_BSHELL
;
387 /* if we're forking a csh, we want to slightly muck the args */
388 if (iscsh
== UNSET
) {
389 p
= strrchr(shell
, '/');
394 iscsh
= strcmp(p
, "csh") ? (strcmp(p
, "tcsh") ? NO
: YES
) : YES
;
396 setpriority(PRIO_PROCESS
, 0, prio
);
399 * PAM modules might add supplementary groups in pam_setcred(), so
400 * initialize them first.
402 if (setusercontext(lc
, pwd
, pwd
->pw_uid
, LOGIN_SETGROUP
) < 0)
403 err(1, "setusercontext");
405 retcode
= pam_setcred(pamh
, PAM_ESTABLISH_CRED
);
406 if (retcode
!= PAM_SUCCESS
) {
407 syslog(LOG_ERR
, "pam_setcred: %s",
408 pam_strerror(pamh
, retcode
));
409 errx(1, "failed to establish credentials.");
412 retcode
= pam_open_session(pamh
, 0);
413 if (retcode
!= PAM_SUCCESS
) {
414 syslog(LOG_ERR
, "pam_open_session: %s",
415 pam_strerror(pamh
, retcode
));
416 errx(1, "failed to open session.");
421 * We must fork() before setuid() because we need to call
422 * pam_setcred(pamh, PAM_DELETE_CRED) as root.
424 sa
.sa_flags
= SA_RESTART
;
425 sa
.sa_handler
= SIG_IGN
;
426 sigemptyset(&sa
.sa_mask
);
427 sigaction(SIGINT
, &sa
, &sa_int
);
428 sigaction(SIGQUIT
, &sa
, &sa_quit
);
429 sigaction(SIGPIPE
, &sa
, &sa_pipe
);
430 sa
.sa_handler
= SIG_DFL
;
431 sigaction(SIGTSTP
, &sa
, NULL
);
433 if (pipe(fds
) == -1) {
440 sa
.sa_handler
= SIG_IGN
;
441 sigaction(SIGTTOU
, &sa
, NULL
);
443 setpgid(child_pid
, child_pid
);
444 if (tcgetpgrp(STDERR_FILENO
) == getpgrp())
445 tcsetpgrp(STDERR_FILENO
, child_pid
);
447 sigaction(SIGPIPE
, &sa_pipe
, NULL
);
448 while ((pid
= waitpid(child_pid
, &statusp
, WUNTRACED
)) != -1) {
449 if (WIFSTOPPED(statusp
)) {
450 child_pgrp
= getpgid(child_pid
);
451 if (tcgetpgrp(STDERR_FILENO
) == child_pgrp
)
452 tcsetpgrp(STDERR_FILENO
, getpgrp());
453 kill(getpid(), SIGSTOP
);
454 if (tcgetpgrp(STDERR_FILENO
) == getpgrp()) {
455 child_pgrp
= getpgid(child_pid
);
456 tcsetpgrp(STDERR_FILENO
, child_pgrp
);
458 kill(child_pid
, SIGCONT
);
464 tcsetpgrp(STDERR_FILENO
, getpgrp());
468 exit(WEXITSTATUS(statusp
));
474 read(fds
[0], &temp
, 1);
476 sigaction(SIGPIPE
, &sa_pipe
, NULL
);
477 sigaction(SIGINT
, &sa_int
, NULL
);
478 sigaction(SIGQUIT
, &sa_quit
, NULL
);
481 * Set all user context except for: Environmental variables
482 * Umask Login records (wtmp, etc) Path
483 * XXX Missing LOGIN_SETMAC
485 setwhat
= LOGIN_SETALL
& ~(LOGIN_SETENV
| LOGIN_SETUMASK
|
486 LOGIN_SETLOGIN
| LOGIN_SETPATH
| LOGIN_SETGROUP
);
489 * If -s is present, also set the MAC label.
492 setwhat
|= LOGIN_SETMAC
;
495 * Don't touch resource/priority settings if -m has been used
496 * or -l and -c hasn't, and we're not su'ing to root.
498 if ((asme
|| (!asthem
&& class == NULL
)) && pwd
->pw_uid
)
499 setwhat
&= ~(LOGIN_SETPRIORITY
| LOGIN_SETRESOURCES
);
500 if (setusercontext(lc
, pwd
, pwd
->pw_uid
, setwhat
) < 0)
501 err(1, "setusercontext");
509 if (asthem
|| pwd
->pw_uid
) {
510 if (setenv("USER", pwd
->pw_name
, 1) == -1) {
511 err(1, "setenv: cannot set USER=%s",
515 if (setenv("HOME", pwd
->pw_dir
, 1) == -1) {
516 err(1, "setenv: cannot set HOME=%s",
519 if (setenv("SHELL", shell
, 1) == -1)
520 err(1, "setenv: cannot set SHELL=%s", shell
);
524 * Add any environmental variables that the
525 * PAM modules may have set.
527 environ_pam
= pam_getenvlist(pamh
);
529 export_pam_environment();
531 /* set the su'd user's environment & umask */
532 setusercontext(lc
, pwd
, pwd
->pw_uid
,
533 LOGIN_SETPATH
| LOGIN_SETUMASK
|
536 if (setenv("TERM", p
, 1) == -1) {
538 "setenv: cannot set TERM=%s",
543 p
= pam_getenv(pamh
, "HOME");
544 if (chdir(p
? p
: pwd
->pw_dir
) < 0)
545 errx(1, "no directory");
556 /* csh strips the first character... */
557 *np
.a
= asthem
? "-su" : iscsh
== YES
? "_su" : "su";
560 syslog(LOG_NOTICE
, "%s to %s%s", username
, user
,
569 export_pam_environment(void)
574 for (pp
= environ_pam
; *pp
!= NULL
; pp
++) {
575 if (ok_to_export(*pp
)) {
576 p
= strchr(*pp
, '=');
578 if (setenv(*pp
, p
+ 1, 1) == -1)
579 err(1, "setenv: cannot set %s=%s", *pp
, p
+ 1);
586 * Sanity checks on PAM environmental variables:
587 * - Make sure there is an '=' in the string.
588 * - Make sure the string doesn't run on too long.
589 * - Do not export certain variables. This list was taken from the
590 * Solaris pam_putenv(3) man page.
591 * Note that if the user is chrooted, PAM may have a better idea than we
592 * do of where her home directory is.
595 ok_to_export(const char *s
)
597 static const char *noexport
[] = {
598 "SHELL", /* "HOME", */ "LOGNAME", "MAIL", "CDPATH",
604 if (strlen(s
) > 1024 || strchr(s
, '=') == NULL
)
606 if (strncmp(s
, "LD_", 3) == 0)
608 for (pp
= noexport
; *pp
!= NULL
; pp
++) {
610 if (s
[n
] == '=' && strncmp(s
, *pp
, n
) == 0)
620 fprintf(stderr
, "usage: su [-] [-flm] [-c class] [login [args]]\n");
626 chshell(const char *sh
)
633 while ((cp
= getusershell()) != NULL
&& !r
)
634 r
= (strcmp(cp
, sh
) == 0);
643 static char buf
[MAXPATHLEN
+ 4];
646 p
= ttyname(STDERR_FILENO
);
648 snprintf(buf
, sizeof(buf
), " on %s", p
);