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