/*
 *	LocalShell like RTTY LoginSh, only local, instead of remote.
 *
 *		Must be setuid-root for the chown's to work.
 *
 *		Peter B. Kessler, September 26, 1990 9:10:17 pm PDT
 *
 */

static char 	sccsid[] = "@(#)LocalShell.c 1.2	10/4/90 13:10:33";

#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";
	
FILE	*debugStream;

main(argc, argv)
	int	argc;
	char	**argv;
{
	int	argn;  	/* in [0 .. argc] */
	char	**argp;	/* pointer to elements of argv */
	int	consoleFlag = 0;
	int	cleanFlag = 1;
	char	*ptyName = 0;
	char	*ttyName = 0;
	char	*termEnvItem = 0;
	int	child = -1;
	int	exitStatus = 0;
	void	showState();
	
	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;
			case 'f':
				cleanFlag = 0;
				argp += 1;
				argn += 1;
				termEnvItem = *argp;
				break;
			default:
				goto usage;
			};
		};
	if (ptyName == 0 || ttyName == 0) {
		goto usage;
		};
	{
		debugStream = fopen("/dev/console", "a");
		if (debugStream == NULL) {
			fprintf(stderr, "couldn't get console, errno = %d\n", errno);
			exit(1);
			};
		};
	showState(debugStream, "Before:");
	{
		close(0);
		close(1);
		close(2);
		}
	child = fork();
	switch (child) {
		default:	/* we are the parent */
			exitStatus = BabySit();
			Shutdown(ptyName, ttyName);
			break;
		case 0:	/* we are the child */
			showState(debugStream, "Child, before setsid:");
			{
				/* try to make ourselves a new session/pgrp */
				int	result;
				
				result = setpgrp(0, 0);
				if (result < 0) {
					fprintf(debugStream, "setpgrp(0, 0) returns %d errno = %d\n", result, errno);
					};
				};
			showState(debugStream, "Child, after setsid:");
			Setup(ptyName, ttyName, consoleFlag);
			showState(debugStream, "Child, after Setup:");
			BecomeOurselves();
			exitStatus = DoShell(argp, cleanFlag, termEnvItem);
			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);
	}

/*
 *	wait for child to return.
 */
int
BabySit()
{
	union wait	status;
	int			exited = FALSE;
	int			waitResult;
	int			result = 0;
	
	while (! exited) {
		waitResult = wait(&status);
		switch (waitResult) {
			case -1:
				result = errno;
				exited = TRUE;	/* might as well be */
				break;
			default:
				exited = (WIFEXITED(status) || WIFSIGNALED(status));
				result = status.w←retcode;
				break;
			};
		};
	return result;
	};

/*
 * Release named pty/tty pair.
 */
Shutdown(ptyName, ttyName)
	char *ptyName;
	char *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(ttyName, "", "");
	}

Setup(ptyName, ttyName, consoleFlag)
	char	*ptyName;
	char	*ttyName;
	int	consoleFlag;
{
	int fileDescriptor;
	struct termios terminalParameters;
	int gotParameters = FALSE;
	char *getenv();
	
	{
		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(ttyName, getenv("USER"), "PCedar");
	fileDescriptor = open(ttyName, O←RDWR);
	if (fileDescriptor < 0) {
		fprintf(debugStream, "Can't open(%s)\n", ttyName);
		exit(1);
		};
	{
		int	result = dup2(fileDescriptor, 0);
		
		if (result < 0) {
			fprintf(debugStream, "open(ttyName, O←RDWR) returns %d errno = %d\n", result, errno);
			};
		};
	{
		int	result = dup2(fileDescriptor, 1);
		
		if (result < 0) {
			fprintf(debugStream, "dup2(fileDescriptor, 1) returns %d errno = %d\n", result, errno);
			};
		};
	{
		int	result = dup2(fileDescriptor, 2);
		
		if (result < 0) {
			fprintf(debugStream, "dup2(fileDescriptor, 2) returns %d errno = %d\n", result, errno);
			};
		};
	if (fileDescriptor > 2) {
		close(fileDescriptor);
		};
	if(gotParameters) {
		ioctl(0, TCSETSF, &terminalParameters);
	};
	if (consoleFlag) {
		ioctl(0, TIOCCONS, 0);
		};
	{
		/* 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");
			};
		};
	}

/*
 * utmp entry management
 */
#define UTMPNAME "/etc/utmp"
WriteUtmp(ttyName, name, host)
	char	*ttyName;		/* ttyName name */
	char	*name;			/* user name */
	char	*host;				/* host name */
{
	int			slot;
	struct utmp	utmpEntry;
	int			f;
	char			*rindex();
	
	{
		int	fileDescriptor = open(ttyName, O←RDWR);
		
		if (fileDescriptor < 0) {
			fprintf(debugStream, "WriteUtmp: Can't open(%s)\n", ttyName);
			exit(1);
			};
		slot = ttyslot();
		if (slot < 0) {
			fprintf(debugStream, "WriteUtmp: ttyslot() => -1\n");
			exit(1);
			};
		close(fileDescriptor);
		};
	{
		char *temp = rindex(ttyName, '/');
		
		strncpy(
			utmpEntry.ut←line, 
			((temp != NULL) ? temp+1 : ttyName), 
			(sizeof utmpEntry.ut←line));
		strncpy(utmpEntry.ut←name, name, (sizeof utmpEntry.ut←name));
		strncpy(utmpEntry.ut←host, host, (sizeof utmpEntry.ut←host));
		}
	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);
	}

BecomeOurselves()
{
	setuid(getuid());
	};

#define DEFAULT←SHELL	"/bin/sh"
DoShell(argp, cleanFlag, termEnvItem)
	char	**argp;
	int	cleanFlag;
	char	*termEnvItem;
{
	char	*myShell;
	char	*minusName;
	char	*getenv();
	char	*rindex();
	char	*Concat();
	void	CleanEnvironment();
	
	{
		char	*myHome = getenv("HOME");
		
		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");
	if (myShell == NULL) {
		myShell = DEFAULT←SHELL;
		};
	{
		char	*shellTail;
		
		shellTail = rindex(myShell, '/');
		shellTail = ((shellTail != NULL) ? shellTail+1 : myShell);
		minusName = Concat("-", shellTail);
		};
	if (termEnvItem !=0) {
		Resetenv("TERM", termEnvItem);
		}
	if (cleanFlag) {
		CleanEnvironment();
		}
	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;
	};
	

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) {
		for ((dp = *dirty, np = name); (*dp && *np && *dp == *np); (dp += 1, np += 1)) {
			continue;
			};
		if ((*dp != '=') || (*np != '\0')) {
			*clean = *dirty;
			clean += 1;
			};
		};
	*clean = 0;
	};
	
Resetenv(name,new)
	char	*name, *new;
{
	char	**dirty = environ;
	char	*dp;
	char	*np;
	
	for (dirty = environ; *dirty != 0; dirty++) {
		for ((dp = *dirty, np = name); (*dp && *np && *dp == *np); (dp++, np++)) ;
		if ((*dp == '=') && (*np == '\0')) {
			*dirty = new;
return;
			};
		};
	};

#define VERBOSE	0

void
showState(stream, prompt)
	char	*prompt;
	FILE	*stream;
{
	int	fileDescriptor;
	
	if (VERBOSE) {
		fprintf(stream, "%s\n", prompt);
		fileDescriptor = open("/dev/tty", O←RDWR, 0666);
		if (fileDescriptor < 0) {
			fprintf(stream, "	I don't have a /dev/tty.  errno = %d\n", errno);
		} else {
			fprintf(stream, "	I have a /dev/tty\n");
			close(fileDescriptor);
			};
		fprintf(stream, "	my  pid  = %d\n", getpid());
		fprintf(stream, "	my  pgrp = %d\n", getpgrp(0));
		{
			int	pgrp = -1;
			
			(void) ioctl(0, TIOCGPGRP, &pgrp);
			fprintf(stream, "	0's pgrp = %d\n", pgrp);
			};
		};
	};