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