Correct param.h entry for this version.
[dragonfly.git] / usr.bin / su / su.c
blobc45795f02254eda132c11acb299766a991170340
1 /*
2 * Copyright (c) 2002, 2005 Networks Associates Technologies, Inc.
3 * All rights reserved.
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
12 * are met:
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
29 * SUCH DAMAGE.
31 /*-
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
37 * are met:
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
57 * SUCH DAMAGE.
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>
64 #include <sys/time.h>
65 #include <sys/resource.h>
66 #include <sys/wait.h>
68 #ifdef USE_BSM_AUDIT
69 #include <bsm/libbsm.h>
70 #include <bsm/audit_uevents.h>
71 #endif
73 #include <err.h>
74 #include <errno.h>
75 #include <grp.h>
76 #include <login_cap.h>
77 #include <paths.h>
78 #include <pwd.h>
79 #include <signal.h>
80 #include <stdio.h>
81 #include <stdlib.h>
82 #include <string.h>
83 #include <syslog.h>
84 #include <unistd.h>
85 #include <stdarg.h>
87 #include <security/pam_appl.h>
88 #include <security/openpam.h>
90 #define PAM_END() do { \
91 int local_ret; \
92 if (pamh != NULL) { \
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)); \
97 if (asthem) { \
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)); \
108 } while (0)
111 #define PAM_SET_ITEM(what, item) do { \
112 int local_ret; \
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)); \
119 /* NOTREACHED */ \
121 } while (0)
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;
140 struct passwd *pwd;
141 struct pam_conv conv = { openpam_ttyconv, NULL };
142 enum tristate iscsh;
143 login_cap_t *lc;
144 union {
145 const char **a;
146 char * const *b;
147 } np;
148 uid_t ruid;
149 pid_t child_pid, child_pgrp, pid;
150 int asme, ch, asthem, fastlogin, prio, i, retcode,
151 statusp;
152 u_int setwhat;
153 char *username, *class, shellbuf[MAXPATHLEN];
154 const char *p = NULL, *user, *shell, *mytty, **nargv;
155 const void *v;
156 struct sigaction sa, sa_int, sa_quit, sa_pipe;
157 int temp, fds[2];
158 #ifdef USE_BSM_AUDIT
159 const char *aerr;
160 au_id_t auid;
161 #endif
163 shell = class = cleanenv = NULL;
164 asme = asthem = fastlogin = statusp = 0;
165 user = "root";
166 iscsh = UNSET;
168 while ((ch = getopt(argc, argv, "-flmc:")) != -1)
169 switch ((char)ch) {
170 case 'f':
171 fastlogin = 1;
172 break;
173 case '-':
174 case 'l':
175 asme = 0;
176 asthem = 1;
177 break;
178 case 'm':
179 asme = 1;
180 asthem = 0;
181 break;
182 case 'c':
183 class = optarg;
184 break;
185 case '?':
186 default:
187 usage();
188 /* NOTREACHED */
191 if (optind < argc && strcmp(argv[optind], "-") == 0) {
192 asme = 0;
193 asthem = 1;
194 optind++;
197 if (optind < argc)
198 user = argv[optind++];
200 if (user == NULL)
201 usage();
202 /* NOTREACHED */
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.
208 if (geteuid() != 0)
209 errx(1, "not running setuid");
211 #ifdef USE_BSM_AUDIT
212 if (getauid(&auid) < 0 && errno != ENOSYS) {
213 syslog(LOG_AUTH | LOG_ERR, "getauid: %s", strerror(errno));
214 errx(1, "Permission denied");
216 #endif
217 if (strlen(user) > MAXLOGNAME - 1) {
218 #ifdef USE_BSM_AUDIT
219 if (audit_submit(AUE_su, auid,
220 1, EPERM, "username too long: '%s'", user))
221 errx(1, "Permission denied");
222 #endif
223 errx(1, "username too long");
226 nargv = malloc(sizeof(char *) * (size_t)(argc + 4));
227 if (nargv == NULL)
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];
235 argv += optind;
237 errno = 0;
238 prio = getpriority(PRIO_PROCESS, 0);
239 if (errno)
240 prio = 0;
242 setpriority(PRIO_PROCESS, 0, -2);
243 openlog("su", LOG_CONS, LOG_AUTH);
245 /* get current login name, real uid and shell */
246 ruid = getuid();
247 username = getlogin();
248 pwd = getpwnam(username);
249 if (username == NULL || pwd == NULL || pwd->pw_uid != ruid)
250 pwd = getpwuid(ruid);
251 if (pwd == NULL) {
252 #ifdef USE_BSM_AUDIT
253 if (audit_submit(AUE_su, auid, 1, EPERM,
254 "unable to determine invoking subject: '%s'", username))
255 errx(1, "Permission denied");
256 #endif
257 errx(1, "who are you?");
260 username = strdup(pwd->pw_name);
261 if (username == NULL)
262 err(1, "strdup failure");
264 if (asme) {
265 if (pwd->pw_shell != NULL && *pwd->pw_shell != '\0') {
266 /* must copy - pwd memory is recycled */
267 shell = strncpy(shellbuf, pwd->pw_shell,
268 sizeof(shellbuf));
269 shellbuf[sizeof(shellbuf) - 1] = '\0';
271 else {
272 shell = _PATH_BSHELL;
273 iscsh = NO;
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);
287 if (!mytty)
288 mytty = "tty";
289 PAM_SET_ITEM(PAM_TTY, mytty);
291 retcode = pam_authenticate(pamh, 0);
292 if (retcode != PAM_SUCCESS) {
293 #ifdef USE_BSM_AUDIT
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");
297 #endif
298 syslog(LOG_AUTH|LOG_WARNING, "BAD SU %s to %s on %s",
299 username, user, mytty);
300 errx(1, "Sorry");
302 #ifdef USE_BSM_AUDIT
303 if (audit_submit(AUE_su, auid, 0, 0, "successful authentication"))
304 errx(1, "Permission denied");
305 #endif
306 retcode = pam_get_item(pamh, PAM_USER, &v);
307 if (retcode == PAM_SUCCESS)
308 user = v;
309 else
310 syslog(LOG_ERR, "pam_get_item(PAM_USER): %s",
311 pam_strerror(pamh, retcode));
312 pwd = getpwnam(user);
313 if (pwd == NULL) {
314 #ifdef USE_BSM_AUDIT
315 if (audit_submit(AUE_su, auid, 1, EPERM,
316 "unknown subject: %s", user))
317 errx(1, "Permission denied");
318 #endif
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) {
327 #ifdef USE_BSM_AUDIT
328 aerr = pam_strerror(pamh, retcode);
329 if (aerr == NULL)
330 aerr = "Unknown PAM error";
331 if (audit_submit(AUE_su, auid, 1, EPERM,
332 "pam_chauthtok: %s", aerr))
333 errx(1, "Permission denied");
334 #endif
335 syslog(LOG_ERR, "pam_chauthtok: %s",
336 pam_strerror(pamh, retcode));
337 errx(1, "Sorry");
340 if (retcode != PAM_SUCCESS) {
341 #ifdef USE_BSM_AUDIT
342 if (audit_submit(AUE_su, auid, 1, EPERM, "pam_acct_mgmt: %s",
343 pam_strerror(pamh, retcode)))
344 errx(1, "Permission denied");
345 #endif
346 syslog(LOG_ERR, "pam_acct_mgmt: %s",
347 pam_strerror(pamh, retcode));
348 errx(1, "Sorry");
351 /* get target login information */
352 if (class == NULL)
353 lc = login_getpwclass(pwd);
354 else {
355 if (ruid != 0) {
356 #ifdef USE_BSM_AUDIT
357 if (audit_submit(AUE_su, auid, 1, EPERM,
358 "only root may use -c"))
359 errx(1, "Permission denied");
360 #endif
361 errx(1, "only root may use -c");
363 lc = login_getclass(class);
364 if (lc == NULL)
365 errx(1, "unknown class: %s", class);
368 /* if asme and non-standard target shell, must be root */
369 if (asme) {
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;
375 iscsh = UNSET;
377 else {
378 shell = _PATH_BSHELL;
379 iscsh = NO;
382 /* if we're forking a csh, we want to slightly muck the args */
383 if (iscsh == UNSET) {
384 p = strrchr(shell, '/');
385 if (p)
386 ++p;
387 else
388 p = 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.");
406 if (asthem) {
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);
427 statusp = 1;
428 if (pipe(fds) == -1) {
429 PAM_END();
430 err(1, "pipe");
432 child_pid = fork();
433 switch (child_pid) {
434 default:
435 sa.sa_handler = SIG_IGN;
436 sigaction(SIGTTOU, &sa, NULL);
437 close(fds[0]);
438 setpgid(child_pid, child_pid);
439 if (tcgetpgrp(STDERR_FILENO) == getpgrp())
440 tcsetpgrp(STDERR_FILENO, child_pid);
441 close(fds[1]);
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);
454 statusp = 1;
455 continue;
457 break;
459 tcsetpgrp(STDERR_FILENO, getpgrp());
460 if (pid == -1)
461 err(1, "waitpid");
462 PAM_END();
463 exit(WEXITSTATUS(statusp));
464 case -1:
465 PAM_END();
466 err(1, "fork");
467 case 0:
468 close(fds[1]);
469 read(fds[0], &temp, 1);
470 close(fds[0]);
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);
482 #if 0
484 * If -s is present, also set the MAC label.
486 if (setmaclabel)
487 setwhat |= LOGIN_SETMAC;
488 #endif
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");
498 if (!asme) {
499 if (asthem) {
500 p = getenv("TERM");
501 environ = &cleanenv;
504 if (asthem || pwd->pw_uid) {
505 if (setenv("USER", pwd->pw_name, 1) == -1) {
506 err(1, "setenv: cannot set USER=%s",
507 pwd->pw_name);
510 if (setenv("HOME", pwd->pw_dir, 1) == -1) {
511 err(1, "setenv: cannot set HOME=%s",
512 pwd->pw_dir);
514 if (setenv("SHELL", shell, 1) == -1)
515 err(1, "setenv: cannot set SHELL=%s", shell);
517 if (asthem) {
519 * Add any environmental variables that the
520 * PAM modules may have set.
522 environ_pam = pam_getenvlist(pamh);
523 if (environ_pam)
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 |
529 LOGIN_SETENV);
530 if (p) {
531 if (setenv("TERM", p, 1) == -1) {
532 err(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");
543 login_close(lc);
545 if (iscsh == YES) {
546 if (fastlogin)
547 *np.a-- = "-f";
548 if (asme)
549 *np.a-- = "-m";
551 /* csh strips the first character... */
552 *np.a = asthem ? "-su" : iscsh == YES ? "_su" : "su";
554 if (ruid != 0)
555 syslog(LOG_NOTICE, "%s to %s%s", username, user,
556 ontty());
558 execv(shell, np.b);
559 err(1, "%s", shell);
563 static void
564 export_pam_environment(void)
566 char **pp;
567 char *p;
569 for (pp = environ_pam; *pp != NULL; pp++) {
570 if (ok_to_export(*pp)) {
571 p = strchr(*pp, '=');
572 *p = '\0';
573 if (setenv(*pp, p + 1, 1) == -1)
574 err(1, "setenv: cannot set %s=%s", *pp, p + 1);
576 free(*pp);
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.
589 static int
590 ok_to_export(const char *s)
592 static const char *noexport[] = {
593 "SHELL", /* "HOME", */ "LOGNAME", "MAIL", "CDPATH",
594 "IFS", "PATH", NULL
596 const char **pp;
597 size_t n;
599 if (strlen(s) > 1024 || strchr(s, '=') == NULL)
600 return 0;
601 if (strncmp(s, "LD_", 3) == 0)
602 return 0;
603 for (pp = noexport; *pp != NULL; pp++) {
604 n = strlen(*pp);
605 if (s[n] == '=' && strncmp(s, *pp, n) == 0)
606 return 0;
608 return 1;
611 static void
612 usage(void)
615 fprintf(stderr, "usage: su [-] [-flm] [-c class] [login [args]]\n");
616 exit(1);
617 /* NOTREACHED */
620 static int
621 chshell(const char *sh)
623 int r;
624 char *cp;
626 r = 0;
627 setusershell();
628 while ((cp = getusershell()) != NULL && !r)
629 r = (strcmp(cp, sh) == 0);
630 endusershell();
631 return r;
634 static char *
635 ontty(void)
637 char *p;
638 static char buf[MAXPATHLEN + 4];
640 buf[0] = 0;
641 p = ttyname(STDERR_FILENO);
642 if (p)
643 snprintf(buf, sizeof(buf), " on %s", p);
644 return buf;