xref: /onnv-gate/usr/src/cmd/su/su.c (revision 12957:9d6fa5bd011f)
10Sstevel@tonic-gate /*
20Sstevel@tonic-gate  * CDDL HEADER START
30Sstevel@tonic-gate  *
40Sstevel@tonic-gate  * The contents of this file are subject to the terms of the
52246Sgww  * Common Development and Distribution License (the "License").
62246Sgww  * You may not use this file except in compliance with the License.
70Sstevel@tonic-gate  *
80Sstevel@tonic-gate  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
90Sstevel@tonic-gate  * or http://www.opensolaris.org/os/licensing.
100Sstevel@tonic-gate  * See the License for the specific language governing permissions
110Sstevel@tonic-gate  * and limitations under the License.
120Sstevel@tonic-gate  *
130Sstevel@tonic-gate  * When distributing Covered Code, include this CDDL HEADER in each
140Sstevel@tonic-gate  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
150Sstevel@tonic-gate  * If applicable, add the following below this CDDL HEADER, with the
160Sstevel@tonic-gate  * fields enclosed by brackets "[]" replaced with your own identifying
170Sstevel@tonic-gate  * information: Portions Copyright [yyyy] [name of copyright owner]
180Sstevel@tonic-gate  *
190Sstevel@tonic-gate  * CDDL HEADER END
200Sstevel@tonic-gate  */
210Sstevel@tonic-gate /*
22*12957SMarek.Pospisil@Sun.COM  * Copyright (c) 1988, 2010, Oracle and/or its affiliates. All rights reserved.
230Sstevel@tonic-gate  */
240Sstevel@tonic-gate /*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
250Sstevel@tonic-gate /*	  All Rights Reserved	*/
260Sstevel@tonic-gate 
270Sstevel@tonic-gate /*	Copyright (c) 1987, 1988 Microsoft Corporation	*/
280Sstevel@tonic-gate /*	  All Rights Reserved	*/
290Sstevel@tonic-gate 
300Sstevel@tonic-gate /*
310Sstevel@tonic-gate  *	su [-] [name [arg ...]] change userid, `-' changes environment.
320Sstevel@tonic-gate  *	If SULOG is defined, all attempts to su to another user are
330Sstevel@tonic-gate  *	logged there.
340Sstevel@tonic-gate  *	If CONSOLE is defined, all successful attempts to su to uid 0
350Sstevel@tonic-gate  *	are also logged there.
360Sstevel@tonic-gate  *
370Sstevel@tonic-gate  *	If su cannot create, open, or write entries into SULOG,
380Sstevel@tonic-gate  *	(or on the CONSOLE, if defined), the entry will not
390Sstevel@tonic-gate  *	be logged -- thus losing a record of the su's attempted
400Sstevel@tonic-gate  *	during this period.
410Sstevel@tonic-gate  */
420Sstevel@tonic-gate 
430Sstevel@tonic-gate #include <stdio.h>
440Sstevel@tonic-gate #include <sys/types.h>
450Sstevel@tonic-gate #include <sys/stat.h>
460Sstevel@tonic-gate #include <sys/param.h>
470Sstevel@tonic-gate #include <unistd.h>
480Sstevel@tonic-gate #include <stdlib.h>
490Sstevel@tonic-gate #include <crypt.h>
500Sstevel@tonic-gate #include <pwd.h>
510Sstevel@tonic-gate #include <shadow.h>
520Sstevel@tonic-gate #include <time.h>
530Sstevel@tonic-gate #include <signal.h>
540Sstevel@tonic-gate #include <fcntl.h>
550Sstevel@tonic-gate #include <string.h>
560Sstevel@tonic-gate #include <locale.h>
570Sstevel@tonic-gate #include <syslog.h>
580Sstevel@tonic-gate #include <sys/utsname.h>
592246Sgww #include <sys/wait.h>
600Sstevel@tonic-gate #include <grp.h>
610Sstevel@tonic-gate #include <deflt.h>
620Sstevel@tonic-gate #include <limits.h>
630Sstevel@tonic-gate #include <errno.h>
640Sstevel@tonic-gate #include <stdarg.h>
652246Sgww #include <user_attr.h>
662246Sgww #include <priv.h>
670Sstevel@tonic-gate 
6879Sgww #include <bsm/adt.h>
6979Sgww #include <bsm/adt_event.h>
7079Sgww 
710Sstevel@tonic-gate #include <security/pam_appl.h>
720Sstevel@tonic-gate 
730Sstevel@tonic-gate #define	PATH	"/usr/bin:"		/* path for users other than root */
740Sstevel@tonic-gate #define	SUPATH	"/usr/sbin:/usr/bin"	/* path for root */
750Sstevel@tonic-gate #define	SUPRMT	"PS1=# "		/* primary prompt for root */
760Sstevel@tonic-gate #define	ELIM 128
770Sstevel@tonic-gate #define	ROOT 0
780Sstevel@tonic-gate #ifdef	DYNAMIC_SU
790Sstevel@tonic-gate #define	EMBEDDED_NAME	"embedded_su"
8079Sgww #define	DEF_ATTEMPTS	3		/* attempts to change password */
810Sstevel@tonic-gate #endif	/* DYNAMIC_SU */
820Sstevel@tonic-gate 
8379Sgww #define	PW_FALSE	1		/* no password change */
8479Sgww #define	PW_TRUE		2		/* successful password change */
8579Sgww #define	PW_FAILED	3		/* failed password change */
8679Sgww 
870Sstevel@tonic-gate /*
880Sstevel@tonic-gate  * Intervals to sleep after failed su
890Sstevel@tonic-gate  */
900Sstevel@tonic-gate #ifndef SLEEPTIME
910Sstevel@tonic-gate #define	SLEEPTIME	4
920Sstevel@tonic-gate #endif
930Sstevel@tonic-gate 
940Sstevel@tonic-gate #define	DEFAULT_LOGIN "/etc/default/login"
950Sstevel@tonic-gate #define	DEFFILE "/etc/default/su"
960Sstevel@tonic-gate 
970Sstevel@tonic-gate 
980Sstevel@tonic-gate char	*Sulog, *Console;
990Sstevel@tonic-gate char	*Path, *Supath;
1000Sstevel@tonic-gate 
1010Sstevel@tonic-gate /*
1020Sstevel@tonic-gate  * Locale variables to be propagated to "su -" environment
1030Sstevel@tonic-gate  */
1040Sstevel@tonic-gate static char *initvar;
1050Sstevel@tonic-gate static char *initenv[] = {
1060Sstevel@tonic-gate 	"TZ", "LANG", "LC_CTYPE",
1070Sstevel@tonic-gate 	"LC_NUMERIC", "LC_TIME", "LC_COLLATE",
1080Sstevel@tonic-gate 	"LC_MONETARY", "LC_MESSAGES", "LC_ALL", 0};
1090Sstevel@tonic-gate static char mail[30] = { "MAIL=/var/mail/" };
1100Sstevel@tonic-gate 
1110Sstevel@tonic-gate static void envalt(void);
11279Sgww static void log(char *, char *, int);
11379Sgww static void to(int);
1140Sstevel@tonic-gate 
1150Sstevel@tonic-gate enum messagemode { USAGE, ERR, WARN };
11679Sgww static void message(enum messagemode, char *, ...);
1170Sstevel@tonic-gate 
11879Sgww static char *alloc_vsprintf(const char *, va_list);
11979Sgww static char *tail(char *);
12079Sgww 
12179Sgww static void audit_success(int, struct passwd *);
1222246Sgww static void audit_logout(adt_session_data_t *, au_event_t);
1232246Sgww static void audit_failure(int, struct passwd *, char *, int);
1240Sstevel@tonic-gate 
1250Sstevel@tonic-gate #ifdef DYNAMIC_SU
12679Sgww static void validate(char *, int *);
12779Sgww static int legalenvvar(char *);
1280Sstevel@tonic-gate static int su_conv(int, struct pam_message **, struct pam_response **, void *);
1290Sstevel@tonic-gate static int emb_su_conv(int, struct pam_message **, struct pam_response **,
1300Sstevel@tonic-gate     void *);
13179Sgww static void freeresponse(int, struct pam_response **response);
1320Sstevel@tonic-gate static struct pam_conv pam_conv = {su_conv, NULL};
1330Sstevel@tonic-gate static struct pam_conv emb_pam_conv = {emb_su_conv, NULL};
13479Sgww static void quotemsg(char *, ...);
1350Sstevel@tonic-gate static void readinitblock(void);
13679Sgww #else	/* !DYNAMIC_SU */
13779Sgww static void update_audit(struct passwd *pwd);
1380Sstevel@tonic-gate #endif	/* DYNAMIC_SU */
1390Sstevel@tonic-gate 
14079Sgww static pam_handle_t	*pamh = NULL;	/* Authentication handle */
1410Sstevel@tonic-gate struct	passwd pwd;
1420Sstevel@tonic-gate char	pwdbuf[1024];			/* buffer for getpwnam_r() */
1430Sstevel@tonic-gate char	shell[] = "/usr/bin/sh";	/* default shell */
1440Sstevel@tonic-gate char	safe_shell[] = "/sbin/sh";	/* "fallback" shell */
1450Sstevel@tonic-gate char	su[PATH_MAX] = "su";		/* arg0 for exec of shprog */
1460Sstevel@tonic-gate char	homedir[PATH_MAX] = "HOME=";
1470Sstevel@tonic-gate char	logname[20] = "LOGNAME=";
1480Sstevel@tonic-gate char	*suprmt = SUPRMT;
1490Sstevel@tonic-gate char	termtyp[PATH_MAX] = "TERM=";
1500Sstevel@tonic-gate char	*term;
1510Sstevel@tonic-gate char	shelltyp[PATH_MAX] = "SHELL=";
1520Sstevel@tonic-gate char	*hz;
1530Sstevel@tonic-gate char	tznam[PATH_MAX];
1540Sstevel@tonic-gate char	hzname[10] = "HZ=";
1550Sstevel@tonic-gate char	path[PATH_MAX] = "PATH=";
1560Sstevel@tonic-gate char	supath[PATH_MAX] = "PATH=";
1570Sstevel@tonic-gate char	*envinit[ELIM];
1580Sstevel@tonic-gate extern	char **environ;
1590Sstevel@tonic-gate char *ttyn;
1600Sstevel@tonic-gate char *username;					/* the invoker */
1610Sstevel@tonic-gate static	int	dosyslog = 0;			/* use syslog? */
1620Sstevel@tonic-gate char	*myname;
1630Sstevel@tonic-gate #ifdef	DYNAMIC_SU
1648126SJoep.Vesseur@Sun.COM int pam_flags = 0;
1650Sstevel@tonic-gate boolean_t embedded = B_FALSE;
1660Sstevel@tonic-gate #endif	/* DYNAMIC_SU */
1670Sstevel@tonic-gate 
1680Sstevel@tonic-gate int
main(int argc,char ** argv)1690Sstevel@tonic-gate main(int argc, char **argv)
1700Sstevel@tonic-gate {
1710Sstevel@tonic-gate #ifndef DYNAMIC_SU
1720Sstevel@tonic-gate 	struct spwd sp;
1730Sstevel@tonic-gate 	char  spbuf[1024];		/* buffer for getspnam_r() */
1740Sstevel@tonic-gate 	char *password;
17579Sgww #endif	/* !DYNAMIC_SU */
1760Sstevel@tonic-gate 	char *nptr;
1770Sstevel@tonic-gate 	char *pshell;
1780Sstevel@tonic-gate 	int eflag = 0;
1790Sstevel@tonic-gate 	int envidx = 0;
1800Sstevel@tonic-gate 	uid_t uid;
1810Sstevel@tonic-gate 	gid_t gid;
1820Sstevel@tonic-gate 	char *dir, *shprog, *name;
1830Sstevel@tonic-gate 	char *ptr;
1840Sstevel@tonic-gate 	char *prog = argv[0];
1850Sstevel@tonic-gate #ifdef DYNAMIC_SU
1860Sstevel@tonic-gate 	int sleeptime = SLEEPTIME;
1870Sstevel@tonic-gate 	char **pam_env = 0;
1880Sstevel@tonic-gate 	int flags = 0;
1890Sstevel@tonic-gate 	int retcode;
1900Sstevel@tonic-gate 	int idx = 0;
1910Sstevel@tonic-gate #endif	/* DYNAMIC_SU */
19279Sgww 	int pw_change = PW_FALSE;
1930Sstevel@tonic-gate 
1940Sstevel@tonic-gate 	(void) setlocale(LC_ALL, "");
1950Sstevel@tonic-gate #if !defined(TEXT_DOMAIN)	/* Should be defined by cc -D */
1960Sstevel@tonic-gate #define	TEXT_DOMAIN "SYS_TEST"	/* Use this only if it wasn't */
1970Sstevel@tonic-gate #endif
1980Sstevel@tonic-gate 	(void) textdomain(TEXT_DOMAIN);
1990Sstevel@tonic-gate 
2000Sstevel@tonic-gate 	myname = tail(argv[0]);
2010Sstevel@tonic-gate 
20279Sgww #ifdef	DYNAMIC_SU
2030Sstevel@tonic-gate 	if (strcmp(myname, EMBEDDED_NAME) == 0) {
2040Sstevel@tonic-gate 		embedded = B_TRUE;
2050Sstevel@tonic-gate 		setbuf(stdin, NULL);
2060Sstevel@tonic-gate 		setbuf(stdout, NULL);
2070Sstevel@tonic-gate 		readinitblock();
2080Sstevel@tonic-gate 	}
20979Sgww #endif	/* DYNAMIC_SU */
2100Sstevel@tonic-gate 
2110Sstevel@tonic-gate 	if (argc > 1 && *argv[1] == '-') {
2120Sstevel@tonic-gate 		/* Explicitly check for just `-' (no trailing chars) */
2130Sstevel@tonic-gate 		if (strlen(argv[1]) == 1) {
2140Sstevel@tonic-gate 			eflag++;	/* set eflag if `-' is specified */
2150Sstevel@tonic-gate 			argv++;
2160Sstevel@tonic-gate 			argc--;
2170Sstevel@tonic-gate 		} else {
2180Sstevel@tonic-gate 			message(USAGE,
2190Sstevel@tonic-gate 			    gettext("Usage: %s [-] [ username [ arg ... ] ]"),
2208255SJan.Kryl@Sun.COM 			    prog);
2210Sstevel@tonic-gate 			exit(1);
2220Sstevel@tonic-gate 		}
2230Sstevel@tonic-gate 	}
2240Sstevel@tonic-gate 
2250Sstevel@tonic-gate 	/*
2260Sstevel@tonic-gate 	 * Determine specified userid, get their password file entry,
2270Sstevel@tonic-gate 	 * and set variables to values in password file entry fields.
2280Sstevel@tonic-gate 	 */
2290Sstevel@tonic-gate 	if (argc > 1) {
2300Sstevel@tonic-gate 		/*
2310Sstevel@tonic-gate 		 * Usernames can't start with a `-', so we check for that to
2320Sstevel@tonic-gate 		 * catch bad usage (like "su - -c ls").
2330Sstevel@tonic-gate 		 */
2340Sstevel@tonic-gate 		if (*argv[1] == '-') {
2350Sstevel@tonic-gate 			message(USAGE,
2360Sstevel@tonic-gate 			    gettext("Usage: %s [-] [ username [ arg ... ] ]"),
2378255SJan.Kryl@Sun.COM 			    prog);
2380Sstevel@tonic-gate 			exit(1);
2390Sstevel@tonic-gate 		} else
2400Sstevel@tonic-gate 			nptr = argv[1];	/* use valid command-line username */
2410Sstevel@tonic-gate 	} else
2420Sstevel@tonic-gate 		nptr = "root";		/* use default "root" username */
2430Sstevel@tonic-gate 
2440Sstevel@tonic-gate 	if (defopen(DEFFILE) == 0) {
2450Sstevel@tonic-gate 
2460Sstevel@tonic-gate 		if (Sulog = defread("SULOG="))
2470Sstevel@tonic-gate 			Sulog = strdup(Sulog);
2480Sstevel@tonic-gate 		if (Console = defread("CONSOLE="))
2490Sstevel@tonic-gate 			Console = strdup(Console);
2500Sstevel@tonic-gate 		if (Path = defread("PATH="))
2510Sstevel@tonic-gate 			Path = strdup(Path);
2520Sstevel@tonic-gate 		if (Supath = defread("SUPATH="))
2530Sstevel@tonic-gate 			Supath = strdup(Supath);
2540Sstevel@tonic-gate 		if ((ptr = defread("SYSLOG=")) != NULL)
2550Sstevel@tonic-gate 			dosyslog = strcmp(ptr, "YES") == 0;
2560Sstevel@tonic-gate 
2570Sstevel@tonic-gate 		(void) defopen(NULL);
2580Sstevel@tonic-gate 	}
2590Sstevel@tonic-gate 	(void) strlcat(path, (Path) ? Path : PATH, sizeof (path));
2600Sstevel@tonic-gate 	(void) strlcat(supath, (Supath) ? Supath : SUPATH, sizeof (supath));
2610Sstevel@tonic-gate 
2620Sstevel@tonic-gate 	if ((ttyn = ttyname(0)) == NULL)
2630Sstevel@tonic-gate 		if ((ttyn = ttyname(1)) == NULL)
2640Sstevel@tonic-gate 			if ((ttyn = ttyname(2)) == NULL)
2650Sstevel@tonic-gate 				ttyn = "/dev/???";
2660Sstevel@tonic-gate 	if ((username = cuserid(NULL)) == NULL)
2670Sstevel@tonic-gate 		username = "(null)";
2680Sstevel@tonic-gate 
2690Sstevel@tonic-gate 	/*
2700Sstevel@tonic-gate 	 * if Sulog defined, create SULOG, if it does not exist, with
2710Sstevel@tonic-gate 	 * mode read/write user. Change owner and group to root
2720Sstevel@tonic-gate 	 */
2730Sstevel@tonic-gate 	if (Sulog != NULL) {
2740Sstevel@tonic-gate 		(void) close(open(Sulog, O_WRONLY | O_APPEND | O_CREAT,
2750Sstevel@tonic-gate 		    (S_IRUSR|S_IWUSR)));
2760Sstevel@tonic-gate 		(void) chown(Sulog, (uid_t)ROOT, (gid_t)ROOT);
2770Sstevel@tonic-gate 	}
2780Sstevel@tonic-gate 
2790Sstevel@tonic-gate #ifdef DYNAMIC_SU
2800Sstevel@tonic-gate 	if (pam_start(embedded ? EMBEDDED_NAME : "su", nptr,
2810Sstevel@tonic-gate 	    embedded ? &emb_pam_conv : &pam_conv, &pamh) != PAM_SUCCESS)
2820Sstevel@tonic-gate 		exit(1);
2830Sstevel@tonic-gate 	if (pam_set_item(pamh, PAM_TTY, ttyn) != PAM_SUCCESS)
2840Sstevel@tonic-gate 		exit(1);
2850Sstevel@tonic-gate #endif	/* DYNAMIC_SU */
2860Sstevel@tonic-gate 
2870Sstevel@tonic-gate 	openlog("su", LOG_CONS, LOG_AUTH);
2880Sstevel@tonic-gate 
2890Sstevel@tonic-gate #ifdef DYNAMIC_SU
2900Sstevel@tonic-gate 
2910Sstevel@tonic-gate 	/*
29279Sgww 	 * Use the same value of sleeptime and password required that
29379Sgww 	 * login(1) uses.
29479Sgww 	 * This is obtained by reading the file /etc/default/login
29579Sgww 	 * using the def*() functions
29679Sgww 	 */
29779Sgww 	if (defopen(DEFAULT_LOGIN) == 0) {
29879Sgww 		if ((ptr = defread("SLEEPTIME=")) != NULL) {
29979Sgww 			sleeptime = atoi(ptr);
30079Sgww 			if (sleeptime < 0 || sleeptime > 5)
30179Sgww 				sleeptime = SLEEPTIME;
30279Sgww 		}
30379Sgww 
30479Sgww 		if ((ptr = defread("PASSREQ=")) != NULL &&
30579Sgww 		    strcasecmp("YES", ptr) == 0)
3068126SJoep.Vesseur@Sun.COM 			pam_flags |= PAM_DISALLOW_NULL_AUTHTOK;
30779Sgww 
30879Sgww 		(void) defopen((char *)NULL);
30979Sgww 	}
31079Sgww 	/*
3110Sstevel@tonic-gate 	 * Ignore SIGQUIT and SIGINT
3120Sstevel@tonic-gate 	 */
3130Sstevel@tonic-gate 	(void) signal(SIGQUIT, SIG_IGN);
3140Sstevel@tonic-gate 	(void) signal(SIGINT, SIG_IGN);
3150Sstevel@tonic-gate 
3160Sstevel@tonic-gate 	/* call pam_authenticate() to authenticate the user through PAM */
3170Sstevel@tonic-gate 	if (getpwnam_r(nptr, &pwd, pwdbuf, sizeof (pwdbuf)) == NULL)
3180Sstevel@tonic-gate 		retcode = PAM_USER_UNKNOWN;
3190Sstevel@tonic-gate 	else if ((flags = (getuid() != (uid_t)ROOT)) != 0) {
3208126SJoep.Vesseur@Sun.COM 		retcode = pam_authenticate(pamh, pam_flags);
3210Sstevel@tonic-gate 	} else /* root user does not need to authenticate */
3220Sstevel@tonic-gate 		retcode = PAM_SUCCESS;
3230Sstevel@tonic-gate 
3240Sstevel@tonic-gate 	if (retcode != PAM_SUCCESS) {
3250Sstevel@tonic-gate 		/*
32679Sgww 		 * 1st step: audit and log the error.
3270Sstevel@tonic-gate 		 * 2nd step: sleep.
3280Sstevel@tonic-gate 		 * 3rd step: print out message to user.
3290Sstevel@tonic-gate 		 */
3302246Sgww 		/* don't let audit_failure distinguish a role here */
3312246Sgww 		audit_failure(PW_FALSE, NULL, nptr, retcode);
3320Sstevel@tonic-gate 		switch (retcode) {
3330Sstevel@tonic-gate 		case PAM_USER_UNKNOWN:
3340Sstevel@tonic-gate 			closelog();
3350Sstevel@tonic-gate 			(void) sleep(sleeptime);
3360Sstevel@tonic-gate 			message(ERR, gettext("Unknown id: %s"), nptr);
3370Sstevel@tonic-gate 			break;
3380Sstevel@tonic-gate 
3390Sstevel@tonic-gate 		case PAM_AUTH_ERR:
3400Sstevel@tonic-gate 			if (Sulog != NULL)
3410Sstevel@tonic-gate 				log(Sulog, nptr, 0);	/* log entry */
3420Sstevel@tonic-gate 			if (dosyslog)
3430Sstevel@tonic-gate 				syslog(LOG_CRIT, "'su %s' failed for %s on %s",
3440Sstevel@tonic-gate 				    pwd.pw_name, username, ttyn);
3450Sstevel@tonic-gate 			closelog();
3460Sstevel@tonic-gate 			(void) sleep(sleeptime);
3470Sstevel@tonic-gate 			message(ERR, gettext("Sorry"));
3480Sstevel@tonic-gate 			break;
3490Sstevel@tonic-gate 
3500Sstevel@tonic-gate 		case PAM_CONV_ERR:
3510Sstevel@tonic-gate 		default:
3520Sstevel@tonic-gate 			if (dosyslog)
3530Sstevel@tonic-gate 				syslog(LOG_CRIT, "'su %s' failed for %s on %s",
3540Sstevel@tonic-gate 				    pwd.pw_name, username, ttyn);
3550Sstevel@tonic-gate 			closelog();
3560Sstevel@tonic-gate 			(void) sleep(sleeptime);
3570Sstevel@tonic-gate 			message(ERR, gettext("Sorry"));
3580Sstevel@tonic-gate 			break;
3590Sstevel@tonic-gate 		}
3600Sstevel@tonic-gate 
3610Sstevel@tonic-gate 		(void) signal(SIGQUIT, SIG_DFL);
3620Sstevel@tonic-gate 		(void) signal(SIGINT, SIG_DFL);
3630Sstevel@tonic-gate 		exit(1);
3640Sstevel@tonic-gate 	}
3650Sstevel@tonic-gate 	if (flags)
36679Sgww 		validate(username, &pw_change);
3670Sstevel@tonic-gate 	if (pam_setcred(pamh, PAM_REINITIALIZE_CRED) != PAM_SUCCESS) {
3680Sstevel@tonic-gate 		message(ERR, gettext("unable to set credentials"));
3690Sstevel@tonic-gate 		exit(2);
3700Sstevel@tonic-gate 	}
3710Sstevel@tonic-gate 	if (dosyslog)
3728255SJan.Kryl@Sun.COM 		syslog(pwd.pw_uid == 0 ? LOG_NOTICE : LOG_INFO,
3730Sstevel@tonic-gate 		    "'su %s' succeeded for %s on %s",
3740Sstevel@tonic-gate 		    pwd.pw_name, username, ttyn);
3750Sstevel@tonic-gate 	closelog();
3760Sstevel@tonic-gate 	(void) signal(SIGQUIT, SIG_DFL);
3770Sstevel@tonic-gate 	(void) signal(SIGINT, SIG_DFL);
37879Sgww #else	/* !DYNAMIC_SU */
3790Sstevel@tonic-gate 	if ((getpwnam_r(nptr, &pwd, pwdbuf, sizeof (pwdbuf)) == NULL) ||
3800Sstevel@tonic-gate 	    (getspnam_r(nptr, &sp, spbuf, sizeof (spbuf)) == NULL)) {
3810Sstevel@tonic-gate 		message(ERR, gettext("Unknown id: %s"), nptr);
3822246Sgww 		audit_failure(PW_FALSE, NULL, nptr, PAM_USER_UNKNOWN);
3830Sstevel@tonic-gate 		closelog();
3840Sstevel@tonic-gate 		exit(1);
3850Sstevel@tonic-gate 	}
3860Sstevel@tonic-gate 
3870Sstevel@tonic-gate 	/*
3880Sstevel@tonic-gate 	 * Prompt for password if invoking user is not root or
3890Sstevel@tonic-gate 	 * if specified(new) user requires a password
3900Sstevel@tonic-gate 	 */
3910Sstevel@tonic-gate 	if (sp.sp_pwdp[0] == '\0' || getuid() == (uid_t)ROOT)
3920Sstevel@tonic-gate 		goto ok;
3930Sstevel@tonic-gate 	password = getpass(gettext("Password:"));
3940Sstevel@tonic-gate 
3950Sstevel@tonic-gate 	if ((strcmp(sp.sp_pwdp, crypt(password, sp.sp_pwdp)) != 0)) {
3960Sstevel@tonic-gate 		/* clear password file entry */
3970Sstevel@tonic-gate 		(void) memset((void *)spbuf, 0, sizeof (spbuf));
3980Sstevel@tonic-gate 		if (Sulog != NULL)
3990Sstevel@tonic-gate 			log(Sulog, nptr, 0);    /* log entry */
4000Sstevel@tonic-gate 		message(ERR, gettext("Sorry"));
4012246Sgww 		audit_failure(PW_FALSE, NULL, nptr, PAM_AUTH_ERR);
4020Sstevel@tonic-gate 		if (dosyslog)
4030Sstevel@tonic-gate 			syslog(LOG_CRIT, "'su %s' failed for %s on %s",
4040Sstevel@tonic-gate 			    pwd.pw_name, username, ttyn);
4050Sstevel@tonic-gate 		closelog();
4060Sstevel@tonic-gate 		exit(2);
4070Sstevel@tonic-gate 	}
4080Sstevel@tonic-gate 	/* clear password file entry */
4090Sstevel@tonic-gate 	(void) memset((void *)spbuf, 0, sizeof (spbuf));
4100Sstevel@tonic-gate ok:
41179Sgww 	/* update audit session in a non-pam environment */
41279Sgww 	update_audit(&pwd);
4130Sstevel@tonic-gate 	if (dosyslog)
4148255SJan.Kryl@Sun.COM 		syslog(pwd.pw_uid == 0 ? LOG_NOTICE : LOG_INFO,
4150Sstevel@tonic-gate 		    "'su %s' succeeded for %s on %s",
4160Sstevel@tonic-gate 		    pwd.pw_name, username, ttyn);
41779Sgww #endif	/* DYNAMIC_SU */
4180Sstevel@tonic-gate 
41979Sgww 	audit_success(pw_change, &pwd);
4200Sstevel@tonic-gate 	uid = pwd.pw_uid;
4210Sstevel@tonic-gate 	gid = pwd.pw_gid;
4220Sstevel@tonic-gate 	dir = strdup(pwd.pw_dir);
4230Sstevel@tonic-gate 	shprog = strdup(pwd.pw_shell);
4240Sstevel@tonic-gate 	name = strdup(pwd.pw_name);
4250Sstevel@tonic-gate 
4260Sstevel@tonic-gate 	if (Sulog != NULL)
4270Sstevel@tonic-gate 		log(Sulog, nptr, 1);	/* log entry */
4280Sstevel@tonic-gate 
4290Sstevel@tonic-gate 	/* set user and group ids to specified user */
4300Sstevel@tonic-gate 
4310Sstevel@tonic-gate 	/* set the real (and effective) GID */
4320Sstevel@tonic-gate 	if (setgid(gid) == -1) {
4330Sstevel@tonic-gate 		message(ERR, gettext("Invalid GID"));
4340Sstevel@tonic-gate 		exit(2);
4350Sstevel@tonic-gate 	}
4360Sstevel@tonic-gate 	/* Initialize the supplementary group access list. */
4370Sstevel@tonic-gate 	if (!nptr)
4380Sstevel@tonic-gate 		exit(2);
4390Sstevel@tonic-gate 	if (initgroups(nptr, gid) == -1) {
4400Sstevel@tonic-gate 		exit(2);
4410Sstevel@tonic-gate 	}
4420Sstevel@tonic-gate 	/* set the real (and effective) UID */
4430Sstevel@tonic-gate 	if (setuid(uid) == -1) {
4440Sstevel@tonic-gate 		message(ERR, gettext("Invalid UID"));
4450Sstevel@tonic-gate 		exit(2);
4460Sstevel@tonic-gate 	}
4470Sstevel@tonic-gate 
4480Sstevel@tonic-gate 	/*
4490Sstevel@tonic-gate 	 * If new user's shell field is neither NULL nor equal to /usr/bin/sh,
4500Sstevel@tonic-gate 	 * set:
4510Sstevel@tonic-gate 	 *
4520Sstevel@tonic-gate 	 *	pshell = their shell
4530Sstevel@tonic-gate 	 *	su = [-]last component of shell's pathname
4540Sstevel@tonic-gate 	 *
4550Sstevel@tonic-gate 	 * Otherwise, set the shell to /usr/bin/sh and set argv[0] to '[-]su'.
4560Sstevel@tonic-gate 	 */
4570Sstevel@tonic-gate 	if (shprog[0] != '\0' && strcmp(shell, shprog) != 0) {
4580Sstevel@tonic-gate 		char *p;
4590Sstevel@tonic-gate 
4600Sstevel@tonic-gate 		pshell = shprog;
4610Sstevel@tonic-gate 		(void) strcpy(su, eflag ? "-" : "");
4620Sstevel@tonic-gate 
4630Sstevel@tonic-gate 		if ((p = strrchr(pshell, '/')) != NULL)
4640Sstevel@tonic-gate 			(void) strlcat(su, p + 1, sizeof (su));
4650Sstevel@tonic-gate 		else
4660Sstevel@tonic-gate 			(void) strlcat(su, pshell, sizeof (su));
4670Sstevel@tonic-gate 	} else {
4680Sstevel@tonic-gate 		pshell = shell;
4690Sstevel@tonic-gate 		(void) strcpy(su, eflag ? "-su" : "su");
4700Sstevel@tonic-gate 	}
4710Sstevel@tonic-gate 
4720Sstevel@tonic-gate 	/*
4730Sstevel@tonic-gate 	 * set environment variables for new user;
4740Sstevel@tonic-gate 	 * arg0 for exec of shprog must now contain `-'
4750Sstevel@tonic-gate 	 * so that environment of new user is given
4760Sstevel@tonic-gate 	 */
4770Sstevel@tonic-gate 	if (eflag) {
4780Sstevel@tonic-gate 		int j;
4790Sstevel@tonic-gate 		char *var;
4800Sstevel@tonic-gate 
4810Sstevel@tonic-gate 		if (strlen(dir) == 0) {
4820Sstevel@tonic-gate 			(void) strcpy(dir, "/");
4830Sstevel@tonic-gate 			message(WARN, gettext("No directory! Using home=/"));
4840Sstevel@tonic-gate 		}
4850Sstevel@tonic-gate 		(void) strlcat(homedir, dir, sizeof (homedir));
4860Sstevel@tonic-gate 		(void) strlcat(logname, name, sizeof (logname));
4870Sstevel@tonic-gate 		if (hz = getenv("HZ"))
4880Sstevel@tonic-gate 			(void) strlcat(hzname, hz, sizeof (hzname));
4890Sstevel@tonic-gate 
4900Sstevel@tonic-gate 		(void) strlcat(shelltyp, pshell, sizeof (shelltyp));
4910Sstevel@tonic-gate 
4920Sstevel@tonic-gate 		if (chdir(dir) < 0) {
4930Sstevel@tonic-gate 			message(ERR, gettext("No directory!"));
4940Sstevel@tonic-gate 			exit(1);
4950Sstevel@tonic-gate 		}
4960Sstevel@tonic-gate 		envinit[envidx = 0] = homedir;
4970Sstevel@tonic-gate 		envinit[++envidx] = ((uid == (uid_t)ROOT) ? supath : path);
4980Sstevel@tonic-gate 		envinit[++envidx] = logname;
4990Sstevel@tonic-gate 		envinit[++envidx] = hzname;
5000Sstevel@tonic-gate 		if ((term = getenv("TERM")) != NULL) {
5010Sstevel@tonic-gate 			(void) strlcat(termtyp, term, sizeof (termtyp));
5020Sstevel@tonic-gate 			envinit[++envidx] = termtyp;
5030Sstevel@tonic-gate 		}
5040Sstevel@tonic-gate 		envinit[++envidx] = shelltyp;
5050Sstevel@tonic-gate 
5060Sstevel@tonic-gate 		(void) strlcat(mail, name, sizeof (mail));
5070Sstevel@tonic-gate 		envinit[++envidx] = mail;
5080Sstevel@tonic-gate 
5090Sstevel@tonic-gate 		/*
5100Sstevel@tonic-gate 		 * Fetch the relevant locale/TZ environment variables from
5110Sstevel@tonic-gate 		 * the inherited environment.
5120Sstevel@tonic-gate 		 *
5130Sstevel@tonic-gate 		 * We have a priority here for setting TZ. If TZ is set in
5140Sstevel@tonic-gate 		 * in the inherited environment, that value remains top
5150Sstevel@tonic-gate 		 * priority. If the file /etc/default/login has TIMEZONE set,
5160Sstevel@tonic-gate 		 * that has second highest priority.
5170Sstevel@tonic-gate 		 */
5180Sstevel@tonic-gate 		tznam[0] = '\0';
5190Sstevel@tonic-gate 		for (j = 0; initenv[j] != 0; j++) {
5200Sstevel@tonic-gate 			if (initvar = getenv(initenv[j])) {
5210Sstevel@tonic-gate 
5220Sstevel@tonic-gate 				/*
5230Sstevel@tonic-gate 				 * Skip over values beginning with '/' for
5240Sstevel@tonic-gate 				 * security.
5250Sstevel@tonic-gate 				 */
5260Sstevel@tonic-gate 				if (initvar[0] == '/')  continue;
5270Sstevel@tonic-gate 
5280Sstevel@tonic-gate 				if (strcmp(initenv[j], "TZ") == 0) {
5290Sstevel@tonic-gate 					(void) strcpy(tznam, "TZ=");
5300Sstevel@tonic-gate 					(void) strlcat(tznam, initvar,
5318255SJan.Kryl@Sun.COM 					    sizeof (tznam));
5320Sstevel@tonic-gate 
5330Sstevel@tonic-gate 				} else {
5340Sstevel@tonic-gate 					var = (char *)
5358255SJan.Kryl@Sun.COM 					    malloc(strlen(initenv[j])
5368255SJan.Kryl@Sun.COM 					    + strlen(initvar)
5378255SJan.Kryl@Sun.COM 					    + 2);
5380Sstevel@tonic-gate 					(void) strcpy(var, initenv[j]);
5390Sstevel@tonic-gate 					(void) strcat(var, "=");
5400Sstevel@tonic-gate 					(void) strcat(var, initvar);
5410Sstevel@tonic-gate 					envinit[++envidx] = var;
5420Sstevel@tonic-gate 				}
5430Sstevel@tonic-gate 			}
5440Sstevel@tonic-gate 		}
5450Sstevel@tonic-gate 
5460Sstevel@tonic-gate 		/*
5470Sstevel@tonic-gate 		 * Check if TZ was found. If not then try to read it from
5480Sstevel@tonic-gate 		 * /etc/default/login.
5490Sstevel@tonic-gate 		 */
5500Sstevel@tonic-gate 		if (tznam[0] == '\0') {
5510Sstevel@tonic-gate 			if (defopen(DEFAULT_LOGIN) == 0) {
5520Sstevel@tonic-gate 				if (initvar = defread("TIMEZONE=")) {
5530Sstevel@tonic-gate 					(void) strcpy(tznam, "TZ=");
5540Sstevel@tonic-gate 					(void) strlcat(tznam, initvar,
5558255SJan.Kryl@Sun.COM 					    sizeof (tznam));
5560Sstevel@tonic-gate 				}
5570Sstevel@tonic-gate 				(void) defopen(NULL);
5580Sstevel@tonic-gate 			}
5590Sstevel@tonic-gate 		}
5600Sstevel@tonic-gate 
5610Sstevel@tonic-gate 		if (tznam[0] != '\0')
5620Sstevel@tonic-gate 			envinit[++envidx] = tznam;
5630Sstevel@tonic-gate 
5640Sstevel@tonic-gate #ifdef DYNAMIC_SU
5650Sstevel@tonic-gate 		/*
5660Sstevel@tonic-gate 		 * set the PAM environment variables -
5670Sstevel@tonic-gate 		 * check for legal environment variables
5680Sstevel@tonic-gate 		 */
5690Sstevel@tonic-gate 		if ((pam_env = pam_getenvlist(pamh)) != 0) {
5700Sstevel@tonic-gate 			while (pam_env[idx] != 0) {
5710Sstevel@tonic-gate 				if (envidx + 2 < ELIM &&
5720Sstevel@tonic-gate 				    legalenvvar(pam_env[idx])) {
5730Sstevel@tonic-gate 					envinit[++envidx] = pam_env[idx];
5740Sstevel@tonic-gate 				}
5750Sstevel@tonic-gate 				idx++;
5760Sstevel@tonic-gate 			}
5770Sstevel@tonic-gate 		}
57879Sgww #endif	/* DYNAMIC_SU */
5790Sstevel@tonic-gate 		envinit[++envidx] = NULL;
5800Sstevel@tonic-gate 		environ = envinit;
5810Sstevel@tonic-gate 	} else {
5820Sstevel@tonic-gate 		char **pp = environ, **qq, *p;
5830Sstevel@tonic-gate 
5840Sstevel@tonic-gate 		while ((p = *pp) != NULL) {
5850Sstevel@tonic-gate 			if (*p == 'L' && p[1] == 'D' && p[2] == '_') {
5860Sstevel@tonic-gate 				for (qq = pp; (*qq = qq[1]) != NULL; qq++)
5870Sstevel@tonic-gate 					;
5880Sstevel@tonic-gate 				/* pp is not advanced */
5890Sstevel@tonic-gate 			} else {
5900Sstevel@tonic-gate 				pp++;
5910Sstevel@tonic-gate 			}
5920Sstevel@tonic-gate 		}
5930Sstevel@tonic-gate 	}
5940Sstevel@tonic-gate 
5950Sstevel@tonic-gate #ifdef DYNAMIC_SU
5960Sstevel@tonic-gate 	if (pamh)
5970Sstevel@tonic-gate 		(void) pam_end(pamh, PAM_SUCCESS);
59879Sgww #endif	/* DYNAMIC_SU */
5990Sstevel@tonic-gate 
6000Sstevel@tonic-gate 	/*
6010Sstevel@tonic-gate 	 * if new user is root:
6020Sstevel@tonic-gate 	 *	if CONSOLE defined, log entry there;
6030Sstevel@tonic-gate 	 *	if eflag not set, change environment to that of root.
6040Sstevel@tonic-gate 	 */
6050Sstevel@tonic-gate 	if (uid == (uid_t)ROOT) {
6060Sstevel@tonic-gate 		if (Console != NULL)
6070Sstevel@tonic-gate 			if (strcmp(ttyn, Console) != 0) {
6080Sstevel@tonic-gate 				(void) signal(SIGALRM, to);
6090Sstevel@tonic-gate 				(void) alarm(30);
6100Sstevel@tonic-gate 				log(Console, nptr, 1);
6110Sstevel@tonic-gate 				(void) alarm(0);
6120Sstevel@tonic-gate 			}
6130Sstevel@tonic-gate 		if (!eflag)
6140Sstevel@tonic-gate 			envalt();
6150Sstevel@tonic-gate 	}
6160Sstevel@tonic-gate 
6170Sstevel@tonic-gate 	/*
6180Sstevel@tonic-gate 	 * Default for SIGCPU and SIGXFSZ.  Shells inherit
6190Sstevel@tonic-gate 	 * signal disposition from parent.  And the
6200Sstevel@tonic-gate 	 * shells should have default dispositions for these
6210Sstevel@tonic-gate 	 * signals.
6220Sstevel@tonic-gate 	 */
6230Sstevel@tonic-gate 	(void) signal(SIGXCPU, SIG_DFL);
6240Sstevel@tonic-gate 	(void) signal(SIGXFSZ, SIG_DFL);
6250Sstevel@tonic-gate 
6260Sstevel@tonic-gate #ifdef	DYNAMIC_SU
6270Sstevel@tonic-gate 	if (embedded) {
6280Sstevel@tonic-gate 		(void) puts("SUCCESS");
6290Sstevel@tonic-gate 		/*
6300Sstevel@tonic-gate 		 * After this point, we're no longer talking the
6310Sstevel@tonic-gate 		 * embedded_su protocol, so turn it off.
6320Sstevel@tonic-gate 		 */
6330Sstevel@tonic-gate 		embedded = B_FALSE;
6340Sstevel@tonic-gate 	}
6350Sstevel@tonic-gate #endif	/* DYNAMIC_SU */
6360Sstevel@tonic-gate 
6370Sstevel@tonic-gate 	/*
6380Sstevel@tonic-gate 	 * if additional arguments, exec shell program with array
6390Sstevel@tonic-gate 	 * of pointers to arguments:
6400Sstevel@tonic-gate 	 *	-> if shell = default, then su = [-]su
6410Sstevel@tonic-gate 	 *	-> if shell != default, then su = [-]last component of
6420Sstevel@tonic-gate 	 *						shell's pathname
6430Sstevel@tonic-gate 	 *
6440Sstevel@tonic-gate 	 * if no additional arguments, exec shell with arg0 of su
6450Sstevel@tonic-gate 	 * where:
6460Sstevel@tonic-gate 	 *	-> if shell = default, then su = [-]su
6470Sstevel@tonic-gate 	 *	-> if shell != default, then su = [-]last component of
6480Sstevel@tonic-gate 	 *						shell's pathname
6490Sstevel@tonic-gate 	 */
6500Sstevel@tonic-gate 	if (argc > 2) {
6510Sstevel@tonic-gate 		argv[1] = su;
6520Sstevel@tonic-gate 		(void) execv(pshell, &argv[1]);
6530Sstevel@tonic-gate 	} else
6540Sstevel@tonic-gate 		(void) execl(pshell, su, 0);
6550Sstevel@tonic-gate 
6560Sstevel@tonic-gate 
6570Sstevel@tonic-gate 	/*
6580Sstevel@tonic-gate 	 * Try to clean up after an administrator who has made a mistake
6590Sstevel@tonic-gate 	 * configuring root's shell; if root's shell is other than /sbin/sh,
6600Sstevel@tonic-gate 	 * try exec'ing /sbin/sh instead.
6610Sstevel@tonic-gate 	 */
6620Sstevel@tonic-gate 	if ((uid == (uid_t)ROOT) && (strcmp(name, "root") == 0) &&
6630Sstevel@tonic-gate 	    (strcmp(safe_shell, pshell) != 0)) {
6640Sstevel@tonic-gate 		message(WARN,
6650Sstevel@tonic-gate 		    gettext("No shell %s.  Trying fallback shell %s."),
6660Sstevel@tonic-gate 		    pshell, safe_shell);
6670Sstevel@tonic-gate 
6680Sstevel@tonic-gate 		if (eflag) {
6690Sstevel@tonic-gate 			(void) strcpy(su, "-sh");
6700Sstevel@tonic-gate 			(void) strlcpy(shelltyp + strlen("SHELL="),
6710Sstevel@tonic-gate 			    safe_shell, sizeof (shelltyp) - strlen("SHELL="));
6720Sstevel@tonic-gate 		} else {
6730Sstevel@tonic-gate 			(void) strcpy(su, "sh");
6740Sstevel@tonic-gate 		}
6750Sstevel@tonic-gate 
6760Sstevel@tonic-gate 		if (argc > 2) {
6770Sstevel@tonic-gate 			argv[1] = su;
6780Sstevel@tonic-gate 			(void) execv(safe_shell, &argv[1]);
6790Sstevel@tonic-gate 		} else {
6800Sstevel@tonic-gate 			(void) execl(safe_shell, su, 0);
6810Sstevel@tonic-gate 		}
6820Sstevel@tonic-gate 		message(ERR, gettext("Couldn't exec fallback shell %s: %s"),
6830Sstevel@tonic-gate 		    safe_shell, strerror(errno));
6840Sstevel@tonic-gate 	} else {
6850Sstevel@tonic-gate 		message(ERR, gettext("No shell"));
6860Sstevel@tonic-gate 	}
6870Sstevel@tonic-gate 	return (3);
6880Sstevel@tonic-gate }
6890Sstevel@tonic-gate 
6900Sstevel@tonic-gate /*
6910Sstevel@tonic-gate  * Environment altering routine -
6920Sstevel@tonic-gate  *	This routine is called when a user is su'ing to root
6930Sstevel@tonic-gate  *	without specifying the - flag.
6940Sstevel@tonic-gate  *	The user's PATH and PS1 variables are reset
6950Sstevel@tonic-gate  *	to the correct value for root.
6960Sstevel@tonic-gate  *	All of the user's other environment variables retain
6970Sstevel@tonic-gate  *	their current values after the su (if they are exported).
6980Sstevel@tonic-gate  */
6990Sstevel@tonic-gate static void
envalt(void)7000Sstevel@tonic-gate envalt(void)
7010Sstevel@tonic-gate {
7020Sstevel@tonic-gate 	/*
7030Sstevel@tonic-gate 	 * If user has PATH variable in their environment, change its value
7040Sstevel@tonic-gate 	 *		to /bin:/etc:/usr/bin ;
7050Sstevel@tonic-gate 	 * if user does not have PATH variable, add it to the user's
7060Sstevel@tonic-gate 	 *		environment;
7070Sstevel@tonic-gate 	 * if either of the above fail, an error message is printed.
7080Sstevel@tonic-gate 	 */
7090Sstevel@tonic-gate 	if (putenv(supath) != 0) {
7100Sstevel@tonic-gate 		message(ERR,
7110Sstevel@tonic-gate 		    gettext("unable to obtain memory to expand environment"));
7120Sstevel@tonic-gate 		exit(4);
7130Sstevel@tonic-gate 	}
7140Sstevel@tonic-gate 
7150Sstevel@tonic-gate 	/*
7160Sstevel@tonic-gate 	 * If user has PROMPT variable in their environment, change its value
7170Sstevel@tonic-gate 	 *		to # ;
7180Sstevel@tonic-gate 	 * if user does not have PROMPT variable, add it to the user's
7190Sstevel@tonic-gate 	 *		environment;
7200Sstevel@tonic-gate 	 * if either of the above fail, an error message is printed.
7210Sstevel@tonic-gate 	 */
7220Sstevel@tonic-gate 	if (putenv(suprmt) != 0) {
7230Sstevel@tonic-gate 		message(ERR,
7240Sstevel@tonic-gate 		    gettext("unable to obtain memory to expand environment"));
7250Sstevel@tonic-gate 		exit(4);
7260Sstevel@tonic-gate 	}
7270Sstevel@tonic-gate }
7280Sstevel@tonic-gate 
7290Sstevel@tonic-gate /*
7300Sstevel@tonic-gate  * Logging routine -
7310Sstevel@tonic-gate  *	where = SULOG or CONSOLE
7320Sstevel@tonic-gate  *	towho = specified user ( user being su'ed to )
7330Sstevel@tonic-gate  *	how = 0 if su attempt failed; 1 if su attempt succeeded
7340Sstevel@tonic-gate  */
7350Sstevel@tonic-gate static void
log(char * where,char * towho,int how)7360Sstevel@tonic-gate log(char *where, char *towho, int how)
7370Sstevel@tonic-gate {
7380Sstevel@tonic-gate 	FILE *logf;
7390Sstevel@tonic-gate 	time_t now;
7400Sstevel@tonic-gate 	struct tm *tmp;
7410Sstevel@tonic-gate 
7420Sstevel@tonic-gate 	/*
7430Sstevel@tonic-gate 	 * open SULOG or CONSOLE - if open fails, return
7440Sstevel@tonic-gate 	 */
7450Sstevel@tonic-gate 	if ((logf = fopen(where, "a")) == NULL)
7460Sstevel@tonic-gate 		return;
7470Sstevel@tonic-gate 
7480Sstevel@tonic-gate 	now = time(0);
7490Sstevel@tonic-gate 	tmp = localtime(&now);
7500Sstevel@tonic-gate 
7510Sstevel@tonic-gate 	/*
7520Sstevel@tonic-gate 	 * write entry into SULOG or onto CONSOLE - if write fails, return
7530Sstevel@tonic-gate 	 */
7540Sstevel@tonic-gate 	(void) fprintf(logf, "SU %.2d/%.2d %.2d:%.2d %c %s %s-%s\n",
7550Sstevel@tonic-gate 	    tmp->tm_mon + 1, tmp->tm_mday, tmp->tm_hour, tmp->tm_min,
7560Sstevel@tonic-gate 	    how ? '+' : '-', ttyn + sizeof ("/dev/") - 1, username, towho);
7570Sstevel@tonic-gate 
7580Sstevel@tonic-gate 	(void) fclose(logf);	/* close SULOG or CONSOLE */
7590Sstevel@tonic-gate }
7600Sstevel@tonic-gate 
7610Sstevel@tonic-gate /*ARGSUSED*/
7620Sstevel@tonic-gate static void
to(int sig)7630Sstevel@tonic-gate to(int sig)
7640Sstevel@tonic-gate {}
7650Sstevel@tonic-gate 
76679Sgww /*
76779Sgww  * audit_success - audit successful su
76879Sgww  *
76979Sgww  *	Entry	process audit context established -- i.e., pam_setcred()
77079Sgww  *			or equivalent called.
77179Sgww  *		pw_change = PW_TRUE, if successful password change audit
77279Sgww  *				required.
77379Sgww  *		pwd = passwd entry for new user.
77479Sgww  */
77579Sgww 
77679Sgww static void
audit_success(int pw_change,struct passwd * pwd)77779Sgww audit_success(int pw_change, struct passwd *pwd)
77879Sgww {
77979Sgww 	adt_session_data_t	*ah = NULL;
78079Sgww 	adt_event_data_t	*event;
7812246Sgww 	au_event_t		event_id = ADT_su;
7822246Sgww 	userattr_t		*user_entry;
7832246Sgww 	char			*kva_value;
78479Sgww 
78579Sgww 	if (adt_start_session(&ah, NULL, ADT_USE_PROC_DATA) != 0) {
78679Sgww 		syslog(LOG_AUTH | LOG_ALERT,
78779Sgww 		    "adt_start_session(ADT_su): %m");
78879Sgww 		return;
78979Sgww 	}
7902246Sgww 	if (((user_entry = getusernam(pwd->pw_name)) != NULL) &&
7912246Sgww 	    ((kva_value = kva_match((kva_t *)user_entry->attr,
7922246Sgww 	    USERATTR_TYPE_KW)) != NULL) &&
7932246Sgww 	    ((strcmp(kva_value, USERATTR_TYPE_NONADMIN_KW) == 0) ||
7942246Sgww 	    (strcmp(kva_value, USERATTR_TYPE_ADMIN_KW) == 0))) {
7952246Sgww 		event_id = ADT_role_login;
7962246Sgww 	}
7972246Sgww 	free_userattr(user_entry);	/* OK to use, checks for NULL */
7982246Sgww 
79979Sgww 	/* since proc uid/gid not yet updated */
80079Sgww 	if (adt_set_user(ah, pwd->pw_uid, pwd->pw_gid, pwd->pw_uid,
80179Sgww 	    pwd->pw_gid, NULL, ADT_USER) != 0) {
80279Sgww 		syslog(LOG_AUTH | LOG_ERR,
80379Sgww 		    "adt_set_user(ADT_su, ADT_FAILURE): %m");
80479Sgww 	}
8052246Sgww 	if ((event = adt_alloc_event(ah, event_id)) == NULL) {
80679Sgww 		syslog(LOG_AUTH | LOG_ALERT, "adt_alloc_event(ADT_su): %m");
80779Sgww 	} else if (adt_put_event(event, ADT_SUCCESS, ADT_SUCCESS) != 0) {
80879Sgww 		syslog(LOG_AUTH | LOG_ALERT,
80979Sgww 		    "adt_put_event(ADT_su, ADT_SUCCESS): %m");
81079Sgww 	}
81179Sgww 
81279Sgww 	if (pw_change == PW_TRUE) {
81379Sgww 		/* Also audit password change */
81479Sgww 		adt_free_event(event);
81579Sgww 		if ((event = adt_alloc_event(ah, ADT_passwd)) == NULL) {
81679Sgww 			syslog(LOG_AUTH | LOG_ALERT,
81779Sgww 			    "adt_alloc_event(ADT_passwd): %m");
81879Sgww 		} else if (adt_put_event(event, ADT_SUCCESS,
81979Sgww 		    ADT_SUCCESS) != 0) {
82079Sgww 			syslog(LOG_AUTH | LOG_ALERT,
82179Sgww 			    "adt_put_event(ADT_passwd, ADT_SUCCESS): %m");
82279Sgww 		}
82379Sgww 	}
82479Sgww 	adt_free_event(event);
8252246Sgww 	/*
8262246Sgww 	 * The preceeding code is a noop if audit isn't enabled,
8272246Sgww 	 * but, let's not make a new process when it's not necessary.
8282246Sgww 	 */
829*12957SMarek.Pospisil@Sun.COM 	if (adt_audit_state(AUC_AUDITING)) {
8303050Sgww 		audit_logout(ah, event_id);	/* fork to catch logout */
8312246Sgww 	}
8322246Sgww 	(void) adt_end_session(ah);
8332246Sgww }
8342246Sgww 
8352246Sgww 
8362246Sgww /*
8372246Sgww  * audit_logout - audit successful su logout
8382246Sgww  *
8392246Sgww  *	Entry	ah = Successful su audit handle
8402246Sgww  *		event_id = su event ID: ADT_su, ADT_role_login
8412246Sgww  *
8422246Sgww  *	Exit	Errors are just ignored and we go on.
8432246Sgww  *		su logout event written.
8442246Sgww  */
8452246Sgww static void
audit_logout(adt_session_data_t * ah,au_event_t event_id)8462246Sgww audit_logout(adt_session_data_t *ah, au_event_t event_id)
8472246Sgww {
8482246Sgww 	adt_event_data_t	*event;
8492246Sgww 	int			status;		/* wait status */
8502246Sgww 	pid_t			pid;
8512246Sgww 	priv_set_t		*priv;		/* waiting process privs */
8522246Sgww 
8533050Sgww 	if (event_id == ADT_su) {
8543050Sgww 		event_id = ADT_su_logout;
8552246Sgww 	} else {
8563050Sgww 		event_id = ADT_role_logout;
8573050Sgww 	}
8583050Sgww 	if ((event = adt_alloc_event(ah, event_id)) == NULL) {
8593050Sgww 		syslog(LOG_AUTH | LOG_ALERT,
8603050Sgww 		    "adt_alloc_event(ADT_su_logout): %m");
8613050Sgww 		return;
8623050Sgww 	}
8633050Sgww 	if ((priv = priv_allocset())  == NULL) {
8643050Sgww 		syslog(LOG_AUTH | LOG_ALERT,
86511537SCasper.Dik@Sun.COM 		    "su audit_logout: could not alloc basic privs: %m");
8663050Sgww 		adt_free_event(event);
8673050Sgww 		return;
8683050Sgww 	}
8693050Sgww 
8703050Sgww 	/*
8713050Sgww 	 * The child returns and continues su processing.
8723050Sgww 	 * The parent's sole job is to wait for child exit, write the
8733050Sgww 	 * logout audit record, and replay the child's exit code.
8743050Sgww 	 */
8753050Sgww 	if ((pid = fork()) == 0) {
8763050Sgww 		/* child */
8773050Sgww 
8783050Sgww 		adt_free_event(event);
8793050Sgww 		priv_freeset(priv);
8803050Sgww 		return;
8813050Sgww 	}
8823050Sgww 	if (pid == -1) {
8833050Sgww 		/* failure */
8843050Sgww 
8853050Sgww 		syslog(LOG_AUTH | LOG_ALERT,
8863050Sgww 		    "su audit_logout: could not fork: %m");
8873050Sgww 		adt_free_event(event);
8882246Sgww 		priv_freeset(priv);
8893050Sgww 		return;
8903050Sgww 	}
8913050Sgww 
8923050Sgww 	/* parent process */
8932246Sgww 
8943050Sgww 	/*
8953050Sgww 	 * When this routine is called, the current working
8963050Sgww 	 * directory is the unknown and there are unknown open
8973050Sgww 	 * files. For the waiting process, change the current
8983050Sgww 	 * directory to root and close open files so that
8993050Sgww 	 * directories can be unmounted if necessary.
9003050Sgww 	 */
9013050Sgww 	if (chdir("/") != 0) {
9023050Sgww 		syslog(LOG_AUTH | LOG_ALERT,
9033050Sgww 		    "su audit_logout: could not chdir /: %m");
9043050Sgww 	}
9053050Sgww 	/*
9063050Sgww 	 * Reduce privileges to just those needed.
9073050Sgww 	 */
90811537SCasper.Dik@Sun.COM 	priv_basicset(priv);
90911537SCasper.Dik@Sun.COM 	(void) priv_delset(priv, PRIV_PROC_EXEC);
91011537SCasper.Dik@Sun.COM 	(void) priv_delset(priv, PRIV_PROC_FORK);
91111537SCasper.Dik@Sun.COM 	(void) priv_delset(priv, PRIV_PROC_INFO);
91211537SCasper.Dik@Sun.COM 	(void) priv_delset(priv, PRIV_PROC_SESSION);
91311537SCasper.Dik@Sun.COM 	(void) priv_delset(priv, PRIV_FILE_LINK_ANY);
9143050Sgww 	if ((priv_addset(priv, PRIV_PROC_AUDIT) != 0) ||
9153050Sgww 	    (setppriv(PRIV_SET, PRIV_PERMITTED, priv) != 0)) {
9163050Sgww 		syslog(LOG_AUTH | LOG_ALERT,
9173050Sgww 		    "su audit_logout: could not reduce privs: %m");
9183050Sgww 	}
9193050Sgww 	closefrom(0);
9203050Sgww 	priv_freeset(priv);
921*12957SMarek.Pospisil@Sun.COM 
922*12957SMarek.Pospisil@Sun.COM 	for (;;) {
923*12957SMarek.Pospisil@Sun.COM 		if (pid != waitpid(pid, &status, WUNTRACED)) {
924*12957SMarek.Pospisil@Sun.COM 			if (errno == ECHILD) {
925*12957SMarek.Pospisil@Sun.COM 				/*
926*12957SMarek.Pospisil@Sun.COM 				 * No existing child with the given pid. Lets
927*12957SMarek.Pospisil@Sun.COM 				 * audit the logout.
928*12957SMarek.Pospisil@Sun.COM 				 */
929*12957SMarek.Pospisil@Sun.COM 				break;
930*12957SMarek.Pospisil@Sun.COM 			}
931*12957SMarek.Pospisil@Sun.COM 			continue;
932*12957SMarek.Pospisil@Sun.COM 		}
933*12957SMarek.Pospisil@Sun.COM 
934*12957SMarek.Pospisil@Sun.COM 		if (WIFEXITED(status) || WIFSIGNALED(status)) {
935*12957SMarek.Pospisil@Sun.COM 			/*
936*12957SMarek.Pospisil@Sun.COM 			 * The child shell exited or was terminated by
937*12957SMarek.Pospisil@Sun.COM 			 * a signal. Lets audit logout.
938*12957SMarek.Pospisil@Sun.COM 			 */
939*12957SMarek.Pospisil@Sun.COM 			break;
940*12957SMarek.Pospisil@Sun.COM 		} else if (WIFSTOPPED(status)) {
941*12957SMarek.Pospisil@Sun.COM 			pid_t pgid;
942*12957SMarek.Pospisil@Sun.COM 			int fd;
943*12957SMarek.Pospisil@Sun.COM 			void (*sg_handler)();
944*12957SMarek.Pospisil@Sun.COM 			/*
945*12957SMarek.Pospisil@Sun.COM 			 * The child shell has been stopped/suspended.
946*12957SMarek.Pospisil@Sun.COM 			 * We need to suspend here as well and pass down
947*12957SMarek.Pospisil@Sun.COM 			 * the control to the parent process.
948*12957SMarek.Pospisil@Sun.COM 			 */
949*12957SMarek.Pospisil@Sun.COM 			sg_handler = signal(WSTOPSIG(status), SIG_DFL);
950*12957SMarek.Pospisil@Sun.COM 			(void) sigsend(P_PGID, getpgrp(), WSTOPSIG(status));
951*12957SMarek.Pospisil@Sun.COM 			/*
952*12957SMarek.Pospisil@Sun.COM 			 * We stop here. When resumed, mark the child
953*12957SMarek.Pospisil@Sun.COM 			 * shell group as foreground process group
954*12957SMarek.Pospisil@Sun.COM 			 * which gives the child shell a control over
955*12957SMarek.Pospisil@Sun.COM 			 * the controlling terminal.
956*12957SMarek.Pospisil@Sun.COM 			 */
957*12957SMarek.Pospisil@Sun.COM 			(void) signal(WSTOPSIG(status), sg_handler);
958*12957SMarek.Pospisil@Sun.COM 
959*12957SMarek.Pospisil@Sun.COM 			pgid = getpgid(pid);
960*12957SMarek.Pospisil@Sun.COM 			if ((fd = open("/dev/tty", O_RDWR)) != -1) {
961*12957SMarek.Pospisil@Sun.COM 				/*
962*12957SMarek.Pospisil@Sun.COM 				 * Pass down the control over the controlling
963*12957SMarek.Pospisil@Sun.COM 				 * terminal iff we are in a foreground process
964*12957SMarek.Pospisil@Sun.COM 				 * group. Otherwise, we are in a background
965*12957SMarek.Pospisil@Sun.COM 				 * process group and the kernel will send
966*12957SMarek.Pospisil@Sun.COM 				 * SIGTTOU signal to stop us (by default).
967*12957SMarek.Pospisil@Sun.COM 				 */
968*12957SMarek.Pospisil@Sun.COM 				if (tcgetpgrp(fd) == getpgrp()) {
969*12957SMarek.Pospisil@Sun.COM 					(void) tcsetpgrp(fd, pgid);
970*12957SMarek.Pospisil@Sun.COM 				}
971*12957SMarek.Pospisil@Sun.COM 				(void) close(fd);
972*12957SMarek.Pospisil@Sun.COM 			}
973*12957SMarek.Pospisil@Sun.COM 			/* Wake up the child shell */
974*12957SMarek.Pospisil@Sun.COM 			(void) sigsend(P_PGID, pgid, SIGCONT);
975*12957SMarek.Pospisil@Sun.COM 		}
976*12957SMarek.Pospisil@Sun.COM 	}
9772246Sgww 
9783050Sgww 	(void) adt_put_event(event, ADT_SUCCESS, ADT_SUCCESS);
9793050Sgww 	adt_free_event(event);
9803050Sgww 	(void) adt_end_session(ah);
9813050Sgww 	exit(WEXITSTATUS(status));
98279Sgww }
98379Sgww 
98479Sgww 
98579Sgww /*
98679Sgww  * audit_failure - audit failed su
98779Sgww  *
98879Sgww  *	Entry	New audit context not set.
98979Sgww  *		pw_change == PW_FALSE, if no password change requested.
99079Sgww  *			     PW_FAILED, if failed password change audit
99179Sgww  *				      required.
9922246Sgww  *		pwd = NULL, or password entry to use.
9932246Sgww  *		user = username entered.  Add to record if pwd == NULL.
99479Sgww  *		pamerr = PAM error code; reason for failure.
99579Sgww  */
99679Sgww 
99779Sgww static void
audit_failure(int pw_change,struct passwd * pwd,char * user,int pamerr)9982246Sgww audit_failure(int pw_change, struct passwd *pwd, char *user, int pamerr)
99979Sgww {
100079Sgww 	adt_session_data_t	*ah;	/* audit session handle */
100179Sgww 	adt_event_data_t	*event;	/* event to generate */
10022246Sgww 	au_event_t		event_id = ADT_su;
10032246Sgww 	userattr_t		*user_entry;
10042246Sgww 	char			*kva_value;
100579Sgww 
100679Sgww 	if (adt_start_session(&ah, NULL, ADT_USE_PROC_DATA) != 0) {
100779Sgww 		syslog(LOG_AUTH | LOG_ALERT,
100879Sgww 		    "adt_start_session(ADT_su, ADT_FAILURE): %m");
100979Sgww 		return;
101079Sgww 	}
10112246Sgww 
101279Sgww 	if (pwd != NULL) {
101379Sgww 		/* target user authenticated, merge audit state */
101479Sgww 		if (adt_set_user(ah, pwd->pw_uid, pwd->pw_gid, pwd->pw_uid,
101579Sgww 		    pwd->pw_gid, NULL, ADT_UPDATE) != 0) {
101679Sgww 			syslog(LOG_AUTH | LOG_ERR,
101779Sgww 			    "adt_set_user(ADT_su, ADT_FAILURE): %m");
101879Sgww 		}
10192246Sgww 		if (((user_entry = getusernam(pwd->pw_name)) != NULL) &&
10202246Sgww 		    ((kva_value = kva_match((kva_t *)user_entry->attr,
10212246Sgww 		    USERATTR_TYPE_KW)) != NULL) &&
10222246Sgww 		    ((strcmp(kva_value, USERATTR_TYPE_NONADMIN_KW) == 0) ||
10232246Sgww 		    (strcmp(kva_value, USERATTR_TYPE_ADMIN_KW) == 0))) {
10242246Sgww 			event_id = ADT_role_login;
10252246Sgww 		}
10262246Sgww 		free_userattr(user_entry);	/* OK to use, checks for NULL */
102779Sgww 	}
10282246Sgww 	if ((event = adt_alloc_event(ah, event_id)) == NULL) {
102979Sgww 		syslog(LOG_AUTH | LOG_ALERT,
103079Sgww 		    "adt_alloc_event(ADT_su, ADT_FAILURE): %m");
103179Sgww 		return;
10322246Sgww 	}
10332246Sgww 	/*
10342246Sgww 	 * can't tell if user not found is a role, so always use su
10352246Sgww 	 * If we do pass in pwd when the JNI is fixed, then can
10362246Sgww 	 * distinguish and set name in both su and role_login
10372246Sgww 	 */
10382246Sgww 	if (pwd == NULL) {
10392246Sgww 		/*
10402246Sgww 		 * this should be "fail_user" rather than "message"
10412246Sgww 		 * see adt_xml.  The JNI breaks, so for now we leave
10422246Sgww 		 * this alone.
10432246Sgww 		 */
10442246Sgww 		event->adt_su.message = user;
10452246Sgww 	}
10462246Sgww 	if (adt_put_event(event, ADT_FAILURE,
104779Sgww 	    ADT_FAIL_PAM + pamerr) != 0) {
104879Sgww 		syslog(LOG_AUTH | LOG_ALERT,
104979Sgww 		    "adt_put_event(ADT_su(ADT_FAIL, %s): %m",
105079Sgww 		    pam_strerror(pamh, pamerr));
105179Sgww 	}
105279Sgww 	if (pw_change != PW_FALSE) {
105379Sgww 		/* Also audit password change failed */
105479Sgww 		adt_free_event(event);
105579Sgww 		if ((event = adt_alloc_event(ah, ADT_passwd)) == NULL) {
105679Sgww 			syslog(LOG_AUTH | LOG_ALERT,
10573050Sgww 			    "su: adt_alloc_event(ADT_passwd): %m");
105879Sgww 		} else if (adt_put_event(event, ADT_FAILURE,
105979Sgww 		    ADT_FAIL_PAM + pamerr) != 0) {
106079Sgww 			syslog(LOG_AUTH | LOG_ALERT,
10613050Sgww 			    "su: adt_put_event(ADT_passwd, ADT_FAILURE): %m");
106279Sgww 		}
106379Sgww 	}
106479Sgww 	adt_free_event(event);
10652246Sgww 	(void) adt_end_session(ah);
106679Sgww }
106779Sgww 
10680Sstevel@tonic-gate #ifdef DYNAMIC_SU
10690Sstevel@tonic-gate /*
10700Sstevel@tonic-gate  * su_conv():
10710Sstevel@tonic-gate  *	This is the conv (conversation) function called from
10720Sstevel@tonic-gate  *	a PAM authentication module to print error messages
10730Sstevel@tonic-gate  *	or garner information from the user.
10740Sstevel@tonic-gate  */
10750Sstevel@tonic-gate /*ARGSUSED*/
10760Sstevel@tonic-gate static int
su_conv(int num_msg,struct pam_message ** msg,struct pam_response ** response,void * appdata_ptr)10770Sstevel@tonic-gate su_conv(int num_msg, struct pam_message **msg, struct pam_response **response,
10780Sstevel@tonic-gate     void *appdata_ptr)
10790Sstevel@tonic-gate {
10800Sstevel@tonic-gate 	struct pam_message	*m;
10810Sstevel@tonic-gate 	struct pam_response	*r;
10820Sstevel@tonic-gate 	char			*temp;
10830Sstevel@tonic-gate 	int			k;
10840Sstevel@tonic-gate 	char			respbuf[PAM_MAX_RESP_SIZE];
10850Sstevel@tonic-gate 
10860Sstevel@tonic-gate 	if (num_msg <= 0)
10870Sstevel@tonic-gate 		return (PAM_CONV_ERR);
10880Sstevel@tonic-gate 
10890Sstevel@tonic-gate 	*response = (struct pam_response *)calloc(num_msg,
10900Sstevel@tonic-gate 	    sizeof (struct pam_response));
10910Sstevel@tonic-gate 	if (*response == NULL)
10920Sstevel@tonic-gate 		return (PAM_BUF_ERR);
10930Sstevel@tonic-gate 
10940Sstevel@tonic-gate 	k = num_msg;
10950Sstevel@tonic-gate 	m = *msg;
10960Sstevel@tonic-gate 	r = *response;
10970Sstevel@tonic-gate 	while (k--) {
10980Sstevel@tonic-gate 
10990Sstevel@tonic-gate 		switch (m->msg_style) {
11000Sstevel@tonic-gate 
11010Sstevel@tonic-gate 		case PAM_PROMPT_ECHO_OFF:
11020Sstevel@tonic-gate 			errno = 0;
11030Sstevel@tonic-gate 			temp = getpassphrase(m->msg);
11040Sstevel@tonic-gate 			if (errno == EINTR)
11050Sstevel@tonic-gate 				return (PAM_CONV_ERR);
11060Sstevel@tonic-gate 			if (temp != NULL) {
11070Sstevel@tonic-gate 				r->resp = strdup(temp);
11080Sstevel@tonic-gate 				if (r->resp == NULL) {
11090Sstevel@tonic-gate 					freeresponse(num_msg, response);
11100Sstevel@tonic-gate 					return (PAM_BUF_ERR);
11110Sstevel@tonic-gate 				}
11120Sstevel@tonic-gate 			}
11130Sstevel@tonic-gate 			break;
11140Sstevel@tonic-gate 
11150Sstevel@tonic-gate 		case PAM_PROMPT_ECHO_ON:
11160Sstevel@tonic-gate 			if (m->msg != NULL) {
11170Sstevel@tonic-gate 				(void) fputs(m->msg, stdout);
11180Sstevel@tonic-gate 			}
11190Sstevel@tonic-gate 
11200Sstevel@tonic-gate 			(void) fgets(respbuf, sizeof (respbuf), stdin);
11210Sstevel@tonic-gate 			temp = strchr(respbuf, '\n');
11220Sstevel@tonic-gate 			if (temp != NULL)
11230Sstevel@tonic-gate 				*temp = '\0';
11240Sstevel@tonic-gate 
11250Sstevel@tonic-gate 			r->resp = strdup(respbuf);
11260Sstevel@tonic-gate 			if (r->resp == NULL) {
11270Sstevel@tonic-gate 				freeresponse(num_msg, response);
11280Sstevel@tonic-gate 				return (PAM_BUF_ERR);
11290Sstevel@tonic-gate 			}
11300Sstevel@tonic-gate 			break;
11310Sstevel@tonic-gate 
11320Sstevel@tonic-gate 		case PAM_ERROR_MSG:
11330Sstevel@tonic-gate 			if (m->msg != NULL) {
11340Sstevel@tonic-gate 				(void) fputs(m->msg, stderr);
11350Sstevel@tonic-gate 				(void) fputs("\n", stderr);
11360Sstevel@tonic-gate 			}
11370Sstevel@tonic-gate 			break;
11380Sstevel@tonic-gate 
11390Sstevel@tonic-gate 		case PAM_TEXT_INFO:
11400Sstevel@tonic-gate 			if (m->msg != NULL) {
11410Sstevel@tonic-gate 				(void) fputs(m->msg, stdout);
11420Sstevel@tonic-gate 				(void) fputs("\n", stdout);
11430Sstevel@tonic-gate 			}
11440Sstevel@tonic-gate 			break;
11450Sstevel@tonic-gate 
11460Sstevel@tonic-gate 		default:
11470Sstevel@tonic-gate 			break;
11480Sstevel@tonic-gate 		}
11490Sstevel@tonic-gate 		m++;
11500Sstevel@tonic-gate 		r++;
11510Sstevel@tonic-gate 	}
11520Sstevel@tonic-gate 	return (PAM_SUCCESS);
11530Sstevel@tonic-gate }
11540Sstevel@tonic-gate 
11550Sstevel@tonic-gate /*
11560Sstevel@tonic-gate  * emb_su_conv():
11570Sstevel@tonic-gate  *	This is the conv (conversation) function called from
11580Sstevel@tonic-gate  *	a PAM authentication module to print error messages
11590Sstevel@tonic-gate  *	or garner information from the user.
11600Sstevel@tonic-gate  *	This version is used for embedded_su.
11610Sstevel@tonic-gate  */
11620Sstevel@tonic-gate /*ARGSUSED*/
11630Sstevel@tonic-gate static int
emb_su_conv(int num_msg,struct pam_message ** msg,struct pam_response ** response,void * appdata_ptr)11640Sstevel@tonic-gate emb_su_conv(int num_msg, struct pam_message **msg,
11650Sstevel@tonic-gate     struct pam_response **response, void *appdata_ptr)
11660Sstevel@tonic-gate {
11670Sstevel@tonic-gate 	struct pam_message	*m;
11680Sstevel@tonic-gate 	struct pam_response	*r;
11690Sstevel@tonic-gate 	char			*temp;
11700Sstevel@tonic-gate 	int			k;
11710Sstevel@tonic-gate 	char			respbuf[PAM_MAX_RESP_SIZE];
11720Sstevel@tonic-gate 
11730Sstevel@tonic-gate 	if (num_msg <= 0)
11740Sstevel@tonic-gate 		return (PAM_CONV_ERR);
11750Sstevel@tonic-gate 
11760Sstevel@tonic-gate 	*response = (struct pam_response *)calloc(num_msg,
11770Sstevel@tonic-gate 	    sizeof (struct pam_response));
11780Sstevel@tonic-gate 	if (*response == NULL)
11790Sstevel@tonic-gate 		return (PAM_BUF_ERR);
11800Sstevel@tonic-gate 
11810Sstevel@tonic-gate 	/* First, send the prompts */
11820Sstevel@tonic-gate 	(void) printf("CONV %d\n", num_msg);
11830Sstevel@tonic-gate 	k = num_msg;
11840Sstevel@tonic-gate 	m = *msg;
11850Sstevel@tonic-gate 	while (k--) {
11860Sstevel@tonic-gate 		switch (m->msg_style) {
11870Sstevel@tonic-gate 
11880Sstevel@tonic-gate 		case PAM_PROMPT_ECHO_OFF:
11890Sstevel@tonic-gate 			(void) puts("PAM_PROMPT_ECHO_OFF");
11900Sstevel@tonic-gate 			goto msg_common;
11910Sstevel@tonic-gate 
11920Sstevel@tonic-gate 		case PAM_PROMPT_ECHO_ON:
11930Sstevel@tonic-gate 			(void) puts("PAM_PROMPT_ECHO_ON");
11940Sstevel@tonic-gate 			goto msg_common;
11950Sstevel@tonic-gate 
11960Sstevel@tonic-gate 		case PAM_ERROR_MSG:
11970Sstevel@tonic-gate 			(void) puts("PAM_ERROR_MSG");
11980Sstevel@tonic-gate 			goto msg_common;
11990Sstevel@tonic-gate 
12000Sstevel@tonic-gate 		case PAM_TEXT_INFO:
12010Sstevel@tonic-gate 			(void) puts("PAM_TEXT_INFO");
12020Sstevel@tonic-gate 			/* fall through to msg_common */
12030Sstevel@tonic-gate msg_common:
12040Sstevel@tonic-gate 			if (m->msg == NULL)
12050Sstevel@tonic-gate 				quotemsg(NULL);
12060Sstevel@tonic-gate 			else
12070Sstevel@tonic-gate 				quotemsg("%s", m->msg);
12080Sstevel@tonic-gate 			break;
12090Sstevel@tonic-gate 
12100Sstevel@tonic-gate 		default:
12110Sstevel@tonic-gate 			break;
12120Sstevel@tonic-gate 		}
12130Sstevel@tonic-gate 		m++;
12140Sstevel@tonic-gate 	}
12150Sstevel@tonic-gate 
12160Sstevel@tonic-gate 	/* Next, collect the responses */
12170Sstevel@tonic-gate 	k = num_msg;
12180Sstevel@tonic-gate 	m = *msg;
12190Sstevel@tonic-gate 	r = *response;
12200Sstevel@tonic-gate 	while (k--) {
12210Sstevel@tonic-gate 
12220Sstevel@tonic-gate 		switch (m->msg_style) {
12230Sstevel@tonic-gate 
12240Sstevel@tonic-gate 		case PAM_PROMPT_ECHO_OFF:
12250Sstevel@tonic-gate 		case PAM_PROMPT_ECHO_ON:
12260Sstevel@tonic-gate 			(void) fgets(respbuf, sizeof (respbuf), stdin);
12270Sstevel@tonic-gate 
12280Sstevel@tonic-gate 			temp = strchr(respbuf, '\n');
12290Sstevel@tonic-gate 			if (temp != NULL)
12300Sstevel@tonic-gate 				*temp = '\0';
12310Sstevel@tonic-gate 
12320Sstevel@tonic-gate 			r->resp = strdup(respbuf);
12330Sstevel@tonic-gate 			if (r->resp == NULL) {
12340Sstevel@tonic-gate 				freeresponse(num_msg, response);
12350Sstevel@tonic-gate 				return (PAM_BUF_ERR);
12360Sstevel@tonic-gate 			}
12370Sstevel@tonic-gate 
12380Sstevel@tonic-gate 			break;
12390Sstevel@tonic-gate 
12400Sstevel@tonic-gate 		case PAM_ERROR_MSG:
12410Sstevel@tonic-gate 		case PAM_TEXT_INFO:
12420Sstevel@tonic-gate 			break;
12430Sstevel@tonic-gate 
12440Sstevel@tonic-gate 		default:
12450Sstevel@tonic-gate 			break;
12460Sstevel@tonic-gate 		}
12470Sstevel@tonic-gate 		m++;
12480Sstevel@tonic-gate 		r++;
12490Sstevel@tonic-gate 	}
12500Sstevel@tonic-gate 	return (PAM_SUCCESS);
12510Sstevel@tonic-gate }
12520Sstevel@tonic-gate 
12530Sstevel@tonic-gate static void
freeresponse(int num_msg,struct pam_response ** response)12540Sstevel@tonic-gate freeresponse(int num_msg, struct pam_response **response)
12550Sstevel@tonic-gate {
12560Sstevel@tonic-gate 	struct pam_response *r;
12570Sstevel@tonic-gate 	int i;
12580Sstevel@tonic-gate 
12590Sstevel@tonic-gate 	/* free responses */
12600Sstevel@tonic-gate 	r = *response;
12610Sstevel@tonic-gate 	for (i = 0; i < num_msg; i++, r++) {
12620Sstevel@tonic-gate 		if (r->resp != NULL) {
12630Sstevel@tonic-gate 			/* Zap it in case it's a password */
12640Sstevel@tonic-gate 			(void) memset(r->resp, '\0', strlen(r->resp));
12650Sstevel@tonic-gate 			free(r->resp);
12660Sstevel@tonic-gate 		}
12670Sstevel@tonic-gate 	}
12680Sstevel@tonic-gate 	free(*response);
12690Sstevel@tonic-gate 	*response = NULL;
12700Sstevel@tonic-gate }
12710Sstevel@tonic-gate 
12720Sstevel@tonic-gate /*
12730Sstevel@tonic-gate  * Print a message, applying quoting for lines starting with '.'.
12740Sstevel@tonic-gate  *
12750Sstevel@tonic-gate  * I18n note:  \n is "safe" in all locales, and all locales use
12760Sstevel@tonic-gate  * a high-bit-set character to start multibyte sequences, so
12770Sstevel@tonic-gate  * scanning for a \n followed by a '.' is safe.
12780Sstevel@tonic-gate  */
12790Sstevel@tonic-gate static void
quotemsg(char * fmt,...)12800Sstevel@tonic-gate quotemsg(char *fmt, ...)
12810Sstevel@tonic-gate {
12820Sstevel@tonic-gate 	if (fmt != NULL) {
12830Sstevel@tonic-gate 		char *msg;
12840Sstevel@tonic-gate 		char *p;
12850Sstevel@tonic-gate 		boolean_t bol;
12860Sstevel@tonic-gate 		va_list v;
12870Sstevel@tonic-gate 
12880Sstevel@tonic-gate 		va_start(v, fmt);
12890Sstevel@tonic-gate 		msg = alloc_vsprintf(fmt, v);
12900Sstevel@tonic-gate 		va_end(v);
12910Sstevel@tonic-gate 
12920Sstevel@tonic-gate 		bol = B_TRUE;
12930Sstevel@tonic-gate 		for (p = msg; *p != '\0'; p++) {
12940Sstevel@tonic-gate 			if (bol) {
12950Sstevel@tonic-gate 				if (*p == '.')
12960Sstevel@tonic-gate 					(void) putchar('.');
12970Sstevel@tonic-gate 				bol = B_FALSE;
12980Sstevel@tonic-gate 			}
12990Sstevel@tonic-gate 			(void) putchar(*p);
13000Sstevel@tonic-gate 			if (*p == '\n')
13010Sstevel@tonic-gate 				bol = B_TRUE;
13020Sstevel@tonic-gate 		}
13030Sstevel@tonic-gate 		(void) putchar('\n');
13040Sstevel@tonic-gate 		free(msg);
13050Sstevel@tonic-gate 	}
13060Sstevel@tonic-gate 	(void) putchar('.');
13070Sstevel@tonic-gate 	(void) putchar('\n');
13080Sstevel@tonic-gate }
13090Sstevel@tonic-gate 
13100Sstevel@tonic-gate /*
13110Sstevel@tonic-gate  * validate - Check that the account is valid for switching to.
13120Sstevel@tonic-gate  */
13130Sstevel@tonic-gate static void
validate(char * usernam,int * pw_change)131479Sgww validate(char *usernam, int *pw_change)
13150Sstevel@tonic-gate {
131679Sgww 	int error;
131779Sgww 	int tries;
13180Sstevel@tonic-gate 
13198126SJoep.Vesseur@Sun.COM 	if ((error = pam_acct_mgmt(pamh, pam_flags)) != PAM_SUCCESS) {
13200Sstevel@tonic-gate 		if (Sulog != NULL)
13210Sstevel@tonic-gate 			log(Sulog, pwd.pw_name, 0);    /* log entry */
13220Sstevel@tonic-gate 		if (error == PAM_NEW_AUTHTOK_REQD) {
132379Sgww 			tries = 0;
13240Sstevel@tonic-gate 			message(ERR, gettext("Password for user "
132579Sgww 			    "'%s' has expired"), pwd.pw_name);
13261419Sdarrenm 			while ((error = pam_chauthtok(pamh,
13271419Sdarrenm 			    PAM_CHANGE_EXPIRED_AUTHTOK)) != PAM_SUCCESS) {
132879Sgww 				if ((error == PAM_AUTHTOK_ERR ||
132979Sgww 				    error == PAM_TRY_AGAIN) &&
133079Sgww 				    (tries++ < DEF_ATTEMPTS)) {
133179Sgww 					continue;
133279Sgww 				}
133379Sgww 				message(ERR, gettext("Sorry"));
13342246Sgww 				audit_failure(PW_FAILED, &pwd, NULL, error);
133579Sgww 				if (dosyslog)
133679Sgww 					syslog(LOG_CRIT,
133779Sgww 					    "'su %s' failed for %s on %s",
133879Sgww 					    pwd.pw_name, usernam, ttyn);
133979Sgww 				closelog();
134079Sgww 				exit(1);
134179Sgww 			}
134279Sgww 			*pw_change = PW_TRUE;
134379Sgww 			return;
13440Sstevel@tonic-gate 		} else {
13450Sstevel@tonic-gate 			message(ERR, gettext("Sorry"));
13462246Sgww 			audit_failure(PW_FALSE, &pwd, NULL, error);
13470Sstevel@tonic-gate 			if (dosyslog)
13488255SJan.Kryl@Sun.COM 				syslog(LOG_CRIT, "'su %s' failed for %s on %s",
13498255SJan.Kryl@Sun.COM 				    pwd.pw_name, usernam, ttyn);
13500Sstevel@tonic-gate 			closelog();
13510Sstevel@tonic-gate 			exit(3);
13520Sstevel@tonic-gate 		}
13530Sstevel@tonic-gate 	}
13540Sstevel@tonic-gate }
13550Sstevel@tonic-gate 
13560Sstevel@tonic-gate static char *illegal[] = {
13570Sstevel@tonic-gate 	"SHELL=",
13580Sstevel@tonic-gate 	"HOME=",
13590Sstevel@tonic-gate 	"LOGNAME=",
13600Sstevel@tonic-gate #ifndef NO_MAIL
13610Sstevel@tonic-gate 	"MAIL=",
13620Sstevel@tonic-gate #endif
13630Sstevel@tonic-gate 	"CDPATH=",
13640Sstevel@tonic-gate 	"IFS=",
13650Sstevel@tonic-gate 	"PATH=",
13660Sstevel@tonic-gate 	"TZ=",
13670Sstevel@tonic-gate 	"HZ=",
13680Sstevel@tonic-gate 	"TERM=",
13690Sstevel@tonic-gate 	0
13700Sstevel@tonic-gate };
13710Sstevel@tonic-gate 
13720Sstevel@tonic-gate /*
13730Sstevel@tonic-gate  * legalenvvar - can PAM modules insert this environmental variable?
13740Sstevel@tonic-gate  */
13750Sstevel@tonic-gate 
13760Sstevel@tonic-gate static int
legalenvvar(char * s)13770Sstevel@tonic-gate legalenvvar(char *s)
13780Sstevel@tonic-gate {
13790Sstevel@tonic-gate 	register char **p;
13800Sstevel@tonic-gate 
13810Sstevel@tonic-gate 	for (p = illegal; *p; p++)
13820Sstevel@tonic-gate 		if (strncmp(s, *p, strlen(*p)) == 0)
13830Sstevel@tonic-gate 			return (0);
13840Sstevel@tonic-gate 
13850Sstevel@tonic-gate 	if (s[0] == 'L' && s[1] == 'D' && s[2] == '_')
13860Sstevel@tonic-gate 		return (0);
13870Sstevel@tonic-gate 
13880Sstevel@tonic-gate 	return (1);
13890Sstevel@tonic-gate }
13900Sstevel@tonic-gate 
13910Sstevel@tonic-gate /*
13920Sstevel@tonic-gate  * The embedded_su protocol allows the client application to supply
13930Sstevel@tonic-gate  * an initialization block terminated by a line with just a "." on it.
13940Sstevel@tonic-gate  *
13950Sstevel@tonic-gate  * This initialization block is currently unused, reserved for future
13960Sstevel@tonic-gate  * expansion.  Ignore it.  This is made very slightly more complex by
13970Sstevel@tonic-gate  * the desire to cleanly ignore input lines of any length, while still
13980Sstevel@tonic-gate  * correctly detecting a line with just a "." on it.
13990Sstevel@tonic-gate  *
14000Sstevel@tonic-gate  * I18n note:  It appears that none of the Solaris-supported locales
14010Sstevel@tonic-gate  * use 0x0a for any purpose other than newline, so looking for '\n'
14020Sstevel@tonic-gate  * seems safe.
14030Sstevel@tonic-gate  * All locales use high-bit-set leadin characters for their multi-byte
14040Sstevel@tonic-gate  * sequences, so a line consisting solely of ".\n" is what it appears
14050Sstevel@tonic-gate  * to be.
14060Sstevel@tonic-gate  */
14070Sstevel@tonic-gate static void
readinitblock(void)14080Sstevel@tonic-gate readinitblock(void)
14090Sstevel@tonic-gate {
14100Sstevel@tonic-gate 	char buf[100];
14110Sstevel@tonic-gate 	boolean_t bol;
14120Sstevel@tonic-gate 
14130Sstevel@tonic-gate 	bol = B_TRUE;
14140Sstevel@tonic-gate 	for (;;) {
14150Sstevel@tonic-gate 		if (fgets(buf, sizeof (buf), stdin) == NULL)
14160Sstevel@tonic-gate 			return;
14170Sstevel@tonic-gate 		if (bol && strcmp(buf, ".\n") == 0)
14180Sstevel@tonic-gate 			return;
14190Sstevel@tonic-gate 		bol = (strchr(buf, '\n') != NULL);
14200Sstevel@tonic-gate 	}
14210Sstevel@tonic-gate }
142279Sgww #else	/* !DYNAMIC_SU */
142379Sgww static void
update_audit(struct passwd * pwd)142479Sgww update_audit(struct passwd *pwd)
142579Sgww {
142679Sgww 	adt_session_data_t	*ah;	/* audit session handle */
142779Sgww 
142879Sgww 	if (adt_start_session(&ah, NULL, ADT_USE_PROC_DATA) != 0) {
142979Sgww 		message(ERR, gettext("Sorry"));
143079Sgww 		if (dosyslog)
143179Sgww 			syslog(LOG_CRIT, "'su %s' failed for %s "
143279Sgww 			    "cannot start audit session %m",
143379Sgww 			    pwd->pw_name, username);
143479Sgww 		closelog();
143579Sgww 		exit(2);
143679Sgww 	}
143779Sgww 	if (adt_set_user(ah, pwd->pw_uid, pwd->pw_gid, pwd->pw_uid,
143879Sgww 	    pwd->pw_gid, NULL, ADT_UPDATE) != 0) {
143979Sgww 		if (dosyslog)
144079Sgww 			syslog(LOG_CRIT, "'su %s' failed for %s "
144179Sgww 			    "cannot update audit session %m",
144279Sgww 			    pwd->pw_name, username);
144379Sgww 		closelog();
144479Sgww 		exit(2);
144579Sgww 	}
144679Sgww }
14470Sstevel@tonic-gate #endif	/* DYNAMIC_SU */
14480Sstevel@tonic-gate 
14490Sstevel@tonic-gate /*
14500Sstevel@tonic-gate  * Report an error, either a fatal one, a warning, or a usage message,
14510Sstevel@tonic-gate  * depending on the mode parameter.
14520Sstevel@tonic-gate  */
14530Sstevel@tonic-gate /*ARGSUSED*/
14540Sstevel@tonic-gate static void
message(enum messagemode mode,char * fmt,...)14550Sstevel@tonic-gate message(enum messagemode mode, char *fmt, ...)
14560Sstevel@tonic-gate {
14570Sstevel@tonic-gate 	char *s;
14580Sstevel@tonic-gate 	va_list v;
14590Sstevel@tonic-gate 
14600Sstevel@tonic-gate 	va_start(v, fmt);
14610Sstevel@tonic-gate 	s = alloc_vsprintf(fmt, v);
14620Sstevel@tonic-gate 	va_end(v);
14630Sstevel@tonic-gate 
14640Sstevel@tonic-gate #ifdef	DYNAMIC_SU
14650Sstevel@tonic-gate 	if (embedded) {
14660Sstevel@tonic-gate 		if (mode == WARN) {
14670Sstevel@tonic-gate 			(void) printf("CONV 1\n");
14680Sstevel@tonic-gate 			(void) printf("PAM_ERROR_MSG\n");
14690Sstevel@tonic-gate 		} else { /* ERR, USAGE */
14700Sstevel@tonic-gate 			(void) printf("ERROR\n");
14710Sstevel@tonic-gate 		}
14720Sstevel@tonic-gate 		if (mode == USAGE) {
14730Sstevel@tonic-gate 			quotemsg("%s", s);
14740Sstevel@tonic-gate 		} else { /* ERR, WARN */
14750Sstevel@tonic-gate 			quotemsg("%s: %s", myname, s);
14760Sstevel@tonic-gate 		}
14770Sstevel@tonic-gate 	} else {
14780Sstevel@tonic-gate #endif	/* DYNAMIC_SU */
14790Sstevel@tonic-gate 		if (mode == USAGE) {
14800Sstevel@tonic-gate 			(void) fprintf(stderr, "%s\n", s);
14810Sstevel@tonic-gate 		} else { /* ERR, WARN */
14820Sstevel@tonic-gate 			(void) fprintf(stderr, "%s: %s\n", myname, s);
14830Sstevel@tonic-gate 		}
14840Sstevel@tonic-gate #ifdef	DYNAMIC_SU
14850Sstevel@tonic-gate 	}
14860Sstevel@tonic-gate #endif	/* DYNAMIC_SU */
14870Sstevel@tonic-gate 
14880Sstevel@tonic-gate 	free(s);
14890Sstevel@tonic-gate }
14900Sstevel@tonic-gate 
14910Sstevel@tonic-gate /*
14920Sstevel@tonic-gate  * Return a pointer to the last path component of a.
14930Sstevel@tonic-gate  */
14940Sstevel@tonic-gate static char *
tail(char * a)14950Sstevel@tonic-gate tail(char *a)
14960Sstevel@tonic-gate {
14970Sstevel@tonic-gate 	char *p;
14980Sstevel@tonic-gate 
14990Sstevel@tonic-gate 	p = strrchr(a, '/');
15000Sstevel@tonic-gate 	if (p == NULL)
15010Sstevel@tonic-gate 		p = a;
15020Sstevel@tonic-gate 	else
15030Sstevel@tonic-gate 		p++;	/* step over the '/' */
15040Sstevel@tonic-gate 
15050Sstevel@tonic-gate 	return (p);
15060Sstevel@tonic-gate }
15070Sstevel@tonic-gate 
15080Sstevel@tonic-gate static char *
alloc_vsprintf(const char * fmt,va_list ap1)15090Sstevel@tonic-gate alloc_vsprintf(const char *fmt, va_list ap1)
15100Sstevel@tonic-gate {
15110Sstevel@tonic-gate 	va_list ap2;
15120Sstevel@tonic-gate 	int n;
15130Sstevel@tonic-gate 	char buf[1];
15140Sstevel@tonic-gate 	char *s;
15150Sstevel@tonic-gate 
15160Sstevel@tonic-gate 	/*
15170Sstevel@tonic-gate 	 * We need to scan the argument list twice.  Save off a copy
15180Sstevel@tonic-gate 	 * of the argument list pointer(s) for the second pass.  Note that
15190Sstevel@tonic-gate 	 * we are responsible for va_end'ing our copy.
15200Sstevel@tonic-gate 	 */
15210Sstevel@tonic-gate 	va_copy(ap2, ap1);
15220Sstevel@tonic-gate 
15230Sstevel@tonic-gate 	/*
15240Sstevel@tonic-gate 	 * vsnprintf into a dummy to get a length.  One might
15250Sstevel@tonic-gate 	 * think that passing 0 as the length to snprintf would
15260Sstevel@tonic-gate 	 * do what we want, but it's defined not to.
15270Sstevel@tonic-gate 	 *
15280Sstevel@tonic-gate 	 * Perhaps we should sprintf into a 100 character buffer
15290Sstevel@tonic-gate 	 * or something like that, to avoid two calls to snprintf
15300Sstevel@tonic-gate 	 * in most cases.
15310Sstevel@tonic-gate 	 */
15320Sstevel@tonic-gate 	n = vsnprintf(buf, sizeof (buf), fmt, ap2);
15330Sstevel@tonic-gate 	va_end(ap2);
15340Sstevel@tonic-gate 
15350Sstevel@tonic-gate 	/*
15360Sstevel@tonic-gate 	 * Allocate an appropriately-sized buffer.
15370Sstevel@tonic-gate 	 */
15380Sstevel@tonic-gate 	s = malloc(n + 1);
15390Sstevel@tonic-gate 	if (s == NULL) {
15400Sstevel@tonic-gate 		perror("malloc");
15410Sstevel@tonic-gate 		exit(4);
15420Sstevel@tonic-gate 	}
15430Sstevel@tonic-gate 
15440Sstevel@tonic-gate 	(void) vsnprintf(s, n+1, fmt, ap1);
15450Sstevel@tonic-gate 
15460Sstevel@tonic-gate 	return (s);
15470Sstevel@tonic-gate }
1548