xref: /illumos-gate/usr/src/cmd/su/su.c (revision 7c478bd9)
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, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
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 #pragma ident	"%Z%%M%	%I%	%E% SMI"
34 
35 /*
36  *	su [-] [name [arg ...]] change userid, `-' changes environment.
37  *	If SULOG is defined, all attempts to su to another user are
38  *	logged there.
39  *	If CONSOLE is defined, all successful attempts to su to uid 0
40  *	are also logged there.
41  *
42  *	If su cannot create, open, or write entries into SULOG,
43  *	(or on the CONSOLE, if defined), the entry will not
44  *	be logged -- thus losing a record of the su's attempted
45  *	during this period.
46  */
47 
48 #include <stdio.h>
49 #include <sys/types.h>
50 #include <sys/stat.h>
51 #include <sys/param.h>
52 #include <unistd.h>
53 #include <stdlib.h>
54 #include <crypt.h>
55 #include <pwd.h>
56 #include <shadow.h>
57 #include <time.h>
58 #include <signal.h>
59 #include <fcntl.h>
60 #include <string.h>
61 #include <locale.h>
62 #include <syslog.h>
63 #include <sys/utsname.h>
64 #include <grp.h>
65 #include <deflt.h>
66 #include <limits.h>
67 #include <errno.h>
68 #include <stdarg.h>
69 
70 #ifdef DYNAMIC_SU
71 #include <security/pam_appl.h>
72 #endif
73 
74 #define	PATH	"/usr/bin:"		/* path for users other than root */
75 #define	SUPATH	"/usr/sbin:/usr/bin"	/* path for root */
76 #define	SUPRMT	"PS1=# "		/* primary prompt for root */
77 #define	ELIM 128
78 #define	ROOT 0
79 #ifdef	DYNAMIC_SU
80 #define	EMBEDDED_NAME	"embedded_su"
81 #endif	/* DYNAMIC_SU */
82 
83 /*
84  * Intervals to sleep after failed su
85  */
86 #ifndef SLEEPTIME
87 #define	SLEEPTIME	4
88 #endif
89 
90 #define	DEFAULT_LOGIN "/etc/default/login"
91 #define	DEFFILE "/etc/default/su"
92 
93 
94 char	*Sulog, *Console;
95 char	*Path, *Supath;
96 
97 /*
98  * Locale variables to be propagated to "su -" environment
99  */
100 static char *initvar;
101 static char *initenv[] = {
102 	"TZ", "LANG", "LC_CTYPE",
103 	"LC_NUMERIC", "LC_TIME", "LC_COLLATE",
104 	"LC_MONETARY", "LC_MESSAGES", "LC_ALL", 0};
105 static char mail[30] = { "MAIL=/var/mail/" };
106 
107 static void envalt(void);
108 static void log(char *where, char *towho, int how);
109 static void to(int sig);
110 
111 enum messagemode { USAGE, ERR, WARN };
112 static void message(enum messagemode mode, char *fmt, ...);
113 
114 static char *alloc_vsprintf(const char *fmt, va_list ap1);
115 static char *tail(char *a);
116 
117 /*
118  * Obsolescent Audit hooks for su command
119  */
120 extern void audit_su_bad_authentication(void);
121 extern void audit_su_bad_username(void);
122 extern void audit_su_init_info(char *, char *);
123 extern void audit_su_reset_ai(void);
124 extern void audit_su_success(void);
125 extern void audit_su_unknown_failure(void);
126 
127 #ifdef DYNAMIC_SU
128 static void validate(char *usernam);
129 static int legalenvvar(char *s);
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 num_msg, 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 pam_handle_t	*pamh;		/* Authentication handle */
137 static void quotemsg(char *fmt, ...);
138 static void readinitblock(void);
139 #endif	/* DYNAMIC_SU */
140 
141 struct	passwd pwd;
142 char	pwdbuf[1024];			/* buffer for getpwnam_r() */
143 char	shell[] = "/usr/bin/sh";	/* default shell */
144 char	safe_shell[] = "/sbin/sh";	/* "fallback" shell */
145 char	su[PATH_MAX] = "su";		/* arg0 for exec of shprog */
146 char	homedir[PATH_MAX] = "HOME=";
147 char	logname[20] = "LOGNAME=";
148 char	*suprmt = SUPRMT;
149 char	termtyp[PATH_MAX] = "TERM=";
150 char	*term;
151 char	shelltyp[PATH_MAX] = "SHELL=";
152 char	*hz;
153 char	tznam[PATH_MAX];
154 char	hzname[10] = "HZ=";
155 char	path[PATH_MAX] = "PATH=";
156 char	supath[PATH_MAX] = "PATH=";
157 char	*envinit[ELIM];
158 extern	char **environ;
159 char *ttyn;
160 char *username;					/* the invoker */
161 static	int	dosyslog = 0;			/* use syslog? */
162 char	*myname;
163 #ifdef	DYNAMIC_SU
164 boolean_t embedded = B_FALSE;
165 #endif	/* DYNAMIC_SU */
166 
167 int
168 main(int argc, char **argv)
169 {
170 #ifndef DYNAMIC_SU
171 	struct spwd sp;
172 	char  spbuf[1024];		/* buffer for getspnam_r() */
173 	char *password;
174 #endif
175 	char *nptr;
176 	char *pshell;
177 	int eflag = 0;
178 	int envidx = 0;
179 	uid_t uid;
180 	gid_t gid;
181 	char *dir, *shprog, *name;
182 	char *ptr;
183 	char *prog = argv[0];
184 #ifdef DYNAMIC_SU
185 	int sleeptime = SLEEPTIME;
186 	char **pam_env = 0;
187 	int flags = 0;
188 	int retcode;
189 	int idx = 0;
190 #endif	/* DYNAMIC_SU */
191 
192 	(void) setlocale(LC_ALL, "");
193 #if !defined(TEXT_DOMAIN)	/* Should be defined by cc -D */
194 #define	TEXT_DOMAIN "SYS_TEST"	/* Use this only if it wasn't */
195 #endif
196 	(void) textdomain(TEXT_DOMAIN);
197 
198 	myname = tail(argv[0]);
199 
200 #if	defined(DYNAMIC_SU)
201 	if (strcmp(myname, EMBEDDED_NAME) == 0) {
202 		embedded = B_TRUE;
203 		setbuf(stdin, NULL);
204 		setbuf(stdout, NULL);
205 		readinitblock();
206 	}
207 #endif	/* defined(DYNAMIC_SU) */
208 
209 	if (argc > 1 && *argv[1] == '-') {
210 		/* Explicitly check for just `-' (no trailing chars) */
211 		if (strlen(argv[1]) == 1) {
212 			eflag++;	/* set eflag if `-' is specified */
213 			argv++;
214 			argc--;
215 		} else {
216 			message(USAGE,
217 			    gettext("Usage: %s [-] [ username [ arg ... ] ]"),
218 				prog);
219 			exit(1);
220 		}
221 	}
222 
223 	/*
224 	 * Determine specified userid, get their password file entry,
225 	 * and set variables to values in password file entry fields.
226 	 */
227 	if (argc > 1) {
228 		/*
229 		 * Usernames can't start with a `-', so we check for that to
230 		 * catch bad usage (like "su - -c ls").
231 		 */
232 		if (*argv[1] == '-') {
233 			message(USAGE,
234 			    gettext("Usage: %s [-] [ username [ arg ... ] ]"),
235 				prog);
236 			exit(1);
237 		} else
238 			nptr = argv[1];	/* use valid command-line username */
239 	} else
240 		nptr = "root";		/* use default "root" username */
241 
242 	if (defopen(DEFFILE) == 0) {
243 
244 		if (Sulog = defread("SULOG="))
245 			Sulog = strdup(Sulog);
246 		if (Console = defread("CONSOLE="))
247 			Console = strdup(Console);
248 		if (Path = defread("PATH="))
249 			Path = strdup(Path);
250 		if (Supath = defread("SUPATH="))
251 			Supath = strdup(Supath);
252 		if ((ptr = defread("SYSLOG=")) != NULL)
253 			dosyslog = strcmp(ptr, "YES") == 0;
254 
255 		(void) defopen(NULL);
256 	}
257 	(void) strlcat(path, (Path) ? Path : PATH, sizeof (path));
258 	(void) strlcat(supath, (Supath) ? Supath : SUPATH, sizeof (supath));
259 
260 	if ((ttyn = ttyname(0)) == NULL)
261 		if ((ttyn = ttyname(1)) == NULL)
262 			if ((ttyn = ttyname(2)) == NULL)
263 				ttyn = "/dev/???";
264 	if ((username = cuserid(NULL)) == NULL)
265 		username = "(null)";
266 
267 	/*
268 	 * if Sulog defined, create SULOG, if it does not exist, with
269 	 * mode read/write user. Change owner and group to root
270 	 */
271 	if (Sulog != NULL) {
272 		(void) close(open(Sulog, O_WRONLY | O_APPEND | O_CREAT,
273 		    (S_IRUSR|S_IWUSR)));
274 		(void) chown(Sulog, (uid_t)ROOT, (gid_t)ROOT);
275 	}
276 
277 #ifdef DYNAMIC_SU
278 	if (pam_start(embedded ? EMBEDDED_NAME : "su", nptr,
279 	    embedded ? &emb_pam_conv : &pam_conv, &pamh) != PAM_SUCCESS)
280 		exit(1);
281 	if (pam_set_item(pamh, PAM_TTY, ttyn) != PAM_SUCCESS)
282 		exit(1);
283 #endif	/* DYNAMIC_SU */
284 
285 	/*
286 	 * Save away the following for writing user-level audit records:
287 	 *	username desired
288 	 *	current tty
289 	 *	whether or not username desired is expired
290 	 *	auditinfo structure of current process
291 	 *	uid's and gid's of current process
292 	 */
293 	audit_su_init_info(nptr, ttyn);
294 	openlog("su", LOG_CONS, LOG_AUTH);
295 
296 #ifdef DYNAMIC_SU
297 
298 	/*
299 	 * Ignore SIGQUIT and SIGINT
300 	 */
301 	(void) signal(SIGQUIT, SIG_IGN);
302 	(void) signal(SIGINT, SIG_IGN);
303 
304 	/* call pam_authenticate() to authenticate the user through PAM */
305 	if (getpwnam_r(nptr, &pwd, pwdbuf, sizeof (pwdbuf)) == NULL)
306 		retcode = PAM_USER_UNKNOWN;
307 	else if ((flags = (getuid() != (uid_t)ROOT)) != 0) {
308 		retcode = pam_authenticate(pamh, 0);
309 	} else /* root user does not need to authenticate */
310 		retcode = PAM_SUCCESS;
311 
312 	if (retcode != PAM_SUCCESS) {
313 		/*
314 		 * Use the same value of sleeptime that login(1) uses.
315 		 * This is obtained by reading the file /etc/default/login
316 		 * using the def*() functions
317 		 */
318 		if (defopen(DEFAULT_LOGIN) == 0) {
319 			if ((ptr = defread("SLEEPTIME=")) != NULL)
320 				sleeptime = atoi(ptr);
321 			(void) defopen((char *)NULL);
322 
323 			if (sleeptime < 0 || sleeptime > 5)
324 				sleeptime = SLEEPTIME;
325 		}
326 
327 		/*
328 		 * 1st step: log the error.
329 		 * 2nd step: sleep.
330 		 * 3rd step: print out message to user.
331 		 */
332 		switch (retcode) {
333 		case PAM_USER_UNKNOWN:
334 			audit_su_bad_username();
335 			closelog();
336 			(void) sleep(sleeptime);
337 			message(ERR, gettext("Unknown id: %s"), nptr);
338 			break;
339 
340 		case PAM_AUTH_ERR:
341 			if (Sulog != NULL)
342 				log(Sulog, nptr, 0);	/* log entry */
343 			audit_su_bad_authentication();
344 			if (dosyslog)
345 				syslog(LOG_CRIT, "'su %s' failed for %s on %s",
346 				    pwd.pw_name, username, ttyn);
347 			closelog();
348 			(void) sleep(sleeptime);
349 			message(ERR, gettext("Sorry"));
350 			break;
351 
352 		case PAM_CONV_ERR:
353 		default:
354 			audit_su_unknown_failure();
355 			if (dosyslog)
356 				syslog(LOG_CRIT, "'su %s' failed for %s on %s",
357 				    pwd.pw_name, username, ttyn);
358 			closelog();
359 			(void) sleep(sleeptime);
360 			message(ERR, gettext("Sorry"));
361 			break;
362 		}
363 
364 		(void) signal(SIGQUIT, SIG_DFL);
365 		(void) signal(SIGINT, SIG_DFL);
366 		exit(1);
367 	}
368 	if (flags)
369 		validate(username);
370 	if (pam_setcred(pamh, PAM_REINITIALIZE_CRED) != PAM_SUCCESS) {
371 		message(ERR, gettext("unable to set credentials"));
372 		exit(2);
373 	}
374 	audit_su_success();
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	/* STATIC !PAM */
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_su_bad_username();
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_su_bad_authentication();
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 	audit_su_reset_ai();
416 	audit_su_success();
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	/* STATIC !PAM */
422 
423 	uid = pwd.pw_uid;
424 	gid = pwd.pw_gid;
425 	dir = strdup(pwd.pw_dir);
426 	shprog = strdup(pwd.pw_shell);
427 	name = strdup(pwd.pw_name);
428 
429 	if (Sulog != NULL)
430 		log(Sulog, nptr, 1);	/* log entry */
431 
432 	/* set user and group ids to specified user */
433 
434 	/* set the real (and effective) GID */
435 	if (setgid(gid) == -1) {
436 		message(ERR, gettext("Invalid GID"));
437 		exit(2);
438 	}
439 	/* Initialize the supplementary group access list. */
440 	if (!nptr)
441 		exit(2);
442 	if (initgroups(nptr, gid) == -1) {
443 		exit(2);
444 	}
445 	/* set the real (and effective) UID */
446 	if (setuid(uid) == -1) {
447 		message(ERR, gettext("Invalid UID"));
448 		exit(2);
449 	}
450 
451 	/*
452 	 * If new user's shell field is neither NULL nor equal to /usr/bin/sh,
453 	 * set:
454 	 *
455 	 *	pshell = their shell
456 	 *	su = [-]last component of shell's pathname
457 	 *
458 	 * Otherwise, set the shell to /usr/bin/sh and set argv[0] to '[-]su'.
459 	 */
460 	if (shprog[0] != '\0' && strcmp(shell, shprog) != 0) {
461 		char *p;
462 
463 		pshell = shprog;
464 		(void) strcpy(su, eflag ? "-" : "");
465 
466 		if ((p = strrchr(pshell, '/')) != NULL)
467 			(void) strlcat(su, p + 1, sizeof (su));
468 		else
469 			(void) strlcat(su, pshell, sizeof (su));
470 	} else {
471 		pshell = shell;
472 		(void) strcpy(su, eflag ? "-su" : "su");
473 	}
474 
475 	/*
476 	 * set environment variables for new user;
477 	 * arg0 for exec of shprog must now contain `-'
478 	 * so that environment of new user is given
479 	 */
480 	if (eflag) {
481 		int j;
482 		char *var;
483 
484 		if (strlen(dir) == 0) {
485 			(void) strcpy(dir, "/");
486 			message(WARN, gettext("No directory! Using home=/"));
487 		}
488 		(void) strlcat(homedir, dir, sizeof (homedir));
489 		(void) strlcat(logname, name, sizeof (logname));
490 		if (hz = getenv("HZ"))
491 			(void) strlcat(hzname, hz, sizeof (hzname));
492 
493 		(void) strlcat(shelltyp, pshell, sizeof (shelltyp));
494 
495 		if (chdir(dir) < 0) {
496 			message(ERR, gettext("No directory!"));
497 			exit(1);
498 		}
499 		envinit[envidx = 0] = homedir;
500 		envinit[++envidx] = ((uid == (uid_t)ROOT) ? supath : path);
501 		envinit[++envidx] = logname;
502 		envinit[++envidx] = hzname;
503 		if ((term = getenv("TERM")) != NULL) {
504 			(void) strlcat(termtyp, term, sizeof (termtyp));
505 			envinit[++envidx] = termtyp;
506 		}
507 		envinit[++envidx] = shelltyp;
508 
509 		(void) strlcat(mail, name, sizeof (mail));
510 		envinit[++envidx] = mail;
511 
512 		/*
513 		 * Fetch the relevant locale/TZ environment variables from
514 		 * the inherited environment.
515 		 *
516 		 * We have a priority here for setting TZ. If TZ is set in
517 		 * in the inherited environment, that value remains top
518 		 * priority. If the file /etc/default/login has TIMEZONE set,
519 		 * that has second highest priority.
520 		 */
521 		tznam[0] = '\0';
522 		for (j = 0; initenv[j] != 0; j++) {
523 			if (initvar = getenv(initenv[j])) {
524 
525 				/*
526 				 * Skip over values beginning with '/' for
527 				 * security.
528 				 */
529 				if (initvar[0] == '/')  continue;
530 
531 				if (strcmp(initenv[j], "TZ") == 0) {
532 					(void) strcpy(tznam, "TZ=");
533 					(void) strlcat(tznam, initvar,
534 						sizeof (tznam));
535 
536 				} else {
537 					var = (char *)
538 						malloc(strlen(initenv[j])
539 							+ strlen(initvar)
540 							+ 2);
541 					(void) strcpy(var, initenv[j]);
542 					(void) strcat(var, "=");
543 					(void) strcat(var, initvar);
544 					envinit[++envidx] = var;
545 				}
546 			}
547 		}
548 
549 		/*
550 		 * Check if TZ was found. If not then try to read it from
551 		 * /etc/default/login.
552 		 */
553 		if (tznam[0] == '\0') {
554 			if (defopen(DEFAULT_LOGIN) == 0) {
555 				if (initvar = defread("TIMEZONE=")) {
556 					(void) strcpy(tznam, "TZ=");
557 					(void) strlcat(tznam, initvar,
558 							sizeof (tznam));
559 				}
560 				(void) defopen(NULL);
561 			}
562 		}
563 
564 		if (tznam[0] != '\0')
565 			envinit[++envidx] = tznam;
566 
567 #ifdef DYNAMIC_SU
568 		/*
569 		 * set the PAM environment variables -
570 		 * check for legal environment variables
571 		 */
572 		if ((pam_env = pam_getenvlist(pamh)) != 0) {
573 			while (pam_env[idx] != 0) {
574 				if (envidx + 2 < ELIM &&
575 				    legalenvvar(pam_env[idx])) {
576 					envinit[++envidx] = pam_env[idx];
577 				}
578 				idx++;
579 			}
580 		}
581 #endif
582 		envinit[++envidx] = NULL;
583 		environ = envinit;
584 	} else {
585 		char **pp = environ, **qq, *p;
586 
587 		while ((p = *pp) != NULL) {
588 			if (*p == 'L' && p[1] == 'D' && p[2] == '_') {
589 				for (qq = pp; (*qq = qq[1]) != NULL; qq++)
590 					;
591 				/* pp is not advanced */
592 			} else {
593 				pp++;
594 			}
595 		}
596 	}
597 
598 #ifdef DYNAMIC_SU
599 	if (pamh)
600 		(void) pam_end(pamh, PAM_SUCCESS);
601 #endif
602 
603 	/*
604 	 * if new user is root:
605 	 *	if CONSOLE defined, log entry there;
606 	 *	if eflag not set, change environment to that of root.
607 	 */
608 	if (uid == (uid_t)ROOT) {
609 		if (Console != NULL)
610 			if (strcmp(ttyn, Console) != 0) {
611 				(void) signal(SIGALRM, to);
612 				(void) alarm(30);
613 				log(Console, nptr, 1);
614 				(void) alarm(0);
615 			}
616 		if (!eflag)
617 			envalt();
618 	}
619 
620 	/*
621 	 * Default for SIGCPU and SIGXFSZ.  Shells inherit
622 	 * signal disposition from parent.  And the
623 	 * shells should have default dispositions for these
624 	 * signals.
625 	 */
626 	(void) signal(SIGXCPU, SIG_DFL);
627 	(void) signal(SIGXFSZ, SIG_DFL);
628 
629 #ifdef	DYNAMIC_SU
630 	if (embedded) {
631 		(void) puts("SUCCESS");
632 		/*
633 		 * After this point, we're no longer talking the
634 		 * embedded_su protocol, so turn it off.
635 		 */
636 		embedded = B_FALSE;
637 	}
638 #endif	/* DYNAMIC_SU */
639 
640 	/*
641 	 * if additional arguments, exec shell program with array
642 	 * of pointers to arguments:
643 	 *	-> if shell = default, then su = [-]su
644 	 *	-> if shell != default, then su = [-]last component of
645 	 *						shell's pathname
646 	 *
647 	 * if no additional arguments, exec shell with arg0 of su
648 	 * where:
649 	 *	-> if shell = default, then su = [-]su
650 	 *	-> if shell != default, then su = [-]last component of
651 	 *						shell's pathname
652 	 */
653 	if (argc > 2) {
654 		argv[1] = su;
655 		(void) execv(pshell, &argv[1]);
656 	} else
657 		(void) execl(pshell, su, 0);
658 
659 
660 	/*
661 	 * Try to clean up after an administrator who has made a mistake
662 	 * configuring root's shell; if root's shell is other than /sbin/sh,
663 	 * try exec'ing /sbin/sh instead.
664 	 */
665 	if ((uid == (uid_t)ROOT) && (strcmp(name, "root") == 0) &&
666 	    (strcmp(safe_shell, pshell) != 0)) {
667 		message(WARN,
668 		    gettext("No shell %s.  Trying fallback shell %s."),
669 		    pshell, safe_shell);
670 
671 		if (eflag) {
672 			(void) strcpy(su, "-sh");
673 			(void) strlcpy(shelltyp + strlen("SHELL="),
674 			    safe_shell, sizeof (shelltyp) - strlen("SHELL="));
675 		} else {
676 			(void) strcpy(su, "sh");
677 		}
678 
679 		if (argc > 2) {
680 			argv[1] = su;
681 			(void) execv(safe_shell, &argv[1]);
682 		} else {
683 			(void) execl(safe_shell, su, 0);
684 		}
685 		message(ERR, gettext("Couldn't exec fallback shell %s: %s"),
686 		    safe_shell, strerror(errno));
687 	} else {
688 		message(ERR, gettext("No shell"));
689 	}
690 	return (3);
691 }
692 
693 /*
694  * Environment altering routine -
695  *	This routine is called when a user is su'ing to root
696  *	without specifying the - flag.
697  *	The user's PATH and PS1 variables are reset
698  *	to the correct value for root.
699  *	All of the user's other environment variables retain
700  *	their current values after the su (if they are exported).
701  */
702 static void
703 envalt(void)
704 {
705 	/*
706 	 * If user has PATH variable in their environment, change its value
707 	 *		to /bin:/etc:/usr/bin ;
708 	 * if user does not have PATH variable, add it to the user's
709 	 *		environment;
710 	 * if either of the above fail, an error message is printed.
711 	 */
712 	if (putenv(supath) != 0) {
713 		message(ERR,
714 		    gettext("unable to obtain memory to expand environment"));
715 		exit(4);
716 	}
717 
718 	/*
719 	 * If user has PROMPT variable in their environment, change its value
720 	 *		to # ;
721 	 * if user does not have PROMPT variable, add it to the user's
722 	 *		environment;
723 	 * if either of the above fail, an error message is printed.
724 	 */
725 	if (putenv(suprmt) != 0) {
726 		message(ERR,
727 		    gettext("unable to obtain memory to expand environment"));
728 		exit(4);
729 	}
730 }
731 
732 /*
733  * Logging routine -
734  *	where = SULOG or CONSOLE
735  *	towho = specified user ( user being su'ed to )
736  *	how = 0 if su attempt failed; 1 if su attempt succeeded
737  */
738 static void
739 log(char *where, char *towho, int how)
740 {
741 	FILE *logf;
742 	time_t now;
743 	struct tm *tmp;
744 
745 	/*
746 	 * open SULOG or CONSOLE - if open fails, return
747 	 */
748 	if ((logf = fopen(where, "a")) == NULL)
749 		return;
750 
751 	now = time(0);
752 	tmp = localtime(&now);
753 
754 	/*
755 	 * write entry into SULOG or onto CONSOLE - if write fails, return
756 	 */
757 	(void) fprintf(logf, "SU %.2d/%.2d %.2d:%.2d %c %s %s-%s\n",
758 	    tmp->tm_mon + 1, tmp->tm_mday, tmp->tm_hour, tmp->tm_min,
759 	    how ? '+' : '-', ttyn + sizeof ("/dev/") - 1, username, towho);
760 
761 	(void) fclose(logf);	/* close SULOG or CONSOLE */
762 }
763 
764 /*ARGSUSED*/
765 static void
766 to(int sig)
767 {}
768 
769 #ifdef DYNAMIC_SU
770 /*
771  * su_conv():
772  *	This is the conv (conversation) function called from
773  *	a PAM authentication module to print error messages
774  *	or garner information from the user.
775  */
776 /*ARGSUSED*/
777 static int
778 su_conv(int num_msg, struct pam_message **msg, struct pam_response **response,
779     void *appdata_ptr)
780 {
781 	struct pam_message	*m;
782 	struct pam_response	*r;
783 	char			*temp;
784 	int			k;
785 	char			respbuf[PAM_MAX_RESP_SIZE];
786 
787 	if (num_msg <= 0)
788 		return (PAM_CONV_ERR);
789 
790 	*response = (struct pam_response *)calloc(num_msg,
791 	    sizeof (struct pam_response));
792 	if (*response == NULL)
793 		return (PAM_BUF_ERR);
794 
795 	k = num_msg;
796 	m = *msg;
797 	r = *response;
798 	while (k--) {
799 
800 		switch (m->msg_style) {
801 
802 		case PAM_PROMPT_ECHO_OFF:
803 			errno = 0;
804 			temp = getpassphrase(m->msg);
805 			if (errno == EINTR)
806 				return (PAM_CONV_ERR);
807 			if (temp != NULL) {
808 				r->resp = strdup(temp);
809 				if (r->resp == NULL) {
810 					freeresponse(num_msg, response);
811 					return (PAM_BUF_ERR);
812 				}
813 			}
814 			break;
815 
816 		case PAM_PROMPT_ECHO_ON:
817 			if (m->msg != NULL) {
818 				(void) fputs(m->msg, stdout);
819 			}
820 
821 			(void) fgets(respbuf, sizeof (respbuf), stdin);
822 			temp = strchr(respbuf, '\n');
823 			if (temp != NULL)
824 				*temp = '\0';
825 
826 			r->resp = strdup(respbuf);
827 			if (r->resp == NULL) {
828 				freeresponse(num_msg, response);
829 				return (PAM_BUF_ERR);
830 			}
831 			break;
832 
833 		case PAM_ERROR_MSG:
834 			if (m->msg != NULL) {
835 				(void) fputs(m->msg, stderr);
836 				(void) fputs("\n", stderr);
837 			}
838 			break;
839 
840 		case PAM_TEXT_INFO:
841 			if (m->msg != NULL) {
842 				(void) fputs(m->msg, stdout);
843 				(void) fputs("\n", stdout);
844 			}
845 			break;
846 
847 		default:
848 			break;
849 		}
850 		m++;
851 		r++;
852 	}
853 	return (PAM_SUCCESS);
854 }
855 
856 /*
857  * emb_su_conv():
858  *	This is the conv (conversation) function called from
859  *	a PAM authentication module to print error messages
860  *	or garner information from the user.
861  *	This version is used for embedded_su.
862  */
863 /*ARGSUSED*/
864 static int
865 emb_su_conv(int num_msg, struct pam_message **msg,
866     struct pam_response **response, void *appdata_ptr)
867 {
868 	struct pam_message	*m;
869 	struct pam_response	*r;
870 	char			*temp;
871 	int			k;
872 	char			respbuf[PAM_MAX_RESP_SIZE];
873 
874 	if (num_msg <= 0)
875 		return (PAM_CONV_ERR);
876 
877 	*response = (struct pam_response *)calloc(num_msg,
878 	    sizeof (struct pam_response));
879 	if (*response == NULL)
880 		return (PAM_BUF_ERR);
881 
882 	/* First, send the prompts */
883 	(void) printf("CONV %d\n", num_msg);
884 	k = num_msg;
885 	m = *msg;
886 	while (k--) {
887 		switch (m->msg_style) {
888 
889 		case PAM_PROMPT_ECHO_OFF:
890 			(void) puts("PAM_PROMPT_ECHO_OFF");
891 			goto msg_common;
892 
893 		case PAM_PROMPT_ECHO_ON:
894 			(void) puts("PAM_PROMPT_ECHO_ON");
895 			goto msg_common;
896 
897 		case PAM_ERROR_MSG:
898 			(void) puts("PAM_ERROR_MSG");
899 			goto msg_common;
900 
901 		case PAM_TEXT_INFO:
902 			(void) puts("PAM_TEXT_INFO");
903 			/* fall through to msg_common */
904 msg_common:
905 			if (m->msg == NULL)
906 				quotemsg(NULL);
907 			else
908 				quotemsg("%s", m->msg);
909 			break;
910 
911 		default:
912 			break;
913 		}
914 		m++;
915 	}
916 
917 	/* Next, collect the responses */
918 	k = num_msg;
919 	m = *msg;
920 	r = *response;
921 	while (k--) {
922 
923 		switch (m->msg_style) {
924 
925 		case PAM_PROMPT_ECHO_OFF:
926 		case PAM_PROMPT_ECHO_ON:
927 			(void) fgets(respbuf, sizeof (respbuf), stdin);
928 
929 			temp = strchr(respbuf, '\n');
930 			if (temp != NULL)
931 				*temp = '\0';
932 
933 			r->resp = strdup(respbuf);
934 			if (r->resp == NULL) {
935 				freeresponse(num_msg, response);
936 				return (PAM_BUF_ERR);
937 			}
938 
939 			break;
940 
941 		case PAM_ERROR_MSG:
942 		case PAM_TEXT_INFO:
943 			break;
944 
945 		default:
946 			break;
947 		}
948 		m++;
949 		r++;
950 	}
951 	return (PAM_SUCCESS);
952 }
953 
954 static void
955 freeresponse(int num_msg, struct pam_response **response)
956 {
957 	struct pam_response *r;
958 	int i;
959 
960 	/* free responses */
961 	r = *response;
962 	for (i = 0; i < num_msg; i++, r++) {
963 		if (r->resp != NULL) {
964 			/* Zap it in case it's a password */
965 			(void) memset(r->resp, '\0', strlen(r->resp));
966 			free(r->resp);
967 		}
968 	}
969 	free(*response);
970 	*response = NULL;
971 }
972 
973 /*
974  * Print a message, applying quoting for lines starting with '.'.
975  *
976  * I18n note:  \n is "safe" in all locales, and all locales use
977  * a high-bit-set character to start multibyte sequences, so
978  * scanning for a \n followed by a '.' is safe.
979  */
980 static void
981 quotemsg(char *fmt, ...)
982 {
983 	if (fmt != NULL) {
984 		char *msg;
985 		char *p;
986 		boolean_t bol;
987 		va_list v;
988 
989 		va_start(v, fmt);
990 		msg = alloc_vsprintf(fmt, v);
991 		va_end(v);
992 
993 		bol = B_TRUE;
994 		for (p = msg; *p != '\0'; p++) {
995 			if (bol) {
996 				if (*p == '.')
997 					(void) putchar('.');
998 				bol = B_FALSE;
999 			}
1000 			(void) putchar(*p);
1001 			if (*p == '\n')
1002 				bol = B_TRUE;
1003 		}
1004 		(void) putchar('\n');
1005 		free(msg);
1006 	}
1007 	(void) putchar('.');
1008 	(void) putchar('\n');
1009 }
1010 
1011 /*
1012  * validate - Check that the account is valid for switching to.
1013  *
1014  * If the password has expired, we must refuse the 'su' attempt
1015  * regardless of whether the password is null or not.
1016  *
1017  * If the password is NULL but has NOT expired then we allow the
1018  * su attempt to succeed.
1019  */
1020 static void
1021 validate(char *usernam)
1022 {
1023 	int error = 0;
1024 
1025 	if ((error = pam_acct_mgmt(pamh, 0)) != 0) {
1026 		if (Sulog != NULL)
1027 			log(Sulog, pwd.pw_name, 0);    /* log entry */
1028 		if (error == PAM_NEW_AUTHTOK_REQD) {
1029 			message(ERR, gettext("Password for user "
1030 			    "'%s' has expired - use passwd(1) to update it"),
1031 			    pwd.pw_name);
1032 			    audit_su_bad_authentication();
1033 			    if (dosyslog)
1034 				syslog(LOG_CRIT, "'su %s' failed for %s on %s",
1035 				    pwd.pw_name, usernam, ttyn);
1036 			closelog();
1037 			exit(1);
1038 		} else {
1039 			message(ERR, gettext("Sorry"));
1040 			audit_su_bad_authentication();
1041 			if (dosyslog)
1042 			    syslog(LOG_CRIT, "'su %s' failed for %s on %s",
1043 				pwd.pw_name, usernam, ttyn);
1044 			closelog();
1045 			exit(3);
1046 		}
1047 	}
1048 }
1049 
1050 static char *illegal[] = {
1051 	"SHELL=",
1052 	"HOME=",
1053 	"LOGNAME=",
1054 #ifndef NO_MAIL
1055 	"MAIL=",
1056 #endif
1057 	"CDPATH=",
1058 	"IFS=",
1059 	"PATH=",
1060 	"TZ=",
1061 	"HZ=",
1062 	"TERM=",
1063 	0
1064 };
1065 
1066 /*
1067  * legalenvvar - can PAM modules insert this environmental variable?
1068  */
1069 
1070 static int
1071 legalenvvar(char *s)
1072 {
1073 	register char **p;
1074 
1075 	for (p = illegal; *p; p++)
1076 		if (strncmp(s, *p, strlen(*p)) == 0)
1077 			return (0);
1078 
1079 	if (s[0] == 'L' && s[1] == 'D' && s[2] == '_')
1080 		return (0);
1081 
1082 	return (1);
1083 }
1084 
1085 /*
1086  * The embedded_su protocol allows the client application to supply
1087  * an initialization block terminated by a line with just a "." on it.
1088  *
1089  * This initialization block is currently unused, reserved for future
1090  * expansion.  Ignore it.  This is made very slightly more complex by
1091  * the desire to cleanly ignore input lines of any length, while still
1092  * correctly detecting a line with just a "." on it.
1093  *
1094  * I18n note:  It appears that none of the Solaris-supported locales
1095  * use 0x0a for any purpose other than newline, so looking for '\n'
1096  * seems safe.
1097  * All locales use high-bit-set leadin characters for their multi-byte
1098  * sequences, so a line consisting solely of ".\n" is what it appears
1099  * to be.
1100  */
1101 static void
1102 readinitblock(void)
1103 {
1104 	char buf[100];
1105 	boolean_t bol;
1106 
1107 	bol = B_TRUE;
1108 	for (;;) {
1109 		if (fgets(buf, sizeof (buf), stdin) == NULL)
1110 			return;
1111 		if (bol && strcmp(buf, ".\n") == 0)
1112 			return;
1113 		bol = (strchr(buf, '\n') != NULL);
1114 	}
1115 }
1116 #endif	/* DYNAMIC_SU */
1117 
1118 /*
1119  * Report an error, either a fatal one, a warning, or a usage message,
1120  * depending on the mode parameter.
1121  */
1122 /*ARGSUSED*/
1123 static void
1124 message(enum messagemode mode, char *fmt, ...)
1125 {
1126 	char *s;
1127 	va_list v;
1128 
1129 	va_start(v, fmt);
1130 	s = alloc_vsprintf(fmt, v);
1131 	va_end(v);
1132 
1133 #ifdef	DYNAMIC_SU
1134 	if (embedded) {
1135 		if (mode == WARN) {
1136 			(void) printf("CONV 1\n");
1137 			(void) printf("PAM_ERROR_MSG\n");
1138 		} else { /* ERR, USAGE */
1139 			(void) printf("ERROR\n");
1140 		}
1141 		if (mode == USAGE) {
1142 			quotemsg("%s", s);
1143 		} else { /* ERR, WARN */
1144 			quotemsg("%s: %s", myname, s);
1145 		}
1146 	} else {
1147 #endif	/* DYNAMIC_SU */
1148 		if (mode == USAGE) {
1149 			(void) fprintf(stderr, "%s\n", s);
1150 		} else { /* ERR, WARN */
1151 			(void) fprintf(stderr, "%s: %s\n", myname, s);
1152 		}
1153 #ifdef	DYNAMIC_SU
1154 	}
1155 #endif	/* DYNAMIC_SU */
1156 
1157 	free(s);
1158 }
1159 
1160 /*
1161  * Return a pointer to the last path component of a.
1162  */
1163 static char *
1164 tail(char *a)
1165 {
1166 	char *p;
1167 
1168 	p = strrchr(a, '/');
1169 	if (p == NULL)
1170 		p = a;
1171 	else
1172 		p++;	/* step over the '/' */
1173 
1174 	return (p);
1175 }
1176 
1177 static char *
1178 alloc_vsprintf(const char *fmt, va_list ap1)
1179 {
1180 	va_list ap2;
1181 	int n;
1182 	char buf[1];
1183 	char *s;
1184 
1185 	/*
1186 	 * We need to scan the argument list twice.  Save off a copy
1187 	 * of the argument list pointer(s) for the second pass.  Note that
1188 	 * we are responsible for va_end'ing our copy.
1189 	 */
1190 	va_copy(ap2, ap1);
1191 
1192 	/*
1193 	 * vsnprintf into a dummy to get a length.  One might
1194 	 * think that passing 0 as the length to snprintf would
1195 	 * do what we want, but it's defined not to.
1196 	 *
1197 	 * Perhaps we should sprintf into a 100 character buffer
1198 	 * or something like that, to avoid two calls to snprintf
1199 	 * in most cases.
1200 	 */
1201 	n = vsnprintf(buf, sizeof (buf), fmt, ap2);
1202 	va_end(ap2);
1203 
1204 	/*
1205 	 * Allocate an appropriately-sized buffer.
1206 	 */
1207 	s = malloc(n + 1);
1208 	if (s == NULL) {
1209 		perror("malloc");
1210 		exit(4);
1211 	}
1212 
1213 	(void) vsnprintf(s, n+1, fmt, ap1);
1214 
1215 	return (s);
1216 }
1217