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