/*
 *	LocalShell like RTTY LoginSh, only local, instead of remote.
 *
 *		Must be setuid-root for the chown's to work.
 *
 *	%Z%%M% %I%	%H% %T%
 *
 */

#include <sys/types.h>
#include <errno.h>
#include <lastlog.h>
#include <signal.h>
#include <stdio.h>
#include <sysexits.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <utmp.h>
#include <sys/wait.h>
#include <sys/termios.h>

#define	FALSE	0
#define	TRUE 	1

char	myName[] = "LocalShell";
	
main(argc, argv)
	int	argc;
	char	**argv;
{
	int	argn;  	/* in [0 .. argc] */
	char	**argp;	/* pointer to elements of argv */
	int	cleanFlag = 1;
	int	consoleFlag = 0;
	char	*ptyName = 0;
	char	*ttyName = 0;
	int	child = -1;
	int	exitStatus = 0;
	
	for (argn = 1, argp = argv+1; argn < argc; argn += 1, argp += 1) {
		if (argp == 0 || *argp == 0 || (*argp)[0] != '-' || (*argp)[1] == '\0') {
			break;
			};
		switch ((*argp)[1]) {
			case 'C':
				consoleFlag = 1;
				break;
			case 'p':
				argp += 1;
				argn += 1;
				ptyName = *argp;
				break;
			case 't':
				argp += 1;
				argn += 1;
				ttyName = *argp;
				break;
			case 'e':
				cleanFlag = 0;
				break;
			default:
				goto usage;
			};
		};
	if (ptyName == 0 || ttyName == 0) {
		goto usage;
		};
	/* fprintf(stderr, "forking\n"); */
	child = fork();
	switch (child) {
		default:	/* we are the parent */
			close(0);
			close(1);
			close(2);
			exitStatus = BabySit();
			/* fprintf(stderr, "shutting down pty\n"); */
			Shutdown(ptyName, ttyName);
			break;
		case 0:	/* we are the child */
			/* fprintf(stderr, "setting up pty\n"); */
			Setup(ptyName, ttyName, consoleFlag);
			/* fprintf(stderr, "becoming ourselves\n"); */
			BecomeOurselves();
			/* fprintf(stderr, "doing a shell\n"); */
			exitStatus = DoShell(argp, cleanFlag);
			break;
		case -1:	/* something went wrong */
			perror("can't fork.");
			exitStatus = 1;
			break;
		};
	exit(exitStatus);
usage:
	fprintf(stderr, "usage is: %s -p ptyName -t ttyName [-C]\n", myName);
	exit(1);
	}

BecomeOurselves()
{
	setuid(getuid());
	};
	
#define DEFAULT←SHELL	"/bin/sh"
DoShell(argp, cleanFlag)
	char	**argp;
        int	cleanFlag;
{
	char *myHome;
	char *myShell;
	char *minusName;
	char *shellTail;
	int t;
	char *getenv();
	char *rindex();
	char *Concat();
	void	CleanEnvironment();
	
	myHome = getenv("HOME");
	/* fprintf(stderr, "myHome = %s\n", myHome); */
	if(myHome == NULL) {
		printf("No HOME\n");
		exit(1); /*??*/
		}
	if(chdir(myHome) < 0) {
		printf("Can't chdir to %s\n", myHome);
		/* exit(1); ??*/
		}
	myShell = getenv("SHELL");
	/* fprintf(stderr, "myShell = %s\n", myShell); */
	if(myShell == NULL) {
		myShell = DEFAULT←SHELL;
		};
	shellTail = rindex(myShell, '/');
	shellTail = ((shellTail != NULL) ? shellTail+1 : myShell);
	minusName = Concat("-", shellTail);
	if (cleanFlag) CleanEnvironment();
	/* fprintf(stderr, "execlp(%s, %s, 0)\n", myShell, minusName); */
	execlp(myShell, minusName, (char *)0);
	perror(myShell);
	printf("Can't exec %s\n", myShell);
	exit(1);
	}
	
/*
 * string concatenation routine -- should be in library
 */
char *
Concat(this, that)
	char *this;
	char *that;
{
	int length = 0;
	char *result = 0;
	char	*calloc();
	
	length = strlen(this) + strlen(that) + 1;
	result = calloc(length, 1);
	if(result != NULL) {
		strcat(result, this);
		strcat(result, that);
		}
	return result;
	};
	
/*
 * utmp entry management
 */

#define UTMPNAME "/etc/utmp"


WriteUtmp(slot, line, name, host)
	int slot;		/* slot in utmp file, -1 ==> erase prev slot */
	char *line;		/* line name (NULL okay if slot == -1) */
	char *name;		/* user name (NULL okay if slot == -1) */
	char *host;		/* host name (NULL okay if slot == -1) */
{
	static int				oldSlot = -1;
	static struct utmp	utmpEntry;
	int	f;
	char	*rindex();
	
	/* fprintf(stderr, "writing utmp slot %d\n", slot); */
	if( slot >= 0 ) {
		char *temp = rindex(line, '/');
		strncpy( utmpEntry.ut←line, ((temp != NULL) ? temp+1 : line),
			(sizeof utmpEntry.ut←line) );
		strncpy( utmpEntry.ut←name, ((name != NULL) ? name : ""),
			(sizeof utmpEntry.ut←name) );
		strncpy( utmpEntry.ut←host, ((host != NULL) ? host : ""),
			(sizeof utmpEntry.ut←host) );
		oldSlot = slot;
	} else {
		if( oldSlot < 0 ) return;
		strncpy( utmpEntry.ut←name, "", (sizeof utmpEntry.ut←name) );
		strncpy( utmpEntry.ut←host, "", (sizeof utmpEntry.ut←host) );
		slot = oldSlot; oldSlot = -1;
	}
	
	time( &utmpEntry.ut←time );
	
	if( (f = open(UTMPNAME, O←WRONLY)) < 0 ) return;
	lseek(f, (long)(slot*(sizeof utmpEntry)), 0);
	write(f, (char *)(&utmpEntry), (sizeof utmpEntry));
	close(f);
	/* fprintf(stderr, "utmp %d written\n", slot); */
	}
	
Setup(ptyName, ttyName, consoleFlag)
	char	*ptyName;
	char	*ttyName;
	int	consoleFlag;
{
	int fileDescriptor;
	struct termios terminalParameters;
	int gotParameters = FALSE;
	char *getenv();
	
	/* fprintf(stderr, "Setup(%s, %s, %d) begun\n", ptyName, ttyName, consoleFlag); */
	fileDescriptor = open("/dev/tty", O←RDWR, 0666);
	if (fileDescriptor >= 0) {
		ioctl(fileDescriptor, TCGETS, &terminalParameters);
		gotParameters = TRUE;
		(void) ioctl(fileDescriptor, TIOCNOTTY, (char *) 0);
		close(fileDescriptor);
		};
	WriteUtmp(-1, NULL, NULL, NULL); /* erase any prev entry */
	WriteUtmp(ttyslot(), ttyName, getenv("USER"), "PCedar");
	fileDescriptor = open(ttyName, O←RDWR);
	if (fileDescriptor < 0) {
		perror("Can't open tty");
		exit(1);
		};
	(void) dup2(fileDescriptor, 0);
	(void) dup2(fileDescriptor, 1);
	(void) dup2(fileDescriptor, 2);
	(void) setpgrp(0, getpid());
		/* take this out when Alan does it in XR←Spawn. */
	{
		int	mypgroup = getpgrp();
		
		(void) ioctl(fileDescriptor, TIOCSPGRP, &mypgroup);
		};
	close(fileDescriptor);
	if(gotParameters) {
		ioctl(0, TCSETSF, &terminalParameters);
	};
	if (consoleFlag) {
		ioctl(0, TIOCCONS, 0);
		};
	/* fprintf(stderr, "Setup tty opened, doing chowns\n"); */
	{
		/* must be suid-root here */
		int myuid = getuid();
		int mygid = getgid();
		
		if (chown(ptyName, myuid, mygid) != 0) {
			perror("Couldn't chown pty");
			};
		if (chmod(ptyName, 0600) != 0) {
			perror("Couldn't chmod pty");
			};
		if (chown(ttyName, myuid, mygid) != 0) {
			perror("Couldn't chown tty");
			};
		if (chmod(ttyName, 0622) != 0) {
			perror("Couldn't chmod tty");
			};
		};
	/* fprintf(stderr, "Setup(%s, %s, %d) done\n", ptyName, ttyName, consoleFlag); */
	}
	

/*
 * Release named pty/tty pair.
 */
Shutdown(ptyName, ttyName)
	char *ptyName;
	char *ttyName;
{
	/* fprintf(stderr, "Shutdown(%s, %s) begun\n", ptyName, ttyName); */
	/* must be suid-root here */
	if (chown(ttyName, 0, 0) != 0) {
		perror("Couldn't chown tty");
		};
	if (chmod(ttyName, 0666) != 0) {
		perror("Couldn't chmod tty");
		};
	if (chown(ptyName, 0, 0) != 0) {
		perror("Couldn't chown pty");
		};
	if (chmod(ptyName, 0666) != 0) {
		perror("Couldn't chmod pty");
		};
	WriteUtmp(-1, NULL, NULL, NULL);
	/* fprintf(stderr, "Shutdown(%s, %s) done\n", ptyName, ttyName); */
	}


int
BabySit()
{
	union wait	status;
	int			waitResult;
	int			result = 0;
	
	/* fprintf(stderr, "baby sitting\n"); */
	waitResult = wait(&status);
	switch (waitResult) {
		case -1:
			result = errno;
			break;
		default:
			result = status.w←retcode;
			break;
		};
	return result;
	};

void
CleanEnvironment()
{
	void	Unsetenv();
	
	Unsetenv("TERM");
	Unsetenv("TERMCAP");
	};

extern char	**environ;
	
void
Unsetenv(name)
	char	*name;
{
	char	**clean = environ;
	char	**dirty = environ;
	char	*dp;
	char	*np;
	
	for (dirty = environ; *dirty != 0; dirty += 1) {
		/* fprintf(stderr, "name = %s *dirty = %s", name, *dirty); */
		for ((dp = *dirty, np = name); (*dp && *np && *dp == *np); (dp += 1, np += 1)) {
			continue;
			};
		if ((*dp != '=') || (*np != '\0')) {
			/* fprintf(stderr, "	clean"); */
			*clean = *dirty;
			clean += 1;
			};
		/* fprintf(stderr, "\n"); */
		};
	*clean = 0;
	};