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