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