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