xref: /titanic_50/usr/src/cmd/su/su.c (revision 6aa4fc89ec1cf2cdf7d7c3b9ec059802ac9abe65)
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 
100 char	*Sulog, *Console;
101 char	*Path, *Supath;
102 
103 /*
104  * Locale variables to be propagated to "su -" environment
105  */
106 static char *initvar;
107 static char *initenv[] = {
108 	"TZ", "LANG", "LC_CTYPE",
109 	"LC_NUMERIC", "LC_TIME", "LC_COLLATE",
110 	"LC_MONETARY", "LC_MESSAGES", "LC_ALL", 0};
111 static char mail[30] = { "MAIL=/var/mail/" };
112 
113 static void envalt(void);
114 static void log(char *, char *, int);
115 static void to(int);
116 
117 enum messagemode { USAGE, ERR, WARN };
118 static void message(enum messagemode, char *, ...);
119 
120 static char *alloc_vsprintf(const char *, va_list);
121 static char *tail(char *);
122 
123 static void audit_success(int, struct passwd *);
124 static void audit_logout(adt_session_data_t *, au_event_t);
125 static void audit_failure(int, struct passwd *, char *, int);
126 
127 #ifdef DYNAMIC_SU
128 static void validate(char *, int *);
129 static int legalenvvar(char *);
130 static int su_conv(int, struct pam_message **, struct pam_response **, void *);
131 static int emb_su_conv(int, struct pam_message **, struct pam_response **,
132     void *);
133 static void freeresponse(int, struct pam_response **response);
134 static struct pam_conv pam_conv = {su_conv, NULL};
135 static struct pam_conv emb_pam_conv = {emb_su_conv, NULL};
136 static void quotemsg(char *, ...);
137 static void readinitblock(void);
138 #else	/* !DYNAMIC_SU */
139 static void update_audit(struct passwd *pwd);
140 #endif	/* DYNAMIC_SU */
141 
142 static pam_handle_t	*pamh = NULL;	/* Authentication handle */
143 struct	passwd pwd;
144 char	pwdbuf[1024];			/* buffer for getpwnam_r() */
145 char	shell[] = "/usr/bin/sh";	/* default shell */
146 char	safe_shell[] = "/sbin/sh";	/* "fallback" shell */
147 char	su[PATH_MAX] = "su";		/* arg0 for exec of shprog */
148 char	homedir[PATH_MAX] = "HOME=";
149 char	logname[20] = "LOGNAME=";
150 char	*suprmt = SUPRMT;
151 char	termtyp[PATH_MAX] = "TERM=";
152 char	*term;
153 char	shelltyp[PATH_MAX] = "SHELL=";
154 char	*hz;
155 char	tznam[PATH_MAX];
156 char	hzname[10] = "HZ=";
157 char	path[PATH_MAX] = "PATH=";
158 char	supath[PATH_MAX] = "PATH=";
159 char	*envinit[ELIM];
160 extern	char **environ;
161 char *ttyn;
162 char *username;					/* the invoker */
163 static	int	dosyslog = 0;			/* use syslog? */
164 char	*myname;
165 #ifdef	DYNAMIC_SU
166 int pam_flags = 0;
167 boolean_t embedded = B_FALSE;
168 #endif	/* DYNAMIC_SU */
169 
170 int
171 main(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));
415 ok:
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  */
708 static void
709 envalt(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  */
744 static void
745 log(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*/
771 static void
772 to(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 
785 static void
786 audit_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  */
854 static void
855 audit_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 
1006 static void
1007 audit_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*/
1085 static int
1086 su_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*/
1172 static int
1173 emb_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 */
1212 msg_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 
1262 static void
1263 freeresponse(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  */
1288 static void
1289 quotemsg(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  */
1322 static void
1323 validate(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 
1365 static 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 
1385 static int
1386 legalenvvar(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  */
1416 static void
1417 readinitblock(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 */
1432 static void
1433 update_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*/
1463 static void
1464 message(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  */
1503 static char *
1504 tail(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 
1517 static char *
1518 alloc_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