sigwait.3: Add missing '.'
[dragonfly.git] / usr.bin / su / su.c
blobd4018b4f2c9523a43e3c0bd7d1a09fe0a1517429
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. 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
61 * SUCH DAMAGE.
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>
69 #include <sys/time.h>
70 #include <sys/resource.h>
71 #include <sys/wait.h>
73 #ifdef USE_BSM_AUDIT
74 #include <bsm/libbsm.h>
75 #include <bsm/audit_uevents.h>
76 #endif
78 #include <err.h>
79 #include <errno.h>
80 #include <grp.h>
81 #include <login_cap.h>
82 #include <paths.h>
83 #include <pwd.h>
84 #include <signal.h>
85 #include <stdio.h>
86 #include <stdlib.h>
87 #include <string.h>
88 #include <syslog.h>
89 #include <unistd.h>
90 #include <stdarg.h>
92 #include <security/pam_appl.h>
93 #include <security/openpam.h>
95 #define PAM_END() do { \
96 int local_ret; \
97 if (pamh != NULL) { \
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)); \
102 if (asthem) { \
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)); \
113 } while (0)
116 #define PAM_SET_ITEM(what, item) do { \
117 int local_ret; \
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)); \
124 /* NOTREACHED */ \
126 } while (0)
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;
145 struct passwd *pwd;
146 struct pam_conv conv = { openpam_ttyconv, NULL };
147 enum tristate iscsh;
148 login_cap_t *lc;
149 union {
150 const char **a;
151 char * const *b;
152 } np;
153 uid_t ruid;
154 pid_t child_pid, child_pgrp, pid;
155 int asme, ch, asthem, fastlogin, prio, i, retcode,
156 statusp;
157 u_int setwhat;
158 char *username, *class, shellbuf[MAXPATHLEN];
159 const char *p = p, *user, *shell, *mytty, **nargv;
160 const void *v;
161 struct sigaction sa, sa_int, sa_quit, sa_pipe;
162 int temp, fds[2];
163 #ifdef USE_BSM_AUDIT
164 const char *aerr;
165 au_id_t auid;
166 #endif
168 shell = class = cleanenv = NULL;
169 asme = asthem = fastlogin = statusp = 0;
170 user = "root";
171 iscsh = UNSET;
173 while ((ch = getopt(argc, argv, "-flmc:")) != -1)
174 switch ((char)ch) {
175 case 'f':
176 fastlogin = 1;
177 break;
178 case '-':
179 case 'l':
180 asme = 0;
181 asthem = 1;
182 break;
183 case 'm':
184 asme = 1;
185 asthem = 0;
186 break;
187 case 'c':
188 class = optarg;
189 break;
190 case '?':
191 default:
192 usage();
193 /* NOTREACHED */
196 if (optind < argc && strcmp(argv[optind], "-") == 0) {
197 asme = 0;
198 asthem = 1;
199 optind++;
202 if (optind < argc)
203 user = argv[optind++];
205 if (user == NULL)
206 usage();
207 /* NOTREACHED */
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.
213 if (geteuid() != 0)
214 errx(1, "not running setuid");
216 #ifdef USE_BSM_AUDIT
217 if (getauid(&auid) < 0 && errno != ENOSYS) {
218 syslog(LOG_AUTH | LOG_ERR, "getauid: %s", strerror(errno));
219 errx(1, "Permission denied");
221 #endif
222 if (strlen(user) > MAXLOGNAME - 1) {
223 #ifdef USE_BSM_AUDIT
224 if (audit_submit(AUE_su, auid,
225 1, EPERM, "username too long: '%s'", user))
226 errx(1, "Permission denied");
227 #endif
228 errx(1, "username too long");
231 nargv = malloc(sizeof(char *) * (size_t)(argc + 4));
232 if (nargv == NULL)
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];
240 argv += optind;
242 errno = 0;
243 prio = getpriority(PRIO_PROCESS, 0);
244 if (errno)
245 prio = 0;
247 setpriority(PRIO_PROCESS, 0, -2);
248 openlog("su", LOG_CONS, LOG_AUTH);
250 /* get current login name, real uid and shell */
251 ruid = getuid();
252 username = getlogin();
253 pwd = getpwnam(username);
254 if (username == NULL || pwd == NULL || pwd->pw_uid != ruid)
255 pwd = getpwuid(ruid);
256 if (pwd == NULL) {
257 #ifdef USE_BSM_AUDIT
258 if (audit_submit(AUE_su, auid, 1, EPERM,
259 "unable to determine invoking subject: '%s'", username))
260 errx(1, "Permission denied");
261 #endif
262 errx(1, "who are you?");
265 username = strdup(pwd->pw_name);
266 if (username == NULL)
267 err(1, "strdup failure");
269 if (asme) {
270 if (pwd->pw_shell != NULL && *pwd->pw_shell != '\0') {
271 /* must copy - pwd memory is recycled */
272 shell = strncpy(shellbuf, pwd->pw_shell,
273 sizeof(shellbuf));
274 shellbuf[sizeof(shellbuf) - 1] = '\0';
276 else {
277 shell = _PATH_BSHELL;
278 iscsh = NO;
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);
292 if (!mytty)
293 mytty = "tty";
294 PAM_SET_ITEM(PAM_TTY, mytty);
296 retcode = pam_authenticate(pamh, 0);
297 if (retcode != PAM_SUCCESS) {
298 #ifdef USE_BSM_AUDIT
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");
302 #endif
303 syslog(LOG_AUTH|LOG_WARNING, "BAD SU %s to %s on %s",
304 username, user, mytty);
305 errx(1, "Sorry");
307 #ifdef USE_BSM_AUDIT
308 if (audit_submit(AUE_su, auid, 0, 0, "successful authentication"))
309 errx(1, "Permission denied");
310 #endif
311 retcode = pam_get_item(pamh, PAM_USER, &v);
312 if (retcode == PAM_SUCCESS)
313 user = v;
314 else
315 syslog(LOG_ERR, "pam_get_item(PAM_USER): %s",
316 pam_strerror(pamh, retcode));
317 pwd = getpwnam(user);
318 if (pwd == NULL) {
319 #ifdef USE_BSM_AUDIT
320 if (audit_submit(AUE_su, auid, 1, EPERM,
321 "unknown subject: %s", user))
322 errx(1, "Permission denied");
323 #endif
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) {
332 #ifdef USE_BSM_AUDIT
333 aerr = pam_strerror(pamh, retcode);
334 if (aerr == NULL)
335 aerr = "Unknown PAM error";
336 if (audit_submit(AUE_su, auid, 1, EPERM,
337 "pam_chauthtok: %s", aerr))
338 errx(1, "Permission denied");
339 #endif
340 syslog(LOG_ERR, "pam_chauthtok: %s",
341 pam_strerror(pamh, retcode));
342 errx(1, "Sorry");
345 if (retcode != PAM_SUCCESS) {
346 #ifdef USE_BSM_AUDIT
347 if (audit_submit(AUE_su, auid, 1, EPERM, "pam_acct_mgmt: %s",
348 pam_strerror(pamh, retcode)))
349 errx(1, "Permission denied");
350 #endif
351 syslog(LOG_ERR, "pam_acct_mgmt: %s",
352 pam_strerror(pamh, retcode));
353 errx(1, "Sorry");
356 /* get target login information */
357 if (class == NULL)
358 lc = login_getpwclass(pwd);
359 else {
360 if (ruid != 0) {
361 #ifdef USE_BSM_AUDIT
362 if (audit_submit(AUE_su, auid, 1, EPERM,
363 "only root may use -c"))
364 errx(1, "Permission denied");
365 #endif
366 errx(1, "only root may use -c");
368 lc = login_getclass(class);
369 if (lc == NULL)
370 errx(1, "unknown class: %s", class);
373 /* if asme and non-standard target shell, must be root */
374 if (asme) {
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;
380 iscsh = UNSET;
382 else {
383 shell = _PATH_BSHELL;
384 iscsh = NO;
387 /* if we're forking a csh, we want to slightly muck the args */
388 if (iscsh == UNSET) {
389 p = strrchr(shell, '/');
390 if (p)
391 ++p;
392 else
393 p = 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.");
411 if (asthem) {
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);
432 statusp = 1;
433 if (pipe(fds) == -1) {
434 PAM_END();
435 err(1, "pipe");
437 child_pid = fork();
438 switch (child_pid) {
439 default:
440 sa.sa_handler = SIG_IGN;
441 sigaction(SIGTTOU, &sa, NULL);
442 close(fds[0]);
443 setpgid(child_pid, child_pid);
444 if (tcgetpgrp(STDERR_FILENO) == getpgrp())
445 tcsetpgrp(STDERR_FILENO, child_pid);
446 close(fds[1]);
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);
459 statusp = 1;
460 continue;
462 break;
464 tcsetpgrp(STDERR_FILENO, getpgrp());
465 if (pid == -1)
466 err(1, "waitpid");
467 PAM_END();
468 exit(WEXITSTATUS(statusp));
469 case -1:
470 PAM_END();
471 err(1, "fork");
472 case 0:
473 close(fds[1]);
474 read(fds[0], &temp, 1);
475 close(fds[0]);
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);
487 #if 0
489 * If -s is present, also set the MAC label.
491 if (setmaclabel)
492 setwhat |= LOGIN_SETMAC;
493 #endif
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");
503 if (!asme) {
504 if (asthem) {
505 p = getenv("TERM");
506 environ = &cleanenv;
509 if (asthem || pwd->pw_uid) {
510 if (setenv("USER", pwd->pw_name, 1) == -1) {
511 err(1, "setenv: cannot set USER=%s",
512 pwd->pw_name);
515 if (setenv("HOME", pwd->pw_dir, 1) == -1) {
516 err(1, "setenv: cannot set HOME=%s",
517 pwd->pw_dir);
519 if (setenv("SHELL", shell, 1) == -1)
520 err(1, "setenv: cannot set SHELL=%s", shell);
522 if (asthem) {
524 * Add any environmental variables that the
525 * PAM modules may have set.
527 environ_pam = pam_getenvlist(pamh);
528 if (environ_pam)
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 |
534 LOGIN_SETENV);
535 if (p) {
536 if (setenv("TERM", p, 1) == -1) {
537 err(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");
548 login_close(lc);
550 if (iscsh == YES) {
551 if (fastlogin)
552 *np.a-- = "-f";
553 if (asme)
554 *np.a-- = "-m";
556 /* csh strips the first character... */
557 *np.a = asthem ? "-su" : iscsh == YES ? "_su" : "su";
559 if (ruid != 0)
560 syslog(LOG_NOTICE, "%s to %s%s", username, user,
561 ontty());
563 execv(shell, np.b);
564 err(1, "%s", shell);
568 static void
569 export_pam_environment(void)
571 char **pp;
572 char *p;
574 for (pp = environ_pam; *pp != NULL; pp++) {
575 if (ok_to_export(*pp)) {
576 p = strchr(*pp, '=');
577 *p = '\0';
578 if (setenv(*pp, p + 1, 1) == -1)
579 err(1, "setenv: cannot set %s=%s", *pp, p + 1);
581 free(*pp);
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.
594 static int
595 ok_to_export(const char *s)
597 static const char *noexport[] = {
598 "SHELL", /* "HOME", */ "LOGNAME", "MAIL", "CDPATH",
599 "IFS", "PATH", NULL
601 const char **pp;
602 size_t n;
604 if (strlen(s) > 1024 || strchr(s, '=') == NULL)
605 return 0;
606 if (strncmp(s, "LD_", 3) == 0)
607 return 0;
608 for (pp = noexport; *pp != NULL; pp++) {
609 n = strlen(*pp);
610 if (s[n] == '=' && strncmp(s, *pp, n) == 0)
611 return 0;
613 return 1;
616 static void
617 usage(void)
620 fprintf(stderr, "usage: su [-] [-flm] [-c class] [login [args]]\n");
621 exit(1);
622 /* NOTREACHED */
625 static int
626 chshell(const char *sh)
628 int r;
629 char *cp;
631 r = 0;
632 setusershell();
633 while ((cp = getusershell()) != NULL && !r)
634 r = (strcmp(cp, sh) == 0);
635 endusershell();
636 return r;
639 static char *
640 ontty(void)
642 char *p;
643 static char buf[MAXPATHLEN + 4];
645 buf[0] = 0;
646 p = ttyname(STDERR_FILENO);
647 if (p)
648 snprintf(buf, sizeof(buf), " on %s", p);
649 return buf;