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 #include <sys/param.h>
63 #include <sys/time.h>
64 #include <sys/resource.h>
65 #include <sys/wait.h>
66
67 #ifdef USE_BSM_AUDIT
68 #include <bsm/libbsm.h>
69 #include <bsm/audit_uevents.h>
70 #endif
71
72 #include <err.h>
73 #include <errno.h>
74 #include <grp.h>
75 #include <login_cap.h>
76 #include <paths.h>
77 #include <pwd.h>
78 #include <signal.h>
79 #include <stdio.h>
80 #include <stdlib.h>
81 #include <string.h>
82 #include <syslog.h>
83 #include <unistd.h>
84 #include <stdarg.h>
85
86 #include <security/pam_appl.h>
87 #include <security/openpam.h>
88
89 #define PAM_END() do { \
90 int local_ret; \
91 if (pamh != NULL) { \
92 local_ret = pam_setcred(pamh, PAM_DELETE_CRED); \
93 if (local_ret != PAM_SUCCESS) \
94 syslog(LOG_ERR, "pam_setcred: %s", \
95 pam_strerror(pamh, local_ret)); \
96 if (asthem) { \
97 local_ret = pam_close_session(pamh, 0); \
98 if (local_ret != PAM_SUCCESS) \
99 syslog(LOG_ERR, "pam_close_session: %s",\
100 pam_strerror(pamh, local_ret)); \
101 } \
102 local_ret = pam_end(pamh, local_ret); \
103 if (local_ret != PAM_SUCCESS) \
104 syslog(LOG_ERR, "pam_end: %s", \
105 pam_strerror(pamh, local_ret)); \
106 } \
107 } while (0)
108
109
110 #define PAM_SET_ITEM(what, item) do { \
111 int local_ret; \
112 local_ret = pam_set_item(pamh, what, item); \
113 if (local_ret != PAM_SUCCESS) { \
114 syslog(LOG_ERR, "pam_set_item(" #what "): %s", \
115 pam_strerror(pamh, local_ret)); \
116 errx(1, "pam_set_item(" #what "): %s", \
117 pam_strerror(pamh, local_ret)); \
118 /* NOTREACHED */ \
119 } \
120 } while (0)
121
122 enum tristate { UNSET, YES, NO };
123
124 static pam_handle_t *pamh = NULL;
125 static char **environ_pam;
126
127 static char *ontty(void);
128 static int chshell(const char *);
129 static void usage(void) __dead2;
130 static void export_pam_environment(void);
131 static int ok_to_export(const char *);
132
133 extern char **environ;
134
135 int
main(int argc,char * argv[])136 main(int argc, char *argv[])
137 {
138 static char *cleanenv;
139 struct passwd *pwd = NULL;
140 struct pam_conv conv = { openpam_ttyconv, NULL };
141 enum tristate iscsh;
142 login_cap_t *lc;
143 union {
144 const char **a;
145 char * const *b;
146 } np;
147 uid_t ruid;
148 pid_t child_pid, child_pgrp, pid;
149 int asme, ch, asthem, fastlogin, prio, i, retcode,
150 statusp, setmaclabel;
151 u_int setwhat;
152 char *username, *class, shellbuf[MAXPATHLEN];
153 const char *p, *user, *shell, *mytty, **nargv;
154 const void *v;
155 struct sigaction sa, sa_int, sa_quit, sa_pipe;
156 int temp, fds[2];
157 #ifdef USE_BSM_AUDIT
158 const char *aerr;
159 au_id_t auid;
160 #endif
161
162 p = shell = class = cleanenv = NULL;
163 asme = asthem = fastlogin = statusp = 0;
164 user = "root";
165 iscsh = UNSET;
166 setmaclabel = 0;
167
168 while ((ch = getopt(argc, argv, "-flmsc:")) != -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 's':
183 setmaclabel = 1;
184 break;
185 case 'c':
186 class = optarg;
187 break;
188 case '?':
189 default:
190 usage();
191 /* NOTREACHED */
192 }
193
194 if (optind < argc)
195 user = argv[optind++];
196
197 if (user == NULL)
198 usage();
199 /* NOTREACHED */
200
201 /*
202 * Try to provide more helpful debugging output if su(1) is running
203 * non-setuid, or was run from a file system not mounted setuid.
204 */
205 if (geteuid() != 0)
206 errx(1, "not running setuid");
207
208 #ifdef USE_BSM_AUDIT
209 if (getauid(&auid) < 0 && errno != ENOSYS) {
210 syslog(LOG_AUTH | LOG_ERR, "getauid: %s", strerror(errno));
211 errx(1, "Permission denied");
212 }
213 #endif
214 if (strlen(user) > MAXLOGNAME - 1) {
215 #ifdef USE_BSM_AUDIT
216 if (audit_submit(AUE_su, auid,
217 EPERM, 1, "username too long: '%s'", user))
218 errx(1, "Permission denied");
219 #endif
220 errx(1, "username too long");
221 }
222
223 nargv = malloc(sizeof(char *) * (size_t)(argc + 4));
224 if (nargv == NULL)
225 errx(1, "malloc failure");
226
227 nargv[argc + 3] = NULL;
228 for (i = argc; i >= optind; i--)
229 nargv[i + 3] = argv[i];
230 np.a = &nargv[i + 3];
231
232 argv += optind;
233
234 errno = 0;
235 prio = getpriority(PRIO_PROCESS, 0);
236 if (errno)
237 prio = 0;
238
239 setpriority(PRIO_PROCESS, 0, -2);
240 openlog("su", LOG_CONS, LOG_AUTH);
241
242 /* get current login name, real uid and shell */
243 ruid = getuid();
244 username = getlogin();
245 if (username != NULL)
246 pwd = getpwnam(username);
247 if (pwd == NULL || pwd->pw_uid != ruid)
248 pwd = getpwuid(ruid);
249 if (pwd == NULL) {
250 #ifdef USE_BSM_AUDIT
251 if (audit_submit(AUE_su, auid, EPERM, 1,
252 "unable to determine invoking subject: '%s'", username))
253 errx(1, "Permission denied");
254 #endif
255 errx(1, "who are you?");
256 }
257
258 username = strdup(pwd->pw_name);
259 if (username == NULL)
260 err(1, "strdup failure");
261
262 if (asme) {
263 if (pwd->pw_shell != NULL && *pwd->pw_shell != '\0') {
264 /* must copy - pwd memory is recycled */
265 strlcpy(shellbuf, pwd->pw_shell,
266 sizeof(shellbuf));
267 shell = shellbuf;
268 }
269 else {
270 shell = _PATH_BSHELL;
271 iscsh = NO;
272 }
273 }
274
275 /* Do the whole PAM startup thing */
276 retcode = pam_start("su", user, &conv, &pamh);
277 if (retcode != PAM_SUCCESS) {
278 syslog(LOG_ERR, "pam_start: %s", pam_strerror(pamh, retcode));
279 errx(1, "pam_start: %s", pam_strerror(pamh, retcode));
280 }
281
282 PAM_SET_ITEM(PAM_RUSER, username);
283
284 mytty = ttyname(STDERR_FILENO);
285 if (!mytty)
286 mytty = "tty";
287 PAM_SET_ITEM(PAM_TTY, mytty);
288
289 retcode = pam_authenticate(pamh, 0);
290 if (retcode != PAM_SUCCESS) {
291 #ifdef USE_BSM_AUDIT
292 if (audit_submit(AUE_su, auid, EPERM, 1, "bad su %s to %s on %s",
293 username, user, mytty))
294 errx(1, "Permission denied");
295 #endif
296 syslog(LOG_AUTH|LOG_WARNING, "BAD SU %s to %s on %s",
297 username, user, mytty);
298 errx(1, "Sorry");
299 }
300 #ifdef USE_BSM_AUDIT
301 if (audit_submit(AUE_su, auid, 0, 0, "successful authentication"))
302 errx(1, "Permission denied");
303 #endif
304 retcode = pam_get_item(pamh, PAM_USER, &v);
305 if (retcode == PAM_SUCCESS)
306 user = v;
307 else
308 syslog(LOG_ERR, "pam_get_item(PAM_USER): %s",
309 pam_strerror(pamh, retcode));
310 pwd = getpwnam(user);
311 if (pwd == NULL) {
312 #ifdef USE_BSM_AUDIT
313 if (audit_submit(AUE_su, auid, EPERM, 1,
314 "unknown subject: %s", user))
315 errx(1, "Permission denied");
316 #endif
317 errx(1, "unknown login: %s", user);
318 }
319
320 retcode = pam_acct_mgmt(pamh, 0);
321 if (retcode == PAM_NEW_AUTHTOK_REQD) {
322 retcode = pam_chauthtok(pamh,
323 PAM_CHANGE_EXPIRED_AUTHTOK);
324 if (retcode != PAM_SUCCESS) {
325 #ifdef USE_BSM_AUDIT
326 aerr = pam_strerror(pamh, retcode);
327 if (aerr == NULL)
328 aerr = "Unknown PAM error";
329 if (audit_submit(AUE_su, auid, EPERM, 1,
330 "pam_chauthtok: %s", aerr))
331 errx(1, "Permission denied");
332 #endif
333 syslog(LOG_ERR, "pam_chauthtok: %s",
334 pam_strerror(pamh, retcode));
335 errx(1, "Sorry");
336 }
337 }
338 if (retcode != PAM_SUCCESS) {
339 #ifdef USE_BSM_AUDIT
340 if (audit_submit(AUE_su, auid, EPERM, 1, "pam_acct_mgmt: %s",
341 pam_strerror(pamh, retcode)))
342 errx(1, "Permission denied");
343 #endif
344 syslog(LOG_ERR, "pam_acct_mgmt: %s",
345 pam_strerror(pamh, retcode));
346 errx(1, "Sorry");
347 }
348
349 /* get target login information */
350 if (class == NULL)
351 lc = login_getpwclass(pwd);
352 else {
353 if (ruid != 0) {
354 #ifdef USE_BSM_AUDIT
355 if (audit_submit(AUE_su, auid, EPERM, 1,
356 "only root may use -c"))
357 errx(1, "Permission denied");
358 #endif
359 errx(1, "only root may use -c");
360 }
361 lc = login_getclass(class);
362 if (lc == NULL)
363 err(1, "login_getclass");
364 if (lc->lc_class == NULL || strcmp(class, lc->lc_class) != 0)
365 errx(1, "unknown class: %s", class);
366 }
367
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)");
372 }
373 else if (pwd->pw_shell && *pwd->pw_shell) {
374 shell = pwd->pw_shell;
375 iscsh = UNSET;
376 }
377 else {
378 shell = _PATH_BSHELL;
379 iscsh = NO;
380 }
381
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;
390 }
391 setpriority(PRIO_PROCESS, 0, prio);
392
393 /*
394 * PAM modules might add supplementary groups in pam_setcred(), so
395 * initialize them first.
396 */
397 if (setusercontext(lc, pwd, pwd->pw_uid, LOGIN_SETGROUP) < 0)
398 err(1, "setusercontext");
399
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.");
405 }
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.");
412 }
413 }
414
415 /*
416 * We must fork() before setuid() because we need to call
417 * pam_setcred(pamh, PAM_DELETE_CRED) as root.
418 */
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");
431 }
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);
452 }
453 kill(child_pid, SIGCONT);
454 statusp = 1;
455 continue;
456 }
457 break;
458 }
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);
474
475 /*
476 * Set all user context except for: Environmental variables
477 * Umask Login records (wtmp, etc) Path
478 */
479 setwhat = LOGIN_SETALL & ~(LOGIN_SETENV | LOGIN_SETUMASK |
480 LOGIN_SETLOGIN | LOGIN_SETPATH | LOGIN_SETGROUP |
481 LOGIN_SETMAC);
482 /*
483 * If -s is present, also set the MAC label.
484 */
485 if (setmaclabel)
486 setwhat |= LOGIN_SETMAC;
487 /*
488 * Don't touch resource/priority settings if -m has been used
489 * or -l and -c hasn't, and we're not su'ing to root.
490 */
491 if ((asme || (!asthem && class == NULL)) && pwd->pw_uid)
492 setwhat &= ~(LOGIN_SETPRIORITY | LOGIN_SETRESOURCES);
493 if (setusercontext(lc, pwd, pwd->pw_uid, setwhat) < 0)
494 err(1, "setusercontext");
495
496 if (!asme) {
497 if (asthem) {
498 p = getenv("TERM");
499 environ = &cleanenv;
500 }
501
502 if (asthem || pwd->pw_uid)
503 setenv("USER", pwd->pw_name, 1);
504 setenv("HOME", pwd->pw_dir, 1);
505 setenv("SHELL", shell, 1);
506
507 if (asthem) {
508 /*
509 * Add any environmental variables that the
510 * PAM modules may have set.
511 */
512 environ_pam = pam_getenvlist(pamh);
513 if (environ_pam)
514 export_pam_environment();
515
516 /* set the su'd user's environment & umask */
517 setusercontext(lc, pwd, pwd->pw_uid,
518 LOGIN_SETPATH | LOGIN_SETUMASK |
519 LOGIN_SETENV);
520 if (p)
521 setenv("TERM", p, 1);
522
523 p = pam_getenv(pamh, "HOME");
524 if (chdir(p ? p : pwd->pw_dir) < 0)
525 errx(1, "no directory");
526 }
527 }
528 login_close(lc);
529
530 if (iscsh == YES) {
531 if (fastlogin)
532 *np.a-- = "-f";
533 if (asme)
534 *np.a-- = "-m";
535 }
536 /* csh strips the first character... */
537 *np.a = asthem ? "-su" : iscsh == YES ? "_su" : "su";
538
539 if (ruid != 0)
540 syslog(LOG_NOTICE, "%s to %s%s", username, user,
541 ontty());
542
543 execv(shell, np.b);
544 err(1, "%s", shell);
545 }
546 }
547
548 static void
export_pam_environment(void)549 export_pam_environment(void)
550 {
551 char **pp;
552 char *p;
553
554 for (pp = environ_pam; *pp != NULL; pp++) {
555 if (ok_to_export(*pp)) {
556 p = strchr(*pp, '=');
557 *p = '\0';
558 setenv(*pp, p + 1, 1);
559 }
560 free(*pp);
561 }
562 }
563
564 /*
565 * Sanity checks on PAM environmental variables:
566 * - Make sure there is an '=' in the string.
567 * - Make sure the string doesn't run on too long.
568 * - Do not export certain variables. This list was taken from the
569 * Solaris pam_putenv(3) man page.
570 * Note that if the user is chrooted, PAM may have a better idea than we
571 * do of where her home directory is.
572 */
573 static int
ok_to_export(const char * s)574 ok_to_export(const char *s)
575 {
576 static const char *noexport[] = {
577 "SHELL", /* "HOME", */ "LOGNAME", "MAIL", "CDPATH",
578 "IFS", "PATH", NULL
579 };
580 const char **pp;
581 size_t n;
582
583 if (strlen(s) > 1024 || strchr(s, '=') == NULL)
584 return 0;
585 if (strncmp(s, "LD_", 3) == 0)
586 return 0;
587 for (pp = noexport; *pp != NULL; pp++) {
588 n = strlen(*pp);
589 if (s[n] == '=' && strncmp(s, *pp, n) == 0)
590 return 0;
591 }
592 return 1;
593 }
594
595 static void
usage(void)596 usage(void)
597 {
598
599 fprintf(stderr, "usage: su [-] [-flms] [-c class] [login [args]]\n");
600 exit(1);
601 /* NOTREACHED */
602 }
603
604 static int
chshell(const char * sh)605 chshell(const char *sh)
606 {
607 int r;
608 char *cp;
609
610 r = 0;
611 setusershell();
612 while ((cp = getusershell()) != NULL && !r)
613 r = (strcmp(cp, sh) == 0);
614 endusershell();
615 return r;
616 }
617
618 static char *
ontty(void)619 ontty(void)
620 {
621 char *p;
622 static char buf[MAXPATHLEN + 4];
623
624 buf[0] = 0;
625 p = ttyname(STDERR_FILENO);
626 if (p)
627 snprintf(buf, sizeof(buf), " on %s", p);
628 return buf;
629 }
630