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