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