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, "you are not in the correct group to su %s.", user); 293 } 294 if (strcmp(username, *g) == 0) { 295 #ifdef WHEELSU 296 iswheelsu = 1; 297 #endif /* WHEELSU */ 298 break; 299 } 300 } 301 } 302 /* if target requires a password, verify it */ 303 if (*pwd->pw_passwd) { 304 #ifdef SKEY 305 #ifdef WHEELSU 306 if (iswheelsu) { 307 pwd = getpwnam(username); 308 } 309 #endif /* WHEELSU */ 310 p = skey_getpass("Password:", pwd, 1); 311 if (!(!strcmp(pwd->pw_passwd, skey_crypt(p, pwd->pw_passwd, pwd, 1)) 312 #ifdef WHEELSU 313 || (iswheelsu && !strcmp(targetpass, crypt(p,targetpass))) 314 #endif /* WHEELSU */ 315 )) 316 #else /* !SKEY */ 317 p = getpass("Password:"); 318 if (strcmp(pwd->pw_passwd, crypt(p, pwd->pw_passwd))) 319 #endif /* SKEY */ 320 { 321 { 322 syslog(LOG_AUTH|LOG_WARNING, "BAD SU %s to %s%s", username, user, ontty()); 323 errx(1, "Sorry"); 324 } 325 } 326 #ifdef WHEELSU 327 if (iswheelsu) { 328 pwd = getpwnam(user); 329 } 330 #endif /* WHEELSU */ 331 } 332 if (pwd->pw_expire && time(NULL) >= pwd->pw_expire) { 333 syslog(LOG_AUTH|LOG_WARNING, 334 "BAD SU %s to %s%s", username, 335 user, ontty()); 336 errx(1, "Sorry - account expired"); 337 } 338 } 339 #endif /* USE_PAM */ 340 341 if (asme) { 342 /* if asme and non-standard target shell, must be root */ 343 if (ruid && !chshell(pwd->pw_shell)) 344 errx(1, "permission denied (shell)."); 345 } else if (pwd->pw_shell && *pwd->pw_shell) { 346 shell = pwd->pw_shell; 347 iscsh = UNSET; 348 } else { 349 shell = _PATH_BSHELL; 350 iscsh = NO; 351 } 352 353 /* if we're forking a csh, we want to slightly muck the args */ 354 if (iscsh == UNSET) { 355 p = strrchr(shell, '/'); 356 if (p) 357 ++p; 358 else 359 p = shell; 360 if ((iscsh = strcmp(p, "csh") ? NO : YES) == NO) 361 iscsh = strcmp(p, "tcsh") ? NO : YES; 362 } 363 364 (void)setpriority(PRIO_PROCESS, 0, prio); 365 366 /* 367 * PAM modules might add supplementary groups in 368 * pam_setcred(), so initialize them first. 369 */ 370 if (setusercontext(lc, pwd, pwd->pw_uid, LOGIN_SETGROUP) < 0) 371 err(1, "setusercontext"); 372 373 #ifdef USE_PAM 374 retcode = pam_setcred(pamh, PAM_ESTABLISH_CRED); 375 if (retcode != PAM_SUCCESS) { 376 syslog(LOG_ERR, "pam_setcred: %s", pam_strerror(pamh, retcode)); 377 } 378 379 /* 380 * We must fork() before setuid() because we need to call 381 * pam_setcred(pamh, PAM_DELETE_CRED) as root. 382 */ 383 384 statusp = 1; 385 switch ((child_pid = fork())) { 386 default: 387 while ((ret_pid = waitpid(child_pid, &statusp, WUNTRACED)) != -1) { 388 if (WIFSTOPPED(statusp)) { 389 child_pgrp = tcgetpgrp(1); 390 kill(getpid(), SIGSTOP); 391 tcsetpgrp(1, child_pgrp); 392 kill(child_pid, SIGCONT); 393 statusp = 1; 394 continue; 395 } 396 break; 397 } 398 if (ret_pid == -1) 399 err(1, "waitpid"); 400 PAM_END; 401 exit(statusp); 402 case -1: 403 err(1, "fork"); 404 PAM_END; 405 exit (1); 406 case 0: 407 #endif /* USE_PAM */ 408 409 /* 410 * Set all user context except for: 411 * Environmental variables 412 * Umask 413 * Login records (wtmp, etc) 414 * Path 415 */ 416 setwhat = LOGIN_SETALL & ~(LOGIN_SETENV | LOGIN_SETUMASK | 417 LOGIN_SETLOGIN | LOGIN_SETPATH | LOGIN_SETGROUP); 418 419 /* 420 * Don't touch resource/priority settings if -m has been 421 * used or -l and -c hasn't, and we're not su'ing to root. 422 */ 423 if ((asme || (!asthem && class == NULL)) && pwd->pw_uid) 424 setwhat &= ~(LOGIN_SETPRIORITY|LOGIN_SETRESOURCES); 425 if (setusercontext(lc, pwd, pwd->pw_uid, setwhat) < 0) 426 err(1, "setusercontext"); 427 428 if (!asme) { 429 if (asthem) { 430 p = getenv("TERM"); 431 environ = &cleanenv; 432 433 #ifdef USE_PAM 434 /* 435 * Add any environmental variables that the 436 * PAM modules may have set. 437 */ 438 environ_pam = pam_getenvlist(pamh); 439 if (environ_pam) 440 export_pam_environment(); 441 #endif /* USE_PAM */ 442 443 /* set the su'd user's environment & umask */ 444 setusercontext(lc, pwd, pwd->pw_uid, LOGIN_SETPATH|LOGIN_SETUMASK|LOGIN_SETENV); 445 if (p) 446 (void)setenv("TERM", p, 1); 447 if (chdir(pwd->pw_dir) < 0) 448 errx(1, "no directory"); 449 } 450 if (asthem || pwd->pw_uid) 451 (void)setenv("USER", pwd->pw_name, 1); 452 (void)setenv("HOME", pwd->pw_dir, 1); 453 (void)setenv("SHELL", shell, 1); 454 } 455 456 login_close(lc); 457 458 if (iscsh == YES) { 459 if (fastlogin) 460 *np-- = "-f"; 461 if (asme) 462 *np-- = "-m"; 463 } 464 465 /* csh strips the first character... */ 466 *np = asthem ? "-su" : iscsh == YES ? "_su" : "su"; 467 468 if (ruid != 0) 469 syslog(LOG_NOTICE, "%s to %s%s", 470 username, user, ontty()); 471 472 execv(shell, np); 473 err(1, "%s", shell); 474 #ifdef USE_PAM 475 } 476 #endif /* USE_PAM */ 477 } 478 479 #ifdef USE_PAM 480 static int 481 export_pam_environment() 482 { 483 char **pp; 484 485 for (pp = environ_pam; *pp != NULL; pp++) { 486 if (ok_to_export(*pp)) 487 (void) putenv(*pp); 488 free(*pp); 489 } 490 return PAM_SUCCESS; 491 } 492 493 /* 494 * Sanity checks on PAM environmental variables: 495 * - Make sure there is an '=' in the string. 496 * - Make sure the string doesn't run on too long. 497 * - Do not export certain variables. This list was taken from the 498 * Solaris pam_putenv(3) man page. 499 */ 500 static int 501 ok_to_export(s) 502 const char *s; 503 { 504 static const char *noexport[] = { 505 "SHELL", "HOME", "LOGNAME", "MAIL", "CDPATH", 506 "IFS", "PATH", NULL 507 }; 508 const char **pp; 509 size_t n; 510 511 if (strlen(s) > 1024 || strchr(s, '=') == NULL) 512 return 0; 513 if (strncmp(s, "LD_", 3) == 0) 514 return 0; 515 for (pp = noexport; *pp != NULL; pp++) { 516 n = strlen(*pp); 517 if (s[n] == '=' && strncmp(s, *pp, n) == 0) 518 return 0; 519 } 520 return 1; 521 } 522 #endif /* USE_PAM */ 523 524 static void 525 usage() 526 { 527 errx(1, "usage: su [%s] [login [args]]", ARGSTR); 528 } 529 530 int 531 chshell(sh) 532 char *sh; 533 { 534 int r = 0; 535 char *cp; 536 537 setusershell(); 538 while (!r && (cp = getusershell()) != NULL) 539 r = strcmp(cp, sh) == 0; 540 endusershell(); 541 return r; 542 } 543 544 char * 545 ontty() 546 { 547 char *p; 548 static char buf[MAXPATHLEN + 4]; 549 550 buf[0] = 0; 551 p = ttyname(STDERR_FILENO); 552 if (p) 553 snprintf(buf, sizeof(buf), " on %s", p); 554 return (buf); 555 } 556