/***************************************************************************
 *									   *
 *              WILLOW(tm) Information Retrieval Software		   *
 *     (Washington Information Looker-upper Layered Over Windows)	   *
 * 									   *
 *             Copyright 1992 University of Washington			   *
 *       Willow is a trademark of the University of Washington		   *
 * 									   *
 * Permission to use, copy, modify, and distribute Willow(tm) software	   *
 * and its documentation for any purpose and without fee is hereby	   *
 * granted, provided that the above copyright notice appears in all	   *
 * copies and that both the above copyright notice and this permission	   *
 * notice appear in supporting documentation, and that the name of the	   *
 * University of Washington not be used in advertising or publicity	   *
 * pertaining to distribution of the software without specific, written	   *
 * prior permission.  This software is made available "as is", and	   *
 * 									   *
 * THE UNIVERSITY OF WASHINGTON DISCLAIMS ALL WARRANTIES, EXPRESS OR	   *
 * IMPLIED, WITH REGARD TO THIS SOFTWARE, INCLUDING WITHOUT LIMITATION	   *
 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR  *
 * PURPOSE, AND IN NO EVENT SHALL THE UNIVERSITY OF WASHINGTON BE LIABLE   *
 * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES	   *
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN   *
 * ACTION OF CONTRACT, TORT (INCLUDING NEGLIGENCE) OR STRICT LIABILITY,	   *
 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS	   *
 * SOFTWARE.								   *
 * 								
 *
 * For further information please
  contact:
 * Bill Cattey, MIT Information Systems wdc@mit.edu
 *
 *******************************************************/

/*************************************************************
 * driver.c
 * This ws core of the table-driven database driver.

 * The driver takes a single optional argument, an integer representing 
 * the level of debugging. The only debugging that is done is that if
 * debug is >0, the type of each packet received is echoed.
 *
 * Now it is a hack to telnet to a host and look for an open
 * account to log in to.  Once the login succeeds, the input
 * and output character streams are passed directly to the user.
 **********************************************************/

#include <stdio.h>
#ifdef SOLARIS
#include <string.h>
#include <sys/ttold.h>
#include <sgtty.h>
#else  /* SOLARIS */
#ifdef sgi
#include <string.h>
#include <termio.h>
#include <sys/ttold.h>
#include <sgtty.h>
#else /* SGI */
#include <strings.h>
#endif /* SGI */
#endif /* SOLARIS */
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/wait.h>
#include <ctype.h>
#include <signal.h>
#include <errno.h>

#ifdef _AIX
#include <sys/select.h>
#endif /* _AIX */

#ifdef sgi
#define CRMOD       020
#endif /* sgi */

#define MAIN 
#include "driver.h"

char *malloc();
char *realloc();

/* Forward Routines */
#ifdef (_SVR4_SOURCE) && !defined(_POSIX_SOURCE) && !defined(_XOPEN_SOURCE)
char *strdup(char *s);
#endif
int dead_kid(void);

/***** GLOBALS *****/

/* Hold text read between matches in this buffer */
char *parse_buf;

/* Level of debugging, passed in on command line */
int debug_level = 0;

/* Current characters in parse_buf */
int    parse_buf_size;
int    parse_buf_used = 0;

char   fresh_input;  /* flag */

int    host_pid;     /* process id */

extern state_table states;           /* The state table */

/* The current state of the remote computer */
int    state;

/* The current state of our connection with willow */
char   connected_to_willow = TRUE;

/* Are we currently echoing interactions with host? */
char   echo = FALSE;

/* a couple of pipes for talking to telnet */
int  to_host[2],
     from_host[2];

/* File descriptor mask for select call */
fd_set read_fds;
int    num_fds;

int connected_to_host = FALSE;
int logged_on = FALSE;

struct	sgttyb ottyb, nttyb;

/* Takes one argument, an integer representing the debugging level */

int main_loop (void);
int pass_host_to_user(void);
int pass_user_to_host(void);
int perform_connect(void);
int cleanup (void);
int error (char *msg);
int disconnect_wcb (void);
int close_willow(void);
int set_alarm(int time_out);
int force_quit_hcb (void);
int read_from_host (void);
int host_send (char *str);
int scan (char *string);
int de_0_fy (char *buffer, int size);
int grow_parse_buf (void);
int retry_login(void);
int dump_state (int state);
int dump (char *str);

/* external routines */

extern int init_table(void);

/*
int read_from_willow (void);
int echo_packet (packet_rec *packet);
int login_wcb (packet_rec *packet);
int search_wcb (packet_rec *packet);
extern int get_full_wcb (packet_rec *packet);
int echo_wcb (packet_rec *packet);
int cheat_wcb (packet_rec *packet);
int update_hcb (char *msg);
int misc_error_hcb (char *msg);
*/

main(int argc, char **argv)
{
    int databaseRequested;

    if (argc > 2) 
	debug_level = atoi(argv[2]);
    if (debug_level) 
      echo = TRUE;

    if (argc < 2 )
	databaseRequested = SET_ONE;
    else
	databaseRequested = atoi(argv[1]);

    switch (databaseRequested) {

	case SET_TWO:

	    account = SECOND_ACCT;
	    password  = SECOND_PASSWORD;
	    break;

	case SET_THREE:    

	    account = THIRD_ACCT;
	    password = THIRD_PASSWORD;
	    break;

	default:

	    account = FIRST_ACCT;
	    password = FIRST_PASSWORD;
	    break;
    }

    parse_buf = malloc(PARSE_BUF_SIZE);
    parse_buf[0] = 0;
    parse_buf_size = PARSE_BUF_SIZE;
    init_table();

    state = NO_CON;

    num_fds = getdtablesize();

    /* preserve old modes Reset modes in cleanup. */
    ioctl(0, TIOCGETP, (char *)&ottyb);
    nttyb = ottyb;
  
  nttyb.sg_flags |= (CRMOD | ECHO);



  ioctl(0, TIOCSETP, (char *)&nttyb);
  
  perform_connect();

  main_loop();
}

/* This is the main loop of the program. We block on select until
   we get input from one of the pipes we are listening on, then
   take appropriate action.
 */
main_loop(void)
{

    while (1) {
	FD_ZERO(&read_fds);
	if (logged_on)
	    FD_SET(TO_DRIVER_FD, &read_fds);
	if (connected_to_host)
	    FD_SET(from_host[R], &read_fds);

	/* Block forever on select */
	if (select(num_fds, &read_fds, NULL, NULL, NULL) < 0) {
	    error("error on select");
	}
	/* Decide which pipe has input */
	if (FD_ISSET(from_host[R], &read_fds)) {
	    if(logged_on)
		pass_host_to_user();
	    else
		read_from_host();
	}
	else if (FD_ISSET(TO_DRIVER_FD, &read_fds)) {
	    pass_user_to_host();
	}
	else
	    error("select on unknown pipe");
    }
}

pass_host_to_user(void)
{
  int    n;
  char   read_buffer[IN_BUF_SIZE];

  /* The -1 is so there is always room to append a \0 */
  n = read(from_host[R], read_buffer, sizeof(read_buffer) - 1);
  write(FROM_DRIVER_FD, read_buffer, n);
  
}

pass_user_to_host(void)
{
  int    n;
  char   read_buffer[IN_BUF_SIZE];

  /* The -1 is so there is always room to append a \0 */
  n = read(TO_DRIVER_FD, read_buffer, sizeof(read_buffer) - 1);
  write(to_host[W], read_buffer, n);
}

perform_connect(void)
{
    int pos, i, swap;
    long random();


    pipe(to_host);
    pipe(from_host);

    signal(SIGCHLD, dead_kid);

    /* fork off a child, which will overlay telnet on itself */

    host_pid = fork();

    if (host_pid == 0) {
	/* in the child, redirect stdin and out */
	close(fileno(stdin)); 
	dup(to_host[R]);
	close(fileno(stdout));
	dup(from_host[W]);
	close(to_host[R]);
	close(to_host[W]); 
	close(from_host[R]); 
	close(from_host[W]);

#ifdef _AIX
	/* We're out of here */
/*	if (execl("/afs/athena.mit.edu/project/library/rsaixbin", "ucbtelnet", FIRST_SERVER, NULL) <0) */
	if (execlp("ucbtelnet", "ucbtelnet", FIRST_SERVER, NULL) <0) 
#else

	if (execlp("telnet", "telnet", FIRST_SERVER, NULL) < 0)
#endif AIX
	    error("Could not exec telnet");
    }
    else if (host_pid < 0) {
	error("Could not fork");
    }

    connected_to_host = TRUE;
    state = AWAIT_LOGIN;
    if (debug_level)
	dump_state(state);
}

/* Close things up, and quit */
cleanup(void)
{
    /* Restore old tty modes. */
    ioctl(0, TIOCSETP, (char *)&ottyb);

    exit(0);
}

/* If child dies */
int dead_kid(void)
{
  /* short pause so our packets don't over-run each other */
  sleep(1);
  cleanup();
}

#ifdef (_SVR4_SOURCE) && !defined(_POSIX_SOURCE) && !defined(_XOPEN_SOURCE)
/* Copy a string into a newly created string */
char *strdup(char *s)
{
  char *new_str;

  new_str = malloc(strlen(s) + 1);
  return(strcpy(new_str, s));
}
#endif
error(char *msg)
{
  fprintf(stderr, "FATAL ERROR IN DRIVER: %s\n",msg);fflush(stderr);
  disconnect_wcb();
}

/* Start the process of disconnecting from host database, and quitting. */
disconnect_wcb(void)
{
  if (state == NO_CON)
    cleanup();
  else
    close_willow();

  /* queue_cmd(QUIT_CMD, QUITTING); */
  
  /* Set an alarm, to force a quit */
  set_alarm(QUIT_TIMEOUT);
}

/* close off our connection to Willow, we are on our own */
close_willow(void)
{
  connected_to_willow = FALSE;
  close(TO_DRIVER_FD);
  close(FROM_DRIVER_FD);
}

/* Set up an alarm handler */
set_alarm(int time_out)
{
  signal(SIGALRM, force_quit_hcb);
  alarm(time_out);
}

force_quit_hcb(void)
{
/*#if defined(POSIX) || defined (_POSIX_SOURCE)
  int status;
#else /* defined(POSIX) || defined (_POSIX_SOURCE) 
  union wait status;
#endif /* defined(POSIX) || defined (_POSIX_SOURCE)*/

  int status;

  signal(SIGCHLD, SIG_IGN);
  /* host_send(LOGOFF_CMD); */
  sleep(1);
  kill(host_pid, SIGKILL);
  wait(&status);
  close(to_host[W]);
  close(from_host[R]);
  cleanup();
}

/* This function sends a string out on the pipe to the remote host */
host_send(char *str)
{
  write(to_host[W], str, strlen(str));
}

/* This is the heart of the finite state machine. It scans the 
   input coming in from the remote host, and follows the state table.
 */
read_from_host(void)
{
  int i;
  char reset_ready;  /* flag for resetting READY state */

  fresh_input = TRUE;
  for (i=0; i < MAX_TRANSITIONS; i++) {

    if (!states[state][i].in_string)
      return;
    if (scan(states[state][i].in_string)) {
      /* call action routine first, in case it needs to modify table
	 in order to branch. */

     if (states[state][i].action)
	states[state][i].action();
      
      if (states[state][i].out_string)
	host_send(states[state][i].out_string);
      
      /* Reset the parse_buf. Do this out here because the action
	 routine might have needed it. */
      parse_buf_used = 0;
      parse_buf[0] = 0;
      
      reset_ready = (state == READY);
      /* Do this last, since action routine might need to know state */
      state = states[state][i].next_state;
      if (debug_level)
	  dump(states[state][i].out_string);
      if (debug_level)
	  dump_state(state);

      /* READY is special state, used to queue single commands, must
         reset after use */
      if (reset_ready) {
	states[READY][0].out_string = NULL;
	states[READY][0].next_state = READY;
      }
      return;
    }
  }      
}

/* Scan input for requested string, return true if found, else false,
   We always re-read the old buffer, unless the fresh_input flag
   has been reset.

   Occasionally input stream will break in middle of a target string. 
   To guard against that, we search the parse buffer, which starts from 
   the end of our previous hit. This is slightly inefficient.

   The calling routine has responsibility for resetting the parse buffer.
 */
scan(char *string)
{
  int    n;
  char   read_buffer[IN_BUF_SIZE];
  
  if (fresh_input)
    {
      fresh_input = FALSE;
      
      /* The -1 is so there is always room to append a \0 */
      n = read(from_host[R], read_buffer, sizeof(read_buffer) - 1);
      de_0_fy(read_buffer, n);

      /* Save old buffer */
      if ((parse_buf_used += strlen(read_buffer)) > parse_buf_size)
	grow_parse_buf();
      strcat(parse_buf, read_buffer);
      
      if (echo) {
	dump(read_buffer);
      }
    }
  
  /* Use built in regular expression handler to check for string */
  if (re_comp(string) != 0)
    error("could not re_comp string");
  
  return(re_exec(parse_buf));
}

/* Take out any NULL's from the buffer, cause we want to treat this
   as a string. Then stick a NULL at the end.
 */
de_0_fy(char *buffer, int size)
{
  int sdex = 0, tdex = 0; /* source and target indices */
  
  /* Look for first 0, or end of buffer */
  while ((sdex < size) && (buffer[sdex] != 0)) 
    sdex++;
  
  /* if no 0's, terminate string and return */
  if (sdex == size) {
    buffer[sdex] = 0;
    return;
  }
  
  tdex = sdex;
  /* until end of string, copy over nulls */
  while (sdex < size) {
    if (buffer[sdex] != 0) {
      buffer[tdex] = buffer[sdex];
      tdex++;
    }
    sdex++;
  }
  buffer[tdex] = 0;
}

/* Called if parse buf gets full */
grow_parse_buf(void)
{
  static int call_count = 0;
  call_count++;
  if (call_count > 100)
    error("Parse buf getting way too big");
  parse_buf_size = (call_count + 1) * PARSE_BUF_SIZE;
  parse_buf = realloc(parse_buf, parse_buf_size);
}

dump_state (int state)
{
  fprintf(stderr, "NEW STATE: %s\n", 
	  state == NO_CON       ? "NO_CON" :
	  state == AWAIT_LOGIN  ? "AWAIT_LOGIN" :
	  state == AWAIT_PW     ? "AWAIT_PW" :
	  state == AWAIT_SHELL  ? "AWAIT_SHELL" :
	  state == AWAIT_DB     ? "AWAIT_DB" :
	  state == READY        ? "READY" :
	  state == QUITTING     ? "QUITTING" :
	  "UNKNOWN???");
  fflush(stderr);
}

misc_error_hcb(char *msg)
{
    fprintf(stderr, "%s\n\r", msg);
    fflush(stderr);
}

update_hcb(char *msg)
{
    fprintf(stderr, "%s\n\r", msg);
    fflush(stderr);
}

login_msg_hcb(void)
{
  update_hcb("Logging-in to database server...");
}

starting_msg_hcb(void)
{
  update_hcb("Starting database program...");
}


bad_uid_hcb(void)
{
  misc_error_hcb("Invalid User Id");
  force_quit_hcb();
}

bad_login_hcb(void)
{
  misc_error_hcb("Invalid Account or Password");
  force_quit_hcb();
}

bad_db_hcb(void)
{
  misc_error_hcb("Unable to access specified database");
}

bad_startup_hcb(void)
{
  misc_error_hcb("BRS/Search failed to start");
}

assert_logged_on(void)
{
    logged_on = TRUE;
/*    update_hcb("You are now connected to the database.\n\r");
    update_hcb("Strike RETURN to begin running "); */
}

/* Dump the string to wherever we want all the interactions dumped.
  Although it slows things down a bit, we filter out
  the clear screen codes from the dumped output stream.
  This could be replaced later with a general filtering facility
  with a table of things to translate/drop on the floor.

  Note: on those rare few C compilers with read-only strings,
  if you ever pass in to dump a read-only string with a code to be
  filtered, you'll get a seg fault.  The solution is to compile
  explicitly asking for read/write strings.  (This is extreme
  esoterica for porting, but I just couldn't bring myself to
  malloc and free for such a tiny eventuality.  I couldn't be
  silent about it either -wdc.)
  */
dump(char *str)
{
    char *tstring;

    if ((tstring = index(str, '\033')) != NULL) {
	int count = (tstring - str) + 1;
	char *fstring;
	switch (str[count]) {
	    case '[':
		count++;
		while(isdigit(str[count]) ||
		      str[count] == ';' ) count++;
		break;
	    case '#':
		count++;
		break;
	    default:
		break;
	}
	count++;
	*tstring = '\0';
	fprintf (stdout, "%s", str);
	/* Call recursively.  There may be more screen clear codes. */
	dump (str + count);
	*tstring = '\033';

    } else {
	fprintf(stdout, "%s", str);
    }
    fflush(stdout);
}

