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