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