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 2008 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(getuid() == 0 ? LOG_INFO : LOG_NOTICE, 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(getuid() == 0 ? LOG_INFO : LOG_NOTICE, 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 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_emptyset(priv); 911 if ((priv_addset(priv, PRIV_PROC_AUDIT) != 0) || 912 (setppriv(PRIV_SET, PRIV_PERMITTED, priv) != 0)) { 913 syslog(LOG_AUTH | LOG_ALERT, 914 "su audit_logout: could not reduce privs: %m"); 915 } 916 closefrom(0); 917 priv_freeset(priv); 918 while (pid != waitpid(pid, &status, 0)) 919 continue; 920 921 (void) adt_put_event(event, ADT_SUCCESS, ADT_SUCCESS); 922 adt_free_event(event); 923 (void) adt_end_session(ah); 924 exit(WEXITSTATUS(status)); 925 } 926 927 928 /* 929 * audit_failure - audit failed su 930 * 931 * Entry New audit context not set. 932 * pw_change == PW_FALSE, if no password change requested. 933 * PW_FAILED, if failed password change audit 934 * required. 935 * pwd = NULL, or password entry to use. 936 * user = username entered. Add to record if pwd == NULL. 937 * pamerr = PAM error code; reason for failure. 938 */ 939 940 static void 941 audit_failure(int pw_change, struct passwd *pwd, char *user, int pamerr) 942 { 943 adt_session_data_t *ah; /* audit session handle */ 944 adt_event_data_t *event; /* event to generate */ 945 au_event_t event_id = ADT_su; 946 userattr_t *user_entry; 947 char *kva_value; 948 949 if (adt_start_session(&ah, NULL, ADT_USE_PROC_DATA) != 0) { 950 syslog(LOG_AUTH | LOG_ALERT, 951 "adt_start_session(ADT_su, ADT_FAILURE): %m"); 952 return; 953 } 954 955 if (pwd != NULL) { 956 /* target user authenticated, merge audit state */ 957 if (adt_set_user(ah, pwd->pw_uid, pwd->pw_gid, pwd->pw_uid, 958 pwd->pw_gid, NULL, ADT_UPDATE) != 0) { 959 syslog(LOG_AUTH | LOG_ERR, 960 "adt_set_user(ADT_su, ADT_FAILURE): %m"); 961 } 962 if (((user_entry = getusernam(pwd->pw_name)) != NULL) && 963 ((kva_value = kva_match((kva_t *)user_entry->attr, 964 USERATTR_TYPE_KW)) != NULL) && 965 ((strcmp(kva_value, USERATTR_TYPE_NONADMIN_KW) == 0) || 966 (strcmp(kva_value, USERATTR_TYPE_ADMIN_KW) == 0))) { 967 event_id = ADT_role_login; 968 } 969 free_userattr(user_entry); /* OK to use, checks for NULL */ 970 } 971 if ((event = adt_alloc_event(ah, event_id)) == NULL) { 972 syslog(LOG_AUTH | LOG_ALERT, 973 "adt_alloc_event(ADT_su, ADT_FAILURE): %m"); 974 return; 975 } 976 /* 977 * can't tell if user not found is a role, so always use su 978 * If we do pass in pwd when the JNI is fixed, then can 979 * distinguish and set name in both su and role_login 980 */ 981 if (pwd == NULL) { 982 /* 983 * this should be "fail_user" rather than "message" 984 * see adt_xml. The JNI breaks, so for now we leave 985 * this alone. 986 */ 987 event->adt_su.message = user; 988 } 989 if (adt_put_event(event, ADT_FAILURE, 990 ADT_FAIL_PAM + pamerr) != 0) { 991 syslog(LOG_AUTH | LOG_ALERT, 992 "adt_put_event(ADT_su(ADT_FAIL, %s): %m", 993 pam_strerror(pamh, pamerr)); 994 } 995 if (pw_change != PW_FALSE) { 996 /* Also audit password change failed */ 997 adt_free_event(event); 998 if ((event = adt_alloc_event(ah, ADT_passwd)) == NULL) { 999 syslog(LOG_AUTH | LOG_ALERT, 1000 "su: adt_alloc_event(ADT_passwd): %m"); 1001 } else if (adt_put_event(event, ADT_FAILURE, 1002 ADT_FAIL_PAM + pamerr) != 0) { 1003 syslog(LOG_AUTH | LOG_ALERT, 1004 "su: adt_put_event(ADT_passwd, ADT_FAILURE): %m"); 1005 } 1006 } 1007 adt_free_event(event); 1008 (void) adt_end_session(ah); 1009 } 1010 1011 #ifdef DYNAMIC_SU 1012 /* 1013 * su_conv(): 1014 * This is the conv (conversation) function called from 1015 * a PAM authentication module to print error messages 1016 * or garner information from the user. 1017 */ 1018 /*ARGSUSED*/ 1019 static int 1020 su_conv(int num_msg, struct pam_message **msg, struct pam_response **response, 1021 void *appdata_ptr) 1022 { 1023 struct pam_message *m; 1024 struct pam_response *r; 1025 char *temp; 1026 int k; 1027 char respbuf[PAM_MAX_RESP_SIZE]; 1028 1029 if (num_msg <= 0) 1030 return (PAM_CONV_ERR); 1031 1032 *response = (struct pam_response *)calloc(num_msg, 1033 sizeof (struct pam_response)); 1034 if (*response == NULL) 1035 return (PAM_BUF_ERR); 1036 1037 k = num_msg; 1038 m = *msg; 1039 r = *response; 1040 while (k--) { 1041 1042 switch (m->msg_style) { 1043 1044 case PAM_PROMPT_ECHO_OFF: 1045 errno = 0; 1046 temp = getpassphrase(m->msg); 1047 if (errno == EINTR) 1048 return (PAM_CONV_ERR); 1049 if (temp != NULL) { 1050 r->resp = strdup(temp); 1051 if (r->resp == NULL) { 1052 freeresponse(num_msg, response); 1053 return (PAM_BUF_ERR); 1054 } 1055 } 1056 break; 1057 1058 case PAM_PROMPT_ECHO_ON: 1059 if (m->msg != NULL) { 1060 (void) fputs(m->msg, stdout); 1061 } 1062 1063 (void) fgets(respbuf, sizeof (respbuf), stdin); 1064 temp = strchr(respbuf, '\n'); 1065 if (temp != NULL) 1066 *temp = '\0'; 1067 1068 r->resp = strdup(respbuf); 1069 if (r->resp == NULL) { 1070 freeresponse(num_msg, response); 1071 return (PAM_BUF_ERR); 1072 } 1073 break; 1074 1075 case PAM_ERROR_MSG: 1076 if (m->msg != NULL) { 1077 (void) fputs(m->msg, stderr); 1078 (void) fputs("\n", stderr); 1079 } 1080 break; 1081 1082 case PAM_TEXT_INFO: 1083 if (m->msg != NULL) { 1084 (void) fputs(m->msg, stdout); 1085 (void) fputs("\n", stdout); 1086 } 1087 break; 1088 1089 default: 1090 break; 1091 } 1092 m++; 1093 r++; 1094 } 1095 return (PAM_SUCCESS); 1096 } 1097 1098 /* 1099 * emb_su_conv(): 1100 * This is the conv (conversation) function called from 1101 * a PAM authentication module to print error messages 1102 * or garner information from the user. 1103 * This version is used for embedded_su. 1104 */ 1105 /*ARGSUSED*/ 1106 static int 1107 emb_su_conv(int num_msg, struct pam_message **msg, 1108 struct pam_response **response, void *appdata_ptr) 1109 { 1110 struct pam_message *m; 1111 struct pam_response *r; 1112 char *temp; 1113 int k; 1114 char respbuf[PAM_MAX_RESP_SIZE]; 1115 1116 if (num_msg <= 0) 1117 return (PAM_CONV_ERR); 1118 1119 *response = (struct pam_response *)calloc(num_msg, 1120 sizeof (struct pam_response)); 1121 if (*response == NULL) 1122 return (PAM_BUF_ERR); 1123 1124 /* First, send the prompts */ 1125 (void) printf("CONV %d\n", num_msg); 1126 k = num_msg; 1127 m = *msg; 1128 while (k--) { 1129 switch (m->msg_style) { 1130 1131 case PAM_PROMPT_ECHO_OFF: 1132 (void) puts("PAM_PROMPT_ECHO_OFF"); 1133 goto msg_common; 1134 1135 case PAM_PROMPT_ECHO_ON: 1136 (void) puts("PAM_PROMPT_ECHO_ON"); 1137 goto msg_common; 1138 1139 case PAM_ERROR_MSG: 1140 (void) puts("PAM_ERROR_MSG"); 1141 goto msg_common; 1142 1143 case PAM_TEXT_INFO: 1144 (void) puts("PAM_TEXT_INFO"); 1145 /* fall through to msg_common */ 1146 msg_common: 1147 if (m->msg == NULL) 1148 quotemsg(NULL); 1149 else 1150 quotemsg("%s", m->msg); 1151 break; 1152 1153 default: 1154 break; 1155 } 1156 m++; 1157 } 1158 1159 /* Next, collect the responses */ 1160 k = num_msg; 1161 m = *msg; 1162 r = *response; 1163 while (k--) { 1164 1165 switch (m->msg_style) { 1166 1167 case PAM_PROMPT_ECHO_OFF: 1168 case PAM_PROMPT_ECHO_ON: 1169 (void) fgets(respbuf, sizeof (respbuf), stdin); 1170 1171 temp = strchr(respbuf, '\n'); 1172 if (temp != NULL) 1173 *temp = '\0'; 1174 1175 r->resp = strdup(respbuf); 1176 if (r->resp == NULL) { 1177 freeresponse(num_msg, response); 1178 return (PAM_BUF_ERR); 1179 } 1180 1181 break; 1182 1183 case PAM_ERROR_MSG: 1184 case PAM_TEXT_INFO: 1185 break; 1186 1187 default: 1188 break; 1189 } 1190 m++; 1191 r++; 1192 } 1193 return (PAM_SUCCESS); 1194 } 1195 1196 static void 1197 freeresponse(int num_msg, struct pam_response **response) 1198 { 1199 struct pam_response *r; 1200 int i; 1201 1202 /* free responses */ 1203 r = *response; 1204 for (i = 0; i < num_msg; i++, r++) { 1205 if (r->resp != NULL) { 1206 /* Zap it in case it's a password */ 1207 (void) memset(r->resp, '\0', strlen(r->resp)); 1208 free(r->resp); 1209 } 1210 } 1211 free(*response); 1212 *response = NULL; 1213 } 1214 1215 /* 1216 * Print a message, applying quoting for lines starting with '.'. 1217 * 1218 * I18n note: \n is "safe" in all locales, and all locales use 1219 * a high-bit-set character to start multibyte sequences, so 1220 * scanning for a \n followed by a '.' is safe. 1221 */ 1222 static void 1223 quotemsg(char *fmt, ...) 1224 { 1225 if (fmt != NULL) { 1226 char *msg; 1227 char *p; 1228 boolean_t bol; 1229 va_list v; 1230 1231 va_start(v, fmt); 1232 msg = alloc_vsprintf(fmt, v); 1233 va_end(v); 1234 1235 bol = B_TRUE; 1236 for (p = msg; *p != '\0'; p++) { 1237 if (bol) { 1238 if (*p == '.') 1239 (void) putchar('.'); 1240 bol = B_FALSE; 1241 } 1242 (void) putchar(*p); 1243 if (*p == '\n') 1244 bol = B_TRUE; 1245 } 1246 (void) putchar('\n'); 1247 free(msg); 1248 } 1249 (void) putchar('.'); 1250 (void) putchar('\n'); 1251 } 1252 1253 /* 1254 * validate - Check that the account is valid for switching to. 1255 */ 1256 static void 1257 validate(char *usernam, int *pw_change) 1258 { 1259 int error; 1260 int tries; 1261 1262 if ((error = pam_acct_mgmt(pamh, pam_flags)) != PAM_SUCCESS) { 1263 if (Sulog != NULL) 1264 log(Sulog, pwd.pw_name, 0); /* log entry */ 1265 if (error == PAM_NEW_AUTHTOK_REQD) { 1266 tries = 0; 1267 message(ERR, gettext("Password for user " 1268 "'%s' has expired"), pwd.pw_name); 1269 while ((error = pam_chauthtok(pamh, 1270 PAM_CHANGE_EXPIRED_AUTHTOK)) != PAM_SUCCESS) { 1271 if ((error == PAM_AUTHTOK_ERR || 1272 error == PAM_TRY_AGAIN) && 1273 (tries++ < DEF_ATTEMPTS)) { 1274 continue; 1275 } 1276 message(ERR, gettext("Sorry")); 1277 audit_failure(PW_FAILED, &pwd, NULL, error); 1278 if (dosyslog) 1279 syslog(LOG_CRIT, 1280 "'su %s' failed for %s on %s", 1281 pwd.pw_name, usernam, ttyn); 1282 closelog(); 1283 exit(1); 1284 } 1285 *pw_change = PW_TRUE; 1286 return; 1287 } else { 1288 message(ERR, gettext("Sorry")); 1289 audit_failure(PW_FALSE, &pwd, NULL, error); 1290 if (dosyslog) 1291 syslog(LOG_CRIT, "'su %s' failed for %s on %s", 1292 pwd.pw_name, usernam, ttyn); 1293 closelog(); 1294 exit(3); 1295 } 1296 } 1297 } 1298 1299 static char *illegal[] = { 1300 "SHELL=", 1301 "HOME=", 1302 "LOGNAME=", 1303 #ifndef NO_MAIL 1304 "MAIL=", 1305 #endif 1306 "CDPATH=", 1307 "IFS=", 1308 "PATH=", 1309 "TZ=", 1310 "HZ=", 1311 "TERM=", 1312 0 1313 }; 1314 1315 /* 1316 * legalenvvar - can PAM modules insert this environmental variable? 1317 */ 1318 1319 static int 1320 legalenvvar(char *s) 1321 { 1322 register char **p; 1323 1324 for (p = illegal; *p; p++) 1325 if (strncmp(s, *p, strlen(*p)) == 0) 1326 return (0); 1327 1328 if (s[0] == 'L' && s[1] == 'D' && s[2] == '_') 1329 return (0); 1330 1331 return (1); 1332 } 1333 1334 /* 1335 * The embedded_su protocol allows the client application to supply 1336 * an initialization block terminated by a line with just a "." on it. 1337 * 1338 * This initialization block is currently unused, reserved for future 1339 * expansion. Ignore it. This is made very slightly more complex by 1340 * the desire to cleanly ignore input lines of any length, while still 1341 * correctly detecting a line with just a "." on it. 1342 * 1343 * I18n note: It appears that none of the Solaris-supported locales 1344 * use 0x0a for any purpose other than newline, so looking for '\n' 1345 * seems safe. 1346 * All locales use high-bit-set leadin characters for their multi-byte 1347 * sequences, so a line consisting solely of ".\n" is what it appears 1348 * to be. 1349 */ 1350 static void 1351 readinitblock(void) 1352 { 1353 char buf[100]; 1354 boolean_t bol; 1355 1356 bol = B_TRUE; 1357 for (;;) { 1358 if (fgets(buf, sizeof (buf), stdin) == NULL) 1359 return; 1360 if (bol && strcmp(buf, ".\n") == 0) 1361 return; 1362 bol = (strchr(buf, '\n') != NULL); 1363 } 1364 } 1365 #else /* !DYNAMIC_SU */ 1366 static void 1367 update_audit(struct passwd *pwd) 1368 { 1369 adt_session_data_t *ah; /* audit session handle */ 1370 1371 if (adt_start_session(&ah, NULL, ADT_USE_PROC_DATA) != 0) { 1372 message(ERR, gettext("Sorry")); 1373 if (dosyslog) 1374 syslog(LOG_CRIT, "'su %s' failed for %s " 1375 "cannot start audit session %m", 1376 pwd->pw_name, username); 1377 closelog(); 1378 exit(2); 1379 } 1380 if (adt_set_user(ah, pwd->pw_uid, pwd->pw_gid, pwd->pw_uid, 1381 pwd->pw_gid, NULL, ADT_UPDATE) != 0) { 1382 if (dosyslog) 1383 syslog(LOG_CRIT, "'su %s' failed for %s " 1384 "cannot update audit session %m", 1385 pwd->pw_name, username); 1386 closelog(); 1387 exit(2); 1388 } 1389 } 1390 #endif /* DYNAMIC_SU */ 1391 1392 /* 1393 * Report an error, either a fatal one, a warning, or a usage message, 1394 * depending on the mode parameter. 1395 */ 1396 /*ARGSUSED*/ 1397 static void 1398 message(enum messagemode mode, char *fmt, ...) 1399 { 1400 char *s; 1401 va_list v; 1402 1403 va_start(v, fmt); 1404 s = alloc_vsprintf(fmt, v); 1405 va_end(v); 1406 1407 #ifdef DYNAMIC_SU 1408 if (embedded) { 1409 if (mode == WARN) { 1410 (void) printf("CONV 1\n"); 1411 (void) printf("PAM_ERROR_MSG\n"); 1412 } else { /* ERR, USAGE */ 1413 (void) printf("ERROR\n"); 1414 } 1415 if (mode == USAGE) { 1416 quotemsg("%s", s); 1417 } else { /* ERR, WARN */ 1418 quotemsg("%s: %s", myname, s); 1419 } 1420 } else { 1421 #endif /* DYNAMIC_SU */ 1422 if (mode == USAGE) { 1423 (void) fprintf(stderr, "%s\n", s); 1424 } else { /* ERR, WARN */ 1425 (void) fprintf(stderr, "%s: %s\n", myname, s); 1426 } 1427 #ifdef DYNAMIC_SU 1428 } 1429 #endif /* DYNAMIC_SU */ 1430 1431 free(s); 1432 } 1433 1434 /* 1435 * Return a pointer to the last path component of a. 1436 */ 1437 static char * 1438 tail(char *a) 1439 { 1440 char *p; 1441 1442 p = strrchr(a, '/'); 1443 if (p == NULL) 1444 p = a; 1445 else 1446 p++; /* step over the '/' */ 1447 1448 return (p); 1449 } 1450 1451 static char * 1452 alloc_vsprintf(const char *fmt, va_list ap1) 1453 { 1454 va_list ap2; 1455 int n; 1456 char buf[1]; 1457 char *s; 1458 1459 /* 1460 * We need to scan the argument list twice. Save off a copy 1461 * of the argument list pointer(s) for the second pass. Note that 1462 * we are responsible for va_end'ing our copy. 1463 */ 1464 va_copy(ap2, ap1); 1465 1466 /* 1467 * vsnprintf into a dummy to get a length. One might 1468 * think that passing 0 as the length to snprintf would 1469 * do what we want, but it's defined not to. 1470 * 1471 * Perhaps we should sprintf into a 100 character buffer 1472 * or something like that, to avoid two calls to snprintf 1473 * in most cases. 1474 */ 1475 n = vsnprintf(buf, sizeof (buf), fmt, ap2); 1476 va_end(ap2); 1477 1478 /* 1479 * Allocate an appropriately-sized buffer. 1480 */ 1481 s = malloc(n + 1); 1482 if (s == NULL) { 1483 perror("malloc"); 1484 exit(4); 1485 } 1486 1487 (void) vsnprintf(s, n+1, fmt, ap1); 1488 1489 return (s); 1490 } 1491