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