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