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