/********************************************************************
* $Author: tfurrows
* $Revision: 1.11
* $Date: 2018/01/04
* $Source: /home/jlyman/src/gopher/gopher_master/gopher/gopher.c,v $
*
* 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:
*
* Revision 1.11 2018/01/04 22:00:39 tfurrows
* changed default startup behavior; starts with bookmarks
* unless an address was provided on the command line
*
* added color, and fixed line 9 space, previously
*
* Revision 1.10 2002/04/26 14:59:54 jgoerzen
* Preparations for 3.0.5.
*
* Lots of old revision info removed...
*
*********************************************************************/
/* 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';
/*
** 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
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 (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) {
/* 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
}
}
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);
#ifdef CONTROLX
gripeprompt[1] = Gtxt("Press Control-X to send your message.",50);
#else
gripeprompt[1] = Gtxt("Hit the Enter key to send your message.",50);
#endif
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.
*/
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(GopherStruct *ZeGopher)
{
#ifdef VMS
CursesErrorMsg(Gtxt("Sorry, this machine doesn't support sounds",159));
#else
int sockfd;
BOOLEAN Waitforchld = FALSE;
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 ***/
/**************
** This bit of code catches control-c's, it cleans up the curses stuff.
*/
RETSIGTYPE
controlc_old(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
**/
/*** 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(void)
{
int choice, restricted = FALSE;
char *shell;
/*
* 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);
for (i = 0; i < 3; i++)
free(Responses[i]);
break;
}
} /** End of switch on option type **/
}
static char *search_string = NULL;
int
main(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;
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);
GSsetGplus(RootGophers[0], FALSE);
GSsetGplus(RootGophers[1], FALSE);
startAtHome = FALSE;
Bkmarksfirst = 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("Internet Gopher Information Client v%s.%s.%d\n",8),
GOPHER_MAJOR_VERSION, GOPHER_MINOR_VERSION, PATCHLEVEL);
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) {
startAtHome = TRUE;
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;
Bkmarksfirst = FALSE;
optind++;
} else {
startAtHome = TRUE;
GSsetPort(RootGophers[0], 70); /* Just in case the default */
GSsetPort(RootGophers[1], 70); /* isn't 70 */
/* Restore blank path if a host was specified but -p was not */
/*** 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);
}
/* 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..
*/
/*** 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;
}
if ((CURRequest(CursesScreen,
Gtxt("Connect to an anonymous FTP Server via the Gopher gateway",76),
prompts, responses) == -1) || *responses[0] == '\0')
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
if (CurrentDir != RCgetBookmarkDir(GlobalRC)) {
Download_file(GDgetEntry(CurrentDir,GDgetCurrentItem(CurrentDir)-1));
break;
}
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",
239));
}
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;
#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);
char line[80];
int
Load_Dir(GopherObj *ZeGopher)
{
Searchstring= NULL;
return(Load_Index_or_Dir(ZeGopher, NULL));
}
int
Load_Index_or_Dir(GopherObj *ZeGopher, char *Searchmungestr)
{
int failed = 0;
int sockfd;
int i, numbytes;
static char DirTitle[512];
GopherDirObj *NewDir = NULL;