1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License, Version 1.0 only 6 * (the "License"). You may not use this file except in compliance 7 * with the License. 8 * 9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 10 * or http://www.opensolaris.org/os/licensing. 11 * See the License for the specific language governing permissions 12 * and limitations under the License. 13 * 14 * When distributing Covered Code, include this CDDL HEADER in each 15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 16 * If applicable, add the following below this CDDL HEADER, with the 17 * fields enclosed by brackets "[]" replaced with your own identifying 18 * information: Portions Copyright [yyyy] [name of copyright owner] 19 * 20 * CDDL HEADER END 21 */ 22 /* 23 * Copyright 2005 Sun Microsystems, Inc. All rights reserved. 24 * Use is subject to license terms. 25 */ 26 27 /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ 28 /* All Rights Reserved */ 29 30 /* Copyright (c) 1987, 1988 Microsoft Corporation */ 31 /* All Rights Reserved */ 32 33 #pragma ident "%Z%%M% %I% %E% SMI" 34 35 /* 36 * su [-] [name [arg ...]] change userid, `-' changes environment. 37 * If SULOG is defined, all attempts to su to another user are 38 * logged there. 39 * If CONSOLE is defined, all successful attempts to su to uid 0 40 * are also logged there. 41 * 42 * If su cannot create, open, or write entries into SULOG, 43 * (or on the CONSOLE, if defined), the entry will not 44 * be logged -- thus losing a record of the su's attempted 45 * during this period. 46 */ 47 48 #include <stdio.h> 49 #include <sys/types.h> 50 #include <sys/stat.h> 51 #include <sys/param.h> 52 #include <unistd.h> 53 #include <stdlib.h> 54 #include <crypt.h> 55 #include <pwd.h> 56 #include <shadow.h> 57 #include <time.h> 58 #include <signal.h> 59 #include <fcntl.h> 60 #include <string.h> 61 #include <locale.h> 62 #include <syslog.h> 63 #include <sys/utsname.h> 64 #include <grp.h> 65 #include <deflt.h> 66 #include <limits.h> 67 #include <errno.h> 68 #include <stdarg.h> 69 70 #include <bsm/adt.h> 71 #include <bsm/adt_event.h> 72 73 #include <security/pam_appl.h> 74 75 #define PATH "/usr/bin:" /* path for users other than root */ 76 #define SUPATH "/usr/sbin:/usr/bin" /* path for root */ 77 #define SUPRMT "PS1=# " /* primary prompt for root */ 78 #define ELIM 128 79 #define ROOT 0 80 #ifdef DYNAMIC_SU 81 #define EMBEDDED_NAME "embedded_su" 82 #define DEF_ATTEMPTS 3 /* attempts to change password */ 83 #endif /* DYNAMIC_SU */ 84 85 #define PW_FALSE 1 /* no password change */ 86 #define PW_TRUE 2 /* successful password change */ 87 #define PW_FAILED 3 /* failed password change */ 88 89 /* 90 * Intervals to sleep after failed su 91 */ 92 #ifndef SLEEPTIME 93 #define SLEEPTIME 4 94 #endif 95 96 #define DEFAULT_LOGIN "/etc/default/login" 97 #define DEFFILE "/etc/default/su" 98 99 100 char *Sulog, *Console; 101 char *Path, *Supath; 102 103 /* 104 * Locale variables to be propagated to "su -" environment 105 */ 106 static char *initvar; 107 static char *initenv[] = { 108 "TZ", "LANG", "LC_CTYPE", 109 "LC_NUMERIC", "LC_TIME", "LC_COLLATE", 110 "LC_MONETARY", "LC_MESSAGES", "LC_ALL", 0}; 111 static char mail[30] = { "MAIL=/var/mail/" }; 112 113 static void envalt(void); 114 static void log(char *, char *, int); 115 static void to(int); 116 117 enum messagemode { USAGE, ERR, WARN }; 118 static void message(enum messagemode, char *, ...); 119 120 static char *alloc_vsprintf(const char *, va_list); 121 static char *tail(char *); 122 123 static void audit_success(int, struct passwd *); 124 static void audit_failure(int, struct passwd *, int); 125 126 #ifdef DYNAMIC_SU 127 static void validate(char *, int *); 128 static int legalenvvar(char *); 129 static int su_conv(int, struct pam_message **, struct pam_response **, void *); 130 static int emb_su_conv(int, struct pam_message **, struct pam_response **, 131 void *); 132 static void freeresponse(int, struct pam_response **response); 133 static struct pam_conv pam_conv = {su_conv, NULL}; 134 static struct pam_conv emb_pam_conv = {emb_su_conv, NULL}; 135 static void quotemsg(char *, ...); 136 static void readinitblock(void); 137 #else /* !DYNAMIC_SU */ 138 static void update_audit(struct passwd *pwd); 139 #endif /* DYNAMIC_SU */ 140 141 static pam_handle_t *pamh = NULL; /* Authentication handle */ 142 struct passwd pwd; 143 char pwdbuf[1024]; /* buffer for getpwnam_r() */ 144 char shell[] = "/usr/bin/sh"; /* default shell */ 145 char safe_shell[] = "/sbin/sh"; /* "fallback" shell */ 146 char su[PATH_MAX] = "su"; /* arg0 for exec of shprog */ 147 char homedir[PATH_MAX] = "HOME="; 148 char logname[20] = "LOGNAME="; 149 char *suprmt = SUPRMT; 150 char termtyp[PATH_MAX] = "TERM="; 151 char *term; 152 char shelltyp[PATH_MAX] = "SHELL="; 153 char *hz; 154 char tznam[PATH_MAX]; 155 char hzname[10] = "HZ="; 156 char path[PATH_MAX] = "PATH="; 157 char supath[PATH_MAX] = "PATH="; 158 char *envinit[ELIM]; 159 extern char **environ; 160 char *ttyn; 161 char *username; /* the invoker */ 162 static int dosyslog = 0; /* use syslog? */ 163 char *myname; 164 #ifdef DYNAMIC_SU 165 int passreq = 0; 166 boolean_t embedded = B_FALSE; 167 #endif /* DYNAMIC_SU */ 168 169 int 170 main(int argc, char **argv) 171 { 172 #ifndef DYNAMIC_SU 173 struct spwd sp; 174 char spbuf[1024]; /* buffer for getspnam_r() */ 175 char *password; 176 #endif /* !DYNAMIC_SU */ 177 char *nptr; 178 char *pshell; 179 int eflag = 0; 180 int envidx = 0; 181 uid_t uid; 182 gid_t gid; 183 char *dir, *shprog, *name; 184 char *ptr; 185 char *prog = argv[0]; 186 #ifdef DYNAMIC_SU 187 int sleeptime = SLEEPTIME; 188 char **pam_env = 0; 189 int flags = 0; 190 int retcode; 191 int idx = 0; 192 #endif /* DYNAMIC_SU */ 193 int pw_change = PW_FALSE; 194 195 (void) setlocale(LC_ALL, ""); 196 #if !defined(TEXT_DOMAIN) /* Should be defined by cc -D */ 197 #define TEXT_DOMAIN "SYS_TEST" /* Use this only if it wasn't */ 198 #endif 199 (void) textdomain(TEXT_DOMAIN); 200 201 myname = tail(argv[0]); 202 203 #ifdef DYNAMIC_SU 204 if (strcmp(myname, EMBEDDED_NAME) == 0) { 205 embedded = B_TRUE; 206 setbuf(stdin, NULL); 207 setbuf(stdout, NULL); 208 readinitblock(); 209 } 210 #endif /* DYNAMIC_SU */ 211 212 if (argc > 1 && *argv[1] == '-') { 213 /* Explicitly check for just `-' (no trailing chars) */ 214 if (strlen(argv[1]) == 1) { 215 eflag++; /* set eflag if `-' is specified */ 216 argv++; 217 argc--; 218 } else { 219 message(USAGE, 220 gettext("Usage: %s [-] [ username [ arg ... ] ]"), 221 prog); 222 exit(1); 223 } 224 } 225 226 /* 227 * Determine specified userid, get their password file entry, 228 * and set variables to values in password file entry fields. 229 */ 230 if (argc > 1) { 231 /* 232 * Usernames can't start with a `-', so we check for that to 233 * catch bad usage (like "su - -c ls"). 234 */ 235 if (*argv[1] == '-') { 236 message(USAGE, 237 gettext("Usage: %s [-] [ username [ arg ... ] ]"), 238 prog); 239 exit(1); 240 } else 241 nptr = argv[1]; /* use valid command-line username */ 242 } else 243 nptr = "root"; /* use default "root" username */ 244 245 if (defopen(DEFFILE) == 0) { 246 247 if (Sulog = defread("SULOG=")) 248 Sulog = strdup(Sulog); 249 if (Console = defread("CONSOLE=")) 250 Console = strdup(Console); 251 if (Path = defread("PATH=")) 252 Path = strdup(Path); 253 if (Supath = defread("SUPATH=")) 254 Supath = strdup(Supath); 255 if ((ptr = defread("SYSLOG=")) != NULL) 256 dosyslog = strcmp(ptr, "YES") == 0; 257 258 (void) defopen(NULL); 259 } 260 (void) strlcat(path, (Path) ? Path : PATH, sizeof (path)); 261 (void) strlcat(supath, (Supath) ? Supath : SUPATH, sizeof (supath)); 262 263 if ((ttyn = ttyname(0)) == NULL) 264 if ((ttyn = ttyname(1)) == NULL) 265 if ((ttyn = ttyname(2)) == NULL) 266 ttyn = "/dev/???"; 267 if ((username = cuserid(NULL)) == NULL) 268 username = "(null)"; 269 270 /* 271 * if Sulog defined, create SULOG, if it does not exist, with 272 * mode read/write user. Change owner and group to root 273 */ 274 if (Sulog != NULL) { 275 (void) close(open(Sulog, O_WRONLY | O_APPEND | O_CREAT, 276 (S_IRUSR|S_IWUSR))); 277 (void) chown(Sulog, (uid_t)ROOT, (gid_t)ROOT); 278 } 279 280 #ifdef DYNAMIC_SU 281 if (pam_start(embedded ? EMBEDDED_NAME : "su", nptr, 282 embedded ? &emb_pam_conv : &pam_conv, &pamh) != PAM_SUCCESS) 283 exit(1); 284 if (pam_set_item(pamh, PAM_TTY, ttyn) != PAM_SUCCESS) 285 exit(1); 286 #endif /* DYNAMIC_SU */ 287 288 openlog("su", LOG_CONS, LOG_AUTH); 289 290 #ifdef DYNAMIC_SU 291 292 /* 293 * Use the same value of sleeptime and password required that 294 * login(1) uses. 295 * This is obtained by reading the file /etc/default/login 296 * using the def*() functions 297 */ 298 if (defopen(DEFAULT_LOGIN) == 0) { 299 if ((ptr = defread("SLEEPTIME=")) != NULL) { 300 sleeptime = atoi(ptr); 301 if (sleeptime < 0 || sleeptime > 5) 302 sleeptime = SLEEPTIME; 303 } 304 305 if ((ptr = defread("PASSREQ=")) != NULL && 306 strcasecmp("YES", ptr) == 0) 307 passreq = 1; 308 309 (void) defopen((char *)NULL); 310 } 311 /* 312 * Ignore SIGQUIT and SIGINT 313 */ 314 (void) signal(SIGQUIT, SIG_IGN); 315 (void) signal(SIGINT, SIG_IGN); 316 317 /* call pam_authenticate() to authenticate the user through PAM */ 318 if (getpwnam_r(nptr, &pwd, pwdbuf, sizeof (pwdbuf)) == NULL) 319 retcode = PAM_USER_UNKNOWN; 320 else if ((flags = (getuid() != (uid_t)ROOT)) != 0) { 321 retcode = pam_authenticate(pamh, 0); 322 } else /* root user does not need to authenticate */ 323 retcode = PAM_SUCCESS; 324 325 if (retcode != PAM_SUCCESS) { 326 /* 327 * 1st step: audit and log the error. 328 * 2nd step: sleep. 329 * 3rd step: print out message to user. 330 */ 331 audit_failure(PW_FALSE, NULL, retcode); 332 switch (retcode) { 333 case PAM_USER_UNKNOWN: 334 closelog(); 335 (void) sleep(sleeptime); 336 message(ERR, gettext("Unknown id: %s"), nptr); 337 break; 338 339 case PAM_AUTH_ERR: 340 if (Sulog != NULL) 341 log(Sulog, nptr, 0); /* log entry */ 342 if (dosyslog) 343 syslog(LOG_CRIT, "'su %s' failed for %s on %s", 344 pwd.pw_name, username, ttyn); 345 closelog(); 346 (void) sleep(sleeptime); 347 message(ERR, gettext("Sorry")); 348 break; 349 350 case PAM_CONV_ERR: 351 default: 352 if (dosyslog) 353 syslog(LOG_CRIT, "'su %s' failed for %s on %s", 354 pwd.pw_name, username, ttyn); 355 closelog(); 356 (void) sleep(sleeptime); 357 message(ERR, gettext("Sorry")); 358 break; 359 } 360 361 (void) signal(SIGQUIT, SIG_DFL); 362 (void) signal(SIGINT, SIG_DFL); 363 exit(1); 364 } 365 if (flags) 366 validate(username, &pw_change); 367 if (pam_setcred(pamh, PAM_REINITIALIZE_CRED) != PAM_SUCCESS) { 368 message(ERR, gettext("unable to set credentials")); 369 exit(2); 370 } 371 if (dosyslog) 372 syslog(getuid() == 0 ? LOG_INFO : LOG_NOTICE, 373 "'su %s' succeeded for %s on %s", 374 pwd.pw_name, username, ttyn); 375 closelog(); 376 (void) signal(SIGQUIT, SIG_DFL); 377 (void) signal(SIGINT, SIG_DFL); 378 #else /* !DYNAMIC_SU */ 379 if ((getpwnam_r(nptr, &pwd, pwdbuf, sizeof (pwdbuf)) == NULL) || 380 (getspnam_r(nptr, &sp, spbuf, sizeof (spbuf)) == NULL)) { 381 message(ERR, gettext("Unknown id: %s"), nptr); 382 audit_failure(PW_FALSE, NULL, PAM_USER_UNKNOWN); 383 closelog(); 384 exit(1); 385 } 386 387 /* 388 * Prompt for password if invoking user is not root or 389 * if specified(new) user requires a password 390 */ 391 if (sp.sp_pwdp[0] == '\0' || getuid() == (uid_t)ROOT) 392 goto ok; 393 password = getpass(gettext("Password:")); 394 395 if ((strcmp(sp.sp_pwdp, crypt(password, sp.sp_pwdp)) != 0)) { 396 /* clear password file entry */ 397 (void) memset((void *)spbuf, 0, sizeof (spbuf)); 398 if (Sulog != NULL) 399 log(Sulog, nptr, 0); /* log entry */ 400 message(ERR, gettext("Sorry")); 401 audit_failure(PW_FALSE, NULL, PAM_AUTH_ERR); 402 if (dosyslog) 403 syslog(LOG_CRIT, "'su %s' failed for %s on %s", 404 pwd.pw_name, username, ttyn); 405 closelog(); 406 exit(2); 407 } 408 /* clear password file entry */ 409 (void) memset((void *)spbuf, 0, sizeof (spbuf)); 410 ok: 411 /* update audit session in a non-pam environment */ 412 update_audit(&pwd); 413 if (dosyslog) 414 syslog(getuid() == 0 ? LOG_INFO : LOG_NOTICE, 415 "'su %s' succeeded for %s on %s", 416 pwd.pw_name, username, ttyn); 417 #endif /* DYNAMIC_SU */ 418 419 audit_success(pw_change, &pwd); 420 uid = pwd.pw_uid; 421 gid = pwd.pw_gid; 422 dir = strdup(pwd.pw_dir); 423 shprog = strdup(pwd.pw_shell); 424 name = strdup(pwd.pw_name); 425 426 if (Sulog != NULL) 427 log(Sulog, nptr, 1); /* log entry */ 428 429 /* set user and group ids to specified user */ 430 431 /* set the real (and effective) GID */ 432 if (setgid(gid) == -1) { 433 message(ERR, gettext("Invalid GID")); 434 exit(2); 435 } 436 /* Initialize the supplementary group access list. */ 437 if (!nptr) 438 exit(2); 439 if (initgroups(nptr, gid) == -1) { 440 exit(2); 441 } 442 /* set the real (and effective) UID */ 443 if (setuid(uid) == -1) { 444 message(ERR, gettext("Invalid UID")); 445 exit(2); 446 } 447 448 /* 449 * If new user's shell field is neither NULL nor equal to /usr/bin/sh, 450 * set: 451 * 452 * pshell = their shell 453 * su = [-]last component of shell's pathname 454 * 455 * Otherwise, set the shell to /usr/bin/sh and set argv[0] to '[-]su'. 456 */ 457 if (shprog[0] != '\0' && strcmp(shell, shprog) != 0) { 458 char *p; 459 460 pshell = shprog; 461 (void) strcpy(su, eflag ? "-" : ""); 462 463 if ((p = strrchr(pshell, '/')) != NULL) 464 (void) strlcat(su, p + 1, sizeof (su)); 465 else 466 (void) strlcat(su, pshell, sizeof (su)); 467 } else { 468 pshell = shell; 469 (void) strcpy(su, eflag ? "-su" : "su"); 470 } 471 472 /* 473 * set environment variables for new user; 474 * arg0 for exec of shprog must now contain `-' 475 * so that environment of new user is given 476 */ 477 if (eflag) { 478 int j; 479 char *var; 480 481 if (strlen(dir) == 0) { 482 (void) strcpy(dir, "/"); 483 message(WARN, gettext("No directory! Using home=/")); 484 } 485 (void) strlcat(homedir, dir, sizeof (homedir)); 486 (void) strlcat(logname, name, sizeof (logname)); 487 if (hz = getenv("HZ")) 488 (void) strlcat(hzname, hz, sizeof (hzname)); 489 490 (void) strlcat(shelltyp, pshell, sizeof (shelltyp)); 491 492 if (chdir(dir) < 0) { 493 message(ERR, gettext("No directory!")); 494 exit(1); 495 } 496 envinit[envidx = 0] = homedir; 497 envinit[++envidx] = ((uid == (uid_t)ROOT) ? supath : path); 498 envinit[++envidx] = logname; 499 envinit[++envidx] = hzname; 500 if ((term = getenv("TERM")) != NULL) { 501 (void) strlcat(termtyp, term, sizeof (termtyp)); 502 envinit[++envidx] = termtyp; 503 } 504 envinit[++envidx] = shelltyp; 505 506 (void) strlcat(mail, name, sizeof (mail)); 507 envinit[++envidx] = mail; 508 509 /* 510 * Fetch the relevant locale/TZ environment variables from 511 * the inherited environment. 512 * 513 * We have a priority here for setting TZ. If TZ is set in 514 * in the inherited environment, that value remains top 515 * priority. If the file /etc/default/login has TIMEZONE set, 516 * that has second highest priority. 517 */ 518 tznam[0] = '\0'; 519 for (j = 0; initenv[j] != 0; j++) { 520 if (initvar = getenv(initenv[j])) { 521 522 /* 523 * Skip over values beginning with '/' for 524 * security. 525 */ 526 if (initvar[0] == '/') continue; 527 528 if (strcmp(initenv[j], "TZ") == 0) { 529 (void) strcpy(tznam, "TZ="); 530 (void) strlcat(tznam, initvar, 531 sizeof (tznam)); 532 533 } else { 534 var = (char *) 535 malloc(strlen(initenv[j]) 536 + strlen(initvar) 537 + 2); 538 (void) strcpy(var, initenv[j]); 539 (void) strcat(var, "="); 540 (void) strcat(var, initvar); 541 envinit[++envidx] = var; 542 } 543 } 544 } 545 546 /* 547 * Check if TZ was found. If not then try to read it from 548 * /etc/default/login. 549 */ 550 if (tznam[0] == '\0') { 551 if (defopen(DEFAULT_LOGIN) == 0) { 552 if (initvar = defread("TIMEZONE=")) { 553 (void) strcpy(tznam, "TZ="); 554 (void) strlcat(tznam, initvar, 555 sizeof (tznam)); 556 } 557 (void) defopen(NULL); 558 } 559 } 560 561 if (tznam[0] != '\0') 562 envinit[++envidx] = tznam; 563 564 #ifdef DYNAMIC_SU 565 /* 566 * set the PAM environment variables - 567 * check for legal environment variables 568 */ 569 if ((pam_env = pam_getenvlist(pamh)) != 0) { 570 while (pam_env[idx] != 0) { 571 if (envidx + 2 < ELIM && 572 legalenvvar(pam_env[idx])) { 573 envinit[++envidx] = pam_env[idx]; 574 } 575 idx++; 576 } 577 } 578 #endif /* DYNAMIC_SU */ 579 envinit[++envidx] = NULL; 580 environ = envinit; 581 } else { 582 char **pp = environ, **qq, *p; 583 584 while ((p = *pp) != NULL) { 585 if (*p == 'L' && p[1] == 'D' && p[2] == '_') { 586 for (qq = pp; (*qq = qq[1]) != NULL; qq++) 587 ; 588 /* pp is not advanced */ 589 } else { 590 pp++; 591 } 592 } 593 } 594 595 #ifdef DYNAMIC_SU 596 if (pamh) 597 (void) pam_end(pamh, PAM_SUCCESS); 598 #endif /* DYNAMIC_SU */ 599 600 /* 601 * if new user is root: 602 * if CONSOLE defined, log entry there; 603 * if eflag not set, change environment to that of root. 604 */ 605 if (uid == (uid_t)ROOT) { 606 if (Console != NULL) 607 if (strcmp(ttyn, Console) != 0) { 608 (void) signal(SIGALRM, to); 609 (void) alarm(30); 610 log(Console, nptr, 1); 611 (void) alarm(0); 612 } 613 if (!eflag) 614 envalt(); 615 } 616 617 /* 618 * Default for SIGCPU and SIGXFSZ. Shells inherit 619 * signal disposition from parent. And the 620 * shells should have default dispositions for these 621 * signals. 622 */ 623 (void) signal(SIGXCPU, SIG_DFL); 624 (void) signal(SIGXFSZ, SIG_DFL); 625 626 #ifdef DYNAMIC_SU 627 if (embedded) { 628 (void) puts("SUCCESS"); 629 /* 630 * After this point, we're no longer talking the 631 * embedded_su protocol, so turn it off. 632 */ 633 embedded = B_FALSE; 634 } 635 #endif /* DYNAMIC_SU */ 636 637 /* 638 * if additional arguments, exec shell program with array 639 * of pointers to arguments: 640 * -> if shell = default, then su = [-]su 641 * -> if shell != default, then su = [-]last component of 642 * shell's pathname 643 * 644 * if no additional arguments, exec shell with arg0 of su 645 * where: 646 * -> if shell = default, then su = [-]su 647 * -> if shell != default, then su = [-]last component of 648 * shell's pathname 649 */ 650 if (argc > 2) { 651 argv[1] = su; 652 (void) execv(pshell, &argv[1]); 653 } else 654 (void) execl(pshell, su, 0); 655 656 657 /* 658 * Try to clean up after an administrator who has made a mistake 659 * configuring root's shell; if root's shell is other than /sbin/sh, 660 * try exec'ing /sbin/sh instead. 661 */ 662 if ((uid == (uid_t)ROOT) && (strcmp(name, "root") == 0) && 663 (strcmp(safe_shell, pshell) != 0)) { 664 message(WARN, 665 gettext("No shell %s. Trying fallback shell %s."), 666 pshell, safe_shell); 667 668 if (eflag) { 669 (void) strcpy(su, "-sh"); 670 (void) strlcpy(shelltyp + strlen("SHELL="), 671 safe_shell, sizeof (shelltyp) - strlen("SHELL=")); 672 } else { 673 (void) strcpy(su, "sh"); 674 } 675 676 if (argc > 2) { 677 argv[1] = su; 678 (void) execv(safe_shell, &argv[1]); 679 } else { 680 (void) execl(safe_shell, su, 0); 681 } 682 message(ERR, gettext("Couldn't exec fallback shell %s: %s"), 683 safe_shell, strerror(errno)); 684 } else { 685 message(ERR, gettext("No shell")); 686 } 687 return (3); 688 } 689 690 /* 691 * Environment altering routine - 692 * This routine is called when a user is su'ing to root 693 * without specifying the - flag. 694 * The user's PATH and PS1 variables are reset 695 * to the correct value for root. 696 * All of the user's other environment variables retain 697 * their current values after the su (if they are exported). 698 */ 699 static void 700 envalt(void) 701 { 702 /* 703 * If user has PATH variable in their environment, change its value 704 * to /bin:/etc:/usr/bin ; 705 * if user does not have PATH variable, add it to the user's 706 * environment; 707 * if either of the above fail, an error message is printed. 708 */ 709 if (putenv(supath) != 0) { 710 message(ERR, 711 gettext("unable to obtain memory to expand environment")); 712 exit(4); 713 } 714 715 /* 716 * If user has PROMPT variable in their environment, change its value 717 * to # ; 718 * if user does not have PROMPT variable, add it to the user's 719 * environment; 720 * if either of the above fail, an error message is printed. 721 */ 722 if (putenv(suprmt) != 0) { 723 message(ERR, 724 gettext("unable to obtain memory to expand environment")); 725 exit(4); 726 } 727 } 728 729 /* 730 * Logging routine - 731 * where = SULOG or CONSOLE 732 * towho = specified user ( user being su'ed to ) 733 * how = 0 if su attempt failed; 1 if su attempt succeeded 734 */ 735 static void 736 log(char *where, char *towho, int how) 737 { 738 FILE *logf; 739 time_t now; 740 struct tm *tmp; 741 742 /* 743 * open SULOG or CONSOLE - if open fails, return 744 */ 745 if ((logf = fopen(where, "a")) == NULL) 746 return; 747 748 now = time(0); 749 tmp = localtime(&now); 750 751 /* 752 * write entry into SULOG or onto CONSOLE - if write fails, return 753 */ 754 (void) fprintf(logf, "SU %.2d/%.2d %.2d:%.2d %c %s %s-%s\n", 755 tmp->tm_mon + 1, tmp->tm_mday, tmp->tm_hour, tmp->tm_min, 756 how ? '+' : '-', ttyn + sizeof ("/dev/") - 1, username, towho); 757 758 (void) fclose(logf); /* close SULOG or CONSOLE */ 759 } 760 761 /*ARGSUSED*/ 762 static void 763 to(int sig) 764 {} 765 766 /* 767 * audit_success - audit successful su 768 * 769 * Entry process audit context established -- i.e., pam_setcred() 770 * or equivalent called. 771 * pw_change = PW_TRUE, if successful password change audit 772 * required. 773 * pwd = passwd entry for new user. 774 */ 775 776 static void 777 audit_success(int pw_change, struct passwd *pwd) 778 { 779 adt_session_data_t *ah = NULL; 780 adt_event_data_t *event; 781 782 if (adt_start_session(&ah, NULL, ADT_USE_PROC_DATA) != 0) { 783 syslog(LOG_AUTH | LOG_ALERT, 784 "adt_start_session(ADT_su): %m"); 785 return; 786 } 787 /* since proc uid/gid not yet updated */ 788 if (adt_set_user(ah, pwd->pw_uid, pwd->pw_gid, pwd->pw_uid, 789 pwd->pw_gid, NULL, ADT_USER) != 0) { 790 syslog(LOG_AUTH | LOG_ERR, 791 "adt_set_user(ADT_su, ADT_FAILURE): %m"); 792 } 793 if ((event = adt_alloc_event(ah, ADT_su)) == NULL) { 794 syslog(LOG_AUTH | LOG_ALERT, "adt_alloc_event(ADT_su): %m"); 795 } else if (adt_put_event(event, ADT_SUCCESS, ADT_SUCCESS) != 0) { 796 syslog(LOG_AUTH | LOG_ALERT, 797 "adt_put_event(ADT_su, ADT_SUCCESS): %m"); 798 } 799 800 if (pw_change == PW_TRUE) { 801 /* Also audit password change */ 802 adt_free_event(event); 803 if ((event = adt_alloc_event(ah, ADT_passwd)) == NULL) { 804 syslog(LOG_AUTH | LOG_ALERT, 805 "adt_alloc_event(ADT_passwd): %m"); 806 } else if (adt_put_event(event, ADT_SUCCESS, 807 ADT_SUCCESS) != 0) { 808 syslog(LOG_AUTH | LOG_ALERT, 809 "adt_put_event(ADT_passwd, ADT_SUCCESS): %m"); 810 } 811 } 812 adt_free_event(event); 813 } 814 815 816 /* 817 * audit_failure - audit failed su 818 * 819 * Entry New audit context not set. 820 * pw_change == PW_FALSE, if no password change requested. 821 * PW_FAILED, if failed password change audit 822 * required. 823 * pwent = NULL, or password entry to use. 824 * pamerr = PAM error code; reason for failure. 825 */ 826 827 static void 828 audit_failure(int pw_change, struct passwd *pwd, int pamerr) 829 { 830 adt_session_data_t *ah; /* audit session handle */ 831 adt_event_data_t *event; /* event to generate */ 832 833 if (adt_start_session(&ah, NULL, ADT_USE_PROC_DATA) != 0) { 834 syslog(LOG_AUTH | LOG_ALERT, 835 "adt_start_session(ADT_su, ADT_FAILURE): %m"); 836 return; 837 } 838 if (pwd != NULL) { 839 /* target user authenticated, merge audit state */ 840 if (adt_set_user(ah, pwd->pw_uid, pwd->pw_gid, pwd->pw_uid, 841 pwd->pw_gid, NULL, ADT_UPDATE) != 0) { 842 syslog(LOG_AUTH | LOG_ERR, 843 "adt_set_user(ADT_su, ADT_FAILURE): %m"); 844 } 845 } 846 if ((event = adt_alloc_event(ah, ADT_su)) == NULL) { 847 syslog(LOG_AUTH | LOG_ALERT, 848 "adt_alloc_event(ADT_su, ADT_FAILURE): %m"); 849 return; 850 } else if (adt_put_event(event, ADT_FAILURE, 851 ADT_FAIL_PAM + pamerr) != 0) { 852 syslog(LOG_AUTH | LOG_ALERT, 853 "adt_put_event(ADT_su(ADT_FAIL, %s): %m", 854 pam_strerror(pamh, pamerr)); 855 } 856 if (pw_change != PW_FALSE) { 857 /* Also audit password change failed */ 858 adt_free_event(event); 859 if ((event = adt_alloc_event(ah, ADT_passwd)) == NULL) { 860 syslog(LOG_AUTH | LOG_ALERT, 861 "adt_alloc_event(ADT_passwd): %m"); 862 } else if (adt_put_event(event, ADT_FAILURE, 863 ADT_FAIL_PAM + pamerr) != 0) { 864 syslog(LOG_AUTH | LOG_ALERT, 865 "adt_put_event(ADT_passwd, ADT_FAILURE): %m"); 866 } 867 } 868 adt_free_event(event); 869 } 870 871 #ifdef DYNAMIC_SU 872 /* 873 * su_conv(): 874 * This is the conv (conversation) function called from 875 * a PAM authentication module to print error messages 876 * or garner information from the user. 877 */ 878 /*ARGSUSED*/ 879 static int 880 su_conv(int num_msg, struct pam_message **msg, struct pam_response **response, 881 void *appdata_ptr) 882 { 883 struct pam_message *m; 884 struct pam_response *r; 885 char *temp; 886 int k; 887 char respbuf[PAM_MAX_RESP_SIZE]; 888 889 if (num_msg <= 0) 890 return (PAM_CONV_ERR); 891 892 *response = (struct pam_response *)calloc(num_msg, 893 sizeof (struct pam_response)); 894 if (*response == NULL) 895 return (PAM_BUF_ERR); 896 897 k = num_msg; 898 m = *msg; 899 r = *response; 900 while (k--) { 901 902 switch (m->msg_style) { 903 904 case PAM_PROMPT_ECHO_OFF: 905 errno = 0; 906 temp = getpassphrase(m->msg); 907 if (errno == EINTR) 908 return (PAM_CONV_ERR); 909 if (temp != NULL) { 910 r->resp = strdup(temp); 911 if (r->resp == NULL) { 912 freeresponse(num_msg, response); 913 return (PAM_BUF_ERR); 914 } 915 } 916 break; 917 918 case PAM_PROMPT_ECHO_ON: 919 if (m->msg != NULL) { 920 (void) fputs(m->msg, stdout); 921 } 922 923 (void) fgets(respbuf, sizeof (respbuf), stdin); 924 temp = strchr(respbuf, '\n'); 925 if (temp != NULL) 926 *temp = '\0'; 927 928 r->resp = strdup(respbuf); 929 if (r->resp == NULL) { 930 freeresponse(num_msg, response); 931 return (PAM_BUF_ERR); 932 } 933 break; 934 935 case PAM_ERROR_MSG: 936 if (m->msg != NULL) { 937 (void) fputs(m->msg, stderr); 938 (void) fputs("\n", stderr); 939 } 940 break; 941 942 case PAM_TEXT_INFO: 943 if (m->msg != NULL) { 944 (void) fputs(m->msg, stdout); 945 (void) fputs("\n", stdout); 946 } 947 break; 948 949 default: 950 break; 951 } 952 m++; 953 r++; 954 } 955 return (PAM_SUCCESS); 956 } 957 958 /* 959 * emb_su_conv(): 960 * This is the conv (conversation) function called from 961 * a PAM authentication module to print error messages 962 * or garner information from the user. 963 * This version is used for embedded_su. 964 */ 965 /*ARGSUSED*/ 966 static int 967 emb_su_conv(int num_msg, struct pam_message **msg, 968 struct pam_response **response, void *appdata_ptr) 969 { 970 struct pam_message *m; 971 struct pam_response *r; 972 char *temp; 973 int k; 974 char respbuf[PAM_MAX_RESP_SIZE]; 975 976 if (num_msg <= 0) 977 return (PAM_CONV_ERR); 978 979 *response = (struct pam_response *)calloc(num_msg, 980 sizeof (struct pam_response)); 981 if (*response == NULL) 982 return (PAM_BUF_ERR); 983 984 /* First, send the prompts */ 985 (void) printf("CONV %d\n", num_msg); 986 k = num_msg; 987 m = *msg; 988 while (k--) { 989 switch (m->msg_style) { 990 991 case PAM_PROMPT_ECHO_OFF: 992 (void) puts("PAM_PROMPT_ECHO_OFF"); 993 goto msg_common; 994 995 case PAM_PROMPT_ECHO_ON: 996 (void) puts("PAM_PROMPT_ECHO_ON"); 997 goto msg_common; 998 999 case PAM_ERROR_MSG: 1000 (void) puts("PAM_ERROR_MSG"); 1001 goto msg_common; 1002 1003 case PAM_TEXT_INFO: 1004 (void) puts("PAM_TEXT_INFO"); 1005 /* fall through to msg_common */ 1006 msg_common: 1007 if (m->msg == NULL) 1008 quotemsg(NULL); 1009 else 1010 quotemsg("%s", m->msg); 1011 break; 1012 1013 default: 1014 break; 1015 } 1016 m++; 1017 } 1018 1019 /* Next, collect the responses */ 1020 k = num_msg; 1021 m = *msg; 1022 r = *response; 1023 while (k--) { 1024 1025 switch (m->msg_style) { 1026 1027 case PAM_PROMPT_ECHO_OFF: 1028 case PAM_PROMPT_ECHO_ON: 1029 (void) fgets(respbuf, sizeof (respbuf), stdin); 1030 1031 temp = strchr(respbuf, '\n'); 1032 if (temp != NULL) 1033 *temp = '\0'; 1034 1035 r->resp = strdup(respbuf); 1036 if (r->resp == NULL) { 1037 freeresponse(num_msg, response); 1038 return (PAM_BUF_ERR); 1039 } 1040 1041 break; 1042 1043 case PAM_ERROR_MSG: 1044 case PAM_TEXT_INFO: 1045 break; 1046 1047 default: 1048 break; 1049 } 1050 m++; 1051 r++; 1052 } 1053 return (PAM_SUCCESS); 1054 } 1055 1056 static void 1057 freeresponse(int num_msg, struct pam_response **response) 1058 { 1059 struct pam_response *r; 1060 int i; 1061 1062 /* free responses */ 1063 r = *response; 1064 for (i = 0; i < num_msg; i++, r++) { 1065 if (r->resp != NULL) { 1066 /* Zap it in case it's a password */ 1067 (void) memset(r->resp, '\0', strlen(r->resp)); 1068 free(r->resp); 1069 } 1070 } 1071 free(*response); 1072 *response = NULL; 1073 } 1074 1075 /* 1076 * Print a message, applying quoting for lines starting with '.'. 1077 * 1078 * I18n note: \n is "safe" in all locales, and all locales use 1079 * a high-bit-set character to start multibyte sequences, so 1080 * scanning for a \n followed by a '.' is safe. 1081 */ 1082 static void 1083 quotemsg(char *fmt, ...) 1084 { 1085 if (fmt != NULL) { 1086 char *msg; 1087 char *p; 1088 boolean_t bol; 1089 va_list v; 1090 1091 va_start(v, fmt); 1092 msg = alloc_vsprintf(fmt, v); 1093 va_end(v); 1094 1095 bol = B_TRUE; 1096 for (p = msg; *p != '\0'; p++) { 1097 if (bol) { 1098 if (*p == '.') 1099 (void) putchar('.'); 1100 bol = B_FALSE; 1101 } 1102 (void) putchar(*p); 1103 if (*p == '\n') 1104 bol = B_TRUE; 1105 } 1106 (void) putchar('\n'); 1107 free(msg); 1108 } 1109 (void) putchar('.'); 1110 (void) putchar('\n'); 1111 } 1112 1113 /* 1114 * validate - Check that the account is valid for switching to. 1115 */ 1116 static void 1117 validate(char *usernam, int *pw_change) 1118 { 1119 int error; 1120 int flag = 0; 1121 int tries; 1122 1123 if (passreq) 1124 flag = PAM_DISALLOW_NULL_AUTHTOK; 1125 1126 if ((error = pam_acct_mgmt(pamh, flag)) != PAM_SUCCESS) { 1127 if (Sulog != NULL) 1128 log(Sulog, pwd.pw_name, 0); /* log entry */ 1129 if (error == PAM_NEW_AUTHTOK_REQD) { 1130 tries = 0; 1131 message(ERR, gettext("Password for user " 1132 "'%s' has expired"), pwd.pw_name); 1133 while ((error = pam_chauthtok(pamh, 0)) != 1134 PAM_SUCCESS) { 1135 if ((error == PAM_AUTHTOK_ERR || 1136 error == PAM_TRY_AGAIN) && 1137 (tries++ < DEF_ATTEMPTS)) { 1138 continue; 1139 } 1140 message(ERR, gettext("Sorry")); 1141 audit_failure(PW_FAILED, &pwd, error); 1142 if (dosyslog) 1143 syslog(LOG_CRIT, 1144 "'su %s' failed for %s on %s", 1145 pwd.pw_name, usernam, ttyn); 1146 closelog(); 1147 exit(1); 1148 } 1149 *pw_change = PW_TRUE; 1150 return; 1151 } else { 1152 message(ERR, gettext("Sorry")); 1153 audit_failure(PW_FALSE, &pwd, error); 1154 if (dosyslog) 1155 syslog(LOG_CRIT, "'su %s' failed for %s on %s", 1156 pwd.pw_name, usernam, ttyn); 1157 closelog(); 1158 exit(3); 1159 } 1160 } 1161 } 1162 1163 static char *illegal[] = { 1164 "SHELL=", 1165 "HOME=", 1166 "LOGNAME=", 1167 #ifndef NO_MAIL 1168 "MAIL=", 1169 #endif 1170 "CDPATH=", 1171 "IFS=", 1172 "PATH=", 1173 "TZ=", 1174 "HZ=", 1175 "TERM=", 1176 0 1177 }; 1178 1179 /* 1180 * legalenvvar - can PAM modules insert this environmental variable? 1181 */ 1182 1183 static int 1184 legalenvvar(char *s) 1185 { 1186 register char **p; 1187 1188 for (p = illegal; *p; p++) 1189 if (strncmp(s, *p, strlen(*p)) == 0) 1190 return (0); 1191 1192 if (s[0] == 'L' && s[1] == 'D' && s[2] == '_') 1193 return (0); 1194 1195 return (1); 1196 } 1197 1198 /* 1199 * The embedded_su protocol allows the client application to supply 1200 * an initialization block terminated by a line with just a "." on it. 1201 * 1202 * This initialization block is currently unused, reserved for future 1203 * expansion. Ignore it. This is made very slightly more complex by 1204 * the desire to cleanly ignore input lines of any length, while still 1205 * correctly detecting a line with just a "." on it. 1206 * 1207 * I18n note: It appears that none of the Solaris-supported locales 1208 * use 0x0a for any purpose other than newline, so looking for '\n' 1209 * seems safe. 1210 * All locales use high-bit-set leadin characters for their multi-byte 1211 * sequences, so a line consisting solely of ".\n" is what it appears 1212 * to be. 1213 */ 1214 static void 1215 readinitblock(void) 1216 { 1217 char buf[100]; 1218 boolean_t bol; 1219 1220 bol = B_TRUE; 1221 for (;;) { 1222 if (fgets(buf, sizeof (buf), stdin) == NULL) 1223 return; 1224 if (bol && strcmp(buf, ".\n") == 0) 1225 return; 1226 bol = (strchr(buf, '\n') != NULL); 1227 } 1228 } 1229 #else /* !DYNAMIC_SU */ 1230 static void 1231 update_audit(struct passwd *pwd) 1232 { 1233 adt_session_data_t *ah; /* audit session handle */ 1234 1235 if (adt_start_session(&ah, NULL, ADT_USE_PROC_DATA) != 0) { 1236 message(ERR, gettext("Sorry")); 1237 if (dosyslog) 1238 syslog(LOG_CRIT, "'su %s' failed for %s " 1239 "cannot start audit session %m", 1240 pwd->pw_name, username); 1241 closelog(); 1242 exit(2); 1243 } 1244 if (adt_set_user(ah, pwd->pw_uid, pwd->pw_gid, pwd->pw_uid, 1245 pwd->pw_gid, NULL, ADT_UPDATE) != 0) { 1246 if (dosyslog) 1247 syslog(LOG_CRIT, "'su %s' failed for %s " 1248 "cannot update audit session %m", 1249 pwd->pw_name, username); 1250 closelog(); 1251 exit(2); 1252 } 1253 } 1254 #endif /* DYNAMIC_SU */ 1255 1256 /* 1257 * Report an error, either a fatal one, a warning, or a usage message, 1258 * depending on the mode parameter. 1259 */ 1260 /*ARGSUSED*/ 1261 static void 1262 message(enum messagemode mode, char *fmt, ...) 1263 { 1264 char *s; 1265 va_list v; 1266 1267 va_start(v, fmt); 1268 s = alloc_vsprintf(fmt, v); 1269 va_end(v); 1270 1271 #ifdef DYNAMIC_SU 1272 if (embedded) { 1273 if (mode == WARN) { 1274 (void) printf("CONV 1\n"); 1275 (void) printf("PAM_ERROR_MSG\n"); 1276 } else { /* ERR, USAGE */ 1277 (void) printf("ERROR\n"); 1278 } 1279 if (mode == USAGE) { 1280 quotemsg("%s", s); 1281 } else { /* ERR, WARN */ 1282 quotemsg("%s: %s", myname, s); 1283 } 1284 } else { 1285 #endif /* DYNAMIC_SU */ 1286 if (mode == USAGE) { 1287 (void) fprintf(stderr, "%s\n", s); 1288 } else { /* ERR, WARN */ 1289 (void) fprintf(stderr, "%s: %s\n", myname, s); 1290 } 1291 #ifdef DYNAMIC_SU 1292 } 1293 #endif /* DYNAMIC_SU */ 1294 1295 free(s); 1296 } 1297 1298 /* 1299 * Return a pointer to the last path component of a. 1300 */ 1301 static char * 1302 tail(char *a) 1303 { 1304 char *p; 1305 1306 p = strrchr(a, '/'); 1307 if (p == NULL) 1308 p = a; 1309 else 1310 p++; /* step over the '/' */ 1311 1312 return (p); 1313 } 1314 1315 static char * 1316 alloc_vsprintf(const char *fmt, va_list ap1) 1317 { 1318 va_list ap2; 1319 int n; 1320 char buf[1]; 1321 char *s; 1322 1323 /* 1324 * We need to scan the argument list twice. Save off a copy 1325 * of the argument list pointer(s) for the second pass. Note that 1326 * we are responsible for va_end'ing our copy. 1327 */ 1328 va_copy(ap2, ap1); 1329 1330 /* 1331 * vsnprintf into a dummy to get a length. One might 1332 * think that passing 0 as the length to snprintf would 1333 * do what we want, but it's defined not to. 1334 * 1335 * Perhaps we should sprintf into a 100 character buffer 1336 * or something like that, to avoid two calls to snprintf 1337 * in most cases. 1338 */ 1339 n = vsnprintf(buf, sizeof (buf), fmt, ap2); 1340 va_end(ap2); 1341 1342 /* 1343 * Allocate an appropriately-sized buffer. 1344 */ 1345 s = malloc(n + 1); 1346 if (s == NULL) { 1347 perror("malloc"); 1348 exit(4); 1349 } 1350 1351 (void) vsnprintf(s, n+1, fmt, ap1); 1352 1353 return (s); 1354 } 1355