/********************************************************************
* lindner
* 3.106
* 1995/01/20 04:52:24
* /home/arcwelder/GopherSrc/CVS/gopher+/gopher/gopher.c,v
* Exp
*
* 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:
* gopher.c,v
* Revision 3.106  1995/01/20  04:52:24  lindner
* Modifications for SOCKS
* Use VARIABLE length records for text on VMS
* Don't add the default gopher server when using the -b option.
*
* Revision 3.105  1994/12/07  07:43:22  lindner
* Fix for URLs on the command line from F.Macrdes
*
* Revision 3.104  1994/11/25  17:40:35  lindner
* Fix for folks with non gopher port of 70 in conf.h
*
* Revision 3.103  1994/11/16  18:52:52  lindner
* Fix for adding a text file to bookmarks
*
* Revision 3.102  1994/10/21  04:40:34  lindner
* IETF url format, plus ANSI printer
*
* Revision 3.101  1994/10/13  05:30:17  lindner
* Compiler complaint fixes
*
* Revision 3.100  1994/08/19  16:52:38  lindner
* Fixes for many gripe problems...
*
* Revision 3.99  1994/08/03  03:25:12  lindner
* Fix for osf
*
* Revision 3.98  1994/08/01  21:55:27  lindner
* Add back garbled flag
*
* Revision 3.97  1994/07/25  02:53:30  lindner
* Skip over type 'i' items, secure mode mods
*
* Revision 3.96  1994/07/19  20:20:00  lindner
* Gripe options added, telnet tracer etc.
*
* Revision 3.95  1994/07/08  05:55:28  lindner
* Fix to allow urls in place of hosts..
*
* Revision 3.94  1994/07/07  01:51:49  lindner
* Fix bug
*
* Revision 3.93  1994/07/06  15:36:27  lindner
* These changes are from F. Macrides:
*
* Moved declaration and initialization of setlocale_LangDir to
* globals.h.
*
* Note that setlocale() doesn't yet work with VMS Alphas, so it
* presently just returns without doing anything and the "hardcoded"
* English text is used.  It all works fine with VAXen.
*
* Added code to delete the temporary file when gopher is called with the
* -p or the new -u switch.
*
* Added J. Lance Wilkinson's ([email protected]) internationalization
* port to VMS and: 'L' operation for changing language on the fly.
*
* Pause after telnet/tn3270 to permit reading of any error messages.
* Added telnet and tn3270 to prompt for a URL via the 'w' command.
*
* Modified Alan's dialog box title and prompt for the 'w' command.
*
* NEVERSETOPTIONS compilation symbol disables ability to set options via
* SetOptions() and issues an appropriate message.
*
* NEVERSPAWN compilation symbol disables the spawn command and issues an
* appropriate message.
*
* On VMS, the NEVERSETOPTIONS and NEVERSPAWN symbols can be used to
* create a gopher image for CAPTIVE accounts which still permits
* functions that would be disabled by the -s or -S switch, but issues an
* appropriate message (rather than a DCL error message) on spawn attempt
* and precludes "creative" modifications of the display software and
* print command mappings.
*
* Added missing ! for NoShellMode under binary and and encoded save to
* disk if().
*
* Revision 3.92  1994/07/03  23:11:24  lindner
* Add internal download feature
*
* Revision 3.91  1994/06/29  05:20:37  lindner
* Check for new gopher.rc files, add support for A_APP items
*
* Revision 3.90  1994/06/09  22:13:37  lindner
* More language conversions
*
* Revision 3.89  1994/06/09  04:16:28  lindner
* Added option to allow 'd'elete only for bookmarks via a
* DELETE_BOOKMARKS_ONLY compilation symbol.
*
* Revision 3.88  1994/06/09  03:45:02  lindner
* More attempts to use sunos 4.1.3 setlocale
*
* Revision 3.87  1994/06/03  05:58:58  lindner
* Use LC_MESSAGES variable instead of LANG
*
* Revision 3.86  1994/05/27  18:08:51  lindner
* Fix for SetOptions() on VMS
*
* Revision 3.85  1994/05/27  02:22:23  lindner
* One more fix for International defs
*
* Revision 3.84  1994/05/26  19:36:45  lindner
* Fix bugs for ginternational
*
* Revision 3.83  1994/05/26  19:15:22  lindner
* Add GINTERNATIONAL stuff
*
* Revision 3.82  1994/05/25  21:46:35  lindner
* Fix for suck_sound
*
* Revision 3.81  1994/05/25  21:38:17  lindner
* Fix for mem prob..
*
* Revision 3.80  1994/05/25  20:59:42  lindner
* Fix for play command forking
*
* Revision 3.79  1994/05/24  07:00:38  lindner
* Don't allow users with rsh to specify a shell as an app..
*
* Revision 3.78  1994/05/24  05:50:10  lindner
* Fix for bad free() in SetOptions()
*
* Revision 3.77  1994/05/19  15:53:34  lindner
* Fix for vms
*
* Revision 3.76  1994/05/19  14:07:25  lindner
* use fast malloc on VMS VAXC
*
* Revision 3.75  1994/05/18  03:59:25  lindner
* Change to FIOsystem() for VMS
*
* Revision 3.74  1994/05/17  05:47:57  lindner
* Massive internationalization change
*
* Revision 3.73  1994/05/14  04:13:41  lindner
* Internationalization...
*
* Revision 3.72  1994/04/25  04:05:24  lindner
* FIOsystem() return values were reversed for VMS.
*
* Added code for mapping telnet and/or tn3270 commands to "- none -" for
* disabling them and having the client issue "Sorry, not implemented"
* messages on efforts to use Type 8 or T tuples.
*
* Added FIOsystem() failure messages for both VMS and Unix. (all from
* F.Macrides)
*
* Revision 3.71  1994/04/25  03:37:38  lindner
* Modifications for Debug() and mismatched NULL arguments, added Debugmsg
*
* Revision 3.70  1994/04/13  19:12:50  lindner
* Fix for ASK block bug
*
* Revision 3.69  1994/04/12  21:02:51  lindner
* Move file declaration inside
*
* Revision 3.68  1994/03/30  20:32:58  lindner
* Fix for deleting last item in a directory
*
* Revision 3.67  1994/03/08  15:55:09  lindner
* gcc -Wall fixes
*
* Revision 3.66  1994/03/08  03:25:55  lindner
* Fix for big bad telnet bug, additions for secure process i/o
*
* Revision 3.65  1994/03/04  23:41:17  lindner
* Better gripe, more signal handling, better forms
*
* Revision 3.64  1994/02/20  16:34:22  lindner
* Patch for weird gopher+ servers, add anon-ftp key, and localtime key
*
* Revision 3.63  1994/01/10  03:29:49  lindner
* fix for changes in GDdeleteGS
*
* Revision 3.62  1993/11/29  01:11:25  lindner
* 'v' no longer does anything if user is already viewing bookmarks.
* (Beckett)
*
* Add new routine, do_movie(), which will eventually allow viewing of
* movies.  For now, just display an error message.  In process_request(),
* add a switch case for the A_MOVIE type.  (Wolfe, Macrides)
*
* Fix the 'm' command so that it deletes the current menu's cache files.
* Also prevent 'm' from destroying bookmarks. (Beckett)
*
* In describe_gopher(), add TRUE argument to GStoLink() so that the Admin
* and ModDate information are displayed ('=' and '^').  (Macrides)
*
* Prevent describe_gopher() from destroying the cache file for the current
* item.  (Beckett, Wolfe)
*
* Revision 3.61  1993/11/03  03:55:12  lindner
* More stable file caching, subject line chopping in Gripe
*
* Revision 3.60  1993/11/02  21:17:36  lindner
* Better client side logging code
*
* Revision 3.59  1993/10/27  18:52:14  lindner
* Simplify VMS fixed/variable file code
*
* Revision 3.58  1993/10/26  18:45:09  lindner
* Move screen refresh stuff to CURwgetch()
*
* Revision 3.57  1993/10/26  18:01:01  lindner
* Allow saving to variable length record VMS files, remove Interrupted msg
*
* Revision 3.56  1993/10/22  20:24:14  lindner
* Fixes for VMS tempnam() (Fote)
*
* Revision 3.55  1993/10/22  20:06:37  lindner
* Add optional client logging
*
* Revision 3.54  1993/10/20  03:24:55  lindner
* Cleanup on a SIGHUP signal
*
* Revision 3.53  1993/10/13  16:47:23  lindner
* Fixes for empty bookmark addition problem
*
* Revision 3.52  1993/10/11  17:16:15  lindner
* Fote's mods for display applications
*
* Revision 3.51  1993/10/11  04:54:36  lindner
* Fix for segvs when exiting early
*
* Revision 3.50  1993/10/07  05:09:28  lindner
* Don't redraw the screen if we attempt to go up a directory level from
* the root level.
*
* In the 'o' command, allocate memory for the form based on screen width.
* Fix memory leak.
*
* In Gripe(), allocate memory for the form based on screen width.  Fix
* email parsing under VMS.  Fix problem where gopher did not delete mail
* temp files under VMS.
*
* In process_request(), make the status "Receiving xxxx..." a bit more
* descriptive for images, MIME files, and other files.
*
* Add NULL as third argument to Save_File in a couple of places
*
* Revision 3.49  1993/09/30  22:42:04  lindner
* Add option for bolding of searched words
*
* Revision 3.48  1993/09/29  20:50:57  lindner
* Always cleanupandexit instead of exit, allow deleting of last bookmark
*
* Revision 3.47  1993/09/26  09:19:44  lindner
* Changed errormsg wording
*
* Revision 3.46  1993/09/22  04:14:22  lindner
* Fix core dumps when exiting from bookmark screen, various cleanups
*
* Revision 3.45  1993/09/22  03:56:47  lindner
* Add support for tn3270 on oddball ports..
*
* Revision 3.44  1993/09/22  03:09:59  lindner
* Add command-line searching
*
* Revision 3.43  1993/09/22  01:15:49  lindner
* Add support for DEC HELP key/KEY_HELP
*
* Revision 3.42  1993/09/21  01:50:43  lindner
* Fix for caching of alternate views
*
* Revision 3.41  1993/09/18  04:40:47  lindner
* Additions to fix caching of Multiple view items
*
* Revision 3.40  1993/09/18  03:28:30  lindner
* Remove extra CURexit()
*
* Revision 3.39  1993/09/11  04:46:45  lindner
* Don't allow null applications to be added with the options code, Beep on network error
*
* Revision 3.38  1993/09/08  05:22:30  lindner
* Lobotomized the bolding code
*
* Revision 3.37  1993/09/08  01:11:51  lindner
* Added URL stuff to describe_gopher
*
* Revision 3.36  1993/09/03  03:33:45  lindner
* Inform user about mal-configuration if view has Unix piping on VMS.
*
* Deal with the problem of the gopher+ code setting view to NULL or ""
* here and there as if they were equivalent.
*
* Added code for mailing gripes from VMS systems, and fixed memory leak
* in the Unix code.
*
* Added v1.12b 'S' command for saving titles in a directory.
*
* Added titles to CURGetOneOption() calls.
*
* Revision 3.35  1993/08/25  02:57:52  lindner
* Fix for Fote mod to 'o' command
*
* Revision 3.34  1993/08/23  02:32:28  lindner
* Don't connect if nothing typed in 'o'
*
* Revision 3.33  1993/08/19  20:31:10  lindner
* remove excess variable
*
* Revision 3.32  1993/08/19  20:22:49  lindner
* Mitra's Debug patch
*
* Revision 3.31  1993/08/16  18:10:19  lindner
* Temporary code to fix DEC Alphas screen clearing, exit handler for VMS
*
* Revision 3.30  1993/08/16  17:58:20  lindner
* Removed REMOTEUSER ifdefs
*
* Revision 3.29  1993/08/09  20:28:04  lindner
* Mods for VMS for telnet dialog, argv[0]
*
* Revision 3.28  1993/08/09  20:17:59  lindner
* Fixes for CMULIB and NETLIB for VMS
*
* Revision 3.27  1993/08/05  03:24:21  lindner
* Fix for control-c on startup
*
* Revision 3.26  1993/08/04  22:08:47  lindner
* Fix for problems with '=' and '?' and /bin/mail Gripe mods
*
* Revision 3.25  1993/08/03  20:48:27  lindner
* Audio file fix from jqj
*
* Revision 3.24  1993/08/03  20:26:50  lindner
* Don't allow securemode types to use o
*
* Revision 3.23  1993/08/03  20:24:18  lindner
* Bigger Better Badder Options, inspired by jqj
*
* Revision 3.22  1993/08/03  04:43:56  lindner
* Fix for VMS unresolved variables
*
* Revision 3.21  1993/07/30  17:37:24  lindner
* SecureMode fix from Mitra
*
* Revision 3.20  1993/07/30  14:19:29  lindner
* Mitra autoexit patch
*
* Revision 3.19  1993/07/30  14:12:18  lindner
* Allow non-gplus stuff to cache
*
* Revision 3.18  1993/07/29  17:24:53  lindner
* Removed dead variable, $ and ! are synonomous now
*
* Revision 3.17  1993/07/27  05:28:48  lindner
* Mondo Debug overhaul from Mitra
*
* Revision 3.16  1993/07/27  02:02:22  lindner
* More comments, GDdelete method
*
* Revision 3.15  1993/07/26  20:29:40  lindner
* fix memory usage
*
* Revision 3.14  1993/07/26  15:35:56  lindner
* fix for longjmp params
*
* Revision 3.13  1993/07/23  04:42:58  lindner
* error checking and longjmp'ing
*
* Revision 3.12  1993/07/20  23:15:35  lindner
* Mods to cache askdata..
*
* Revision 3.11  1993/07/07  19:42:59  lindner
* fix for SGIs
*
* Revision 3.10  1993/06/29  06:18:09  lindner
* Prettier error msg for VMS
*
* Revision 3.9  1993/06/22  06:13:28  lindner
* took back fix for perror.h etc.
*
* Revision 3.8  1993/06/11  16:25:43  lindner
* Many bug fixes, open new gopher, shell escape, etc.
*
* Revision 3.7  1993/04/30  16:04:48  lindner
* Cleared gripe memory, fixed bug for long names in  check_sock
*
* Revision 3.6  1993/04/13  04:56:54  lindner
* New code for REMOTEUSERS from Mitra
* Better error messages for socket connections from jqj
*
* Revision 3.5  1993/03/24  16:59:54  lindner
* Major changes to the way things are displayed
*
* Revision 3.4  1993/03/18  23:32:07  lindner
* commented out forkoff stuff for now...
*
* Revision 3.3  1993/02/19  21:08:04  lindner
* Updated most routines to get defaults from gopher+ attribute types,
* Allow commands to be pipelines and almost have forking working :-)
*
* Fixed problems with bookmarks.  Still need to work on g+ bookmarks
*
* Revision 3.2  1993/02/11  18:25:45  lindner
* Fixed helpfile display.
*
*********************************************************************/


#include "gopher.h"
#if defined(VMS) && defined(A_LANGUAGE)
static  CursesObj       *LangScreen = NULL;
#endif
#include "Stdlib.h"
#include "Debug.h"

#include <errno.h>
#include "fileio.h"
#include "Malloc.h"
#include "patchlevel.h"

void describe_gopher();
extern int  twirl();


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

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

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

    char sTelCmd[128];

    char *cp;

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

    clear();
#ifdef VMS
    refresh();
#endif
    Dialogmess[0] = Gtxt("Warning!!!!!, you are about to leave the Internet",34);
    Dialogmess[1] = Gtxt("Gopher program and connect to another host. If",35);
    Dialogmess[2] = Gtxt("you get stuck press the control key and the",36);
#if defined(VMS) && defined(MULTINET)
    Dialogmess[3] = Gtxt("^ key, and then type q.",38);
#else
    Dialogmess[3] = Gtxt("] key, and then type quit",37);
#endif
    Dialogmess[4] = "";

    if (GSgetPort(ZeGopher) != 0)
         sprintf(sMessage1,Gtxt("Connecting to %.40s, port %d using %s.",39),
                 GSgetHost(ZeGopher),GSgetPort(ZeGopher),
                 (GSgetType(ZeGopher) == A_TN3270) ? "tn3270" : "telnet");
    else
         sprintf(sMessage1, Gtxt("Connecting to %.40s using %s.",40),
                 GSgetHost(ZeGopher),
                 (GSgetType(ZeGopher) == A_TN3270) ? "tn3270" : "telnet");

    Dialogmess[5] = sMessage1;

    cp = GSgetPath(ZeGopher);
    if (*cp != '\0')
         sprintf(sMessage2,
                 Gtxt("Use the account name \"%.40s\" to log in",41),
                 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 ****/
         RCdisplayCommand(GlobalRC, "Terminal/tn3270", GSgetHost(ZeGopher),
                          sTelCmd);

    } else {

         RCdisplayCommand(GlobalRC, "Terminal/telnet", GSgetHost(ZeGopher),
                          sTelCmd);
    }

    if (GSgetPort(ZeGopher) != 0 && GSgetPort(ZeGopher) != 23)
#if defined(VMS) && (defined(MULTINET) || defined(CMUIP))
         sprintf(sTelCmd+strlen(sTelCmd),
                 "/PORT=%d", GSgetPort(ZeGopher));
#else
    sprintf(sTelCmd+strlen(sTelCmd), " %d", GSgetPort(ZeGopher));
#endif

    CURexit(CursesScreen);

#ifdef Telnet_Trace
    Telnet_Trace(sTelCmd, ZeGopher);
#endif
    FIOsystem(sTelCmd);
    printf("\n\n");
    printf(Gtxt("\nPress <RETURN> to continue",120));
    fflush(stdout);
    fflush(stdin);
    fflush(stdin);
    (void) getchar();
    CURenter(CursesScreen);
    return;
}


#if defined(VMS) && defined(TELNET_TRACE)

/*
** Some sites are really paranoid about attempts to connect from their site
** to specific telnet hosts (e.g., MUDDs, etc.) -- so provide a log facility
** to track specific connectsions just to see how clients are finding them.
*/
#include <descrip.h>
#include <lnmdef.h>
#include <ssdef.h>
#include "syslog.h"

/*
**  Like logrequest() but should use a different log file just for this...
*/

int
trace_telnet(msg, gs)
 char *msg;
 GopherObj *gs;
{
    static boolean inited = FALSE;
    char *url = "";
    char *name = "";

    if (!gs)
       return(0);

    if (inited == FALSE) {
         openlog("gopher", (LOG_PID), LOG_LOCAL7); /* MULTINET allows LOCAL7 */
         setlogmask(LOG_UPTO(LOG_INFO));
         inited = TRUE;
    }
    if (gs != NULL) {
         url = GSgetURL(gs);
         name = GSgetTitle(gs);
    }

    syslog(LOG_INFO, "%s %s%s%s%s", msg, url, strlen(name)?" \"":"",
                                                   name, strlen(name)?"\"":"");

    return(0);
}

int
Telnet_Trace(char *sTelCmd, GopherStruct *ZeGopher)
{
       char    text[255];
       int     l;
static  int     buf_len;
static  int     i;
static  int     junk;
static  int     max_len;

       int     no_case = LNM$M_CASE_BLIND;
static  $DESCRIPTOR(lname_desc,"GOPHER_TELNET_LOG");
static  $DESCRIPTOR(tabnam,"LNM$SYSTEM_TABLE");
struct itmlst
   {
       short int buf_len;
       short int item_code;
       char *buffer;
       int *buf_length;
   };
static
   struct itmlst
               item_max[] = { {4,LNM$_MAX_INDEX,(char *)&max_len,&buf_len},
                                       {0,0,0,0}};
static
   struct itmlst
               item_str[] = { {4,LNM$_INDEX,(char *)&i,&junk},
                                       {0,LNM$_STRING,0,&buf_len},
                                       {0,0,0,0}};

 if (SS$_NORMAL != SYS$TRNLNM(&no_case,&tabnam,&lname_desc,0,&item_max))
       return;
 item_str[1].buffer = text;
 for (i=0; i<=max_len; i++) {
   item_str[1].buf_len = 255;
   if (SS$_NORMAL==SYS$TRNLNM(&no_case,&tabnam,&lname_desc,0,&item_str)) {
       text[buf_len] = '\0';
       if (strcmp(text,GSgetHost(ZeGopher))==0) {
           /* Log this session */
           text[sprintf(text," *-%.*s>",iLevel,
                       "-------------------------------")]='\0';
           trace_telnet(text,ZeGopher);
           if(GDgetLocation(CurrentDir) != NULL) {
               text[sprintf(text," + %.*s ",iLevel,
                           "                               ")]='\0';
               trace_telnet(text,GDgetLocation(CurrentDir));
           }
           for (l = iLevel; l>0; l--) {
               text[sprintf(text," +%.*s>",l,
                       "-------------------------------")]='\0';
               trace_telnet(text, GDgetEntry(OldDirs[l-1],
                                   GDgetCurrentItem(OldDirs[l-1])-1));
           }
           return;
       }
   }
 }
   return;
}
#endif


void
Gripe(gs)
GopherObj *gs;
{
    char *gripeprompt[15];
    char *gripemess[15];
    char *cp = NULL;
    char  email[128], mailcmd[256], version[128];
    int   i;
    char *mailargv[3];
    FileIO *fio;

#ifdef VMS
    FILE *f;
    char MailAddress[512], *tmpfilename;
#endif

    GSgetginfo(gs, TRUE);

    if (GSisGplus(gs) && (GSgetAdmin(gs) != NULL))
         cp = strchr(GSgetAdmin(gs),'<');

    /* at this point cp is NULL if (and hopefully only if)
     * the item is not Gopher+, there is no Admin for the item,
     * or there is no e-mail address in the admin definition
     */
    if (cp == NULL) {

#ifdef LOCAL_GRIPE_ADMINISTRATOR
# ifdef DOMAIN_FOR_LOCAL_GRIPES
         if (strstr(GSgetHost(gs),DOMAIN_FOR_LOCAL_GRIPES) != NULL )
# endif
              cp = LOCAL_GRIPE_ADMINISTRATOR;
#endif

         /* if the local gripe admin won't take it, either let the user
          * edit it, or put up an error message
          */
         if (cp == NULL) {
#ifdef MODIFIABLE_GRIPE_TO
              cp = "<>";
#else
              CursesErrorMsg(
                Gtxt("Can't find an administrator for this item, sorry!",48));
              return;
#endif
         }
    }

    strncpy(email, cp+1, sizeof(email));
    cp = strrchr(email, '>');
    if (cp != NULL)
         *cp = '\0';

#define ACHARS "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789%.@!-_"

    if (SecureMode || NoShellMode)
         if ( strspn(email, ACHARS) != strlen(email)||
             *email == '-') {
                 CursesErrorMsg("Can't find an administrator for this item, sorry!");
                 return;
         }

    /** Empty out the array **/
    for (i=3; i< 15; i++) {
         cp = (char *) malloc(sizeof(char) * COLS);
         bzero(cp,COLS*sizeof(char));
         gripeprompt[i] = "";
         gripemess[i] = cp;
    }

    for (i=0; i < 3; i++)
         gripemess[i] = NULL;

    gripeprompt[0] = Gtxt("Hit the Tab key at the end of each line you type.",49);
    gripeprompt[1] = Gtxt("Hit the Enter key to send your message.",50);

#ifdef MODIFIABLE_GRIPE_TO
    gripeprompt[2] = Gtxt("To",230);
    gripemess[2] = email;
    gripeprompt[13] = Gtxt("<Last Line>",231);
#else
    gripeprompt[2] = "";
#endif
    gripeprompt[3] = Gtxt("Subject",51);
    gripeprompt[4] = Gtxt("Problem",52);
    gripeprompt[14] = NULL;

    if (CURRequest(CursesScreen, GSgetAdmin(gs), gripeprompt, gripemess)!=0) {
         return;
    }

#ifdef MODIFIABLE_GRIPE_TO
    if (strlen(strcpy(email,gripemess[2])) == 0) {
       CursesErrorMsg(Gtxt("Cannot send mail to null address...",228));
       return;
    }
#endif
#ifdef VMS
    if ((strchr(email, '@') != NULL) && (strchr(email, '\"') == NULL))
          sprintf(MailAddress, MAIL_ADRS, email);
    else
          strcpy(MailAddress, email);

    tmpfilename = tempnam(NULL,NULL);

    if ((f = fopen(tmpfilename, "w")) == NULL) {
         CursesErrorMsg(Gtxt("Cannot send mail...",53));
         return;
    }

    sprintf(mailcmd, "%s/subject=\"%.70s\" %s %s",
          MAIL_COMMAND, gripemess[3], tmpfilename, MailAddress);
    free(gripemess[3]);
#ifdef DESCRIBE_GOPHER_GRIPE
    fprintf(f, Gtxt("Regarding the following Gopher item:\n",229));
    describe_gopher(" ",
                   GDgetEntry(CurrentDir, GDgetCurrentItem(CurrentDir)-1),
                       f);
    fprintf(f, "\n");
#endif
    for (i=14; i>3 && strlen(gripemess[i])==0; i--) {
         free(gripemess[i]);
         gripemess[i] = NULL;
    }

    for (i=4; (i< 15) && gripemess[i]; i++) {
         fprintf(f, "%s\n", gripemess[i]);
         free(gripemess[i]);
    }
    fprintf(f, "\n[Sent from within the ");
    fprintf(f, Gtxt("Internet Gopher Information Client v%s.%s.%d",102),
               GOPHER_MAJOR_VERSION, GOPHER_MINOR_VERSION, PATCHLEVEL);
    fprintf(f, "]\n");

    fclose(f);

    CURexit(CursesScreen);
    printf(Gtxt("Mailing gripe to %s...",54), MailAddress);
    FIOsystem(mailcmd);
    unlink(tmpfilename);
    free(tmpfilename);
    CURenter(CursesScreen);
#else

    mailargv[0] = MAIL_COMMAND;
    mailargv[1] = email;
    mailargv[2] = NULL;

    fio = FIOopenProcess(mailargv[0], mailargv, "w");

    if (fio == NULL) {
         CursesErrorMsg(Gtxt("Cannot send mail...",53));
         return;
    }

    *(gripemess[3] + 71) = '\0';
    FIOwritestring(fio, "Subject: ");
    FIOwritestring(fio, gripemess[3]);
    FIOwritestring(fio, "\n\n");

    free(gripemess[3]);

#ifdef DESCRIBE_GOPHER_GRIPE
   FIOwritestring(fio, Gtxt("Regarding the following Gopher item:\n",229));
   GStoLink(GDgetEntry(CurrentDir, GDgetCurrentItem(CurrentDir)-1),
            FIOgetfd(fio), TRUE);
   FIOwritestring(fio, "\n");
#endif


    for (i=14; i>3 && strlen(gripemess[i])==0; i--) {
           free(gripemess[i]);
           gripemess[i] = NULL;
    }

    for (i=4; i< 15 && gripemess[i]; i++) {
         FIOwritestring(fio, gripemess[i]);
         FIOwritestring(fio, "\n");
         free(gripemess[i]);
    }

    FIOwritestring(fio, "\n[Sent from within the ");
    sprintf(version, Gtxt("Internet Gopher Information Client v%s.%s.%d",102),
            GOPHER_MAJOR_VERSION, GOPHER_MINOR_VERSION, PATCHLEVEL);
    FIOwritestring(fio, version);
    FIOwritestring(fio, "]\n");

    FIOclose(fio);
#endif
}

/*
** 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)
 GopherObj *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"), CleanupandExit(-1);
         *inputline = '\0';
    }

    prompt[0] = Gtxt("Words to search for",55);
    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;
{
    FileIO *Play;
    int j;
    char buf[BUFSIZE];
    char playCmd[BUFSIZE];

    if (!RCdisplayCommand(GlobalRC, "Audio/basic", "", playCmd) ||
        !strncasecmp(playCmd, "- none -", 8) ||
        playCmd == NULL || playCmd[0] == '\0') {
         /*** Hey! no play command, bummer ***/
         /** We're forked at this point, and we can't display anything.. **/
         /*      CursesErrorMsg(Gtxt("Sorry, this machine doesn't support sounds",159));*/

         return;
    }

    Play = FIOopenCmdline(playCmd, "w");


    while(1) {
         j = read(sockfd, buf, BUFSIZE);

         if (j == 0)
              break;

         FIOwriten(Play, buf, j);
    }
}
#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(Gtxt("Sorry, this machine doesn't support sounds",159));
#else
    int sockfd;
    BOOLEAN Waitforchld = FALSE;

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

    /** Send out the request **/
    GStransmit(ZeGopher, sockfd, NULL, NULL, NULL);

    /** 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 */
         suck_sound(sockfd);
         exit(0);
    }

    /* Parent Process */

    closenet(sockfd);
    return;
#endif  /* not VMS */
}




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

void
showfile(ZeGopher)
 GopherObj *ZeGopher;
{
    char    *tmpfilename = NULL;
    FILE    *tmpfile;
    char    inputline[512];
    char    *view = NULL;
    boolean WritePipe = FALSE,
            ForkOff   = FALSE;
    boolean GS2FileSucceeded = TRUE;
    int     Child;

    DebugGSplusPrint(ZeGopher, "showfile:start");

    view = Choose_View(ZeGopher);

    Debug("showfile:Choose_View returned view=%s\n",view);

    if ((view == NULL) || (*view == '\0'))
         return;

    if (!RCdisplayCommand(GlobalRC, view, "", inputline) ||
        !strncasecmp(inputline, "- none -", 8) ||
        inputline == NULL || inputline[0] == '\0') {
         CursesErrorMsg(Gtxt("No display command is mapped to this view!",113));
         return;
    }
    Debug("showfile:inputline=%s\n",inputline);

    if (GSgetLocalFile(ZeGopher) == NULL ||
        GSgetLocalView(ZeGopher) == NULL ||
        strcmp(GSgetLocalView(ZeGopher), view)!= 0) {
         /*** We need to retrieve the file ***/

         if (*inputline == '|') {
#ifdef VMS
              CursesErrorMsg(Gtxt("Unix piping requested.  Check your configuration!",176));
              return;
#endif
              if ((tmpfile = popen(inputline+1, "w")) == NULL)
                   fprintf(stderr, Gtxt("Couldn't execute %s\n",81)
                           ,inputline), CleanupandExit(-1);
              WritePipe = TRUE;
              CURexit(CursesScreen);
         } else {
              /** Open a temporary file **/
#ifdef VMS
              tmpfilename = tempnam(NULL,NULL);
#else
              tmpfilename = tempnam("/tmp","gopher");
#endif

#if defined(VMS)  && defined(VMSRecords)
              if (GSisText(ZeGopher, view))
                   /*** Use VARIABLE length records for text on VMS ***/
                   tmpfile = fopen_VAR(tmpfilename, "w");
              else
                   /*** Use FIXED 512 records for binaries on VMS ***/
                   tmpfile = fopen_FIX(tmpfilename, "w");

              if (tmpfile == NULL) {
#else
              if ((tmpfile = fopen(tmpfilename, "w")) == NULL) {
#endif
                   CURexit(CursesScreen);
                   fprintf(stderr, Gtxt("Couldn't make a tmp file!\n",83)),
                   CleanupandExit(-1);
              }
              if (GSgetLocalFile(ZeGopher) != NULL &&
                  GSgetLocalView(ZeGopher) != NULL &&
                  strcmp(GSgetLocalView(ZeGopher), view) != 0)
                   unlink(GSgetLocalFile(ZeGopher));
              GSsetLocalFile(ZeGopher, tmpfilename);
         } /* | */

         if (inputline[strlen(inputline)-1] == '&') {
              inputline[strlen(inputline)-1] = '\0';
              ForkOff = TRUE;
         }

#ifndef VMS
         /* This is the child process, so exit.. */
         if (ForkOff) {
              if ((Child = fork()) < 0) {
                   ;/** Fork Error ***/
                   CursesErrorMsg("Fork Error!");
              }
              else if (Child >0) {
                   /*** Parent Process ***/
                   ;
                   CURenter(CursesScreen);
                   return;
              }
         }  /* Forkoff */
#endif

         Debug("showfile: view=%s ",view);
         Debug("command=%s ",inputline);
         Debug("file=%s",tmpfilename);
         Debug("/%s\n",GSgetLocalFile(ZeGopher));

         GS2FileSucceeded = GStoFile(ZeGopher, tmpfile, view, twirl);

         if (!GS2FileSucceeded) {
              GSsetLocalFile(ZeGopher, NULL);
              GSsetLocalView(ZeGopher, NULL);
         }
         else
              GSsetLocalView(ZeGopher, view);


         if (WritePipe)
              pclose(tmpfile);  /* data went down pipe - displayed there */
         else
              fclose(tmpfile);  /* data is in tmpfile, will display below */

    } /* GopehrPluss || LocalFile = NULL */

    logrequest("ARRIVED AT", ZeGopher);

    if (!WritePipe && GS2FileSucceeded)
         GSdisplay(ZeGopher);
    if (!GS2FileSucceeded)
         unlink(tmpfilename);

/*     if (!ForkOff) {
         printf("\nPress <RETURN> to continue: ");
         fflush(stdin);
         fflush(stdin);
         getchar();
         CURenter(CursesScreen);
    }*/


    if (tmpfilename!=NULL) free(tmpfilename);

    if (ForkOff)
         exit(-1);

    CURenter(CursesScreen);
    return;
}


/*
* Eventually this routine should allow viewing of the movie type by
* forking off a movie process to siphon the data across the net.
*/

void
do_movie(ZeGopher)
 GopherStruct *ZeGopher;
{
    showfile(ZeGopher);
}


/*
** 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);
}


#ifdef VMS
#include <perror.h>
#else
extern int h_errno;
extern int sys_nerr;
extern char *sys_errlist[];
extern int  errno;
#endif

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

    /* NULL DispStrings entries here, so can override below */
    DispStrings[3] = NULL;

    if (sockfd <0) {
         sprintf(DispString,
                 Gtxt("Cannot connect to host %.40s, port %d.",72),
                 host, port);

         switch (sockfd) {
         case -2:
              DispStrings[2] = Gtxt("Hostname is unknown.",100);
              break;
         case -3:
              DispStrings[2] = Gtxt("Unable to allocate a socket.",175);
              break;
         case -4:
#if defined(VMS) && defined(MULTINET)
              sprintf(DispString2, "%.78s.", vms_errno_string());
              DispStrings[2] = DispString2;
#else
              if (errno > 0 && errno <= sys_nerr) {
                   sprintf(DispString2, Gtxt("Connection failed: %s.",78),
#ifdef VMS
                           strerror(errno));
#else
                   sys_errlist[errno]);
#endif
                   DispStrings[2] = DispString2;
              } else
                   DispStrings[2] =
                        Gtxt("Connection to remote host failed.",79);
#endif
              break;
         default:
              DispStrings[2] = Gtxt("Unknown error.",177);
         }
         DispStrings[0] = DispString;
         DispStrings[1] = "";
         DispStrings[3] = NULL;

         CURBeep(CursesScreen);

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


BOOLEAN
ReallyQuit()
{
    char yesno[3];

    yesno[0] = 'y';
    yesno[1] = '\0';

    CURgetYesorNo(CursesScreen, Gtxt("Really quit (y/n) ?",124), yesno);
    if (*yesno == 'y') {
         return(TRUE);
    }

    return(FALSE);
}


void
CleanupandExit(exitval)
 int exitval;
{
    GopherDirObj *gd;
#ifdef VMS
    extern boolean DidCleanup;
#endif

    if (CursesScreen)
         CURexit(CursesScreen);

    if (ChangedDefs)
         RCtoFile(GlobalRC);

    do {
         if (CurrentDir != NULL && CurrentDir != RCgetBookmarkDir(GlobalRC))
              GDdestroy(CurrentDir);
    }
    while (popgopher(&CurrentDir) != -1);

    if (GlobalRC) {
         gd = RCgetBookmarkDir(GlobalRC);
         if (gd != NULL)
              GDdestroy(gd);
    }
    logrequest("EXIT", NULL);
#ifdef VMS
    DidCleanup = TRUE;
#endif
    exit(exitval);
}



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

#ifdef VMS
    if (!CursesScreen->inCurses) {
         /** Reprime the signal and set flag **/
         if (signal(SIGINT, controlc) == SIG_ERR)
              perror("signal died:\n"), CleanupandExit(-1);
         HadVMSInt = TRUE;
         return;
    }
#endif

    if (CurrentDir == NULL || GDgetNumitems(CurrentDir) <= 0) {
         CURexit(CursesScreen);
         fprintf(stderr,Gtxt("gopher: Nothing received for main menu, can't continue\n",60));
         CleanupandExit(1);
    }

    if (ReallyQuit())
    {
         CleanupandExit(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"), CleanupandExit(-1);


    /** Really should be siglongjmp **/
    longjmp(Jmpenv,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"), CleanupandExit(-1);


#endif

}



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

void
Initialize()
{

    Debugmsg("Initialize\n")
    GlobalRC        = RCnew();

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

    RCfromUser(GlobalRC);


    /*** Set up the curses environment ***/

    CursesScreen = CURnew();

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


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

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

#endif

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

    if (signal(SIGQUIT, CleanupandExit) == SIG_ERR)
         perror("signal died:\n"),
         CleanupandExit(-1);

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

    if (signal(SIGTERM, CleanupandExit) == SIG_ERR)
         perror("signal died:\n"),
         CleanupandExit(-1);

    if (signal(SIGHUP, CleanupandExit) == SIG_ERR)
         perror("signal died:\n"),
         CleanupandExit(-1);



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


    /*** Check for new global configuration file ***/
    if (RCisGlobalNew()) {
         int result;
         char *mess[2];
         mess[0] =  Gtxt("A new configuration is available.  Load it?",218);
         mess[1] = NULL;

         /*** Ask if the user wants to reload values from globalrc ***/
         result = CURDialog(CursesScreen, "", mess);

         if (result == 0) {
              /*** Do it...  ****/
              RCreadGlobalRC(GlobalRC);
         }
         ChangedDefs = TRUE;

    }
}



/*
* This stuff will set the options in a nice way...
*/

void
SetOptions()
{
    int choice, restricted = FALSE;
    char     *shell;

    static char **OptionNewApp = NULL, **OptionsMenu = NULL;
    static char **noyes = NULL;
    static char **printchoice = NULL;

    if (OptionNewApp == NULL) {
         OptionNewApp = (char**) malloc(sizeof(char*) * 4);
         OptionNewApp[0] = Gtxt("Content-Type Name",27);
         OptionNewApp[1] = Gtxt("Display Application",28);
         OptionNewApp[2] = Gtxt("Printing Application",29),
         OptionNewApp[3] = NULL;
    }

    if (OptionsMenu == NULL) {
         OptionsMenu = (char **) malloc(sizeof(char*) * 5);
         OptionsMenu[0] = Gtxt("General Options",30);
         OptionsMenu[1] = Gtxt("Configure Display Applications",31);
         OptionsMenu[2] = Gtxt("Configure Printing Applications",32);
         OptionsMenu[3] = Gtxt("Define New Content-Type",33);
         OptionsMenu[4] = NULL;
    }
    if (noyes == NULL) {
         noyes = (char**) malloc(sizeof(char*) * 3);
         noyes[0] = Gtxt("No",111);
         noyes[1] = Gtxt("Yes",180);
         noyes[2] = NULL;
    }
    if (printchoice == NULL) {
         printchoice = (char**) malloc(sizeof(char*) * 3);
         printchoice[0] = Gtxt("Default System Printer", 219);
         printchoice[1] = Gtxt("ANSI attached Printer", 220);
         printchoice[2] = NULL;
    }

    /*
     * If the user is running a restricted shell, don't allow him/her to
     * set applications, because he/she can specify a shell.  This is
     * independent of -s and -S because rsh users may have a shell.
     */
#ifndef VMS
    restricted = (shell = getenv("SHELL")) == NULL
         || strcmp(shell+strlen(shell) - 3, "rsh") == 0;

#endif

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

    /** Display Configuration Menu and allow user to choose **/
    choice = CURChoice(CursesScreen, Gtxt("Gopher Options",94), OptionsMenu,
                       Gtxt("Your Choice",181), -1);

    if (choice == -1)
         return;

    switch (choice) {

    case 0:
    {
         /** General Options **/

         Requestitem *items[3];
         Requestitem bold;
         Requestitem printer;

         bold.prompt  = Gtxt("Bold Search words in Builtin Pager",66);
         bold.thing   = CUR_CHOICE;
         bold.choices = noyes;
         bold.chooseitem = RCsearchBolding(GlobalRC);

         printer.prompt  = Gtxt("Print to",221);
         printer.thing   = CUR_CHOICE;
         printer.choices = printchoice;
         printer.chooseitem = RCuseANSIprinter(GlobalRC);


         items[0] = &bold;
         items[1] = &printer;
         items[2] = NULL;

         if (CURrequester(CursesScreen, Gtxt("General Options",30), items)==0) {
              RCsetSearchBolding(GlobalRC, items[0]->chooseitem);
              RCsetANSIprinter(GlobalRC,  items[1]->chooseitem);
              ChangedDefs = TRUE;
         }

         break;
    }

    case 1:
    case 2:
    {
         /** Display/Print Applications **/

         char     **gplusviews;
         int      i;
         int      numoptions;
         RCMapObj *rcm;
         char     **Responses;
         char     *zetitle;

         numoptions = RCMAgetNumEntries(GlobalRC->commands);
         gplusviews = (char **) malloc(sizeof(char *) * (numoptions + 1));
         Responses = (char **) malloc(sizeof(char *) * (numoptions + 1));

         for (i = 0 ; i < numoptions; i++) {
              rcm = RCMAgetEntry(GlobalRC->commands, i);
              gplusviews[i] = RCMgetView(rcm);
              Responses[i] = (char *) malloc(sizeof(char) * MAXSTR);
              if (choice == 1)
                   strcpy(Responses[i], RCMgetDisplaycmd(rcm));
              else
                   strcpy(Responses[i], RCMgetPrintcmd(rcm));
         }
         gplusviews[i] = NULL;
         Responses[i] = NULL;

         if (choice == 1)
              zetitle = Gtxt("Configure Display Applications",31);
         else
              zetitle = Gtxt("Configure Printing Applications",32);

         if (CURRequest(CursesScreen, zetitle, gplusviews,
                        Responses) == 0) {
              char tmpstr[512];

              while (i-- > 0) {
                   rcm = RCMAgetEntry(GlobalRC->commands, i);
                   if (choice == 1)
                        /** Display Applications **/
                        sprintf(tmpstr, "%s,%s,%s", gplusviews[i],
                                Responses[i], RCMgetPrintcmd(rcm));
                   else
                        /** Print Applications **/
                        sprintf(tmpstr, "%s,%s,%s", gplusviews[i],
                                RCMgetDisplaycmd(rcm), Responses[i]);

                   RCMAfromLine(GlobalRC->commands, tmpstr);
              }
              ChangedDefs = TRUE;
         }

         for (i = 0 ; i <= numoptions; i++)
              if (Responses[i])
                   free(Responses[i]);
         free(Responses);
         free(gplusviews);

         break;
    }

    case 3:
    {
         /** Add a new Content-Type and define **/
         /** its Display and Printing applications **/

         int  i;
         char *Responses[4];

         for (i = 0; i < 3; i++) {
              Responses[i] = (char *) malloc(sizeof(char) * MAXSTR);
              Responses[i][0] = '\0';
         }
         Responses[3] = NULL;

         if ((CURRequest(CursesScreen, Gtxt("Define New Content-Type",33),
              OptionNewApp, Responses) == 0) && (*Responses[0] != '\0')) {
              char tmpstr[512];

              sprintf(tmpstr, "%s,%s,%s", Responses[0], Responses[1],
                      Responses[2]);
              RCMAfromLine(GlobalRC->commands, tmpstr);
              ChangedDefs = TRUE;
         }

         for (i = 0; i < 3; i++)
              free(Responses[i]);
         break;
    }

    }  /** End of switch on option type **/
}



static char *search_string = NULL;

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 curitem;
    int Garbled = TRUE;
    boolean Bkmarksfirst = FALSE;
    boolean startAtHome = TRUE;


    /*** Initialize international languages ***/
    Gtxtlocale(LC_ALL, "");


#if defined(GINTERNATIONAL) && !defined(VMS)

#ifdef LC_CTYPE
    Gtxtlocale(LC_CTYPE, "iso_8859_1");
#endif

#ifdef LC_MESSAGES
    if (getenv("LC_MESSAGES") != NULL)
        Gtxtlocale(LC_MESSAGES, getenv("LC_MESSAGES"));
#endif /* LC_MESSAGES */

#ifndef __osf__
    Gtxtopen("gopher", 0);  /** OSF seems to mess this up... **/
#endif

    if (Gcatd == (nl_catd) -1) {
         /** Try finding it elsewhere.. **/
         char fname[256];
         if (getenv("LC_MESSAGES") != NULL) {
              sprintf(fname, "%s/gophernls/%s.cat", GOPHERLIB, getenv("LC_MESSAGES"));
              Gtxtopen(fname, 0);
         }
    }
#endif    /* GINTERNATIONAL & !VMS */

#ifdef VMS
    argv[0] = "gopher";
#endif

    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, "DsSbrp:t:T:i:")) != -1)
         switch (c) {
         case 's':
              SecureMode = TRUE;
              break;
         case 'S':     /* Similar to secure, but assumes own Unix account */
                       /* No change in behaviour if this not set */
               NoShellMode = TRUE;
               break;
         case 'i':
              search_string = optarg;
              break;

         case 'p':
              GSsetPath(RootGophers[0], optarg);
              GSsetPath(RootGophers[1], optarg);
              startAtHome = FALSE;
              break;
         case 'T':
              GSsetType(RootGophers[0], *optarg);
              GSsetType(RootGophers[1], *optarg);
              if (optarg[1] == '+') {
                   GSsetGplus(RootGophers[0], TRUE);
                   GSsetGplus(RootGophers[1], TRUE);
              } else if (optarg[1] == '?') {
                   GSsetAsk(RootGophers[0], TRUE);
                   GSsetAsk(RootGophers[1], TRUE);
              }
              startAtHome = FALSE;
              break;
         case 't':
              GSsetTitle(RootGophers[0], optarg);
              GSsetTitle(RootGophers[1], optarg);
              break;
         case 'D':
              DEBUG = TRUE;
              Debugmsg("gopher starting - debug on\n")
              break;
         case 'r':
              RemoteUser = TRUE;
              break;
         case 'b':
              Bkmarksfirst = TRUE;
              break;
         case '?':
              errflag++;
         }


    if (errflag) {

         fprintf(stderr, Gtxt("Usage: %s [-sSbDr] [-T type] [-p path] [-t title] [hostname port]+\n",9), argv[0]);
         fprintf(stderr,
           Gtxt("     -s      secure mode, users without own account\n",10));
         fprintf(stderr,
           Gtxt("     -S      secure mode, users with own account\n",11));
         fprintf(stderr,
           Gtxt("     -p path specify path to initial item\n",12));
         fprintf(stderr,
           Gtxt("     -T type Type of initial item\n",13));
         fprintf(stderr,
           Gtxt("     -i      Search argument (for -T 7)\n",14));
         fprintf(stderr,
           Gtxt("     -b      Bookmarks first\n",15));
         fprintf(stderr,
           Gtxt("     -r      Remote user\n",16));
         fprintf(stderr,
           Gtxt("     -D      Debug mode\n",17));
         CleanupandExit(-1);
    }

    /**** Get host #1 from the command line ****/

    if (optind < argc) {

         if (strchr(argv[optind], ':') != NULL) {
              GopherObj *tempGopher = GSnew();
              int doneflags = 0;

              doneflags = GSfromURL(tempGopher, argv[optind],
                                    AFTP_HOST, AFTP_PORT, doneflags);
              GScpy(RootGophers[0], tempGopher);
              GScpy(RootGophers[1], tempGopher);
              GSdestroy(tempGopher);
              startAtHome = FALSE;
              optind++;
         } else {
              GSsetPort(RootGophers[0], 70);  /* Just in case the default */
              GSsetPort(RootGophers[1], 70);  /* isn't 70 */

              GSsetHost(RootGophers[0], argv[optind]);
              GSsetHost(RootGophers[1], "");  /** Nuke the 2nd alternative host **/
              GSsetPort(RootGophers[1], 0);
              numhosts = 1;
              startAtHome = FALSE;
              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, Gtxt("Home Gopher server: %.59s",97),
                 GSgetHost(RootGophers[0]));
         GSsetTitle(RootGophers[0], sTmp);
         sprintf(sTmp, Gtxt("Home Gopher server: %.59s",97),
                 GSgetHost(RootGophers[1]));
         GSsetTitle(RootGophers[1], sTmp);
    }

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

#ifdef SOCKS
    SOCKSinit(argv[0]);
#endif

    Initialize();

    if (startAtHome && (RCgetHome(GlobalRC) != NULL)) {
         GScpy(RootGophers[0], RCgetHome(GlobalRC));
         GScpy(RootGophers[1], RCgetHome(GlobalRC));
    }

    if (Bkmarksfirst) {
         CurrentDir = RCgetBookmarkDir(GlobalRC);

    } 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 &&
             GSgetType(RootGophers[0]) != A_INDEX) {
              refresh();
              if( GSgetLocalFile(RootGophers[0]) != NULL)
                  unlink(GSgetLocalFile(RootGophers[0]));
              CleanupandExit(0);
         }
    }

#ifdef DEBUGGING
       if (CurrentDir == NULL)  {
            Debugmsg("CurrentDir=NULL\n");
       }
       else
            if (GDgetNumitems(CurrentDir) <=0) {
                 Debugmsg("GDgetNumitems<=0\n");
            }
#endif

    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) {
              Debugmsg("Trying alternate site\n");
              GSsetHost(RootGophers[0], argv[optind]);
              optind++;
                  if (optind < argc) {
                          GSsetPort(RootGophers[0], atoi(argv[optind]));
                          optind++;
                  }
              process_request(RootGophers[0]);
         }
    }

    if (CurrentDir == NULL || GDgetNumitems(CurrentDir) <= 0) {
#ifdef DEBUGGING
       if (CurrentDir == NULL)  {
            Debugmsg("B: CurrentDir=NULL\n");
       }
       else if (GDgetNumitems(CurrentDir) <=0) {
            Debugmsg("B: GDgetNumitems<=0\n");
       }
#endif

       CURexit(CursesScreen);
       fprintf(stderr,
               Gtxt("%s: Nothing received for main menu, can't continue\n",188)
               , argv[0]);
       CleanupandExit(1);
  }

    while (bDone == FALSE)
    {
         /* Set up a longjmp for control-cs **/
         if (setjmp(Jmpenv) != 0) {
              /* This part only runs when returning from a longjmp */
              Garbled = TRUE;
         }

         GetMenu(CurrentDir, &TypedChar, Garbled);
         Garbled = TRUE;
#ifdef DESCRIBE_GOPHER
#undef DESCRIBE_GOPHER
#endif
#ifdef DESCRIBE_GOPHER_GRIPE
#define DESCRIBE_GOPHER(a,b)    describe_gopher((a),(b), NULL)
#else
#define DESCRIBE_GOPHER(a,b)    describe_gopher((a),(b))
#endif

         switch(TypedChar)
         {
         case '\r':
         case '\n':
              /*** Select the designated item ***/
                  if ((curitem = GDgetCurrentItem(CurrentDir)) <= 0)
                          curitem = 1;
              if (process_request(GDgetEntry(CurrentDir, curitem-1))==1)
                   ;  /** Don't do anything if we failed, I guess **/
#if defined(VMS) && defined(A_LANGUAGE)
              if (CurrentDir == setlocale_LangDir) {
                   sprintf(sTmp, Gtxt("Home Gopher server: %.59s",97),
                                                   GSgetHost(RootGophers[0]));
                   GDsetTitle(OldDirs[0], sTmp);
                   GSsetTitle(RootGophers[0], sTmp);
                   Garbled = TRUE;
                   goto Backup_Resetlocale;
              }
#endif
              break;

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

         case 'u':
         case 'U':
#ifdef AUTOEXITONU
         case 'q':
#endif
#if defined(VMS) && defined(A_LANGUAGE)
         Backup_Resetlocale:
#endif
         {
              GopherDirObj *tempGdir;

              logrequest("PREVIOUS DIRECTORY", NULL);

              /*** 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 != RCgetBookmarkDir(GlobalRC))
#if defined(VMS) && defined(A_LANGUAGE)
                     if (tempGdir != setlocale_LangDir)
#endif
                        GDdestroy(tempGdir);
#ifdef AUTOEXITONU
              } else {
                       bDone = TRUE;
                       CURexit(CursesScreen);
#else
              } else {
                   Garbled = FALSE;
#endif
              }
         }
              break;

         case 'g':
              /*** Gripe! ***/
#ifdef NOGRIPE_SECURE
              if (SecureMode || NoShellMode) {
                   CursesErrorMsg(Gtxt
                                  ("Sorry, can't submit gripes in securemode",
                                                       227));
                   break;
              }
#endif
              Gripe(GDgetEntry(CurrentDir,GDgetCurrentItem(CurrentDir)-1));
              break;

         case 's':  /*** Save a file directly ***/
              if (SecureMode || NoShellMode) {
                   CursesErrorMsg(Gtxt("Sorry, can't save files with this account",152));
                   break;
              }
              Save_file(GDgetEntry(CurrentDir,GDgetCurrentItem(CurrentDir)-1), NULL, NULL);
              break;

         case 'S':   /*** Save list of menu items (objects) to a file ***/
              if (SecureMode || NoShellMode) {
                   CursesErrorMsg(Gtxt("Sorry, can't save files with this account",152));
                   break;
              }
              Save_list(CurrentDir);
              break;

         case 'D':
              Download_file(GDgetEntry(CurrentDir,GDgetCurrentItem(CurrentDir)-1));
              break;

         case 'o': /** Open a new session **/
         {
              GopherObj *tempgs = GSnew();
              char *prompts[4];
              char *responses[4];
              int i;

              if (SecureMode) {
                   CursesErrorMsg(Gtxt("Sorry, you're not allowed to do this",164));
                   break;
              }

              prompts[0]=Gtxt("Hostname",99);
              prompts[1]=Gtxt("Port",118);
              prompts[2]=Gtxt("Selector (Optional)",144);
              prompts[3]=NULL;

              for (i=0; i<3; i++) {
                   responses[i] = (char*)malloc(COLS*sizeof(char));
                   *responses[i] = '\0';
              }
              strcpy(responses[1], "70");

              responses[3] = NULL;

              if ((CURRequest(CursesScreen,
                              Gtxt("Connect to a new Gopher Server",75),
                              prompts, responses) != -1)
                  && *responses[0]!='\0') {

                   GSsetType(tempgs, A_DIRECTORY);
                   GSsetTitle(tempgs, responses[0]);
                   GSsetHost(tempgs, responses[0]);
                   GSsetPort(tempgs, atoi(responses[1]));
                   GSsetPath(tempgs, responses[2]);

                   Load_Dir(tempgs);
              }

              GSdestroy(tempgs);
              for (i=0; i<3; i++)
                   free(responses[i]);
              break;
         }


         case 'w': /** Open a new session from a URL **/
         {
              GopherObj *tempgs = GSnew();
              char *prompt[2];
              char *response[2];
              int doneflags = 0;

              if (SecureMode) {
                   CursesErrorMsg("Sorry, you're not allowed to do this");
                   break;
              }

              prompt[0]=
                  "http, gopher, ftp, telnet or tn3270 URL to connect to:";
              prompt[1]=NULL;

               response[0] = (char*)malloc(COLS*sizeof(char));
              *response[0] = '\0';
               response[1] = NULL;

              if ((CURRequest(CursesScreen, "Connect to a new server",
                         prompt, response) != -1) && *response[0]!='\0') {

                   doneflags = GSfromURL(tempgs, response[0],
                                         AFTP_HOST, AFTP_PORT, doneflags);
                   if (doneflags & G_HOST) {
                        if (!(doneflags & G_PATH))
                             GSsetPath(tempgs, "");
                        if (!(doneflags & G_TYPE))
                             GSsetType(tempgs, A_DIRECTORY);
                        if (!(doneflags & G_NAME))
                             GSsetTitle(tempgs, response[0]);

                        process_request(tempgs);
                   }
              }

              GSdestroy(tempgs);
              free(response[0]);
              break;
         }

         case 'f': /** Open an aFTP session **/
         {
              GopherObj *tempgs = GSnew();
              char *prompts[3];
              char *responses[3];
              char FTPpath[256];
              int i;

              if (SecureMode) {
                   CursesErrorMsg(Gtxt("Sorry, you're not allowed to do this",164));
                   break;
              }

              prompts[0]=Gtxt("anonymous FTP Host",187);
              prompts[1]=Gtxt("Selector (Optional)",144);
              prompts[2]=NULL;

              for (i=0; i<2; i++) {
                   responses[i] = (char *) malloc(sizeof(char)*COLS);
                   *responses[i] = '\0';
              }

              responses[2] = NULL;

              if ((CURRequest(CursesScreen,
                  Gtxt("Connect to an anonymous FTP Server via the Gopher gateway",76),
                          prompts, responses) == -1) || *responses[0] == '\0')
                   break;

              GSsetType(tempgs, A_DIRECTORY);
              GSsetTitle(tempgs, responses[0]);
              GSsetHost(tempgs, AFTP_HOST);
              GSsetPort(tempgs, AFTP_PORT);
              if (responses[1][strlen(responses[1])-1] != '/')
                   strcat(responses[1], "/");
              sprintf(FTPpath, "ftp:%s@%s%s", responses[0],
                             (*responses[1] == '/') ? "" : "/", responses[1]);
              GSsetPath(tempgs, FTPpath);

              Load_Dir(tempgs);

              GSdestroy(tempgs);
              for (i=0; i<2; i++)
                   free(responses[i]);
              break;
         }

         case 'r':  /** Go to root menu of current item **/
         {
              char GSType;
              GopherObj *tempgs;

              if (SecureMode) {
                   CursesErrorMsg(Gtxt("Sorry, you're not allowed to do this",164));
                   break;
              }

                  if ((curitem = GDgetCurrentItem(CurrentDir)) <= 0)
                          curitem = 1;
              GSType = GSgetType(GDgetEntry(CurrentDir, curitem-1));

              tempgs = GSnew();

              /** These Gopher objects don't necessarily point
                  to machines running Gopher servers **/
              if (GSType == A_CSO || GSType == A_TELNET ||
                  GSType == A_TN3270 || GSType == A_ERROR ||
                  GSType == A_INFO) {

                   /** Try the parent instead **/
                   if(GDgetLocation(CurrentDir) != NULL)
                        GScpy(tempgs, GDgetLocation(CurrentDir));
                   else
                        GScpy(tempgs, GDgetEntry(OldDirs[iLevel-1],
                                    GDgetCurrentItem(OldDirs[iLevel-1])-1));
              }
              else
                   if ((curitem = GDgetCurrentItem(OldDirs[iLevel-1])) <= 0)
                        curitem = 1;
                   GScpy(tempgs, GDgetEntry(CurrentDir,
                         GDgetCurrentItem(CurrentDir)-1));

              GSsetType(tempgs, A_DIRECTORY);
              GSsetTitle(tempgs, Gtxt("Root menu: ",135));
              STRcat(tempgs->Title, GSgetHost(tempgs));
              GSsetPath(tempgs, NULL);

              Load_Dir(tempgs);
              GSdestroy(tempgs);
              break;
         }

         case 'R':  /** Go to root menu of current menu **/
         {
              GopherObj *tempgs;

              if (SecureMode) {
                   CursesErrorMsg(Gtxt("Sorry, you're not allowed to do this",164));
                   break;
              }

              if (iLevel == 0) {
                   Garbled = FALSE;
                   break;
              }

              tempgs = GSnew();

              if (GDgetLocation(CurrentDir) != NULL)
                   GScpy(tempgs, GDgetLocation(CurrentDir));
              else
                   GScpy(tempgs, GDgetEntry(OldDirs[iLevel-1],
                                  GDgetCurrentItem(OldDirs[iLevel-1])-1));

              GSsetType(tempgs, A_DIRECTORY);
              GSsetTitle(tempgs, Gtxt("Root menu: ",135));
              STRcat(tempgs->Title, GSgetHost(tempgs));
              GSsetPath(tempgs, NULL);

              Load_Dir(tempgs);
              GSdestroy(tempgs);
              break;
         }

         case 'v':  /** View bookmark list **/
         {
              logrequest("VIEW BOOKMARKS", NULL);

              if (RCgetBookmarkDir(GlobalRC) == NULL) {
                   CursesErrorMsg(Gtxt("No bookmarks are defined",112));
                   break;
              }

              /** Don't do anything if we are already viewing bookmarks **/
              if (CurrentDir == RCgetBookmarkDir(GlobalRC)) {
                   Garbled = FALSE;
                   break;
              }

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

              CurrentDir = RCgetBookmarkDir(GlobalRC);

              break;
         }

         case 'a': /** add current item as a bookmark **/
         {
              GopherObj *tmpgs;
              char newtitle[256];

              if (RCgetBookmarkDir(GlobalRC) == NULL) {
                   RCsetBookmarkDir(GlobalRC,GDnew(32));
                   GDsetTitle(RCgetBookmarkDir(GlobalRC), Gtxt("Bookmarks",67));
              }

              tmpgs = GSnew();
              GScpy(tmpgs, GDgetEntry(CurrentDir, GDgetCurrentItem(CurrentDir)-1));

              strcpy(newtitle, GSgetTitle(tmpgs));
              if (CURGetOneOption(CursesScreen, newtitle,
                          Gtxt("Name for this bookmark:",109), newtitle)==0) {
                   if (*newtitle != '\0') {
                        GSsetTitle(tmpgs, newtitle);
                        GDaddGS(RCgetBookmarkDir(GlobalRC), tmpgs);
                        ChangedDefs = TRUE;
                   }
              }

              logrequest("ADD BOOKMARK", tmpgs);

              /* Clear the local file name so the file isn't deleted */
              GSsetLocalFile(tmpgs, NULL);

              GSdestroy(tmpgs);

              break;
         }

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

              if (RCgetBookmarkDir(GlobalRC) == NULL) {
                   RCsetBookmarkDir(GlobalRC,GDnew(32));
                   GDsetTitle(RCgetBookmarkDir(GlobalRC), Gtxt("Bookmarks",67));
              }

              if (CurrentDir == RCgetBookmarkDir(GlobalRC)) {
                   CursesErrorMsg(Gtxt("Sorry, can't make a bookmark of bookmarks",151));
                   break;
              }

              tmpgs = GSnew();
              if (GDgetLocation(CurrentDir) != NULL)
                   GScpy(tmpgs, GDgetLocation(CurrentDir));
              else if (iLevel == 0)
                   GScpy(tmpgs, RootGophers[0]);
              else
                   GScpy(tmpgs, GDgetEntry(OldDirs[iLevel-1],
                                           GDgetCurrentItem(OldDirs[iLevel-1])-1));

                  if ((GSgetType(tmpgs) == '7') &&
                          ((tmps = GSgetPath(tmpgs)) == NULL ||
                               *tmps == '\0' ||
                               Searchstring == NULL ||
                               *Searchstring== '\0')) {
                          CursesErrorMsg("Sorry, can't make a bookmark of this search");
                          GSdestroy(tmpgs);
                          break;
          }
              strcpy(newtitle, GDgetTitle(CurrentDir));
              if (CURGetOneOption(CursesScreen, newtitle,
                       Gtxt("Name for this bookmark:",109), newtitle)==0) {

                   if (*newtitle != '\0') {
                        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(RCgetBookmarkDir(GlobalRC), tmpgs);
                        ChangedDefs = TRUE;
                   }
              }
              logrequest("ADD BOOKMARK", tmpgs);

              GSdestroy(tmpgs);
              break;
         }

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


#ifdef DELETE_BOOKMARKS_ONLY
             if (CurrentDir != RCgetBookmarkDir(GlobalRC)) {
                  CursesErrorMsg(Gtxt("The 'd'elete command is only for bookmarks.",189));
                 break;
            }
#endif

              logrequest("DELETE BOOKMARK", GDgetEntry(CurrentDir, GDgetCurrentItem(CurrentDir)-1));

              if (GDgetNumitems(CurrentDir) == 1)
                   tempgd = NULL;
              else
                   tempgd=GDdeleteGS(CurrentDir,(GDgetCurrentItem(CurrentDir)-1));

              if (CurrentDir == RCgetBookmarkDir(GlobalRC))
                   RCsetBookmarkDir(GlobalRC, tempgd);

              if (tempgd == NULL) {
                   Debugmsg("Last item - pip up a directory")
                   tempgd = CurrentDir;
                   if (popgopher(&CurrentDir)==0 && tempgd != CurrentDir)
                        GDdestroy(tempgd);
                   else {
                        CursesErrorMsg(Gtxt("Sorry, can't delete top level directory.",149));
                        scline(-1, 1, CurrentDir);
                   }
                   ChangedDefs = TRUE;
              } else {
                    CurrentDir = tempgd;

                    ChangedDefs = TRUE;
              }
              break;
         }

#if defined(VMS) && defined(A_LANGUAGE)
         case 'L':  /** add language change    **/
              if (CurrentDir == setlocale_LangDir)
                   break;
              if (setlocale_LangDir== NULL) {
                   CursesErrorMsg(Gtxt("Sorry, no language choice available",
                                       221));
              }
              else
              {
                  /** Don't push an empty gopher directory... **/
                  if (CurrentDir != NULL)
                       pushgopher(CurrentDir);

                  CurrentDir = setlocale_LangDir;
              }
              break;
#endif

         case '$':
         case '!':
#ifdef NEVERSPAWN
              CursesErrorMsg(Gtxt("Sorry, can't spawn in with this account",153));
              break;
#else
              if (SecureMode || NoShellMode) {
                   CursesErrorMsg(Gtxt("Sorry, can't spawn in with this account",153));
                   break;
              }
              /*** Spawn to shell or DCL ***/
              CURexit(CursesScreen);
#ifdef VMS
              printf(Gtxt("Spawning DCL subprocess.  Logout to return to Gopher.\n",169));
              fflush(stdout);
              i = FIOsystem("");
#else
              printf(Gtxt("Spawning your default shell.  Type 'exit' to return to Gopher.\n\n",170));
              fflush(stdout);
              i = FIOsystem(getenv("SHELL"));
#endif
              CURenter(CursesScreen);
              if (i < 0)
#ifdef VMS
                   CursesErrorMsg(Gtxt("Spawn to DCL failed!",167));
#else
                   CursesErrorMsg(Gtxt("Spawn to default shell failed!",168));
#endif
#endif    /* NEVERSPAWN */
              break;


              /*** Pop back to the main menu ***/
         case 'M':
         case 'm':
         {
              GopherDirObj *tempGdir;

              tempGdir = CurrentDir;
              while (popgopher(&CurrentDir) != -1) {
                   if (tempGdir != NULL && tempGdir != RCgetBookmarkDir(GlobalRC))
                        GDdestroy(tempGdir);
                   tempGdir = CurrentDir;
              }

         }

              break;

#ifndef AUTOEXITONU
         case 'q':
              /*** Quit the program ***/
              if (TRUE == ReallyQuit()) {
                   bDone = TRUE;
                   CURexit(CursesScreen);
                   break;
              }

              break;
#endif /*AUTOEXITONU*/
#ifdef VMS
         case '\032': /* ^Z */
#endif
         case 'Q':
              /*** Quit the program, don't ask ***/
              bDone = TRUE;
              CURexit(CursesScreen);
              break;

         case 'O':
              /*** Change various program things ***/
#ifdef NEVERSETOPTIONS
              CursesErrorMsg(Gtxt("Sorry, you're not allowed to do this",164));
#else
              SetOptions();
#endif
              break;

         case '=':
              DESCRIBE_GOPHER(Gtxt("Gopher Item Information",93),
                              GDgetEntry(CurrentDir, GDgetCurrentItem(CurrentDir)-1));
              break;

         case '^':
         {
              if (GDgetLocation(CurrentDir) != NULL)
                   DESCRIBE_GOPHER(Gtxt("Gopher Directory Information",89),
                                   GDgetLocation(CurrentDir));
              else if (iLevel == 0)
                   DESCRIBE_GOPHER(Gtxt("Gopher Directory Information",89),
                                   RootGophers[0]);
              else
                   DESCRIBE_GOPHER(Gtxt("Gopher Directory Information",89),
                                   GDgetEntry(OldDirs[iLevel-1],
                                              GDgetCurrentItem(OldDirs[iLevel-1])-1));
              break;
         }


         case '?':
         case KEY_HELP:
         {
              /*** Display help file ***/
              GopherObj *tmpgs;
              char *tmpurl = (char *)malloc(17+255);

              tmpgs = GSnew();
              GSsetType(tmpgs, A_FILE);
              GSsetTitle(tmpgs, Gtxt("Gopher Help File",92));

#ifdef VMS
              GSsetLocalFile(tmpgs, Gtxt(GOPHERHELP,223));
#else
              GSsetLocalFile(tmpgs, Gtxt(GOPHERHELP,225));
#endif
#ifdef GOPHERHELP_SECURE
              if (SecureMode)
#ifdef VMS
                 GSsetLocalFile(tmpgs, Gtxt(GOPHERHELP_SECURE,224));
#else
                 GSsetLocalFile(tmpgs, Gtxt(GOPHERHELP_SECURE,226));
#endif
#endif
              GSsetLocalView(tmpgs, "text/plain");

              if (tmpurl != NULL) {
                   /* set a URL for logrequest() in showfile() */
                   strcpy(tmpurl, "file://localhost");
#ifdef VMS
                   strcat(tmpurl, Gtxt(GOPHERHELP,223));
#else
                   strcat(tmpurl, Gtxt(GOPHERHELP,225));
#endif
#ifdef GOPHERHELP_SECURE
                   if (SecureMode)
#ifdef VMS
                       strcat(tmpurl, Gtxt(GOPHERHELP_SECURE,224));
#else
                       strcat(tmpurl, Gtxt(GOPHERHELP_SECURE,226));
#endif
#endif
                   GSsetURL(tmpgs, tmpurl);
                   free(tmpurl);
              }

              showfile(tmpgs);

              GSsetLocalFile(tmpgs, NULL);
              GSdestroy(tmpgs);
              break;
         }

         case 20:      /* Control-T */
         {
              /*** Show host's local date and time ***/
              GopherObj *tempgs = GSnew();
              char *prompts[2];
              char *responses[2];

              prompts[0]=Gtxt("Host to query:",98);
              prompts[1]=NULL;

              /*** Offer current gopher host as default ***/
              responses[0] = (char *) malloc(sizeof(char)*COLS);
              if (GDgetLocation(CurrentDir) != NULL)
                   strcpy(responses[0], GSgetHost(GDgetLocation(CurrentDir)));
              else if (iLevel == 0)
                   strcpy(responses[0], GSgetHost(RootGophers[0]));
              else
                   strcpy(responses[0],
                          GSgetHost(GDgetEntry(OldDirs[iLevel-1],
                                    GDgetCurrentItem(OldDirs[iLevel-1])-1)));
              responses[1] = NULL;

              if ((CURRequest(CursesScreen,
                              Gtxt("Show host's local date and time",145),
                          prompts, responses) == -1) || *responses[0] == '\0')
                   break;

              GSsetType(tempgs, A_FILE);
              GSsetTitle(tempgs, Gtxt("Local Date and Time",104));
              GSsetHost(tempgs, responses[0]);
              GSsetPort(tempgs, 13);
              GSsetPath(tempgs, NULL);

              showfile(tempgs);

              GSdestroy(tempgs);
              free(responses[0]);
              break;
         }

         default:
              Debug("main beep %d\r\n",TypedChar)
              CURBeep(CursesScreen);
              Garbled = FALSE;
              break;

         } /* switch */

#ifdef DESCRIBE_GOPHER
#undef DESCRIBE_GOPHER
#endif

    }      /* while */

    CleanupandExit(0);

    exit(0); /* Never get here... */
}


#if defined(VMS) && defined(A_LANGUAGE)
/*
*  This procedure displays the available language menu and reports the
*      user's language selection
*/

int
setlocale_screen()
{
   int status;

   if (!LangScreen)
       LangScreen = CURnew();
   CURenter(LangScreen);
   GetMenu(setlocale_LangDir, &status, 1);
   status = GDgetCurrentItem(setlocale_LangDir)-1;
   CURexit(LangScreen);
   return(status);
}
#endif


int
Load_Index(ZeGopher)
 GopherObj *ZeGopher;
{
    Draw_Status(Gtxt("Searching...",143));
    refresh();

    return(Load_Index_or_Dir(ZeGopher, Searchstring));
}

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


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

    DebugGSplusPrint(ZeGopher,"Load_Index_or_Dir");
    NewDir = GDnew(32);


    Draw_Status(Gtxt("Connecting...",77)); refresh();

    if ((sockfd = GSconnect(ZeGopher)) <0) {
         check_sock(sockfd, GSgetHost(ZeGopher), GSgetPort(ZeGopher));
         failed = 1;
    }
    else {
         if (GSgetType(ZeGopher) == A_DIRECTORY) {
              Draw_Status(Gtxt("Retrieving Directory...",133)); refresh();
              GStransmit(ZeGopher, sockfd, NULL, "$", NULL);
         }
         else if (GSgetType(ZeGopher) == A_INDEX) {
              Draw_Status(Gtxt("Searching...",143)); refresh();
              GStransmit(ZeGopher, sockfd, Searchmungestr, "$", NULL);
         }

         numbytes = GSrecvHeader(ZeGopher, sockfd);

         if (numbytes == 0) {

              Gplus_Error(sockfd);
              failed = 1;
              return(failed);
         }

         if (GSisGplus(ZeGopher))
              GDplusfromNet(NewDir, sockfd, twirl);
         else
              GDfromNet(NewDir, sockfd, twirl);

         logrequest("ARRIVED AT", ZeGopher);


         if (GDgetNumitems(NewDir) <= 0) {
              CursesErrorMsg(Gtxt("Nothing available.", 214));
              failed = 1;
              GDdestroy(NewDir);
              closenet(sockfd);
              return(failed);
         }

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

         GDsetLocation(NewDir, ZeGopher);

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

         CurrentDir = NewDir;

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

/*
* This dispatches the appropriate fcns depending on what the object is..
*/

int
process_request(ZeGopher)
 GopherObj *ZeGopher;
{
    int failed=0;
    char ImageCmd[128];
    char MovieCmd[128];

    if (GSisAsk(ZeGopher)) {
         char **askdata = AskBlock(ZeGopher);

         if (askdata == NULL)
              return(1);
         GSsetAskdata(ZeGopher, askdata);
    }

    /** Decide what to do with it.. **/

    Debug("process_request type=%c\n",GSgetType(ZeGopher));
    logrequest("GOING TO", ZeGopher);

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

#if defined(VMS) && defined(A_LANGUAGE)
    case A_LANGUAGE:
         rsetlocale(GSgetPort(ZeGopher));
         break;
#endif

    case A_INFO:
         /** Do nothing **/
         break;

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

    case A_GIF:
    case A_IMAGE:
         if (!RCdisplayCommand(GlobalRC, "Image", "", ImageCmd) ||
             !strncasecmp(ImageCmd, "- none -", 8) ||
             ImageCmd == NULL || ImageCmd == '\0') {
              CursesErrorMsg(Gtxt("Sorry, this machine doesn't support images",158));
              break;
         }
         Draw_Status(Gtxt("Receiving Image...",132));
         refresh();
         if (RemoteUser)
               Download_file(ZeGopher);
         else
               showfile(ZeGopher);
         break;

    case A_MIME:
         if (!SecureMode) {
              Draw_Status(Gtxt("Receiving MIME File...",129));
              refresh();
              showfile(ZeGopher);
         }
         else
              CursesErrorMsg(Gtxt("Sorry, cannot display this file",148));
         break;

    case A_MACHEX:
    case A_PCBIN:
    case A_UNIXBIN:
       if (RemoteUser || SecureMode || NoShellMode) {
               Draw_Status(Gtxt("Receiving File...",126));
               refresh();
               Download_file(ZeGopher);
       }
       else
              Save_file(ZeGopher, NULL, NULL);
         break;

    case A_DIRECTORY:
         Draw_Status(Gtxt("Receiving Directory...",125));
         refresh();
         failed = Load_Dir(ZeGopher);
         break;

    case A_TELNET:
    case A_TN3270:
         {
             char type, buf[512];

             if ((type=GSgetType(ZeGopher)) == A_TELNET &&
                 (!RCdisplayCommand(GlobalRC, "Terminal/telnet", "", buf) ||
                  !strncasecmp(buf, "- none -", 8) ||
                  buf == NULL || buf[0] == '\0')) {
                 CursesErrorMsg(Gtxt("Sorry, telnet is not available.",157));
                 break;
             }
             else if (type == A_TN3270 &&
                 (!RCdisplayCommand(GlobalRC, "Terminal/tn3270", "", buf) ||
                  !strncasecmp(buf, "- none -", 8) ||
                  buf == NULL || buf[0] == '\0')) {
                 CursesErrorMsg(Gtxt("Sorry, tn3270 is not available.",160));
                 break;
             }
             else
                 do_tel_3270(ZeGopher);
         }
         break;

    case A_INDEX:
         refresh();
         if (search_string) { /* search_string in commandline */
              Searchstring = search_string;
              search_string = NULL;
         } else
              Searchstring = do_index(ZeGopher);

         Draw_Status(Gtxt("Searching Text...",142));
         if (Searchstring != NULL)
              failed=Load_Index(ZeGopher);
         else
              failed = 1;
         break;

    case A_CSO:
         do_cso(ZeGopher);
         break;

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

     case A_MOVIE:
         if (!RCdisplayCommand(GlobalRC, "video", "", MovieCmd) ||
             !strncasecmp(MovieCmd, "- none -", 8) ||
             MovieCmd == NULL || MovieCmd == '\0') {
               CursesErrorMsg(Gtxt("Sorry, this machine doesn't support movies",165));
               break;
         }
         Draw_Status(Gtxt("Receiving Movie...",130));
         refresh();
         if (RemoteUser)
               Download_file(ZeGopher);
         else
               do_movie(ZeGopher);
         break;

    case A_HTML:
         Draw_Status(Gtxt("Receiving HTML page...",127));
         refresh();
         do_html(ZeGopher);
         break;
    }
    if (GSisAsk(ZeGopher) && GSgetLocalFile(ZeGopher) != NULL) {
         unlink(GSgetLocalFile(ZeGopher));
         GSsetLocalFile(ZeGopher, NULL);
    }
    (void)GSsetAskdata(ZeGopher, NULL);
    return(failed);
}


/*
* Provide information about a gopher item...
* Should use curses...  But....
*/

void
#ifdef DESCRIBE_GOPHER_GRIPE
describe_gopher(banner, ZeGopher, gripefile)
 FILE *gripefile;
#else
describe_gopher(banner, ZeGopher)
#endif
 char *banner;
 GopherStruct *ZeGopher;
{
    char *tmpfilename;
    FILE *tmpfile;
    Blockobj *bl;
    int i,j, views;
    GopherObj *infogs;

    infogs = GSnew();
    GSsetType(infogs, A_FILE);
    GSsetTitle(infogs, Gtxt("Link Info",103));

    GSgetginfo(ZeGopher, TRUE);

#ifdef DESCRIBE_GOPHER_GRIPE
    if (!gripefile)
       goto normal_describe;
    tmpfile = gripefile;
    GSdestroy(infogs);
    goto format_description;

normal_describe:
#endif
#ifdef VMS
    Gopenfile = tmpfilename = tempnam(NULL,NULL);
#else
    Gopenfile = tmpfilename = tempnam("/tmp", "gopher");
#endif
    GSsetLocalFile(infogs, tmpfilename);
    GSsetLocalView(infogs, "text/plain");
    if ((tmpfile = fopen(tmpfilename, "w")) == NULL) {
         CURexit(CursesScreen);
         fprintf(stderr, Gtxt("Couldn't make a tmp file!\n",83)),
         CleanupandExit(-1);
    }

    free(tmpfilename);

#ifdef DESCRIBE_GOPHER_GRIPE
format_description:
#endif

    GStoLink(ZeGopher, fileno(tmpfile), TRUE);

    fprintf(tmpfile, "<URL:%s>\n\n", GSgetURL(ZeGopher));
    fflush(tmpfile);

    if (GSisGplus(ZeGopher)) {
         GopherObj *server;

         server = GSnew();
         GScpy(server, ZeGopher);
         GSsetPath(server, "");
         GSsetLocalFile(server, "");

         fputc('\n', tmpfile);
         /** Search out for specific blocks **/
         for (i=0; i<GSgetNumBlocks(ZeGopher); i++) {
              if (strcmp(BLgetName(GSgetBlock(ZeGopher, i)),"ABSTRACT")==0){
                   bl = GSgetBlock(ZeGopher, i);
                   fprintf(tmpfile, "%s\n---------\n\n",BLgetName(bl));
                   for (j=0; j < BLgetNumLines(bl); j++) {
                        fprintf(tmpfile, "%s\n", BLgetLine(bl, j));
                   }
                   fprintf(tmpfile, "\n");
                   break;
              }
         }

         fprintf(tmpfile, Gtxt("Size       Language      Document Type\n",146));
         fprintf(tmpfile, Gtxt("---------- ------------- ----------------------------\n",147));

         for (views=0; views< GSgetNumViews(ZeGopher); views++) {
              char *cp;
              VIewobj *ZeView = GSgetView(ZeGopher, views);

              if (ZeView == NULL)
                   continue;

              cp = VIgetSize(ZeView);
              if (cp != NULL)
                   fprintf(tmpfile, "%s", cp);
              else
                   cp = "";

              for (i=strlen(cp); i<11; i++)
                   fputc(' ', tmpfile);


              cp = VIprettyLang(ZeView, "En_US");
              if (cp != NULL)
                   fprintf(tmpfile, "%s", cp);
              else
                   cp = VIgetLang(ZeView);
              for (i=strlen(cp); i<14; i++)
                   fputc(' ', tmpfile);

              cp = VIgetType(ZeView);
              fprintf(tmpfile, "%s\n", cp);
         }


         for (i=0; i<GSgetNumBlocks(ZeGopher); i++) {
              bl = GSgetBlock(ZeGopher, i);
              if (strcmp(BLgetName(bl),"ADMIN")==0) {
                   fprintf(tmpfile, Gtxt("\n\nServer Information\n",186));
                   fprintf(tmpfile, "------------------\n");
                   for (j=0; j < BLgetNumLines(bl); j++) {
                        char *cp = BLgetLine(bl, j);

                        if ( (strncmp(cp, "Admin:", 5) != 0) &&
                             (strncmp(cp, "Mod-Date:", 9) != 0) &&
                             (strncmp(cp, "TTL:", 4) != 0) ) {
                             fprintf(tmpfile, "%s\n", cp);
                        }
                   }
                   fprintf(tmpfile, "\n");
                   break;
              }
         }
         GSdestroy(server);
    }

#ifdef DESCRIBE_GOPHER_GRIPE
    if (gripefile)
       return;
#endif
    fclose(tmpfile);

    showfile(infogs);

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

    GSdestroy(infogs);
    return;
}