/*
 * $Copyright:	$
 * Copyright (c) 1984, 1985, 1986, 1987, 1988, 1989, 1990 
 * Sequent Computer Systems, Inc.   All rights reserved.
 *  
 * This software is furnished under a license and may be used
 * only in accordance with the terms of that license and with the
 * inclusion of the above copyright notice.   This software may not
 * be provided or otherwise made available to, or used by, any
 * other person.  No title to or ownership of the software is
 * hereby transferred.
 *
 */

/*	Copyright (c) 1984 AT&T	*/
/*	  All Rights Reserved  	*/

/*	THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF AT&T	*/
/*	The copyright notice above does not evidence any   	*/
/*	actual or intended publication of such source code.	*/

#ident	"$Header: su.c 1.12 90/02/27 $@(#)su:su.c	1.9.1.2"
/*
 *	su [-] [name [arg ...]] change userid, `-' changes environment.
 *	If SULOG is defined, all attempts to su to another user are
 *	logged there.
 *	If CONSOLE is defined, all successful attempts to su to uid 0
 *	are also logged there unless the su was done on CONSOLE_TTY.
 *
 *	If su cannot create, open, or write entries into SULOG,
 *	(or on the CONSOLE, if defined), the entry will not
 *	be logged -- thus losing a record of the su's attempted
 *	during this period.
 */
#include <stdio.h>
#include <pwd.h>
#include <shadow.h>
#include <time.h>
#include <signal.h>
#include <fcntl.h>
#define SULOG	"/usr/adm/sulog"	/*log file*/
#define CONSOLE	"/dev/console"		/* console device */
#define CONSOLE_TTY	"/dev/syscon"		/* console device */
#define PATH	"PATH=:/bin:/usr/bin"	/*path for users other than root*/
#define SUPATH	"PATH=/bin:/etc:/usr/bin"	/*path for root*/
#define SUPRMT	"PS1=# "		/*primary prompt for root*/
#define ELIM 128
#define ROOT 0
long time();
void pause();
void to();
struct	passwd *pwd, *getpwnam();
struct	tm *localtime();
char	*malloc(), *strcpy();
char	*getpass(), *ttyname(), *strrchr();
char	*shell = "/bin/sh";	/*default shell*/
char	su[16] = "su";		/*arg0 for exec of shprog*/
char	homedir[64] = "HOME=";
char	logname[20] = "LOGNAME=";
char	*path = PATH;
char	*supath = SUPATH;
char	*suprmt = SUPRMT;
char	*envinit[ELIM];
extern	char **environ;
char *ttyn;

main(argc, argv)
int	argc;
char	**argv;
{
	struct spwd *sp;
	char *nptr, *password, *opasswdp;
	char	*pshell = shell;
	int shadow_exist = 0;
	int eflag = 0;
	int uid, gid;
	char *dir, *shprog, *name;
	int save_count;

	save_count = argc;

	if (argc > 1 && *argv[1] == '-') {
		eflag++;	/*set eflag if `-' is specified*/
		argv++;
		argc--;
	}

	/* check the existence of shadow password file */

	if ( access(SHADOW, 0) == 0)
		shadow_exist = 1;
	/* 
	 * determine specified userid, get their password file entry,
	 * and set variables to values in password file entry fields
	*/

	/*
	 * We still want the system to boot and get a shell exec'd if the
	 * password file gets clobbered.  So if the name lookup fails we
	 * try and figure out if we are init exec'ing /bin/su.
	 *
	 * In this case we are exec'd with no args, parent process will
	 * be 1, and uid will be 0.
	 *
	 */

	nptr = (argc > 1)? argv[1]: "root";
	if ((pwd = getpwnam(nptr)) == NULL) {
		if ( (save_count == 2) &&
		     (getppid()  == 1) &&
		     (getuid()   == 0) &&
		     (eflag      == 1)    ) {
			fprintf(stderr,gettxt("su:7", "su: ERROR - root id not found in password file\n"));
			execl(shell, su, 0);
			fprintf(stderr,gettxt("su:8", "su: ERROR - exec of %s failed\n"), shell);
			exit(1);
		}
		fprintf(stderr,gettxt("su:1", "su: Unknown id: %s\n"),nptr);
		exit(1);
	}
	if (shadow_exist && (sp = getspnam(nptr)) == NULL) {
		if ( (save_count == 2) &&
		     (getppid()  == 1) &&
		     (getuid()   == 0) &&
		     (eflag      == 1)    ) {
			fprintf(stderr,gettxt("su:9", "su: ERROR - root id found in passwd but not shadow file\n"));
			shadow_exist = 0;
		} else {
			fprintf(stderr,gettxt("su:10", "su: id %s in password but not shadow file\n"), nptr);
			exit(1);
		}
	}
	uid = pwd->pw_uid;
	gid = pwd->pw_gid;
	dir = strcpy(malloc(strlen(pwd->pw_dir)+1),pwd->pw_dir);
	shprog = strcpy(malloc(strlen(pwd->pw_shell)+1),pwd->pw_shell);
	name = strcpy(malloc(strlen(pwd->pw_name)+1),pwd->pw_name);
	if((ttyn=ttyname(0))==NULL)
		if((ttyn=ttyname(1))==NULL)
			if((ttyn=ttyname(2))==NULL)
				ttyn="/dev/tty??";

#ifdef SULOG
	/*if SULOG defined, create SULOG, if it does not exist, with
	  mode 600. Change owner and group to root
	*/
	close( open(SULOG, O_WRONLY | O_APPEND | O_CREAT, 0600) );
	chown(SULOG, ROOT, ROOT);
#endif

	/*Prompt for password if invoking user is not root or
	  if specified(new) user requires a password
	*/
	if (shadow_exist)  
		opasswdp = sp->sp_pwdp;
	else
		opasswdp = pwd->pw_passwd;
	if (*opasswdp == '\0' || getuid() == 0)
		goto ok;
	password = getpass(gettxt("su:2", "Password:"));

	if((strcmp(opasswdp, crypt(password, opasswdp)) != 0)) {
#ifdef SULOG
		log(SULOG, nptr, 0);	/*log entry*/
#endif
		fprintf(stderr,gettxt("su:3", "su: Sorry\n"));
		exit(2);
	}
ok:
	endpwent();	/*close password file*/
	if (shadow_exist)
		(void) endspent();	/*close shadow password file*/
#ifdef SULOG
		log(SULOG, nptr, 1);	/*log entry*/
#endif

	/*set user and group ids to specified user*/

	if(setgid(gid) != 0) {
		printf(gettxt("su:4", "su: Invalid ID\n"));
		exit(2);
	}
	initgroups(name);
	if(setuid(uid) != 0) {
		printf(gettxt("su:4", "su: Invalid ID\n"));
		exit(2);
	}

	/*set environment variables for new user;
	  arg0 for exec of shprog must now contain `-'
	  so that environment of new user is given
	*/
	if (eflag) {
		strcat(homedir, dir);
		strcat(logname, name);
		envinit[2] = logname;
		chdir(dir);
		envinit[0] = homedir;
		if (uid == 0)
			envinit[1] = supath;
		else	envinit[1] = path;
		envinit[3] = NULL;
		environ = envinit;
		strcpy(su, "-su");
	}

	/*if new user is root:
		if CONSOLE defined, log entry there;
		if eflag not set, change environment to that of root.
	*/
	if (uid == 0)
	{
#ifdef CONSOLE
		if(strcmp(ttyn, CONSOLE_TTY) != 0) {
			signal(SIGALRM, to);
			alarm(30);
			log(CONSOLE, nptr, 1);
			alarm(0);
		}
#endif
		if (!eflag) envalt();
	}

	/*if new user's shell field is not NULL or equal to /bin/sh,
	  set:
		pshell = their shell
		su = [-]last component of shell's pathname
	*/
	if (shprog[0] != '\0' && (strcmp(shell,shprog) != 0) ) {
		pshell = shprog;
		strcpy(su, eflag ? "-" : "" );
		strcat(su, strrchr(pshell,'/') + 1);
	}

	/*if additional arguments, exec shell program with array
	    of pointers to arguments:
		-> if shell = /bin/sh, then su = [-]su
		-> if shell != /bin/sh, then su = [-]last component of
						     shell's pathname

	  if no additional arguments, exec shell with arg0 of su
	    where:
		-> if shell = /bin/sh, then su = [-]su
		-> if shell != /bin/sh, then su = [-]last component of
						     shell's pathname
	*/
	if (argc > 2) {
		argv[1] = su;
		execv(pshell, &argv[1]);
	} else {
		execl(pshell, su, 0);
	}

	fprintf(stderr,gettxt("su:5", "su: No shell\n"));
	exit(3);
}

/*Environment altering routine -
	This routine is called when a user is su'ing to root
	without specifying the - flag.
	The user's PATH and PS1 variables are reset
	to the correct value for root.
	All of the user's other environment variables retain
	their current values after the su (if they are
	exported).
*/
envalt()
{
	int putenv();

	/*If user has PATH variable in their environment, change its value
			to /bin:/etc:/usr/bin ;
	  if user does not have PATH variable, add it to the user's
			environment;
	  if either of the above fail, an error message is printed.
	*/
	if ( ( putenv(supath) ) != 0 ) {
		printf(gettxt("su:6", "su: unable to obtain memory to expand environment"));
		exit(4);
	}

	/*If user has PROMPT variable in their environment, change its value
			to # ;
	  if user does not have PROMPT variable, add it to the user's
			environment;
	  if either of the above fail, an error message is printed.
	*/
	if ( ( putenv(suprmt) ) != 0 ) {
		printf(gettxt("su:6", "su: unable to obtain memory to expand environment"));
		exit(4);
	}

	return;

}

/*Logging routine -
	where = SULOG or CONSOLE
	towho = specified user ( user being su'ed to )
	how = 0 if su attempt failed; 1 if su attempt succeeded
*/
log(where, towho, how)
char *where, *towho;
int how;
{
	FILE *logf;
	long now;
	struct tm *tmp;

	now = time(0);
	tmp = localtime(&now);

	/*open SULOG or CONSOLE -
		if open fails, return
	*/
	if ((logf=fopen(where,"a")) == NULL) return;

	/*write entry into SULOG or onto CONSOLE -
		 if write fails, return
	*/
	fprintf(logf,"SU %.2d/%.2d %.2d:%.2d %c %s %s-%s\n",
		tmp->tm_mon+1,tmp->tm_mday,tmp->tm_hour,tmp->tm_min,
		how?'+':'-',(ttyn + sizeof("/dev/")-1),
		cuserid((char *)0),towho); 

	fclose(logf);	/*close SULOG or CONSOLE*/

	return;
}

void to(){}
