1 /*-
2 * SPDX-License-Identifier: BSD-3-Clause
3 *
4 * Copyright (c) 2002, 2005 Networks Associates Technologies, Inc.
5 * All rights reserved.
6 *
7 * Portions of this software were developed for the FreeBSD Project by
8 * ThinkSec AS and NAI Labs, the Security Research Division of Network
9 * Associates, Inc. under DARPA/SPAWAR contract N66001-01-C-8035
10 * ("CBOSS"), as part of the DARPA CHATS research program.
11 *
12 * Redistribution and use in source and binary forms, with or without
13 * modification, are permitted provided that the following conditions
14 * are met:
15 * 1. Redistributions of source code must retain the above copyright
16 * notice, this list of conditions and the following disclaimer.
17 * 2. Redistributions in binary form must reproduce the above copyright
18 * notice, this list of conditions and the following disclaimer in the
19 * documentation and/or other materials provided with the distribution.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 * SUCH DAMAGE.
32 */
33 /*-
34 * Copyright (c) 1988, 1993, 1994
35 * The Regents of the University of California. All rights reserved.
36 *
37 * Redistribution and use in source and binary forms, with or without
38 * modification, are permitted provided that the following conditions
39 * are met:
40 * 1. Redistributions of source code must retain the above copyright
41 * notice, this list of conditions and the following disclaimer.
42 * 2. Redistributions in binary form must reproduce the above copyright
43 * notice, this list of conditions and the following disclaimer in the
44 * documentation and/or other materials provided with the distribution.
45 * 3. Neither the name of the University nor the names of its contributors
46 * may be used to endorse or promote products derived from this software
47 * without specific prior written permission.
48 *
49 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
50 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
51 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
52 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
53 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
54 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
55 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
56 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
57 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
58 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
59 * SUCH DAMAGE.
60 */
61
62 #ifndef lint
63 static const char copyright[] =
64 "@(#) Copyright (c) 1988, 1993, 1994\n\
65 The Regents of the University of California. All rights reserved.\n";
66 #endif /* not lint */
67
68 #if 0
69 #ifndef lint
70 static char sccsid[] = "@(#)su.c 8.3 (Berkeley) 4/2/94";
71 #endif /* not lint */
72 #endif
73
74 #include <sys/cdefs.h>
75 #include <sys/param.h>
76 #include <sys/time.h>
77 #include <sys/resource.h>
78 #include <sys/wait.h>
79
80 #ifdef USE_BSM_AUDIT
81 #include <bsm/libbsm.h>
82 #include <bsm/audit_uevents.h>
83 #endif
84
85 #include <err.h>
86 #include <errno.h>
87 #include <grp.h>
88 #include <login_cap.h>
89 #include <paths.h>
90 #include <pwd.h>
91 #include <signal.h>
92 #include <stdio.h>
93 #include <stdlib.h>
94 #include <string.h>
95 #include <syslog.h>
96 #include <unistd.h>
97 #include <stdarg.h>
98
99 #include <security/pam_appl.h>
100 #include <security/openpam.h>
101
102 #define PAM_END() do { \
103 int local_ret; \
104 if (pamh != NULL) { \
105 local_ret = pam_setcred(pamh, PAM_DELETE_CRED); \
106 if (local_ret != PAM_SUCCESS) \
107 syslog(LOG_ERR, "pam_setcred: %s", \
108 pam_strerror(pamh, local_ret)); \
109 if (asthem) { \
110 local_ret = pam_close_session(pamh, 0); \
111 if (local_ret != PAM_SUCCESS) \
112 syslog(LOG_ERR, "pam_close_session: %s",\
113 pam_strerror(pamh, local_ret)); \
114 } \
115 local_ret = pam_end(pamh, local_ret); \
116 if (local_ret != PAM_SUCCESS) \
117 syslog(LOG_ERR, "pam_end: %s", \
118 pam_strerror(pamh, local_ret)); \
119 } \
120 } while (0)
121
122
123 #define PAM_SET_ITEM(what, item) do { \
124 int local_ret; \
125 local_ret = pam_set_item(pamh, what, item); \
126 if (local_ret != PAM_SUCCESS) { \
127 syslog(LOG_ERR, "pam_set_item(" #what "): %s", \
128 pam_strerror(pamh, local_ret)); \
129 errx(1, "pam_set_item(" #what "): %s", \
130 pam_strerror(pamh, local_ret)); \
131 /* NOTREACHED */ \
132 } \
133 } while (0)
134
135 enum tristate { UNSET, YES, NO };
136
137 static pam_handle_t *pamh = NULL;
138 static char **environ_pam;
139
140 static char *ontty(void);
141 static int chshell(const char *);
142 static void usage(void) __dead2;
143 static void export_pam_environment(void);
144 static int ok_to_export(const char *);
145
146 extern char **environ;
147
148 int
main(int argc,char * argv[])149 main(int argc, char *argv[])
150 {
151 static char *cleanenv;
152 struct passwd *pwd = NULL;
153 struct pam_conv conv = { openpam_ttyconv, NULL };
154 enum tristate iscsh;
155 login_cap_t *lc;
156 union {
157 const char **a;
158 char * const *b;
159 } np;
160 uid_t ruid;
161 pid_t child_pid, child_pgrp, pid;
162 int asme, ch, asthem, fastlogin, prio, i, retcode,
163 statusp, setmaclabel;
164 u_int setwhat;
165 char *username, *class, shellbuf[MAXPATHLEN];
166 const char *p, *user, *shell, *mytty, **nargv;
167 const void *v;
168 struct sigaction sa, sa_int, sa_quit, sa_pipe;
169 int temp, fds[2];
170 #ifdef USE_BSM_AUDIT
171 const char *aerr;
172 au_id_t auid;
173 #endif
174
175 p = shell = class = cleanenv = NULL;
176 asme = asthem = fastlogin = statusp = 0;
177 user = "root";
178 iscsh = UNSET;
179 setmaclabel = 0;
180
181 while ((ch = getopt(argc, argv, "-flmsc:")) != -1)
182 switch ((char)ch) {
183 case 'f':
184 fastlogin = 1;
185 break;
186 case '-':
187 case 'l':
188 asme = 0;
189 asthem = 1;
190 break;
191 case 'm':
192 asme = 1;
193 asthem = 0;
194 break;
195 case 's':
196 setmaclabel = 1;
197 break;
198 case 'c':
199 class = optarg;
200 break;
201 case '?':
202 default:
203 usage();
204 /* NOTREACHED */
205 }
206
207 if (optind < argc)
208 user = argv[optind++];
209
210 if (user == NULL)
211 usage();
212 /* NOTREACHED */
213
214 /*
215 * Try to provide more helpful debugging output if su(1) is running
216 * non-setuid, or was run from a file system not mounted setuid.
217 */
218 if (geteuid() != 0)
219 errx(1, "not running setuid");
220
221 #ifdef USE_BSM_AUDIT
222 if (getauid(&auid) < 0 && errno != ENOSYS) {
223 syslog(LOG_AUTH | LOG_ERR, "getauid: %s", strerror(errno));
224 errx(1, "Permission denied");
225 }
226 #endif
227 if (strlen(user) > MAXLOGNAME - 1) {
228 #ifdef USE_BSM_AUDIT
229 if (audit_submit(AUE_su, auid,
230 EPERM, 1, "username too long: '%s'", user))
231 errx(1, "Permission denied");
232 #endif
233 errx(1, "username too long");
234 }
235
236 nargv = malloc(sizeof(char *) * (size_t)(argc + 4));
237 if (nargv == NULL)
238 errx(1, "malloc failure");
239
240 nargv[argc + 3] = NULL;
241 for (i = argc; i >= optind; i--)
242 nargv[i + 3] = argv[i];
243 np.a = &nargv[i + 3];
244
245 argv += optind;
246
247 errno = 0;
248 prio = getpriority(PRIO_PROCESS, 0);
249 if (errno)
250 prio = 0;
251
252 setpriority(PRIO_PROCESS, 0, -2);
253 openlog("su", LOG_CONS, LOG_AUTH);
254
255 /* get current login name, real uid and shell */
256 ruid = getuid();
257 username = getlogin();
258 if (username != NULL)
259 pwd = getpwnam(username);
260 if (pwd == NULL || pwd->pw_uid != ruid)
261 pwd = getpwuid(ruid);
262 if (pwd == NULL) {
263 #ifdef USE_BSM_AUDIT
264 if (audit_submit(AUE_su, auid, EPERM, 1,
265 "unable to determine invoking subject: '%s'", username))
266 errx(1, "Permission denied");
267 #endif
268 errx(1, "who are you?");
269 }
270
271 username = strdup(pwd->pw_name);
272 if (username == NULL)
273 err(1, "strdup failure");
274
275 if (asme) {
276 if (pwd->pw_shell != NULL && *pwd->pw_shell != '\0') {
277 /* must copy - pwd memory is recycled */
278 strlcpy(shellbuf, pwd->pw_shell,
279 sizeof(shellbuf));
280 shell = shellbuf;
281 }
282 else {
283 shell = _PATH_BSHELL;
284 iscsh = NO;
285 }
286 }
287
288 /* Do the whole PAM startup thing */
289 retcode = pam_start("su", user, &conv, &pamh);
290 if (retcode != PAM_SUCCESS) {
291 syslog(LOG_ERR, "pam_start: %s", pam_strerror(pamh, retcode));
292 errx(1, "pam_start: %s", pam_strerror(pamh, retcode));
293 }
294
295 PAM_SET_ITEM(PAM_RUSER, username);
296
297 mytty = ttyname(STDERR_FILENO);
298 if (!mytty)
299 mytty = "tty";
300 PAM_SET_ITEM(PAM_TTY, mytty);
301
302 retcode = pam_authenticate(pamh, 0);
303 if (retcode != PAM_SUCCESS) {
304 #ifdef USE_BSM_AUDIT
305 if (audit_submit(AUE_su, auid, EPERM, 1, "bad su %s to %s on %s",
306 username, user, mytty))
307 errx(1, "Permission denied");
308 #endif
309 syslog(LOG_AUTH|LOG_WARNING, "BAD SU %s to %s on %s",
310 username, user, mytty);
311 errx(1, "Sorry");
312 }
313 #ifdef USE_BSM_AUDIT
314 if (audit_submit(AUE_su, auid, 0, 0, "successful authentication"))
315 errx(1, "Permission denied");
316 #endif
317 retcode = pam_get_item(pamh, PAM_USER, &v);
318 if (retcode == PAM_SUCCESS)
319 user = v;
320 else
321 syslog(LOG_ERR, "pam_get_item(PAM_USER): %s",
322 pam_strerror(pamh, retcode));
323 pwd = getpwnam(user);
324 if (pwd == NULL) {
325 #ifdef USE_BSM_AUDIT
326 if (audit_submit(AUE_su, auid, EPERM, 1,
327 "unknown subject: %s", user))
328 errx(1, "Permission denied");
329 #endif
330 errx(1, "unknown login: %s", user);
331 }
332
333 retcode = pam_acct_mgmt(pamh, 0);
334 if (retcode == PAM_NEW_AUTHTOK_REQD) {
335 retcode = pam_chauthtok(pamh,
336 PAM_CHANGE_EXPIRED_AUTHTOK);
337 if (retcode != PAM_SUCCESS) {
338 #ifdef USE_BSM_AUDIT
339 aerr = pam_strerror(pamh, retcode);
340 if (aerr == NULL)
341 aerr = "Unknown PAM error";
342 if (audit_submit(AUE_su, auid, EPERM, 1,
343 "pam_chauthtok: %s", aerr))
344 errx(1, "Permission denied");
345 #endif
346 syslog(LOG_ERR, "pam_chauthtok: %s",
347 pam_strerror(pamh, retcode));
348 errx(1, "Sorry");
349 }
350 }
351 if (retcode != PAM_SUCCESS) {
352 #ifdef USE_BSM_AUDIT
353 if (audit_submit(AUE_su, auid, EPERM, 1, "pam_acct_mgmt: %s",
354 pam_strerror(pamh, retcode)))
355 errx(1, "Permission denied");
356 #endif
357 syslog(LOG_ERR, "pam_acct_mgmt: %s",
358 pam_strerror(pamh, retcode));
359 errx(1, "Sorry");
360 }
361
362 /* get target login information */
363 if (class == NULL)
364 lc = login_getpwclass(pwd);
365 else {
366 if (ruid != 0) {
367 #ifdef USE_BSM_AUDIT
368 if (audit_submit(AUE_su, auid, EPERM, 1,
369 "only root may use -c"))
370 errx(1, "Permission denied");
371 #endif
372 errx(1, "only root may use -c");
373 }
374 lc = login_getclass(class);
375 if (lc == NULL)
376 err(1, "login_getclass");
377 if (lc->lc_class == NULL || strcmp(class, lc->lc_class) != 0)
378 errx(1, "unknown class: %s", class);
379 }
380
381 /* if asme and non-standard target shell, must be root */
382 if (asme) {
383 if (ruid != 0 && !chshell(pwd->pw_shell))
384 errx(1, "permission denied (shell)");
385 }
386 else if (pwd->pw_shell && *pwd->pw_shell) {
387 shell = pwd->pw_shell;
388 iscsh = UNSET;
389 }
390 else {
391 shell = _PATH_BSHELL;
392 iscsh = NO;
393 }
394
395 /* if we're forking a csh, we want to slightly muck the args */
396 if (iscsh == UNSET) {
397 p = strrchr(shell, '/');
398 if (p)
399 ++p;
400 else
401 p = shell;
402 iscsh = strcmp(p, "csh") ? (strcmp(p, "tcsh") ? NO : YES) : YES;
403 }
404 setpriority(PRIO_PROCESS, 0, prio);
405
406 /*
407 * PAM modules might add supplementary groups in pam_setcred(), so
408 * initialize them first.
409 */
410 if (setusercontext(lc, pwd, pwd->pw_uid, LOGIN_SETGROUP) < 0)
411 err(1, "setusercontext");
412
413 retcode = pam_setcred(pamh, PAM_ESTABLISH_CRED);
414 if (retcode != PAM_SUCCESS) {
415 syslog(LOG_ERR, "pam_setcred: %s",
416 pam_strerror(pamh, retcode));
417 errx(1, "failed to establish credentials.");
418 }
419 if (asthem) {
420 retcode = pam_open_session(pamh, 0);
421 if (retcode != PAM_SUCCESS) {
422 syslog(LOG_ERR, "pam_open_session: %s",
423 pam_strerror(pamh, retcode));
424 errx(1, "failed to open session.");
425 }
426 }
427
428 /*
429 * We must fork() before setuid() because we need to call
430 * pam_setcred(pamh, PAM_DELETE_CRED) as root.
431 */
432 sa.sa_flags = SA_RESTART;
433 sa.sa_handler = SIG_IGN;
434 sigemptyset(&sa.sa_mask);
435 sigaction(SIGINT, &sa, &sa_int);
436 sigaction(SIGQUIT, &sa, &sa_quit);
437 sigaction(SIGPIPE, &sa, &sa_pipe);
438 sa.sa_handler = SIG_DFL;
439 sigaction(SIGTSTP, &sa, NULL);
440 statusp = 1;
441 if (pipe(fds) == -1) {
442 PAM_END();
443 err(1, "pipe");
444 }
445 child_pid = fork();
446 switch (child_pid) {
447 default:
448 sa.sa_handler = SIG_IGN;
449 sigaction(SIGTTOU, &sa, NULL);
450 close(fds[0]);
451 setpgid(child_pid, child_pid);
452 if (tcgetpgrp(STDERR_FILENO) == getpgrp())
453 tcsetpgrp(STDERR_FILENO, child_pid);
454 close(fds[1]);
455 sigaction(SIGPIPE, &sa_pipe, NULL);
456 while ((pid = waitpid(child_pid, &statusp, WUNTRACED)) != -1) {
457 if (WIFSTOPPED(statusp)) {
458 child_pgrp = getpgid(child_pid);
459 if (tcgetpgrp(STDERR_FILENO) == child_pgrp)
460 tcsetpgrp(STDERR_FILENO, getpgrp());
461 kill(getpid(), SIGSTOP);
462 if (tcgetpgrp(STDERR_FILENO) == getpgrp()) {
463 child_pgrp = getpgid(child_pid);
464 tcsetpgrp(STDERR_FILENO, child_pgrp);
465 }
466 kill(child_pid, SIGCONT);
467 statusp = 1;
468 continue;
469 }
470 break;
471 }
472 tcsetpgrp(STDERR_FILENO, getpgrp());
473 if (pid == -1)
474 err(1, "waitpid");
475 PAM_END();
476 exit(WEXITSTATUS(statusp));
477 case -1:
478 PAM_END();
479 err(1, "fork");
480 case 0:
481 close(fds[1]);
482 read(fds[0], &temp, 1);
483 close(fds[0]);
484 sigaction(SIGPIPE, &sa_pipe, NULL);
485 sigaction(SIGINT, &sa_int, NULL);
486 sigaction(SIGQUIT, &sa_quit, NULL);
487
488 /*
489 * Set all user context except for: Environmental variables
490 * Umask Login records (wtmp, etc) Path
491 */
492 setwhat = LOGIN_SETALL & ~(LOGIN_SETENV | LOGIN_SETUMASK |
493 LOGIN_SETLOGIN | LOGIN_SETPATH | LOGIN_SETGROUP |
494 LOGIN_SETMAC);
495 /*
496 * If -s is present, also set the MAC label.
497 */
498 if (setmaclabel)
499 setwhat |= LOGIN_SETMAC;
500 /*
501 * Don't touch resource/priority settings if -m has been used
502 * or -l and -c hasn't, and we're not su'ing to root.
503 */
504 if ((asme || (!asthem && class == NULL)) && pwd->pw_uid)
505 setwhat &= ~(LOGIN_SETPRIORITY | LOGIN_SETRESOURCES);
506 if (setusercontext(lc, pwd, pwd->pw_uid, setwhat) < 0)
507 err(1, "setusercontext");
508
509 if (!asme) {
510 if (asthem) {
511 p = getenv("TERM");
512 environ = &cleanenv;
513 }
514
515 if (asthem || pwd->pw_uid)
516 setenv("USER", pwd->pw_name, 1);
517 setenv("HOME", pwd->pw_dir, 1);
518 setenv("SHELL", shell, 1);
519
520 if (asthem) {
521 /*
522 * Add any environmental variables that the
523 * PAM modules may have set.
524 */
525 environ_pam = pam_getenvlist(pamh);
526 if (environ_pam)
527 export_pam_environment();
528
529 /* set the su'd user's environment & umask */
530 setusercontext(lc, pwd, pwd->pw_uid,
531 LOGIN_SETPATH | LOGIN_SETUMASK |
532 LOGIN_SETENV);
533 if (p)
534 setenv("TERM", p, 1);
535
536 p = pam_getenv(pamh, "HOME");
537 if (chdir(p ? p : pwd->pw_dir) < 0)
538 errx(1, "no directory");
539 }
540 }
541 login_close(lc);
542
543 if (iscsh == YES) {
544 if (fastlogin)
545 *np.a-- = "-f";
546 if (asme)
547 *np.a-- = "-m";
548 }
549 /* csh strips the first character... */
550 *np.a = asthem ? "-su" : iscsh == YES ? "_su" : "su";
551
552 if (ruid != 0)
553 syslog(LOG_NOTICE, "%s to %s%s", username, user,
554 ontty());
555
556 execv(shell, np.b);
557 err(1, "%s", shell);
558 }
559 }
560
561 static void
export_pam_environment(void)562 export_pam_environment(void)
563 {
564 char **pp;
565 char *p;
566
567 for (pp = environ_pam; *pp != NULL; pp++) {
568 if (ok_to_export(*pp)) {
569 p = strchr(*pp, '=');
570 *p = '\0';
571 setenv(*pp, p + 1, 1);
572 }
573 free(*pp);
574 }
575 }
576
577 /*
578 * Sanity checks on PAM environmental variables:
579 * - Make sure there is an '=' in the string.
580 * - Make sure the string doesn't run on too long.
581 * - Do not export certain variables. This list was taken from the
582 * Solaris pam_putenv(3) man page.
583 * Note that if the user is chrooted, PAM may have a better idea than we
584 * do of where her home directory is.
585 */
586 static int
ok_to_export(const char * s)587 ok_to_export(const char *s)
588 {
589 static const char *noexport[] = {
590 "SHELL", /* "HOME", */ "LOGNAME", "MAIL", "CDPATH",
591 "IFS", "PATH", NULL
592 };
593 const char **pp;
594 size_t n;
595
596 if (strlen(s) > 1024 || strchr(s, '=') == NULL)
597 return 0;
598 if (strncmp(s, "LD_", 3) == 0)
599 return 0;
600 for (pp = noexport; *pp != NULL; pp++) {
601 n = strlen(*pp);
602 if (s[n] == '=' && strncmp(s, *pp, n) == 0)
603 return 0;
604 }
605 return 1;
606 }
607
608 static void
usage(void)609 usage(void)
610 {
611
612 fprintf(stderr, "usage: su [-] [-flms] [-c class] [login [args]]\n");
613 exit(1);
614 /* NOTREACHED */
615 }
616
617 static int
chshell(const char * sh)618 chshell(const char *sh)
619 {
620 int r;
621 char *cp;
622
623 r = 0;
624 setusershell();
625 while ((cp = getusershell()) != NULL && !r)
626 r = (strcmp(cp, sh) == 0);
627 endusershell();
628 return r;
629 }
630
631 static char *
ontty(void)632 ontty(void)
633 {
634 char *p;
635 static char buf[MAXPATHLEN + 4];
636
637 buf[0] = 0;
638 p = ttyname(STDERR_FILENO);
639 if (p)
640 snprintf(buf, sizeof(buf), " on %s", p);
641 return buf;
642 }
643