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