xref: /titanic_50/usr/src/cmd/su/su.c (revision d0698e0d179f97729cacdbc2f13446a6b0a3f22a)
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