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