/********************************************************************
 * $Author: lindner $
 * $Revision: 1.15 $
 * $Date: 1993/01/17 03:46:46 $
 * $Source: /home/mudhoney/GopherSrc/gopher1.12/gopher/RCS/gopher.c,v $
 * $State: Rel $
 *
 * Paul Lindner, University of Minnesota CIS.
 *
 * Copyright 1991, 1992 by the Regents of the University of Minnesota
 * see the file "Copyright" in the distribution for conditions of use.
 *********************************************************************
 * MODULE: gopher.c
 * Main functions for the gopher client
 *********************************************************************
 * Revision History:
 * $Log: gopher.c,v $
 * Revision 1.15  1993/01/17  03:46:46  lindner
 * Fixes for tmpname memory leak.
 *
 * Revision 1.14  1993/01/15  20:14:27  lindner
 * Added -T to the usage line
 *
 * Revision 1.13  1993/01/14  22:00:21  lindner
 * added ^R and ^W to redraw the screen for VMS
 * Old search terms don't persist now when highlighting text.
 *
 * Revision 1.12  1993/01/13  16:18:11  lindner
 * Put back in 's' save patch...  Sigh...
 *
 * Revision 1.11  1993/01/12  22:38:56  lindner
 * Rescinded changes for sound on VMS, it doesn't work!
 *
 * Revision 1.10  1993/01/12  21:42:14  lindner
 * Fixed problem with viewing files in secure mode.
 * Fixed problems with suck_sound in VMS.
 * Fixed problems with \n on the system command in VMS
 *
 * Revision 1.9  1993/01/12  17:30:17  lindner
 * Fixed problem with 's' key for save_file.
 *
 * Revision 1.8  1993/01/09  02:33:46  lindner
 * Fixed definitions for controlc() and sizechange()
 *
 * Revision 1.7  1993/01/09  02:18:40  lindner
 * Changed (void*)-1 constructs to SIG_ERR
 *
 * Revision 1.6  1993/01/08  19:25:02  lindner
 * Securemode users can't display graphics now, they get a message instead..
 *
 * Revision 1.5  1993/01/07  22:49:40  lindner
 * Added option -T to set initial type.  Added 'D' command for downloading
 *
 * Revision 1.4  1992/12/31  06:34:49  lindner
 * Okay, okay, Save_File is really Save_file
 *
 * Revision 1.3  1992/12/31  05:38:01  lindner
 * Removed getfile() replaced with Save_File().
 *
 * Revision 1.2  1992/12/31  04:34:38  lindner
 * Added VMS support from fogel and jqj.
 *
 * Revision 1.1  1992/12/10  23:32:16  lindner
 * gopher 1.1 release
 *
 *********************************************************************/


#include "gopher.h"

void describe_gopher();

/*
** Open a connection to another host using telnet or tn3270
*/

void
do_tel_3270(ZeGopher)
  GopherStruct *ZeGopher;
{
     char *Dialogmess[20];

     char sMessage1[128];
     char sMessage2[128];
     char sTelCmd[128]; 

     /* retrieve the gopher information for the telnet command*/

     clear();
     Dialogmess[0] = "Warning!!!!!, you are about to leave the Internet";
     Dialogmess[1] = "Gopher program and connect to another host. If";
     Dialogmess[2] = "you get stuck press the control key and the ] key,";
     Dialogmess[3] = "and then type quit";
     Dialogmess[4] = "";
     
     if (GSgetPort(ZeGopher) != 0)
	  sprintf(sMessage1,"Connecting to %s, port %d using %s.", GSgetHost(ZeGopher),GSgetPort(ZeGopher), (GSgetType(ZeGopher) == A_TN3270) ? "tn3270" : "telnet");
     else
	  sprintf(sMessage1, "Connecting to %s using %s.", GSgetHost(ZeGopher),(GSgetType(ZeGopher) == A_TN3270) ? "tn3270" : "telnet");

     Dialogmess[5] = sMessage1;

     if (*GSgetPath(ZeGopher) != '\0')
	  sprintf(sMessage2,"Use the account name \"%s\" to log in",
		  GSgetPath(ZeGopher));
     else
	  sMessage2[0] = '\0';

     Dialogmess[6] = "";
     Dialogmess[7] = sMessage2;
     Dialogmess[8] = NULL;

     if (CURDialog(CursesScreen, GSgetTitle(ZeGopher), Dialogmess) <0)
	  return;

     CURexit(CursesScreen);

     if (GSgetType(ZeGopher) == 'T') {
	  /**** A TN3270 connection ****/
	  sprintf(sTelCmd, "%s %s", STRget(TN3270Command),
		  GSgetHost(ZeGopher));
     } else

	  if (GSgetPort(ZeGopher) != 0 && GSgetPort(ZeGopher) != 23) 
#if defined(VMS) && defined(MULTINET)
               sprintf(sTelCmd, "%s %s /PORT=%d",
#else
	       sprintf(sTelCmd, "%s %s %d",
#endif
		       STRget(TelnetCommand), GSgetHost(ZeGopher),
		       GSgetPort(ZeGopher)); 
	  else 
	       sprintf(sTelCmd, "%s %s", STRget(TelnetCommand), 
		       GSgetHost(ZeGopher));
     
     CURexit(CursesScreen);
     system(sTelCmd);
     CURenter(CursesScreen);
     return;
}





/*
** do_index gets keywords from the user to search for.  It returns
** it to the calling process.  This storage is volotile. Callers should
** make a copy if they want to call do_index multiple times.
*/

char* do_index(ZeGopher)
  GopherStruct *ZeGopher;
{
     static char *inputline = NULL;
     static char *prompt[2];
     static char *response[2];

     if (inputline == NULL) {
	  inputline = (char *) malloc(sizeof(char)*256);
	  if (inputline == NULL)
	       perror("Out of memory"), exit(-1);
	  *inputline = '\0';
     }

     prompt[0] = "Words to search for";
     prompt[1] = NULL;

     response[0] = inputline;
     response[1] = NULL;

     if (CURRequest(CursesScreen, GSgetTitle(ZeGopher),prompt, response) == -1 )
	  return(NULL);

     if (*inputline == '\0')
	  return(NULL);
     else
	  return(inputline);
}


/*
 * this procedure just retrieves binary data from the socket and
 * pumps it into a "play" process.
 */

#define BUFSIZE 1400  /* A pretty good value for ethernet */

#ifndef VMS
void
suck_sound(sockfd)
  int sockfd;
{
     FILE *Play;
     int j;
     char buf[BUFSIZE];

     if (*STRget(PlayCommand) == '\0') {
	  /*** Hey! no play command, bummer ***/
	  CursesErrorMsg("Sorry, this machine doesn't support sounds");

	  return;
     }

     Play = popen(STRget(PlayCommand), "w");
	  
     
     while(1) {
          j = read(sockfd, buf, BUFSIZE);
	  
	  if (j == 0)
	       break;
	  
	  fwrite(buf, 1, j, Play);
     }
}
#endif

/*
 * fork off a sound process to siphon the data across the net.
 * So the user can listen to tunage while browsing the directories.
 */

void
do_sound(ZeGopher)
  GopherStruct *ZeGopher;
{
#ifdef VMS
     CursesErrorMsg("Sorry, this machine doesn't support sounds");
#else
     int sockfd;
     char sTmp[5];
     BOOLEAN Waitforchld = FALSE;

     sTmp[0] = '\0';

     if ((sockfd = GSconnect(ZeGopher)) <0) {
	  check_sock(sockfd, GSgetHost(ZeGopher), GSgetPort(ZeGopher));
	  return;
     }

     /** Send out the request **/

     writestring(sockfd, GSgetPath(ZeGopher));
     writestring(sockfd, "\r\n");

     /** Okay, it's cool, we can fork off **/

     if (SOUNDCHILD != 0)
	  Waitforchld = TRUE;


     if ( (SOUNDCHILD = fork()) < 0)
	  ;/* Fork Error */
     
     else if (SOUNDCHILD == 0) {  /* Child Process */
	  wait(SIGCHLD);
	  suck_sound(sockfd);
	  exit(0);
     }
     
     /* Parent Process */
     
     closenet(sockfd);
     return;
#endif  /* not VMS */
}



/*
 * Replace the searched words with backspaces and underline characters.
 */

static char sGBoldoutput[20];  /*** Used for stripping weird stuff from
				    term strings ***/
static int iGposition = 0;     /*** Pointer into the Boldoutput string **/

/*** Used by tputs() ***/

int
Boldoutchar(c)
  char c;
{
     sGBoldoutput[iGposition++] = c;
     return(c);
}


void
Boldit(inputline, outputline, MungeSearchstr)
  char *inputline, *outputline, *MungeSearchstr;
{
     char words[20][40];  /** A reasonable guess **/
     int numchars, lowwordnum, wordcount, i;
     char *cp, *lowword;

     outputline[0] = '\0';

     bzero(outputline, 512);
     
     while (isspace(*MungeSearchstr)) /** Strip off spaces **/
	  MungeSearchstr++;
	  
     for (wordcount=0; wordcount<20; wordcount++) {

	  while (isspace(*MungeSearchstr)) /** Strip off spaces **/
	       MungeSearchstr++;
	  
	  numchars = sreadword(MungeSearchstr, words[wordcount], 40);
	  MungeSearchstr += numchars;
	  if (numchars == 0)
	       break;
	  if (strcmp(words[wordcount], "and")==0 ||
	      strcmp(words[wordcount], "or")==0 ||
	      strcmp(words[wordcount], "not")==0) {
	       words[wordcount][0] = '\0';
	       wordcount--;
	  }
     }


     /** Find the first word in the line **/

     while (*inputline!='\0') {
	  lowword = NULL;

	  for (i=0; i< wordcount; i++) {
	       cp = strcasestr(inputline, words[i]);
	       if (cp != NULL)
		    if (cp < lowword || lowword == NULL) {
			 lowword = cp;
			 lowwordnum = i;
		    }
	  }

	  if (lowword == NULL) {
	       strcpy(outputline, inputline);
	       return;
	  }
	  else {
	       strncpy(outputline, inputline, lowword - inputline);
	       outputline += (lowword - inputline);
	       inputline = lowword;
	       
	       iGposition = 0;
	       tputs(CURgetHighon(CursesScreen), 1, Boldoutchar);
	       sGBoldoutput[iGposition] = '\0';
	       strcpy(outputline, sGBoldoutput);
	       outputline += strlen(sGBoldoutput);

	       strncpy(outputline, inputline, strlen(words[lowwordnum]));
	       inputline += strlen(words[lowwordnum]);
	       outputline += strlen(words[lowwordnum]);


	       iGposition = 0;
	       tputs(CURgetHighoff(CursesScreen), 1, Boldoutchar);
	       sGBoldoutput[iGposition] = '\0';
	       strcpy(outputline, sGBoldoutput);
	       outputline += strlen(sGBoldoutput);

	  }
     }
}


/**
*** Show file takes a gopher text thing, writes it to a file
*** and passes it to your favorite pager.
**/

void
showfile(ZeGopher)
  GopherStruct *ZeGopher;
{
     int i=0, iLength, sockfd;
     char *tmpfilename;
     FILE *tmpfile;
     char inputline[512];
     char outputline[512];
     char sTmp[5];
     int twirlcounter=0;

     sTmp[0] = '\0';

     /** Open a temporary file **/

     Gopenfile = tmpfilename = tempnam("/tmp","gopher");

     if ((tmpfile = fopen(tmpfilename, "w")) == NULL)
	  fprintf(stderr, "Couldn't make a tmp file!\n"), exit(-1);


     /** Send out the request **/

     if ((sockfd = GSconnect(ZeGopher)) <0) {
	  check_sock(sockfd, GSgetHost(ZeGopher), GSgetPort(ZeGopher));
	  return;
     }

     writestring(sockfd, GSgetPath(ZeGopher));
     writestring(sockfd, "\r\n");

     /*** Case here on the object type,  Get it line-by-line for text,
          binary mode if it's supported. ***/

     if (GSgetType(ZeGopher) == A_IMAGE || GSgetType(ZeGopher) == A_GIF) {
	  char buf[BUFSIZE]; int cc = 0;
	  int counter = 0;

	  while ( (cc = readn( sockfd, buf, BUFSIZE )) > 0 ) {
	       counter += cc;
	       
	       if (write(fileno(tmpfile), buf, cc ) <=0)
		    CursesErrorMsg("Problems Writing File");
	       twirl();
	  }
	  if ( cc < 0 )
	       CursesErrorMsg( "Warning! File may be corrupt!");
	  else {
	       CursesErrorMsg( "File received successfully" );
	  }
	  fclose( tmpfile );
     } else {
	  while (1) {
	       /** make a mark for every page **/
	       twirlcounter++;
	       
	       if ((twirlcounter % 25) == 0)
		    twirl();
	       
	       iLength = readline(sockfd, inputline, 512);
	       outputline[0] = '\0';
	       if (iLength == 0)
		    break;
	       
	       ZapCRLF(inputline);
	       
	       
	       /*** Ugly hack ahead..... ***/
	       
	       if (GSgetType(ZeGopher) == A_CSO) {
		    if (inputline[0] == '2')
			 break;
		    
		    if ((inputline[0] >= '3') && (inputline[0] <= '9'))  {
			 fprintf(tmpfile, "%s\n", GSgetPath(ZeGopher));
			 fprintf(tmpfile, "%s\n", inputline+4);
			 break;
		    }
		    if (inputline[0] == '-') {


			 if (inputline[1] >= '3' && inputline[1] <= '9') {
			      fprintf(tmpfile, "%s\n", GSgetPath(ZeGopher));
			      fprintf(tmpfile, "%s\n", inputline+5);
			 }
			 else {
			      char *colonpos = strchr(inputline+5,':');
                             if (colonpos != NULL && *(colonpos-1) != i) {
                                  fprintf(tmpfile, "-------------------------------------------------------\n");
                                  i = *(colonpos-1);
                                  }
                             fputs((colonpos ? colonpos+1 : inputline+6), tmpfile);
                             fputc('\n', tmpfile);
			 }
		    }
	       }
	       
	       if (GSgetType(ZeGopher) == A_FILE) {
		    if ((inputline[0] == '.') && (inputline[1] == '\0'))
			 break;
		    else {
			 /*** Underline searched words, except and, or and not ***/
			 if (Searchstring != NULL) {
			      Boldit(inputline, outputline, Searchstring);
			 }
			 else
			      strcpy(outputline, inputline);
			 fputs(outputline, tmpfile);
			 fputc('\n', tmpfile);
		    }
	       }
	       else if (GSgetType(ZeGopher) == A_MIME) {
		    if ((inputline[0] == '.') && (inputline[1] == '\0'))
			 break;
		    else {
			 fputs(inputline, tmpfile);
			 fputc('\n', tmpfile);
		    }
	       }
	  }

	  /** Work around a bug in xterm n' curses*/	  
	  fprintf(tmpfile, "\012 \n\n");  
	  (void)fclose(tmpfile);
     }
     closenet(sockfd);

     if (GSgetType(ZeGopher) == A_MIME)
	  display_mime(tmpfilename, GSgetTitle(ZeGopher));
     else if (GSgetType(ZeGopher) == A_IMAGE || GSgetType(ZeGopher)== A_GIF)
	  display_image(tmpfilename, GSgetTitle(ZeGopher));
     else
	  display_file(tmpfilename, ZeGopher);

     /** Good little clients clean up after themselves..**/

     if (unlink(tmpfilename)!=0)
	  fprintf(stderr, "Couldn't unlink!!!\n"), exit(-1);

     Gopenfile = NULL;
     free(tmpfilename);
     CURenter(CursesScreen);

     return;
}


/*
** Pushgopher takes a GopherThing pointer and adds it to it's stack.
**
** Ick this must be fixed!
*/

void
pushgopher(ZeDir)
  GopherDirObj *ZeDir;
{

     OldDirs[iLevel]= ZeDir;
     iLevel ++;
}

/*
** If the stack is empty, popgopher returns a -1
*/

int
popgopher(ZeDir)
  GopherDirObj **ZeDir;
{

     if (iLevel == 0)
	  return(-1);

     iLevel --;

     *ZeDir =  OldDirs[iLevel];

     return(0);
}


void check_sock(sockfd, host, port)
  int sockfd;
  char *host;
  int port;
{
     char DispString[WHOLELINE];
     char *DispStrings[4];

     if (sockfd <0) {
	  sprintf(DispString, "Cannot connect to host %s, port %d.", host, port);
	  switch (sockfd) {
	  case -2:
	       DispStrings[2] = "Hostname is unknown.";
	       break;
	  case -3:
	       DispStrings[2] = "Unable to allocate a socket.";
	       break;
	  case -4:
	       DispStrings[2] = "Connection refused by host.";
	       break;
	  default:
	       DispStrings[2] = "Unknown error.";
	  }
	  DispStrings[0] = DispString;
	  DispStrings[1] = "";
	  DispStrings[3] = NULL;

	  CURDialog(CursesScreen, "Network Error", DispStrings);
     }
}


BOOLEAN
ReallyQuit()
{
     char yesno[3];
	       
     yesno[0] = 'y';
     yesno[1] = '\0';
     
     CURgetYesorNo(CursesScreen, "Really quit (y/n) ?", yesno);
     if (*yesno == 'y') {
	  return(TRUE);
     }
     
     return(FALSE);
}
     

/***************
** Abort() catches sigpipes and other unknown signals
*/
void
Abort(sig)
  int sig;
{
     if (Gopenfile != NULL)
	  unlink(Gopenfile);
     exit(-1);
}


/**************
** This bit of code catches control-c's, it cleans up the curses stuff.
*/
void
controlc(sig)
  int sig;
{

     if (Gopenfile != NULL) 
	  if (unlink(Gopenfile) < 0)
	       if (errno != ENOENT)
		    fprintf(stderr, "could not unlink %s\n", Gopenfile);
     
#ifdef VMS
     if (1)
#else
     if (ReallyQuit())
#endif
     {
	  CURexit(CursesScreen);
	  if (ChangedDefs)
	       write_rc();
	  exit(0);
     }
     else {
	  CURresize(CursesScreen);
	  scline(-1, 1, CurrentDir);
	  /** Interrupt search, go back a level?? **/
     }

     /*
      * Reprime the signals...
      */

     if (signal(SIGINT, controlc) == SIG_ERR)
	  perror("signal died:\n"), exit(-1);
       
}


/**************
** This bit of code catches window size change signals
**/

void
sizechange(sig)
  int sig;
{
     int lines, cols;
     
#ifdef  TIOCGWINSZ
     static struct      winsize zewinsize;        /* 4.3 BSD window sizing */
#endif

     lines = LINES;
     cols  = COLS;
     
#ifdef  TIOCGWINSZ
     if (ioctl(0, TIOCGWINSZ, (char *) &zewinsize) == 0) {
	  lines = zewinsize.ws_row;
	  cols  = zewinsize.ws_col;
     } else {
#endif
	  /* code here to use sizes from termcap/terminfo, not yet... */
	  ;
#ifdef  TIOCGWINSZ
     }

     if (lines != LINES || cols != COLS) {
	  LINES = lines;
	  COLS  = cols;
	  CURresize(CursesScreen);
     
	  scline(-1, 1, CurrentDir);
     }

     if (signal(SIGWINCH, sizechange)==SIG_ERR)
	  perror("signal died:\n"), exit(-1);

	    
#endif

}



/**********
**
** Set up all the global variables.
**
***********/

void
Initialize()
{

     PagerCommand    = STRnew();
     PrinterCommand  = STRnew();
     TelnetCommand   = STRnew();
     TN3270Command   = STRnew();
     PlayCommand     = STRnew();
     MailCommand     = STRnew();
     EditorCommand   = STRnew();
     MIMECommand     = STRnew();
     ImageCommand    = STRnew();

     /** get defaults from the rc file **/

     set_defs();
     read_rc();
     read_env();


     /*** Set up the curses environment ***/
     
     CursesScreen = CURnew();

     if (strcmp(CURgetTerm(CursesScreen), "unknown")==0)
	  fprintf(stderr, "I don't understand your terminal type\n"), exit(-1);


     /*** Make a signal handler for window size changes ***/

#ifdef SIGWINCH
     CURsetSIGWINCH(CursesScreen, sizechange);
     if (signal(SIGWINCH, sizechange)==SIG_ERR)
	  perror("signal died:\n"), exit(-1);

#endif

     if (signal(SIGINT, controlc) == SIG_ERR)
	  perror("signal died:\n"), exit(-1);

     if (signal(SIGPIPE, Abort) == SIG_ERR)
	  perror("signal died:\n"), exit(-1);

     /*** Init MainWindow ****/
     CURenter(CursesScreen);
}


static char *GlobalOptions[] =  
{"Pager Command", "Print Command", "Telnet Command", "Sound Command", "Mail Command", "3270 Emulator Command", "MIME pager", "Image Viewer", NULL};


void
SetOptions()
{
     static char *Responses[MAXRESP];
     static int inited = FALSE;


     if (SecureMode) {
	  CursesErrorMsg("Sorry, you are not allowed to set options in secure mode.");
	  return;
     }


     if (inited == FALSE) {
	  int i; 

	  for (i=0; i< 8; i++) {
	       Responses[i] = (char *) malloc(sizeof(char) * MAXSTR);
	  }
	  Responses[i] = NULL;
	  inited = TRUE;
     }

     
     strcpy(Responses[0], STRget(PagerCommand));
     strcpy(Responses[1], STRget(PrinterCommand));
     strcpy(Responses[2], STRget(TelnetCommand));
     strcpy(Responses[3], STRget(PlayCommand));
     strcpy(Responses[4], STRget(MailCommand));
     strcpy(Responses[5], STRget(TN3270Command));
     strcpy(Responses[6], STRget(MIMECommand));
     strcpy(Responses[7], STRget(ImageCommand));
     
     if (CURRequest(CursesScreen, "Options", GlobalOptions, Responses) == 0) {

	  STRset(PagerCommand, Responses[0]);
	  STRset(PrinterCommand, Responses[1]);
	  STRset(TelnetCommand, Responses[2]);
	  STRset(PlayCommand, Responses[3]);
	  STRset(MailCommand, Responses[4]);
	  STRset(TN3270Command, Responses[5]);
	  STRset(MIMECommand, Responses[6]);
	  STRset(ImageCommand, Responses[7]);
	  
	  ChangedDefs = TRUE;
     }
}


/* This should be a generalized stack type.  This is icky for now... */

int
main(argc, argv)
  int argc;
  char *argv[];
{
     BOOLEAN bDone = FALSE;
     char sTmp[80];
     GopherStruct *RootGophers[2];
     int numhosts = 2;
     int TypedChar;
     /*** for getopt processing ***/
     int c;
     extern char *optarg;
     extern int optind;
     int errflag =0, i;

     int Garbled = TRUE;
     boolean Bkmarksfirst = FALSE;

     for (i=0; i<2; i++) {
	  RootGophers[i] = GSnew();
	  GSsetType (RootGophers[i], A_DIRECTORY);   
	  GSsetPath (RootGophers[i],"");
     }
     
     /** Should generalize this to >2 hosts .... Sigh... ***/
     GSsetHost (RootGophers[0], CLIENT1_HOST);
     GSsetPort (RootGophers[0], CLIENT1_PORT);

     GSsetHost (RootGophers[1], CLIENT2_HOST);
     GSsetPort (RootGophers[1], CLIENT2_PORT);

     if (CLIENT2_PORT == 0)
	  numhosts = 1;

     sTmp[0] = '\0';

     while ((c = getopt(argc, argv, "Dsbp:t:T:")) != -1)
	  switch (c) {
	  case 's':
	       SecureMode = TRUE;
	       break;
	  case 'p':
	       GSsetPath(RootGophers[0], optarg);
	       GSsetPath(RootGophers[1], optarg);
	       break;
	  case 'T':
	       GSsetType(RootGophers[0], *optarg);
	       GSsetType(RootGophers[1], *optarg);
	       break;
	  case 't':
	       GSsetTitle(RootGophers[0], optarg);
	       GSsetTitle(RootGophers[1], optarg);
	       break;
	  case 'D':
	       DEBUG = TRUE;
	       break;
	  case 'b':
	       Bkmarksfirst = TRUE;
	       break;
	  case '?':
	       errflag++;
	  }


     if (errflag) {
	  fprintf(stderr, "Usage: %s [-sb] [-T type] [-p path] [-t title] [hostname port]+\n", argv[0]);
	  exit(-1);
     }

     /**** Get host #1 from the command line ****/
     
     if (optind < argc) {
	  GSsetHost(RootGophers[0], argv[optind]);
	  GSsetHost(RootGophers[1], "");  /** Nuke the 2nd alternative host **/
	  GSsetPort(RootGophers[1], 0);
	  numhosts = 1;
	  optind++;
     }
     if (optind < argc) {
	  GSsetPort(RootGophers[0], atoi(argv[optind]));
	  optind++;
     }

     /*** Get host #2 from the command line... ***/
     if (optind < argc) {
	  GSsetHost(RootGophers[1], argv[optind]);
	  numhosts = 2;
	  optind++;
     }
     if (optind < argc) {
	  GSsetPort(RootGophers[1], atoi(argv[optind]));
	  optind++;
     }

     /*** If the title hasn't been set, then add a default title **/
     if (GSgetTitle(RootGophers[0]) == NULL) {
	  sprintf(sTmp, "Root gopher server: %s", GSgetHost(RootGophers[0]));
	  GSsetTitle(RootGophers[0], sTmp);
	  sprintf(sTmp, "Root gopher server: %s", GSgetHost(RootGophers[1]));
	  GSsetTitle(RootGophers[1], sTmp);
     }

     /*** Set up global variables, etc. ***/

     Initialize();

     if (Bkmarksfirst) {
	  CurrentDir = BookmarkDir;
	  
	  if (CurrentDir != NULL)
	       GDaddGS(CurrentDir, RootGophers[0]);
     } else {
	  int rnum;

	  srand(time(NULL));
	  rnum = rand() % numhosts;
	  process_request(RootGophers[rnum]);

          /* just process the command line entry */
          if( GSgetType(RootGophers[0]) != A_DIRECTORY ) {
	       refresh();
	       CURexit(CursesScreen);
	       exit(0);
          }
     }

     if (CurrentDir == NULL || GDgetNumitems(CurrentDir) <= 0) {
	  /*
	   * We didn't get anything from that gopher server.  Either
	   * it is down, doesn't exist, or is empty or otherwise
	   * busted.
           *
           * Try any alternative hosts that are available..
	   */

	  while (optind < argc && CurrentDir == NULL) {
	       GSsetHost(RootGophers[0], argv[optind]);
	       optind++;
	       GSsetPort(RootGophers[0], atoi(argv[optind]));
	       optind++;
	       process_request(RootGophers[0]);
	  }
     }	  

     if (CurrentDir == NULL || GDgetNumitems(CurrentDir) <= 0) {
	  CURexit(CursesScreen);
	  fprintf(stderr,
		  "%s: Nothing received for main menu, can't continue\n", argv[0]);
	  exit(1);
     }	  

     while (bDone == FALSE)
     {
	  GetMenu(CurrentDir, &TypedChar, Garbled);

	  Garbled = TRUE;

	  switch(TypedChar)
	  {
	  case '\r':
	  case '\n':
	       /*** Select the designated item ***/
	       if (process_request(GDgetEntry(CurrentDir,GDgetCurrentItem(CurrentDir)-1))==1)
		    ;  /** Don't do anything if we failed, I guess **/
	       break;
	       
	  case '\014': /* ^L */
#ifdef VMS
	  case '\022': /* ^R */
	  case '\027': /* ^W */
#endif
	       /*** Redraw the screen ***/
	       break;

	  case '\0':
	       /*** What the heck? ***/
	       CursesErrorMsg("Strange Error occurred!");
	       break;
	       
	  case 'u':
	  case 'U': 
	  {
	       GopherDirObj *tempGdir;

	       /*** Don't highlight texts which aren't search hits ***/
	       Searchstring = NULL;

	       /*** Go up a directory level ***/
	       tempGdir = CurrentDir;
	       /** Don't destroy root level directory, or bookmarks **/
	       if (popgopher(&CurrentDir)==0 && tempGdir != CurrentDir) {
		    if (tempGdir != BookmarkDir)
			 GDdestroy(tempGdir);
	       }
	  }
	       break;

	  case 's':  /*** Save a file directly ***/
	       if (SecureMode == TRUE) {
		    CursesErrorMsg("Sorry, can't save files in securemode");
		    break;
	       }
	       Save_file(GDgetEntry(CurrentDir,GDgetCurrentItem(CurrentDir)-1), NULL,NULL);
	       break;
	       
	  case 'D':
	       Download_file(GDgetEntry(CurrentDir,GDgetCurrentItem(CurrentDir)-1));
	       break;

	  case 'v':  /** View bookmark list **/
	  {
	       if (BookmarkDir == NULL) {
		    CursesErrorMsg("No bookmarks are defined");
		    break;
	       }

	       /** Don't push an empty gopher directory... **/
	       if (CurrentDir != NULL)
		    pushgopher(CurrentDir); 
	       
	       CurrentDir = BookmarkDir;

	       break;
	  }	       

	  case 'a': /** add current item as a bookmark **/
	  {
	       GopherObj *tmpgs;
	       char newtitle[256];
	       
	       if (BookmarkDir == NULL) {
 		    BookmarkDir = GDnew(32);
		    GDsetTitle(BookmarkDir, "Bookmarks");
	       }

	       tmpgs = GSnew();
	       GScpy(tmpgs, GDgetEntry(CurrentDir, GDgetCurrentItem(CurrentDir)-1));
	       
	       strcpy(newtitle, GSgetTitle(tmpgs));
	       if (CURGetOneOption(CursesScreen, "Name for this bookmark? ", newtitle) <0)
		    break;
	       if (*newtitle == '\0')
		    break;

	       GSsetTitle(tmpgs, newtitle);
	       GDaddGS(BookmarkDir, tmpgs);
	       GSdestroy(tmpgs);

	       ChangedDefs = TRUE;

	       break;
	  }

	  case 'A': /*** Add current directory/search as a bookmark **/
	  {
	       GopherObj *tmpgs;
	       char newtitle[256];

	       if (BookmarkDir == NULL) {
		    BookmarkDir = GDnew(32);
		    GDsetTitle(BookmarkDir, "Bookmarks");
	       }

	       if (CurrentDir == BookmarkDir) {
		    CursesErrorMsg("Sorry, can't make a bookmark of bookmarks");
		    break;
	       }
	       
	       tmpgs = GSnew();
	       if (iLevel == 0)
		    GScpy(tmpgs, RootGophers[0]);
	       else
		    GScpy(tmpgs, GDgetEntry(OldDirs[iLevel-1], GDgetCurrentItem(OldDirs[iLevel-1])));
	       
	       strcpy(newtitle, GDgetTitle(CurrentDir));
	       if (CURGetOneOption(CursesScreen, "Name for this bookmark? ", newtitle)<0)
		    break;
	       if (*newtitle == '\0')
		    break;

	       GSsetTitle(tmpgs, newtitle);

	       /*** Freeze the search, if there was one. ***/
	       if (GSgetType(tmpgs) == '7') {
		    char ickypath[512];
		    strcpy(ickypath, GSgetPath(tmpgs));
		    strcat(ickypath, "\t");
		    strcat(ickypath, Searchstring);
		    GSsetPath(tmpgs, ickypath);
		    GSsetType(tmpgs, '1');
	       }

	       GDaddGS(BookmarkDir, tmpgs);

	       ChangedDefs = TRUE;

	       break;
	  }

	  case 'd':  /*** Delete a bookmark ***/
	  {
	       GopherDirObj *tempgd;
	       int i;


	       if (GDgetNumitems(CurrentDir) == 1) {
		    /** Last item in the directory
		        Pop up a directory **/
		    tempgd = CurrentDir;
		    /** Don't destroy root level directory, or bookmarks **/
		    if (popgopher(&CurrentDir)==0 && tempgd != CurrentDir) {
			 if (tempgd != BookmarkDir)
			      GDdestroy(tempgd);
		    } else {
			 CursesErrorMsg("Sorry, can't delete top level directory.");
			 scline(-1, 1, CurrentDir);
		    }
		    ChangedDefs = TRUE;
	       }


	       tempgd = GDnew(GDgetNumitems(CurrentDir)+1);

	       for (i=0; i<GDgetNumitems(CurrentDir); i++) {
		    if (i != (GDgetCurrentItem(CurrentDir) - 1))
			 GDaddGS(tempgd, GDgetEntry(CurrentDir, i));
	       }
	       GDsetTitle(tempgd, GDgetTitle(CurrentDir));
	       GDsetCurrentItem(tempgd, GDgetCurrentItem(CurrentDir));

	       if (GDgetCurrentItem(CurrentDir) == GDgetNumitems(CurrentDir))
		    GDsetCurrentItem(tempgd, (GDgetCurrentItem(CurrentDir)-1));

	       GDdestroy(CurrentDir);

	       if (CurrentDir == BookmarkDir)
		    BookmarkDir = tempgd;
	       
	       CurrentDir = tempgd;
	       
	       ChangedDefs = TRUE;

	       break;
	  }

	  case 'M':
	  case 'm': 
	  {
	       GopherDirObj *tempGdir = NULL;
	       
	       while (popgopher(&CurrentDir) != -1) {
		    if (tempGdir != NULL)
			 GDdestroy(tempGdir);
		    tempGdir = CurrentDir;
	       }

	  }

	       break;

#ifdef VMS
	  /*
	   * I can't get ^C and ^Y working with PASTHRU on VMS,
	   * so we'll just fake it here.
	   */
	  case 3:
	  case 25:
#endif
	  case 'q':
	       /*** Quit the program ***/
	       if (TRUE == ReallyQuit()) {
		    bDone = TRUE;
		    CURexit(CursesScreen);
		    break;
	       }

	       break;

	  case 'Q':
	       /*** Quit the program, don't ask ***/
	       bDone = TRUE;
	       CURexit(CursesScreen);
	       break;
	       
	  case 'O':
	       /*** Change various program things ***/
	       SetOptions();
	       break;
		      
	  case '=':
	       describe_gopher("Gopher Item Information", 
			       GDgetEntry(CurrentDir, GDgetCurrentItem(CurrentDir)-1));
	       break;

	  case '^':
	  {
	       if (iLevel == 0)
		    describe_gopher("Gopher Directory Information",
				    RootGophers[0]);
	       else
		    describe_gopher("Gopher Directory Information",
				    GDgetEntry(OldDirs[iLevel-1],
					       GDgetCurrentItem(OldDirs[iLevel-1])-1));
	       break;
	  }
			       

	  case '?':
	  {
	       /*** Display help file ***/
	       GopherObj *tmpgs;

	       tmpgs = GSnew();
	       GSsetTitle(tmpgs, "Gopher Help File");
	       
	       CURexit(CursesScreen);
	       display_file(GOPHERHELP, tmpgs);
	       CURenter(CursesScreen);
	       GSdestroy(tmpgs);
	       break;
	  }

	  default :
	       CURBeep(CursesScreen);
	       Garbled = FALSE;
	       break;

	  }
     }
     if (ChangedDefs)
	  write_rc();

     GDdestroy(CurrentDir);
     exit(0);
     
     return(0);
}     


int
Load_Index(ZeGopher)
  GopherStruct *ZeGopher;
{
     Draw_Status("Searching Text...");
     refresh();

     return(Load_Index_or_Dir(ZeGopher, Searchstring));
}

int
Load_Dir(ZeGopher)
  GopherStruct *ZeGopher;
{
     Searchstring= NULL;
     return(Load_Index_or_Dir(ZeGopher, NULL));
}


int
twirl()
{
     static int twirlnum = 0;
     static char *twirls = "-/|\\";

     addch('\b');
     addch(*(twirls + (twirlnum++ % 4 )));
     refresh();

     return(0);
}

int
Load_Index_or_Dir(ZeGopher, Searchmungestr)
  GopherStruct *ZeGopher;
  char *Searchmungestr;
{
     int failed = 0;
     int sockfd;
     int i;
     char sTmp[10];
     static char DirTitle[512];
     GopherDirObj *NewDir = NULL;

     NewDir = GDnew(32);

     sTmp[0]= '\0';

     Draw_Status("Connecting..."); refresh();

     if ((sockfd = GSconnect(ZeGopher)) <0) {
	  check_sock(sockfd, GSgetHost(ZeGopher), GSgetPort(ZeGopher));
	  failed = 1;
     }
     else {
	  if (GSgetType(ZeGopher) == A_DIRECTORY) {
	       Draw_Status("Retrieving Directory..."); refresh();
	       writestring(sockfd, GSgetPath(ZeGopher));
	       writestring(sockfd, "\r\n");
	  }
	  else if (GSgetType(ZeGopher) == A_INDEX) {
	       Draw_Status("Searching..."); refresh();
	       writestring(sockfd, GSgetPath(ZeGopher));
	       writestring(sockfd, "\t");
	       writestring(sockfd, Searchmungestr);
	       writestring(sockfd, "\r\n");
	  }

	  i = GDfromNet(NewDir, sockfd, twirl);

	  if (i <= 0) {
	       CursesErrorMsg("Nothing available.");
	       failed = 1;
	       return(failed);
	  }

	  if (GSgetType(ZeGopher) == A_INDEX) {
	       sprintf(DirTitle, "%s: %s", GSgetTitle(ZeGopher), Searchmungestr);
	       GDsetTitle(NewDir, DirTitle);
	  }
          else
	       GDsetTitle(NewDir, GSgetTitle(ZeGopher));

	  /** Don't push an empty gopher directory... **/
	  if (CurrentDir != NULL)
	       pushgopher(CurrentDir); 

	  CurrentDir = NewDir;
	  
     }
     i = closenet(sockfd);
     return(failed);
}



int
process_request(ZeGopher)
  GopherStruct *ZeGopher;
{
     int failed=0;

     switch(GSgetType(ZeGopher)) {
     case -1:
	  break;

     case A_FILE:
	  Draw_Status("Receiving Information...");
	  refresh();
	  showfile(ZeGopher);
	  break;

     case A_GIF:
     case A_IMAGE:
     case A_MIME:
	  if (!SecureMode) {
	       Draw_Status("Receiving Information...");
	       refresh();
	       showfile(ZeGopher);
	  }
	  else
	       CursesErrorMsg("Sorry Cannot display this file anonymously");
	  break;

     case A_MACHEX:
     case A_PCBIN:
     case A_UNIXBIN:
	  if (!SecureMode)
	       Save_file(ZeGopher, NULL, NULL);
	  else
	       CursesErrorMsg("Sorry, cannot transfer files in securemode");
	  break;
	  
     case A_DIRECTORY:
	  Draw_Status("Receiving Directory...");
	  refresh();
	  failed = Load_Dir(ZeGopher);
	  break;
		    
     case A_TELNET:
     case A_TN3270:
	  do_tel_3270(ZeGopher);
	  break;

     case A_INDEX:
	  refresh();
	  Searchstring = do_index(ZeGopher);
	  Draw_Status("Searching Text...");
	  if (Searchstring != NULL)
	       failed=Load_Index(ZeGopher);
	  else
	       failed = 1;
	  break;
	  
     case A_CSO:
	  do_cso(ZeGopher);
	  break;

     case A_SOUND:
	  Draw_Status("Receiving Sound...");
	  refresh();
	  do_sound(ZeGopher);
	  break;

     case A_HTML:
	  Draw_Status("Receiving HTML page...");
	  refresh();
	  do_html(ZeGopher);
	  break;
     }
     return(failed);
}


void
describe_gopher(banner, ZeGopher)
  char *banner;
  GopherStruct *ZeGopher;
{
     char *tmpfilename;
     FILE *tmpfile;

     CURexit(CursesScreen);	/* do this *before* possible exit() below */

     Gopenfile = tmpfilename = tempnam("/tmp/moo", "gopher");

     if ((tmpfile = fopen(tmpfilename, "w")) == NULL)
	   fprintf(stderr, "Couldn't make a tmp file!\n"), exit(-1);

     fprintf(tmpfile,"Name=%s\nType=%c\nPort=%d\nPath=%s\nHost=%s\n\n",
	     GSgetTitle(ZeGopher),
	     GSgetType(ZeGopher),
	     GSgetPort(ZeGopher),
	     GSgetPath(ZeGopher),
	     GSgetHost(ZeGopher));

     fclose(tmpfile);

     display_file(tmpfilename, ZeGopher);
     if (unlink(tmpfilename) != 0)
	  fprintf(stderr, "Couldn't unlink!!!\n"), exit(-1);
     
     Gopenfile = NULL;
     free(tmpfilename);

     CURenter(CursesScreen); /* do this after unlink fails */

     return;
}
