/*
 * Copyright (c) 1988 Regents of the University of California.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms are permitted
 * provided that the above copyright notice and this paragraph are
 * duplicated in all such forms and that any documentation,
 * advertising materials, and other materials related to such
 * distribution and use acknowledge that the software was developed
 * by the University of California, Berkeley.  The name of the
 * University may not be used to endorse or promote products derived
 * from this software without specific prior written permission.
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
 * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 */

#ifndef lint
static char sccsid[] = "@(#)system.c	4.1 (Berkeley) 12/4/88";
#endif /* not lint */

#include <sys/types.h>

#if     defined(pyr)
#define fd_set fdset_t
#endif  /* defined(pyr) */

#if	!defined(sun) && !defined(pyr)
#include <sys/inode.h>
#else	/* !defined(sun) */
#define	IREAD	00400
#define	IWRITE	00200
#endif	/* !defined(sun) */

#include <sys/file.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/wait.h>

#include <errno.h>
extern int errno;

#include <netdb.h>
#include <signal.h>
#include <stdio.h>
#include <pwd.h>

#include "../general/general.h"
#include "../ctlr/api.h"
#include "../api/api_exch.h"

#include "../general/globals.h"

#ifndef	FD_SETSIZE
/*
 * The following is defined just in case someone should want to run
 * this telnet on a 4.2 system.
 *
 */

#define	FD_SET(n, p)	((p)->fds_bits[0] |= (1<<(n)))
#define	FD_CLR(n, p)	((p)->fds_bits[0] &= ~(1<<(n)))
#define	FD_ISSET(n, p)	((p)->fds_bits[0] & (1<<(n)))
#define FD_ZERO(p)	((p)->fds_bits[0] = 0)

#endif

static int shell_pid = 0;
static char key[50];			/* Actual key */
static char *keyname;			/* Name of file with key in it */

static char *ourENVlist[200];		/* Lots of room */

static int
    sock = -1,				/* Connected socket */
    serversock;				/* Server (listening) socket */

static enum { DEAD, UNCONNECTED, CONNECTED } state;

static long
    storage_location;		/* Address we have */
static short
    storage_length = 0;		/* Length we have */
static int
    storage_must_send = 0,	/* Storage belongs on other side of wire */
    storage_accessed = 0;	/* The storage is accessed (so leave alone)! */

static long storage[1000];

static union REGS inputRegs;
static struct SREGS inputSregs;


static void
kill_connection()
{
    state = UNCONNECTED;
    if (sock != -1) {
	(void) close(sock);
	sock = -1;
    }
}


static int
nextstore()
{
    struct storage_descriptor sd;

    if (api_exch_intype(EXCH_TYPE_STORE_DESC, sizeof sd, (char *)&sd) == -1) {
	storage_length = 0;
	return -1;
    }
    storage_length = sd.length;
    storage_location = sd.location;
    if (storage_length > sizeof storage) {
	fprintf(stderr, "API client tried to send too much storage (%d).\n",
		storage_length);
	storage_length = 0;
	return -1;
    }
    if (api_exch_intype(EXCH_TYPE_BYTES, storage_length, (char *)storage)
							== -1) {
	storage_length = 0;
	return -1;
    }
    return 0;
}


static int
doreject(message)
char	*message;
{
    struct storage_descriptor sd;
    int length = strlen(message);

    if (api_exch_outcommand(EXCH_CMD_REJECTED) == -1) {
	return -1;
    }
    sd.length = length;
    if (api_exch_outtype(EXCH_TYPE_STORE_DESC, sizeof sd, (char *)&sd) == -1) {
	return -1;
    }
    if (api_exch_outtype(EXCH_TYPE_BYTES, length, message) == -1) {
	return -1;
    }
    return 0;
}


/*
 * doassociate()
 *
 * Negotiate with the other side and try to do something.
 *
 * Returns:
 *
 *	-1:	Error in processing
 *	 0:	Invalid password entered
 *	 1:	Association OK
 */

static int
doassociate()
{
    struct passwd *pwent;
    char
	promptbuf[100],
	buffer[200];
    struct storage_descriptor sd;
    extern char *crypt();

    if (api_exch_intype(EXCH_TYPE_STORE_DESC, sizeof sd, (char *)&sd) == -1) {
	return -1;
    }
    sd.length = sd.length;
    if (sd.length > sizeof buffer) {
	doreject("(internal error) Authentication key too long");
	return -1;
    }
    if (api_exch_intype(EXCH_TYPE_BYTES, sd.length, buffer) == -1) {
	return -1;
    }
    buffer[sd.length] = 0;

    if (strcmp(buffer, key) != 0) {
#if	(!defined(sun)) || defined(BSD) && (BSD >= 43)
	extern uid_t geteuid();
#endif	/* (!defined(sun)) || defined(BSD) && (BSD >= 43) */

	if ((pwent = getpwuid((int)geteuid())) == 0) {
	    return -1;
	}
	sprintf(promptbuf, "Enter password for user %s:", pwent->pw_name);
	if (api_exch_outcommand(EXCH_CMD_SEND_AUTH) == -1) {
	    return -1;
	}
	sd.length = strlen(promptbuf);
	if (api_exch_outtype(EXCH_TYPE_STORE_DESC, sizeof sd, (char *)&sd)
									== -1) {
	    return -1;
	}
	if (api_exch_outtype(EXCH_TYPE_BYTES, strlen(promptbuf), promptbuf)
									== -1) {
	    return -1;
	}
	sd.length = strlen(pwent->pw_name);
	if (api_exch_outtype(EXCH_TYPE_STORE_DESC, sizeof sd, (char *)&sd)
									== -1) {
	    return -1;
	}
	if (api_exch_outtype(EXCH_TYPE_BYTES,
			    strlen(pwent->pw_name), pwent->pw_name) == -1) {
	    return -1;
	}
	if (api_exch_incommand(EXCH_CMD_AUTH) == -1) {
	    return -1;
	}
	if (api_exch_intype(EXCH_TYPE_STORE_DESC, sizeof sd, (char *)&sd)
									== -1) {
	    return -1;
	}
	sd.length = sd.length;
	if (sd.length > sizeof buffer) {
	    doreject("Password entered was too long");
	    return -1;
	}
	if (api_exch_intype(EXCH_TYPE_BYTES, sd.length, buffer) == -1) {
	    return -1;
	}
	buffer[sd.length] = 0;

	/* Is this the correct password? */
	if (strlen(pwent->pw_name)) {
	    char *ptr;
	    int i;

	    ptr = pwent->pw_name;
	    i = 0;
	    while (i < sd.length) {
		buffer[i++] ^= *ptr++;
		if (*ptr == 0) {
		    ptr = pwent->pw_name;
		}
	    }
	}
	if (strcmp(crypt(buffer, pwent->pw_passwd), pwent->pw_passwd) != 0) {
	    doreject("Invalid password");
	    sleep(10);		/* Don't let us do too many of these */
	    return 0;
	}
    }
    if (api_exch_outcommand(EXCH_CMD_ASSOCIATED) == -1) {
	return -1;
    } else {
	return 1;
    }
}


void
freestorage()
{
    struct storage_descriptor sd;

    if (storage_accessed) {
	fprintf(stderr, "Internal error - attempt to free accessed storage.\n");
	fprintf(stderr, "(Encountered in file %s at line %d.)\n",
			__FILE__, __LINE__);
	quit();
    }
    if (storage_must_send == 0) {
	return;
    }
    storage_must_send = 0;
    if (api_exch_outcommand(EXCH_CMD_HEREIS) == -1) {
	kill_connection();
	return;
    }
    sd.length = storage_length;
    sd.location = storage_location;
    if (api_exch_outtype(EXCH_TYPE_STORE_DESC, sizeof sd, (char *)&sd) == -1) {
	kill_connection();
	return;
    }
    if (api_exch_outtype(EXCH_TYPE_BYTES, storage_length, (char *)storage)
							    == -1) {
	kill_connection();
	return;
    }
}


static int
getstorage(address, length, copyin)
long
    address;
int
    length,
    copyin;
{
    struct storage_descriptor sd;

    freestorage();
    if (storage_accessed) {
	fprintf(stderr,
		"Internal error - attempt to get while storage accessed.\n");
	fprintf(stderr, "(Encountered in file %s at line %d.)\n",
			__FILE__, __LINE__);
	quit();
    }
    storage_must_send = 0;
    if (api_exch_outcommand(EXCH_CMD_GIMME) == -1) {
	kill_connection();
	return -1;
    }
    storage_location = address;
    storage_length = length;
    if (copyin) {
	sd.location = (long)storage_location;
	sd.length = storage_length;
	if (api_exch_outtype(EXCH_TYPE_STORE_DESC,
					sizeof sd, (char *)&sd) == -1) {
	    kill_connection();
	    return -1;
	}
	if (api_exch_incommand(EXCH_CMD_HEREIS) == -1) {
	    fprintf(stderr, "Bad data from other side.\n");
	    fprintf(stderr, "(Encountered at %s, %d.)\n", __FILE__, __LINE__);
	    return -1;
	}
	if (nextstore() == -1) {
	    kill_connection();
	    return -1;
	}
    }
    return 0;
}

/*ARGSUSED*/
void
movetous(local, es, di, length)
char
    *local;
unsigned int
    es,
    di;
int
    length;
{
    long where = SEG_OFF_BACK(es, di);

    if (length > sizeof storage) {
	fprintf(stderr, "Internal API error - movetous() length too long.\n");
	fprintf(stderr, "(detected in file %s, line %d)\n", __FILE__, __LINE__);
	quit();
    } else if (length == 0) {
	return;
    }
    getstorage(where, length, 1);
    memcpy(local, (char *)(storage+((where-storage_location))), length);
}

/*ARGSUSED*/
void
movetothem(es, di, local, length)
unsigned int
    es,
    di;
char
    *local;
int
    length;
{
    long where = SEG_OFF_BACK(es, di);

    if (length > sizeof storage) {
	fprintf(stderr, "Internal API error - movetothem() length too long.\n");
	fprintf(stderr, "(detected in file %s, line %d)\n", __FILE__, __LINE__);
	quit();
    } else if (length == 0) {
	return;
    }
    freestorage();
    memcpy((char *)storage, local, length);
    storage_length = length;
    storage_location = where;
    storage_must_send = 1;
}


char *
access_api(location, length, copyin)
char *
    location;
int
    length,
    copyin;			/* Do we need to copy in initially? */
{
    if (storage_accessed) {
	fprintf(stderr, "Internal error - storage accessed twice\n");
	fprintf(stderr, "(Encountered in file %s, line %d.)\n",
				__FILE__, __LINE__);
	quit();
    } else if (length != 0) {
	freestorage();
	getstorage((long)location, length, copyin);
	storage_accessed = 1;
    }
    return (char *) storage;
}

/*ARGSUSED*/
void
unaccess_api(location, local, length, copyout)
char 	*location;
char	*local;
int	length;
int	copyout;
{
    if (storage_accessed == 0) {
	fprintf(stderr, "Internal error - unnecessary unaccess_api call.\n");
	fprintf(stderr, "(Encountered in file %s, line %d.)\n",
			__FILE__, __LINE__);
	quit();
    }
    storage_accessed = 0;
    storage_must_send = copyout;	/* if needs to go back */
}

/*
 * Accept a connection from an API client, aborting if the child dies.
 */

static int
doconnect()
{
    fd_set fdset;
    int i;

    sock = -1;
    FD_ZERO(&fdset);
    while (shell_active && (sock == -1)) {
	FD_SET(serversock, &fdset);
	if ((i = select(serversock+1, &fdset,
		    (fd_set *)0, (fd_set *)0, (struct timeval *)0)) < 0) {
	    if (errno = EINTR) {
		continue;
	    } else {
		perror("in select waiting for API connection");
		return -1;
	    }
	} else {
	    i = accept(serversock, (struct sockaddr *)0, (int *)0);
	    if (i == -1) {
		perror("accepting API connection");
		return -1;
	    }
	    sock = i;
	}
    }
    /* If the process has already exited, we may need to close */
    if ((shell_active == 0) && (sock != -1)) {
	extern void setcommandmode();

	(void) close(sock);
	sock = -1;
	setcommandmode();	/* In case child_died sneaked in */
    }
    return 0;
}

/*
 * shell_continue() actually runs the command, and looks for API
 * requests coming back in.
 *
 * We are called from the main loop in telnet.c.
 */

int
shell_continue()
{
    int i;

    switch (state) {
    case DEAD:
	pause();			/* Nothing to do */
	break;
    case UNCONNECTED:
	if (doconnect() == -1) {
	    kill_connection();
	    return -1;
	}
	if (api_exch_init(sock, "server") == -1) {
	    return -1;
	}
	while (state == UNCONNECTED) {
	    if (api_exch_incommand(EXCH_CMD_ASSOCIATE) == -1) {
		kill_connection();
		return -1;
	    } else {
		switch (doassociate()) {
		case -1:
		    kill_connection();
		    return -1;
		case 0:
		    break;
		case 1:
		    state = CONNECTED;
		}
	    }
	}
	break;
    case CONNECTED:
	switch (i = api_exch_nextcommand()) {
	case EXCH_CMD_REQUEST:
	    if (api_exch_intype(EXCH_TYPE_REGS, sizeof inputRegs,
				    (char *)&inputRegs) == -1) {
		kill_connection();
	    } else if (api_exch_intype(EXCH_TYPE_SREGS, sizeof inputSregs,
				    (char *)&inputSregs) == -1) {
		kill_connection();
	    } else if (nextstore() == -1) {
		kill_connection();
	    } else {
		handle_api(&inputRegs, &inputSregs);
		freestorage();			/* Send any storage back */
		if (api_exch_outcommand(EXCH_CMD_REPLY) == -1) {
		    kill_connection();
		} else if (api_exch_outtype(EXCH_TYPE_REGS, sizeof inputRegs,
				    (char *)&inputRegs) == -1) {
		    kill_connection();
		} else if (api_exch_outtype(EXCH_TYPE_SREGS, sizeof inputSregs,
				    (char *)&inputSregs) == -1) {
		    kill_connection();
		}
		/* Done, and it all worked! */
	    }
	    break;
	case EXCH_CMD_DISASSOCIATE:
	    kill_connection();
	    break;
	default:
	    if (i != -1) {
		fprintf(stderr,
			"Looking for a REQUEST or DISASSOCIATE command\n");
		fprintf(stderr, "\treceived 0x%02x.\n", i);
	    }
	    kill_connection();
	    break;
	}
    }
    return shell_active;
}


static int
child_died()
{
    union wait status;
    register int pid;

    while ((pid = wait3(&status, WNOHANG, (struct rusage *)0)) > 0) {
	if (pid == shell_pid) {
	    char inputbuffer[100];
	    extern void setconnmode();
	    extern void ConnectScreen();

	    shell_active = 0;
	    if (sock != -1) {
		(void) close(sock);
		sock = -1;
	    }
	    printf("[Hit return to continue]");
	    fflush(stdout);
	    (void) gets(inputbuffer);
	    setconnmode();
	    ConnectScreen();	/* Turn screen on (if need be) */
	    (void) close(serversock);
	    (void) unlink(keyname);
	}
    }
    signal(SIGCHLD, child_died);
}


/*
 * Called from telnet.c to fork a lower command.com.  We
 * use the spint... routines so that we can pick up
 * interrupts generated by application programs.
 */


int
shell(argc,argv)
int	argc;
char	*argv[];
{
    int length;
    struct sockaddr_in server;
    char sockNAME[100];
    static char **whereAPI = 0;
    int fd;
    struct timeval tv;
    long ikey;
    extern long random();
    extern char *mktemp();
    extern char *strcpy();

    /* First, create verification file. */
    do {
	keyname = mktemp("/tmp/apiXXXXXX");
	fd = open(keyname, O_RDWR|O_CREAT|O_EXCL, IREAD|IWRITE);
    } while ((fd == -1) && (errno == EEXIST));

    if (fd == -1) {
	perror("open");
	return 0;
    }

    /* Now, get seed for random */

    if (gettimeofday(&tv, (struct timezone *)0) == -1) {
	perror("gettimeofday");
	return 0;
    }
    srandom(tv.tv_usec);		/* seed random number generator */
    do {
	ikey = random();
    } while (ikey == 0);
    sprintf(key, "%lu\n", (unsigned long) ikey);
    if (write(fd, key, strlen(key)) != strlen(key)) {
	perror("write");
	return 0;
    }
    key[strlen(key)-1] = 0;		/* Get rid of newline */

    if (close(fd) == -1) {
	perror("close");
	return 0;
    }

    /* Next, create the socket which will be connected to */
    serversock = socket(AF_INET, SOCK_STREAM, 0);
    if (serversock < 0) {
	perror("opening API socket");
	return 0;
    }
    server.sin_family = AF_INET;
    server.sin_addr.s_addr = INADDR_ANY;
    server.sin_port = 0;
    if (bind(serversock, (struct sockaddr *)&server, sizeof server) < 0) {
	perror("binding API socket");
	return 0;
    }
    length = sizeof server;
    if (getsockname(serversock, (struct sockaddr *)&server, &length) < 0) {
	perror("getting API socket name");
	(void) close(serversock);
    }
    listen(serversock, 1);
    /* Get name to advertise in address list */
    strcpy(sockNAME, "API3270=");
    gethostname(sockNAME+strlen(sockNAME), sizeof sockNAME-strlen(sockNAME));
    if (strlen(sockNAME) > (sizeof sockNAME-(10+strlen(keyname)))) {
	fprintf(stderr, "Local hostname too large; using 'localhost'.\n");
	strcpy(sockNAME, "localhost");
    }
    sprintf(sockNAME+strlen(sockNAME), ":%u", ntohs(server.sin_port));
    sprintf(sockNAME+strlen(sockNAME), ":%s", keyname);

    if (whereAPI == 0) {
	char **ptr, **nextenv;
	extern char **environ;

	ptr = environ;
	nextenv = ourENVlist;
	while (*ptr) {
	    if (nextenv >= &ourENVlist[highestof(ourENVlist)-1]) {
		fprintf(stderr, "Too many environmental variables\n");
		break;
	    }
	    *nextenv++ = *ptr++;
	}
	whereAPI = nextenv++;
	*nextenv++ = 0;
	environ = ourENVlist;		/* New environment */
    }
    *whereAPI = sockNAME;

    child_died();			/* Start up signal handler */
    shell_active = 1;			/* We are running down below */
    if (shell_pid = vfork()) {
	if (shell_pid == -1) {
	    perror("vfork");
	    (void) close(serversock);
	} else {
	    state = UNCONNECTED;
	}
    } else {				/* New process */
	register int i;

	for (i = 3; i < 30; i++) {
	    (void) close(i);
	}
	if (argc == 1) {		/* Just get a shell */
	    char *cmdname;
	    extern char *getenv();

	    cmdname = getenv("SHELL");
	    execlp(cmdname, cmdname, 0);
	    perror("Exec'ing new shell...\n");
	    exit(1);
	} else {
	    execvp(argv[1], &argv[1]);
	    perror("Exec'ing command.\n");
	    exit(1);
	}
	/*NOTREACHED*/
    }
    return shell_active;		/* Go back to main loop */
}
