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