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 * @(#) Copyright (c) 1988, 1993, 1994 The Regents of the University of California. All rights reserved. 34 * @(#)su.c 8.3 (Berkeley) 4/2/94 35 * $FreeBSD: src/usr.bin/su/su.c,v 1.34.2.4 2002/06/16 21:04:15 nectar Exp $ 36 * $DragonFly: src/usr.bin/su/su.c,v 1.9 2006/01/12 13:43:11 corecode Exp $ 37 */ 38 39 #include <sys/cdefs.h> 40 #include <sys/param.h> 41 #include <sys/time.h> 42 #include <sys/resource.h> 43 44 #include <err.h> 45 #include <errno.h> 46 #include <grp.h> 47 #include <paths.h> 48 #include <pwd.h> 49 #include <stdio.h> 50 #include <stdlib.h> 51 #include <string.h> 52 #include <syslog.h> 53 #include <unistd.h> 54 #include <libutil.h> 55 56 #ifdef LOGIN_CAP 57 #include <login_cap.h> 58 #endif 59 60 #ifdef SKEY 61 #include <skey.h> 62 #endif 63 64 #ifdef KERBEROS5 65 #include <krb5.h> 66 67 static long get_su_principal(krb5_context context, const char *target_user, 68 const char *current_user, char **su_principal_name, 69 krb5_principal *su_principal); 70 static long kerberos5(krb5_context context, const char *current_user, 71 const char *target_user, krb5_principal su_principal, 72 const char *pass); 73 74 int use_kerberos5 = 1; 75 #endif 76 77 #ifdef LOGIN_CAP 78 #define LOGIN_CAP_ARG(x) x 79 #else 80 #define LOGIN_CAP_ARG(x) 81 #endif 82 #if defined(KERBEROS5) 83 #define KERBEROS_ARG(x) x 84 #else 85 #define KERBEROS_ARG(x) 86 #endif 87 #define COMMON_ARG(x) x 88 #define ARGSTR "-" COMMON_ARG("flm") LOGIN_CAP_ARG("c:") KERBEROS_ARG("K") 89 90 char *ontty(void); 91 int chshell(char *); 92 static void usage(void); 93 94 extern char **environ; 95 96 int 97 main(int argc, char **argv) 98 { 99 struct passwd *pwd; 100 #ifdef WHEELSU 101 char *targetpass; 102 int iswheelsu; 103 #endif /* WHEELSU */ 104 const char *p, *user, *shell = NULL; 105 const char **nargv, **np; 106 char **g, **cleanenv, *username, *entered_pass; 107 struct group *gr; 108 uid_t ruid; 109 gid_t gid; 110 int asme, ch, asthem, fastlogin, prio, i; 111 enum { UNSET, YES, NO } iscsh = UNSET; 112 #ifdef LOGIN_CAP 113 login_cap_t *lc; 114 char *class=NULL; 115 int setwhat; 116 #endif 117 #if defined(KERBEROS5) 118 char *k; 119 #endif 120 #ifdef KERBEROS5 121 char *su_principal_name, *ccname; 122 krb5_context context; 123 krb5_principal su_principal; 124 #endif 125 char shellbuf[MAXPATHLEN]; 126 127 #ifdef WHEELSU 128 iswheelsu = 129 #endif /* WHEELSU */ 130 asme = asthem = fastlogin = 0; 131 user = "root"; 132 while((ch = getopt(argc, argv, ARGSTR)) != -1) 133 switch((char)ch) { 134 #if defined(KERBEROS5) 135 case 'K': 136 use_kerberos5 = 0; 137 break; 138 #endif 139 case 'f': 140 fastlogin = 1; 141 break; 142 case '-': 143 case 'l': 144 asme = 0; 145 asthem = 1; 146 break; 147 case 'm': 148 asme = 1; 149 asthem = 0; 150 break; 151 #ifdef LOGIN_CAP 152 case 'c': 153 class = optarg; 154 break; 155 #endif 156 case '?': 157 default: 158 usage(); 159 } 160 161 if (optind < argc && strcmp(argv[optind], "-") == 0) { 162 asme = 0; 163 asthem = 1; 164 optind++; 165 } 166 167 if (optind < argc) 168 user = argv[optind++]; 169 170 if (strlen(user) > MAXLOGNAME - 1) { 171 fprintf(stderr, "su: username too long.\n"); 172 exit(1); 173 } 174 175 if (user == NULL) 176 usage(); 177 178 if ((nargv = malloc (sizeof (char *) * (argc + 4))) == NULL) { 179 errx(1, "malloc failure"); 180 } 181 182 nargv[argc + 3] = NULL; 183 for (i = argc; i >= optind; i--) 184 nargv[i + 3] = argv[i]; 185 np = &nargv[i + 3]; 186 187 argv += optind; 188 189 #if defined(KERBEROS5) 190 k = auth_getval("auth_list"); 191 if (k && !strstr(k, "kerberos")) { 192 use_kerberos5 = 0; 193 } 194 su_principal_name = NULL; 195 su_principal = NULL; 196 if (krb5_init_context(&context) != 0) 197 use_kerberos5 = 0; 198 #endif 199 errno = 0; 200 prio = getpriority(PRIO_PROCESS, 0); 201 if (errno) 202 prio = 0; 203 setpriority(PRIO_PROCESS, 0, -2); 204 openlog("su", LOG_CONS, 0); 205 206 /* get current login name and shell */ 207 ruid = getuid(); 208 username = getlogin(); 209 if (username == NULL || (pwd = getpwnam(username)) == NULL || 210 pwd->pw_uid != ruid) 211 pwd = getpwuid(ruid); 212 if (pwd == NULL) 213 errx(1, "who are you?"); 214 username = strdup(pwd->pw_name); 215 gid = pwd->pw_gid; 216 if (username == NULL) 217 err(1, NULL); 218 if (asme) { 219 if (pwd->pw_shell != NULL && *pwd->pw_shell != '\0') { 220 /* copy: pwd memory is recycled */ 221 shell = strncpy(shellbuf, pwd->pw_shell, sizeof shellbuf); 222 shellbuf[sizeof shellbuf - 1] = '\0'; 223 } else { 224 shell = _PATH_BSHELL; 225 iscsh = NO; 226 } 227 } 228 229 /* get target login information, default to root */ 230 if ((pwd = getpwnam(user)) == NULL) { 231 errx(1, "unknown login: %s", user); 232 } 233 #ifdef LOGIN_CAP 234 if (class==NULL) { 235 lc = login_getpwclass(pwd); 236 } else { 237 if (ruid) 238 errx(1, "only root may use -c"); 239 lc = login_getclass(class); 240 if (lc == NULL) 241 errx(1, "unknown class: %s", class); 242 } 243 #endif 244 245 #ifdef WHEELSU 246 targetpass = strdup(pwd->pw_passwd); 247 #endif /* WHEELSU */ 248 249 if (ruid) { 250 #ifdef KERBEROS5 251 if (use_kerberos5) { 252 if (get_su_principal(context, user, username, 253 &su_principal_name, &su_principal) != 0 || 254 !krb5_kuserok(context, su_principal, user)) { 255 warnx("kerberos5: not in %s's ACL.", user); 256 use_kerberos5 = 0; 257 } 258 } 259 #endif 260 /* 261 * Only allow those with pw_gid==0 or those listed in 262 * group zero to su to root. If group zero entry is 263 * missing or empty, then allow anyone to su to root. 264 * iswheelsu will only be set if the user is EXPLICITLY 265 * listed in group zero. 266 */ 267 if (pwd->pw_uid == 0 && (gr = getgrgid((gid_t)0)) && 268 gr->gr_mem && *(gr->gr_mem)) { 269 for (g = gr->gr_mem;; ++g) { 270 if (!*g) { 271 if (gid == 0) 272 break; 273 else 274 errx(1, 275 "you are not in the correct group (%s) to su %s.", 276 gr->gr_name, 277 user); 278 } 279 if (strcmp(username, *g) == 0) { 280 #ifdef WHEELSU 281 iswheelsu = 1; 282 #endif /* WHEELSU */ 283 break; 284 } 285 } 286 } 287 /* if target requires a password, verify it */ 288 if (*pwd->pw_passwd) { 289 #ifdef SKEY 290 #ifdef WHEELSU 291 if (iswheelsu) { 292 pwd = getpwnam(username); 293 } 294 #endif /* WHEELSU */ 295 entered_pass = skey_getpass("Password:", pwd, 1); 296 if (!(!strcmp(pwd->pw_passwd, skey_crypt(entered_pass, 297 pwd->pw_passwd, pwd, 1)) 298 #ifdef WHEELSU 299 || (iswheelsu && !strcmp(targetpass, 300 crypt(entered_pass, targetpass))) 301 #endif /* WHEELSU */ 302 )) { 303 #else 304 entered_pass = getpass("Password:"); 305 if (strcmp(pwd->pw_passwd, crypt(entered_pass, 306 pwd->pw_passwd))) { 307 #endif 308 #ifdef KERBEROS5 309 if (use_kerberos5 && kerberos5(context, 310 username, user, su_principal, 311 entered_pass) == 0) 312 goto authok; 313 #endif 314 fprintf(stderr, "Sorry\n"); 315 syslog(LOG_AUTH|LOG_WARNING, 316 "BAD SU %s to %s%s", username, user, 317 ontty()); 318 exit(1); 319 } 320 #if defined(KERBEROS5) 321 authok: 322 ; 323 #endif 324 #ifdef WHEELSU 325 if (iswheelsu) { 326 pwd = getpwnam(user); 327 } 328 #endif /* WHEELSU */ 329 } 330 if (pwd->pw_expire && time(NULL) >= pwd->pw_expire) { 331 fprintf(stderr, "Sorry - account expired\n"); 332 syslog(LOG_AUTH|LOG_WARNING, 333 "BAD SU %s to %s%s", username, 334 user, ontty()); 335 exit(1); 336 } 337 } 338 339 if (asme) { 340 /* if asme and non-standard target shell, must be root */ 341 if (!chshell(pwd->pw_shell) && ruid) 342 errx(1, "permission denied (shell)."); 343 } else if (pwd->pw_shell && *pwd->pw_shell) { 344 shell = pwd->pw_shell; 345 iscsh = UNSET; 346 } else { 347 shell = _PATH_BSHELL; 348 iscsh = NO; 349 } 350 351 /* if we're forking a csh, we want to slightly muck the args */ 352 if (iscsh == UNSET) { 353 p = strrchr(shell, '/'); 354 if (p) 355 ++p; 356 else 357 p = shell; 358 if ((iscsh = strcmp(p, "csh") ? NO : YES) == NO) 359 iscsh = strcmp(p, "tcsh") ? NO : YES; 360 } 361 362 setpriority(PRIO_PROCESS, 0, prio); 363 364 #ifdef LOGIN_CAP 365 /* Set everything now except the environment & umask */ 366 setwhat = LOGIN_SETUSER|LOGIN_SETGROUP|LOGIN_SETRESOURCES|LOGIN_SETPRIORITY; 367 /* 368 * Don't touch resource/priority settings if -m has been 369 * used or -l and -c hasn't, and we're not su'ing to root. 370 */ 371 if ((asme || (!asthem && class == NULL)) && pwd->pw_uid) 372 setwhat &= ~(LOGIN_SETPRIORITY|LOGIN_SETRESOURCES); 373 if (setusercontext(lc, pwd, pwd->pw_uid, setwhat) < 0) 374 err(1, "setusercontext"); 375 #else 376 /* set permissions */ 377 if (setgid(pwd->pw_gid) < 0) 378 err(1, "setgid"); 379 if (initgroups(user, pwd->pw_gid)) 380 errx(1, "initgroups failed"); 381 if (setuid(pwd->pw_uid) < 0) 382 err(1, "setuid"); 383 #endif 384 385 if (!asme) { 386 if (asthem) { 387 p = getenv("TERM"); 388 #ifdef KERBEROS5 389 ccname = getenv("KRB5CCNAME"); 390 #endif 391 if ((cleanenv = calloc(20, sizeof(char*))) == NULL) 392 errx(1, "calloc"); 393 cleanenv[0] = NULL; 394 environ = cleanenv; 395 #ifdef LOGIN_CAP 396 /* set the su'd user's environment & umask */ 397 setusercontext(lc, pwd, pwd->pw_uid, LOGIN_SETPATH|LOGIN_SETUMASK|LOGIN_SETENV); 398 #else 399 if (setenv("PATH", _PATH_DEFPATH, 1) == -1) 400 err(1, "setenv: cannot set PATH=%s", _PATH_DEFPATH); 401 #endif 402 if (p) { 403 if (setenv("TERM", p, 1) == -1) 404 err(1, "setenv: cannot set TERM=%s", p); 405 } 406 #ifdef KERBEROS5 407 if (ccname) { 408 if (setenv("KRB5CCNAME", ccname, 1) == -1) 409 err(1, "setenv: cannot set KRB5CCNAME=%s", ccname); 410 } 411 #endif 412 if (chdir(pwd->pw_dir) < 0) 413 errx(1, "no directory"); 414 } 415 if (asthem || pwd->pw_uid) { 416 if (setenv("USER", pwd->pw_name, 1) == -1) 417 err(1, "setenv: cannot set USER=%s", pwd->pw_name); 418 } 419 if (setenv("HOME", pwd->pw_dir, 1) == -1) 420 err(1, "setenv: cannot set HOME=%s", pwd->pw_dir); 421 if (setenv("SHELL", shell, 1) == -1) 422 err(1, "setenv: cannot set SHELL=%s", shell); 423 } 424 if (iscsh == YES) { 425 if (fastlogin) 426 *np-- = "-f"; 427 if (asme) 428 *np-- = "-m"; 429 } 430 431 /* csh strips the first character... */ 432 *np = asthem ? "-su" : iscsh == YES ? "_su" : "su"; 433 434 if (ruid != 0) 435 syslog(LOG_NOTICE|LOG_AUTH, "%s to %s%s", 436 username, user, ontty()); 437 438 #ifdef LOGIN_CAP 439 login_close(lc); 440 #endif 441 442 execv(shell, __DECONST(char * const *, np)); 443 err(1, "%s", shell); 444 } 445 446 static void 447 usage(void) 448 { 449 fprintf(stderr, "usage: su [-] [-%s] %s[login [args]]\n", 450 KERBEROS_ARG("K") COMMON_ARG("flm"), 451 #ifdef LOGIN_CAP 452 "[-c class] " 453 #else 454 "" 455 #endif 456 ); 457 exit(1); 458 } 459 460 int 461 chshell(char *sh) 462 { 463 int r = 0; 464 char *cp; 465 466 setusershell(); 467 while (!r && (cp = getusershell()) != NULL) 468 r = strcmp(cp, sh) == 0; 469 endusershell(); 470 return r; 471 } 472 473 char * 474 ontty(void) 475 { 476 char *p; 477 static char buf[MAXPATHLEN + 4]; 478 479 buf[0] = 0; 480 p = ttyname(STDERR_FILENO); 481 if (p) 482 snprintf(buf, sizeof(buf), " on %s", p); 483 return (buf); 484 } 485 486 #ifdef KERBEROS5 487 const char superuser[] = "root"; 488 489 /* Authenticate using Kerberos 5. 490 * context -- An initialized krb5_context. 491 * current_user -- The current username. 492 * target_user -- The target account name. 493 * su_principal -- The target krb5_principal. 494 * pass -- The user's password. 495 * Note that a valid keytab in the default location with a host entry 496 * must be available. 497 * Returns 0 if authentication was successful, or a com_err error code if 498 * it was not. 499 */ 500 static long 501 kerberos5(krb5_context context, const char *current_user, 502 const char *target_user, krb5_principal su_principal, 503 const char *pass) 504 { 505 krb5_creds creds; 506 krb5_get_init_creds_opt gic_opt; 507 krb5_verify_init_creds_opt vic_opt; 508 long rv; 509 510 krb5_get_init_creds_opt_init(&gic_opt); 511 krb5_verify_init_creds_opt_init(&vic_opt); 512 rv = krb5_get_init_creds_password(context, &creds, su_principal, 513 pass, NULL, NULL, 0, NULL, &gic_opt); 514 if (rv != 0) { 515 syslog(LOG_NOTICE|LOG_AUTH, "BAD Kerberos5 SU: %s to %s%s: %s", 516 current_user, target_user, ontty(), 517 krb5_get_err_text(context, rv)); 518 return (rv); 519 } 520 krb5_verify_init_creds_opt_set_ap_req_nofail(&vic_opt, 1); 521 rv = krb5_verify_init_creds(context, &creds, NULL, NULL, NULL, 522 &vic_opt); 523 krb5_free_cred_contents(context, &creds); 524 if (rv != 0) { 525 syslog(LOG_NOTICE|LOG_AUTH, "BAD Kerberos5 SU: %s to %s%s: %s", 526 current_user, target_user, ontty(), 527 krb5_get_err_text(context, rv)); 528 return (rv); 529 } 530 return (0); 531 } 532 533 /* Determine the target principal given the current user and the target user. 534 * context -- An initialized krb5_context. 535 * target_user -- The target username. 536 * current_user -- The current username. 537 * su_principal_name -- (out) The target principal name. 538 * su_principal -- (out) The target krb5_principal. 539 * 540 * When target_user is `root', the su_principal will be a `root 541 * instance', e.g. `luser/root@REA.LM'. Otherwise, the su_principal 542 * will simply be the current user's default principal name. Note that 543 * in any case, if KRB5CCNAME is set and a credentials cache exists, the 544 * principal name found there will be the `starting point', rather than 545 * the current_user parameter. 546 * 547 * Returns 0 for success, or a com_err error code on failure. 548 */ 549 static long 550 get_su_principal(krb5_context context, const char *target_user, 551 const char *current_user, char **su_principal_name, 552 krb5_principal *su_principal) 553 { 554 krb5_principal default_principal; 555 krb5_ccache ccache; 556 char *principal_name, *ccname, *p; 557 long rv; 558 uid_t euid, ruid; 559 560 *su_principal = NULL; 561 default_principal = NULL; 562 /* Lower privs while messing about with the credentials 563 * cache. 564 */ 565 ruid = getuid(); 566 euid = geteuid(); 567 rv = seteuid(getuid()); 568 if (rv != 0) 569 return (errno); 570 p = getenv("KRB5CCNAME"); 571 if (p != NULL) 572 ccname = strdup(p); 573 else { 574 asprintf(&ccname, "%s%lu", KRB5_DEFAULT_CCROOT, 575 (unsigned long)ruid); 576 } 577 if (ccname == NULL) 578 return (errno); 579 rv = krb5_cc_resolve(context, ccname, &ccache); 580 free(ccname); 581 if (rv == 0) { 582 rv = krb5_cc_get_principal(context, ccache, 583 &default_principal); 584 krb5_cc_close(context, ccache); 585 if (rv != 0) 586 default_principal = NULL; /* just to be safe */ 587 } 588 rv = seteuid(euid); 589 if (rv != 0) 590 return (errno); 591 if (default_principal == NULL) { 592 rv = krb5_make_principal(context, &default_principal, NULL, 593 current_user, NULL); 594 if (rv != 0) { 595 warnx("Could not determine default principal name."); 596 return (rv); 597 } 598 } 599 /* Now that we have some principal, if the target account is 600 * `root', then transform it into a `root' instance, e.g. 601 * `user@REA.LM' -> `user/root@REA.LM'. 602 */ 603 rv = krb5_unparse_name(context, default_principal, &principal_name); 604 krb5_free_principal(context, default_principal); 605 if (rv != 0) { 606 warnx("krb5_unparse_name: %s", krb5_get_err_text(context, rv)); 607 return (rv); 608 } 609 if (strcmp(target_user, superuser) == 0) { 610 p = strrchr(principal_name, '@'); 611 if (p == NULL) { 612 warnx("malformed principal name `%s'", principal_name); 613 free(principal_name); 614 return (rv); 615 } 616 *p++ = '\0'; 617 asprintf(su_principal_name, "%s/%s@%s", principal_name, 618 superuser, p); 619 free(principal_name); 620 } else 621 *su_principal_name = principal_name; 622 if (*su_principal_name == NULL) 623 return errno; 624 rv = krb5_parse_name(context, *su_principal_name, &default_principal); 625 if (rv != 0) { 626 warnx("krb5_parse_name `%s': %s", *su_principal_name, 627 krb5_get_err_text(context, rv)); 628 free(*su_principal_name); 629 return (rv); 630 } 631 *su_principal = default_principal; 632 return 0; 633 } 634 635 #endif 636