xref: /illumos-gate/usr/src/cmd/su/su.c (revision 4ba5c7f8)
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 }
1553