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