1 /* 2 * Copyright (c) 1988, 1993, 1994 3 * The Regents of the University of California. All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 3. All advertising materials mentioning features or use of this software 14 * must display the following acknowledgement: 15 * This product includes software developed by the University of 16 * California, Berkeley and its contributors. 17 * 4. Neither the name of the University nor the names of its contributors 18 * may be used to endorse or promote products derived from this software 19 * without specific prior written permission. 20 * 21 * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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 #ifndef lint 35 static const char copyright[] = 36 "@(#) Copyright (c) 1988, 1993, 1994\n\ 37 The Regents of the University of California. All rights reserved.\n"; 38 #endif /* not lint */ 39 40 #ifndef lint 41 #if 0 42 static char sccsid[] = "@(#)su.c 8.3 (Berkeley) 4/2/94"; 43 #endif 44 static const char rcsid[] = 45 "$FreeBSD$"; 46 #endif /* not lint */ 47 48 #include <sys/param.h> 49 #include <sys/time.h> 50 #include <sys/resource.h> 51 52 #include <err.h> 53 #include <errno.h> 54 #include <grp.h> 55 #include <paths.h> 56 #include <pwd.h> 57 #include <stdio.h> 58 #include <stdlib.h> 59 #include <string.h> 60 #include <syslog.h> 61 #include <unistd.h> 62 #include <libutil.h> 63 #include <login_cap.h> 64 65 #ifdef USE_PAM 66 #include <security/pam_appl.h> 67 #include <security/pam_misc.h> 68 #include <signal.h> 69 #include <sys/wait.h> 70 71 static int export_pam_environment __P((void)); 72 static int ok_to_export __P((const char *)); 73 74 static pam_handle_t *pamh = NULL; 75 static char **environ_pam; 76 77 #define PAM_END { \ 78 if ((retcode = pam_setcred(pamh, PAM_DELETE_CRED)) != PAM_SUCCESS) { \ 79 syslog(LOG_ERR, "pam_setcred: %s", pam_strerror(pamh, retcode)); \ 80 } \ 81 if ((retcode = pam_end(pamh,retcode)) != PAM_SUCCESS) { \ 82 syslog(LOG_ERR, "pam_end: %s", pam_strerror(pamh, retcode)); \ 83 } \ 84 } 85 #else /* !USE_PAM */ 86 #ifdef SKEY 87 #include <skey.h> 88 #endif 89 #endif /* USE_PAM */ 90 91 #define ARGSTR "-flmc:" 92 93 char *ontty __P((void)); 94 int chshell __P((char *)); 95 static void usage __P((void)); 96 97 int 98 main(argc, argv) 99 int argc; 100 char **argv; 101 { 102 extern char **environ; 103 struct passwd *pwd; 104 #ifdef WHEELSU 105 char *targetpass; 106 int iswheelsu; 107 #endif /* WHEELSU */ 108 char *p, *user, *shell=NULL, *username, *cleanenv = NULL, **nargv, **np; 109 uid_t ruid; 110 gid_t gid; 111 int asme, ch, asthem, fastlogin, prio, i; 112 enum { UNSET, YES, NO } iscsh = UNSET; 113 login_cap_t *lc; 114 char *class=NULL; 115 int setwhat; 116 #ifdef USE_PAM 117 int retcode; 118 struct pam_conv conv = { misc_conv, NULL }; 119 char myhost[MAXHOSTNAMELEN + 1], *mytty; 120 int statusp=0; 121 int child_pid, child_pgrp, ret_pid; 122 #else /* !USE_PAM */ 123 char **g; 124 struct group *gr; 125 #endif /* USE_PAM */ 126 char shellbuf[MAXPATHLEN]; 127 128 #ifdef WHEELSU 129 iswheelsu = 130 #endif /* WHEELSU */ 131 asme = asthem = fastlogin = 0; 132 user = "root"; 133 while((ch = getopt(argc, argv, ARGSTR)) != -1) 134 switch((char)ch) { 135 case 'f': 136 fastlogin = 1; 137 break; 138 case '-': 139 case 'l': 140 asme = 0; 141 asthem = 1; 142 break; 143 case 'm': 144 asme = 1; 145 asthem = 0; 146 break; 147 case 'c': 148 class = optarg; 149 break; 150 case '?': 151 default: 152 usage(); 153 } 154 155 if (optind < argc) 156 user = argv[optind++]; 157 158 if (strlen(user) > MAXLOGNAME - 1) { 159 errx(1, "username too long"); 160 } 161 162 if (user == NULL) 163 usage(); 164 165 if ((nargv = malloc (sizeof (char *) * (argc + 4))) == NULL) { 166 errx(1, "malloc failure"); 167 } 168 169 nargv[argc + 3] = NULL; 170 for (i = argc; i >= optind; i--) 171 nargv[i + 3] = argv[i]; 172 np = &nargv[i + 3]; 173 174 argv += optind; 175 176 errno = 0; 177 prio = getpriority(PRIO_PROCESS, 0); 178 if (errno) 179 prio = 0; 180 (void)setpriority(PRIO_PROCESS, 0, -2); 181 openlog("su", LOG_CONS, LOG_AUTH); 182 183 /* get current login name and shell */ 184 ruid = getuid(); 185 username = getlogin(); 186 if (username == NULL || (pwd = getpwnam(username)) == NULL || 187 pwd->pw_uid != ruid) 188 pwd = getpwuid(ruid); 189 if (pwd == NULL) 190 errx(1, "who are you?"); 191 username = strdup(pwd->pw_name); 192 gid = pwd->pw_gid; 193 if (username == NULL) 194 err(1, NULL); 195 if (asme) { 196 if (pwd->pw_shell != NULL && *pwd->pw_shell != '\0') { 197 /* copy: pwd memory is recycled */ 198 shell = strncpy(shellbuf, pwd->pw_shell, sizeof shellbuf); 199 shellbuf[sizeof shellbuf - 1] = '\0'; 200 } else { 201 shell = _PATH_BSHELL; 202 iscsh = NO; 203 } 204 } 205 206 #ifdef USE_PAM 207 retcode = pam_start("su", user, &conv, &pamh); 208 if (retcode != PAM_SUCCESS) { 209 syslog(LOG_ERR, "pam_start: %s", pam_strerror(pamh, retcode)); 210 errx(1, "pam_start: %s", pam_strerror(pamh, retcode)); 211 } 212 213 gethostname(myhost, sizeof(myhost)); 214 retcode = pam_set_item(pamh, PAM_RHOST, myhost); 215 if (retcode != PAM_SUCCESS) { 216 syslog(LOG_ERR, "pam_set_item(PAM_RHOST): %s", pam_strerror(pamh, retcode)); 217 errx(1, "pam_set_item(PAM_RHOST): %s", pam_strerror(pamh, retcode)); 218 } 219 220 mytty = ttyname(STDERR_FILENO); 221 if (!mytty) 222 mytty = "tty"; 223 retcode = pam_set_item(pamh, PAM_TTY, mytty); 224 if (retcode != PAM_SUCCESS) { 225 syslog(LOG_ERR, "pam_set_item(PAM_TTY): %s", pam_strerror(pamh, retcode)); 226 errx(1, "pam_set_item(PAM_TTY): %s", pam_strerror(pamh, retcode)); 227 } 228 229 if (ruid) { 230 retcode = pam_authenticate(pamh, 0); 231 if (retcode != PAM_SUCCESS) { 232 syslog(LOG_ERR, "pam_authenticate: %s", pam_strerror(pamh, retcode)); 233 errx(1, "Sorry"); 234 } 235 236 if ((retcode = pam_get_item(pamh, PAM_USER, (const void **) &p)) == PAM_SUCCESS) { 237 user = p; 238 } else 239 syslog(LOG_ERR, "pam_get_item(PAM_USER): %s", 240 pam_strerror(pamh, retcode)); 241 242 retcode = pam_acct_mgmt(pamh, 0); 243 if (retcode == PAM_NEW_AUTHTOK_REQD) { 244 retcode = pam_chauthtok(pamh, PAM_CHANGE_EXPIRED_AUTHTOK); 245 if (retcode != PAM_SUCCESS) { 246 syslog(LOG_ERR, "pam_chauthtok: %s", pam_strerror(pamh, retcode)); 247 errx(1, "Sorry"); 248 } 249 } 250 if (retcode != PAM_SUCCESS) { 251 syslog(LOG_ERR, "pam_acct_mgmt: %s", pam_strerror(pamh, retcode)); 252 errx(1, "Sorry"); 253 } 254 } 255 #endif /* USE_PAM */ 256 257 /* get target login information, default to root */ 258 if ((pwd = getpwnam(user)) == NULL) { 259 errx(1, "unknown login: %s", user); 260 } 261 if (class==NULL) { 262 lc = login_getpwclass(pwd); 263 } else { 264 if (ruid) 265 errx(1, "only root may use -c"); 266 lc = login_getclass(class); 267 if (lc == NULL) 268 errx(1, "unknown class: %s", class); 269 } 270 271 #ifndef USE_PAM 272 #ifdef WHEELSU 273 targetpass = strdup(pwd->pw_passwd); 274 #endif /* WHEELSU */ 275 276 if (ruid) { 277 { 278 /* 279 * Only allow those with pw_gid==0 or those listed in 280 * group zero to su to root. If group zero entry is 281 * missing or empty, then allow anyone to su to root. 282 * iswheelsu will only be set if the user is EXPLICITLY 283 * listed in group zero. 284 */ 285 if (pwd->pw_uid == 0 && (gr = getgrgid((gid_t)0)) && 286 gr->gr_mem && *(gr->gr_mem)) 287 for (g = gr->gr_mem;; ++g) { 288 if (!*g) { 289 if (gid == 0) 290 break; 291 else 292 errx(1, 293 "you are not in the correct group (%s) to su %s.", 294 gr->gr_name, 295 user); 296 } 297 if (strcmp(username, *g) == 0) { 298 #ifdef WHEELSU 299 iswheelsu = 1; 300 #endif /* WHEELSU */ 301 break; 302 } 303 } 304 } 305 /* if target requires a password, verify it */ 306 if (*pwd->pw_passwd) { 307 #ifdef SKEY 308 #ifdef WHEELSU 309 if (iswheelsu) { 310 pwd = getpwnam(username); 311 } 312 #endif /* WHEELSU */ 313 p = skey_getpass("Password:", pwd, 1); 314 if (!(!strcmp(pwd->pw_passwd, skey_crypt(p, pwd->pw_passwd, pwd, 1)) 315 #ifdef WHEELSU 316 || (iswheelsu && !strcmp(targetpass, crypt(p,targetpass))) 317 #endif /* WHEELSU */ 318 )) 319 #else /* !SKEY */ 320 p = getpass("Password:"); 321 if (strcmp(pwd->pw_passwd, crypt(p, pwd->pw_passwd))) 322 #endif /* SKEY */ 323 { 324 { 325 syslog(LOG_AUTH|LOG_WARNING, "BAD SU %s to %s%s", username, user, ontty()); 326 errx(1, "Sorry"); 327 } 328 } 329 #ifdef WHEELSU 330 if (iswheelsu) { 331 pwd = getpwnam(user); 332 } 333 #endif /* WHEELSU */ 334 } 335 if (pwd->pw_expire && time(NULL) >= pwd->pw_expire) { 336 syslog(LOG_AUTH|LOG_WARNING, 337 "BAD SU %s to %s%s", username, 338 user, ontty()); 339 errx(1, "Sorry - account expired"); 340 } 341 } 342 #endif /* USE_PAM */ 343 344 if (asme) { 345 /* if asme and non-standard target shell, must be root */ 346 if (ruid && !chshell(pwd->pw_shell)) 347 errx(1, "permission denied (shell)."); 348 } else if (pwd->pw_shell && *pwd->pw_shell) { 349 shell = pwd->pw_shell; 350 iscsh = UNSET; 351 } else { 352 shell = _PATH_BSHELL; 353 iscsh = NO; 354 } 355 356 /* if we're forking a csh, we want to slightly muck the args */ 357 if (iscsh == UNSET) { 358 p = strrchr(shell, '/'); 359 if (p) 360 ++p; 361 else 362 p = shell; 363 if ((iscsh = strcmp(p, "csh") ? NO : YES) == NO) 364 iscsh = strcmp(p, "tcsh") ? NO : YES; 365 } 366 367 (void)setpriority(PRIO_PROCESS, 0, prio); 368 369 /* 370 * PAM modules might add supplementary groups in 371 * pam_setcred(), so initialize them first. 372 */ 373 if (setusercontext(lc, pwd, pwd->pw_uid, LOGIN_SETGROUP) < 0) 374 err(1, "setusercontext"); 375 376 #ifdef USE_PAM 377 retcode = pam_setcred(pamh, PAM_ESTABLISH_CRED); 378 if (retcode != PAM_SUCCESS) { 379 syslog(LOG_ERR, "pam_setcred: %s", pam_strerror(pamh, retcode)); 380 } 381 382 /* 383 * We must fork() before setuid() because we need to call 384 * pam_setcred(pamh, PAM_DELETE_CRED) as root. 385 */ 386 387 statusp = 1; 388 switch ((child_pid = fork())) { 389 default: 390 while ((ret_pid = waitpid(child_pid, &statusp, WUNTRACED)) != -1) { 391 if (WIFSTOPPED(statusp)) { 392 child_pgrp = tcgetpgrp(1); 393 kill(getpid(), SIGSTOP); 394 tcsetpgrp(1, child_pgrp); 395 kill(child_pid, SIGCONT); 396 statusp = 1; 397 continue; 398 } 399 break; 400 } 401 if (ret_pid == -1) 402 err(1, "waitpid"); 403 PAM_END; 404 exit(statusp); 405 case -1: 406 err(1, "fork"); 407 PAM_END; 408 exit (1); 409 case 0: 410 #endif /* USE_PAM */ 411 412 /* 413 * Set all user context except for: 414 * Environmental variables 415 * Umask 416 * Login records (wtmp, etc) 417 * Path 418 */ 419 setwhat = LOGIN_SETALL & ~(LOGIN_SETENV | LOGIN_SETUMASK | 420 LOGIN_SETLOGIN | LOGIN_SETPATH | LOGIN_SETGROUP); 421 422 /* 423 * Don't touch resource/priority settings if -m has been 424 * used or -l and -c hasn't, and we're not su'ing to root. 425 */ 426 if ((asme || (!asthem && class == NULL)) && pwd->pw_uid) 427 setwhat &= ~(LOGIN_SETPRIORITY|LOGIN_SETRESOURCES); 428 if (setusercontext(lc, pwd, pwd->pw_uid, setwhat) < 0) 429 err(1, "setusercontext"); 430 431 if (!asme) { 432 if (asthem) { 433 p = getenv("TERM"); 434 environ = &cleanenv; 435 436 #ifdef USE_PAM 437 /* 438 * Add any environmental variables that the 439 * PAM modules may have set. 440 */ 441 environ_pam = pam_getenvlist(pamh); 442 if (environ_pam) 443 export_pam_environment(); 444 #endif /* USE_PAM */ 445 446 /* set the su'd user's environment & umask */ 447 setusercontext(lc, pwd, pwd->pw_uid, LOGIN_SETPATH|LOGIN_SETUMASK|LOGIN_SETENV); 448 if (p) 449 (void)setenv("TERM", p, 1); 450 if (chdir(pwd->pw_dir) < 0) 451 errx(1, "no directory"); 452 } 453 if (asthem || pwd->pw_uid) 454 (void)setenv("USER", pwd->pw_name, 1); 455 (void)setenv("HOME", pwd->pw_dir, 1); 456 (void)setenv("SHELL", shell, 1); 457 } 458 459 login_close(lc); 460 461 if (iscsh == YES) { 462 if (fastlogin) 463 *np-- = "-f"; 464 if (asme) 465 *np-- = "-m"; 466 } 467 468 /* csh strips the first character... */ 469 *np = asthem ? "-su" : iscsh == YES ? "_su" : "su"; 470 471 if (ruid != 0) 472 syslog(LOG_NOTICE, "%s to %s%s", 473 username, user, ontty()); 474 475 execv(shell, np); 476 err(1, "%s", shell); 477 #ifdef USE_PAM 478 } 479 #endif /* USE_PAM */ 480 } 481 482 #ifdef USE_PAM 483 static int 484 export_pam_environment() 485 { 486 char **pp; 487 488 for (pp = environ_pam; *pp != NULL; pp++) { 489 if (ok_to_export(*pp)) 490 (void) putenv(*pp); 491 free(*pp); 492 } 493 return PAM_SUCCESS; 494 } 495 496 /* 497 * Sanity checks on PAM environmental variables: 498 * - Make sure there is an '=' in the string. 499 * - Make sure the string doesn't run on too long. 500 * - Do not export certain variables. This list was taken from the 501 * Solaris pam_putenv(3) man page. 502 */ 503 static int 504 ok_to_export(s) 505 const char *s; 506 { 507 static const char *noexport[] = { 508 "SHELL", "HOME", "LOGNAME", "MAIL", "CDPATH", 509 "IFS", "PATH", NULL 510 }; 511 const char **pp; 512 size_t n; 513 514 if (strlen(s) > 1024 || strchr(s, '=') == NULL) 515 return 0; 516 if (strncmp(s, "LD_", 3) == 0) 517 return 0; 518 for (pp = noexport; *pp != NULL; pp++) { 519 n = strlen(*pp); 520 if (s[n] == '=' && strncmp(s, *pp, n) == 0) 521 return 0; 522 } 523 return 1; 524 } 525 #endif /* USE_PAM */ 526 527 static void 528 usage() 529 { 530 errx(1, "usage: su [%s] [login [args]]", ARGSTR); 531 } 532 533 int 534 chshell(sh) 535 char *sh; 536 { 537 int r = 0; 538 char *cp; 539 540 setusershell(); 541 while (!r && (cp = getusershell()) != NULL) 542 r = strcmp(cp, sh) == 0; 543 endusershell(); 544 return r; 545 } 546 547 char * 548 ontty() 549 { 550 char *p; 551 static char buf[MAXPATHLEN + 4]; 552 553 buf[0] = 0; 554 p = ttyname(STDERR_FILENO); 555 if (p) 556 snprintf(buf, sizeof(buf), " on %s", p); 557 return (buf); 558 } 559