/*
 * hytelnet.c - C/Unix port of Peter Scott's HYTELNET program.
 *
 * SYNOPSIS:
 *	hytelnet [path]
 *
 * EXPLANATION
 *      This is a general hypertext browser, packaged with
 *      Peter Scott's database of Internet-accessible telnet sites.
 *      Hytelnet lists libraries, Campus-Wide Information Systems,
 *      Freenets and more.  This version was written for Unix, but
 *      should work on any system with a reasonable Curses implementation.
 *     
 *      Peter's version uses HYPERRES (an IBM PC hypertext browser).
 *      This program uses the same file format as HYPERRES, so it should be
 *      possible to plug-and-play other databases as well.
 *     
 *      The database consists of many more-or-less plain text files,
 *      all in a single directory.  Any text in angle brackets (eg <HELP>)
 *      is a link.  Selecting it moves you to the named file.  Filenames
 *      are mapped to lower case (so <HELP> refers to the file 'help').
 *
 *      The program notices telnet commands embedded in the text files,
 *      and will execute them for the user, if desired.
 *
 *      To make on Unix systems:
 *
 *          cc -o hytelnet hytelnet.c -lcurses -ltermcap
 *
 *      To make on VMS systems:
 *
 *          define lnk$library sys$library:vaxcrtl
 *          cc hytelnet.c
 *          link hytelnet.obj,sys$library:vaxccurse/lib
 *
 *      On VMS, execute the command "set term/pasthru" before
 *      running Hytelnet.  I use the following command file:
 *
 *          $! VMS command file to run hytelnet
 *          $ set term/pasthru
 *          $ assign /user sys$command sys$input
 *          $ run pub:[hytelnet]hytelnet
 *          $ set term/nopasthru
 *
 * HISTORY
 *	This is the second release of the Unix/VMS version of Hytelnet.
 *	It corresponds to HYTELNET 5.x for the IBM PC.
 *
 *	Thanks to Michael A. Rowley (mcrowley@mtholyoke.edu) for some
 *      of the interrupt handling code.
 *
 * BUGS
 *    - Should do resize if term is resizable (eg. xterm)
 *    - Ignores the page & line numbers in more complex HYPERRES
 *      links <file page line>.
 *    - Should understand more terminals (other cursor keys, ...)
 *    - Should distinguish between the escape key and escape sequences
 *      (ie. cursor keys).
 *
 * AUTHOR
 *      Earl Fogel, Computing Services, University of Saskatchewan
 *	fogel@sask.usask.ca
 *
 * CHANGE LOG
 *      Feb 1992 - Unix version now handles more terminal types
 *      Jan 1992 - now says -more- iff there is more to see
 *               - does not allow anonymous users to telnet
 *      Dec 1991 - tabs now display properly
 *               - cursor positioning improved in VMS
 *               - last line -more- bug is less obtrusive
 *               - VMS version now checks terminal type itself
 *      Nov 1991 - added interrupt handling
 *               - fixed up arrow at top of file bug
 *               - fixed column 80 ^M bug
 *               - CAN NOW EXEC EMBEDDED TELNET COMMANDS!
 *
 * NOTICE
 *	Permissions to copy, use and redistribute this program are hereby
 *	granted provided the Copyright notice and this comment remain
 *	intact.
 *
 *		    (c) Copyright 1992 Earl Fogel
 */
#include <stdio.h>
#include <ctype.h>
#include <signal.h>
#include <string.h>
#include <curses.h>

/*
 * Things you may want to adjust
 */
#ifdef VMS
#   define STARTDIR "pub:[hytelnet]"
#   define TELNET_COMMAND "telnet"
#   define TN3270_COMMAND NULL
#else
#   define STARTDIR "/usr/local/hytelnet"
#   define TELNET_COMMAND "/usr/ucb/telnet"
#   define TN3270_COMMAND "/usr/local/bin/tn3270"
#endif

int telnet_ok = TRUE;		/* TRUE to exec telnet commands */
int telnet_by_name = TRUE;	/* TRUE to use Internet name for telnet */
int telnet_by_number = TRUE;	/* TRUE to use Internet number for telnet */

#define ANONYMOUS_USER "hytelnet"	/* this user can't telnet out */
#define MAXFNAME 30	/* max filename length DDD/FILENAME.EXT */
#define MAXHIST  65	/* number of links we remember in history */
#define MAXLINKS 65	/* max links on one screen */
#define STARTFILE "start"
#define HELPFILE "help"
#define HELP "Commands: Use arrow keys to move, '?' for help, 'q' to quit"
#define MOREHELP \
  "-- press space for more, use arrow keys to move, '?' for help, 'q' to quit"
#define MORE "-- press space for more --"

#define ON	1
#define OFF	0
#define STREQ(a,b) (strcmp(a,b) == 0)
#define STRNEQ(a,b,c) (strncmp(a,b,c) == 0)
#define printable(c) (((c)>31 && (c)<=127) || (c)==9 || (c)==10)
#define HLINE	-60	/* horizontal line segment */
#define VLINE	-77	/* vertical line segment */
#define VLINE2	-70	/* vertical line segment */
#define UL	-38	/* upper left corner */
#define UR	-65	/* upper right corner */
#define LL	-64	/* lower left corner */
#define LR	-39	/* lower right corner */
#define UL2	-42	/* upper left corner */
#define UR2	-73	/* upper right corner */
#define LL2	-45	/* lower left corner */
#define LR2	-67	/* lower right corner */
#define BULLET	4	/* list item marker */
#define BLOT1	-78
#define BLOT2	-79
#define BLOT3	-80

#define UPARROW	201
#define DNARROW	202
#define RTARROW	203
#define LTARROW	204

struct link {
    char lname[MAXFNAME];
    int lx;
    int ly;
    char taddr[MAXFNAME];
    char *tcmd;
    int tx;
} links[MAXLINKS];
int nlinks = 0;

struct hist {
    char hfname[MAXFNAME];
    int hlinkno;
    int hpageno;
} history[MAXHIST];
int nhist = 0;

int more = FALSE;
char *interaddr(), *nextword(), *pop(), *mystrncpy();

/* may need to comment out this line on IBM RS 6000 */
extern char *strcpy(), *strncpy();

#ifdef VMS
#   define strncasecmp strncmp   /* vms doesn't have strncasecmp */
#endif

/*
 * Interrupt handler.  Stop curses and exit gracefully.
 */
void
cleanup_sig (sig)
int sig;
{
    /* ignore further interrupts */     /*  mhc: 11/2/91 */
    (void) signal (SIGHUP, SIG_IGN);
    (void) signal (SIGINT, SIG_IGN);
    (void) signal (SIGTERM, SIG_IGN);

    cleanup();
    printf("\nExiting via interrupt: exit(%d)\n",sig);
    exit(sig);
}


main(argc, argv)
int argc;
char *argv[];
{
    int  i;
    char *startfile = STARTFILE;
    char *startdir = STARTDIR;
    char *cp;
    FILE *fp;

    /* trap interrupts */     /*  mhc: 11/2/91 */
    (void) signal (SIGHUP, cleanup_sig);
    (void) signal (SIGINT, cleanup_sig);
    (void) signal (SIGTERM, cleanup_sig);


    /*
     * Process arguments - with none, look for the database in STARTDIR,
     * starting with STARTFILE.
     *
     * If a pathname is given, use it as the starting point.  Split it
     * into directory and file components, 'cd' to the directory, and
     * view the file.
     */
    for (i=1; i<argc; i++) {

	if        (strncmp(argv[i], "-telnet", 7) == 0) {
	    telnet_ok = FALSE;
	} else if (strncmp(argv[i], "-name", 5) == 0) {
	    telnet_by_name = FALSE;
	} else if (strncmp(argv[i], "-number", 7) == 0) {
	    telnet_by_number = FALSE;
	} else if (strncmp(argv[i], "-", 1) == 0) {
	    printf("Usage: hytelnet [options] [file]\n");
	    printf("Options are:\n");
	    printf("    -telnet   disable telnet completely\n");
	    printf("    -name     disable telnet by Internet name\n");
	    printf("    -number   disable telnet by Internet number\n");
	    exit(0);
	} else {	/* alternate database path */
	
	    cp = strrchr(argv[i], '/');
	    /* if (cp == NULL || cp == argv[i]) { */
	    if (cp == NULL) {
		startfile = argv[1];
	    } else {
		startdir = argv[1];
		startfile = cp+1;
		*cp = '\0';
	    }
	}
    }
    chdir(startdir);

    /*
     * make sure we can read the first file
     */
    if ((fp=fopen(startfile, "r")) == NULL) {
	printf("Hytelnet: can't find file: %s/%s\n", startdir, startfile);
	exit(0);
    } else
	fclose(fp);

    /*
     * Anonymous users are not allowed to telnet out
     */
    if (STREQ(cuserid((char *) NULL), ANONYMOUS_USER))
	telnet_ok = FALSE;

    /*
     * here's where we do all the work
     */
    if (setup()) {
	mainloop(startfile);
	cleanup();
    }

    exit(0);
}


start_curses()
{
    initscr();	/* start curses */
    /* nonl();   *//* seems to slow things down */

#ifdef VMS
    crmode();
    raw();
#else
    cbreak();
#endif

    noecho();
}


stop_curses()
{
    endwin();	/* stop curses */
}


#ifdef VMS
/*
 * check terminal type, start curses & setup terminal
 */
setup()
{
    int c;
    char *dummy, *cp, term[81];

    /*
     * get terminal type, and convert to lower case
     */
    longname(dummy, term);
    for (cp=term; *cp!='\0'; cp++)
	if (isupper(*cp))
	    *cp = tolower(*cp);

    printf("Terminal = %s\n", term);
    if (strncmp(term, "vt1", 3) != 0 &&
	strncmp(term, "vt2", 3) != 0 &&
	strncmp(term, "vt3", 3) != 0 &&
	strncmp(term, "vt52", 4) != 0 ) {
	printf(
	    "You must use a vt100, 200, etc. terminal with this program.\n");
	printf("Proceed (n/y)? ");
	c = getchar();
	if (c != 'y') {
	    printf("\n");
	    return(0);
	}
    }

    start_curses();

    return(1);
}

#else	/* not VMS */
/*
 * check terminal type, start curses & setup terminal
 */
setup()
{
    char term[81];

    start_curses();

    /* get terminal type (strip 'dec-' from vms style types) */
    if (strncmp(ttytype, "dec-vt", 6) == 0) {
	strcpy(term, ttytype+4);
	setterm(term);
    } else
	strcpy(term, ttytype);

    if (dumbterm()) {
	printw("Please enter your terminal type (default vt100): ");
	refresh();
	echo();
	getstr(term);
	noecho();

	if (strcmp(term, "sun-cmd") == 0) {
	    stop_curses();
	    printf("\nSorry, I can't set your terminal type to sun-cmd.\n");
	    printf("Please set your terminal type before running Hytelnet.\n");
	    return(0);
	}

	if (strlen(term) == 0)
	    strcpy(term, "vt100");

	setterm(term);

	if (dumbterm()) {
	    stop_curses();
	    printf("Hytelnet does not support your terminal (%s)\n", term);
	    return(0);
	}
    }

    return(1);
}
#endif

dumbterm()
{
    int dumb = FALSE;

    if (strcmp(ttytype, "network") == 0 ||
	strcmp(ttytype, "unknown") == 0 ||
	strcmp(ttytype, "dialup") == 0 ||
	strcmp(ttytype, "dumb") == 0 ||
	strcmp(ttytype, "switch") == 0 ||
	strcmp(ttytype, "ethernet") == 0 )
	dumb = TRUE;
    return(dumb);
}

cleanup()
{
    int lastx=1, lasty=1;

    move(LINES-1, 0);
    mvcur(lasty, lastx, LINES-1, 0);
    clrtoeol();
    refresh();

    stop_curses();
}


/*
 * here's where we do all the work
 */
mainloop(startfile)
char *startfile;
{
    int  c, arrowup=FALSE, show_help=FALSE;
    int  cur = 0, savcur = 0;
    int  oldpage = 0, newpage = 1, savpage = 1;
    char oldfile[MAXFNAME], newfile[MAXFNAME], altfile[MAXFNAME];
    char tncommand[81];
    FILE *fp = NULL, *oldfp;

    strcpy(newfile, startfile);

    while (TRUE) {
	if (!STREQ(oldfile, newfile)) {
	    /*
	     * Try to open the new file.
	     * If the file is not found in the current directory, use
	     * the first three letters of the filename as a directory name,
	     * and try again.  This is for compatibility with HYPERRES,
	     * not because I like it.
	     */
	    oldfp = fp;
	    if ((fp=fopen(newfile, "r")) != NULL) {
		if (oldfp != NULL)
		    fclose(oldfp); /* close previous file */
		oldpage = 0; /* to force a showpage() */
		newpage = savpage;
		savpage = 1;
		strcpy(oldfile, newfile);
	    } else {
		mystrncpy(altfile, newfile, 3);
		strcat(altfile, "/");
		strcat(altfile, newfile);
	        if ((fp=fopen(altfile, "r")) != NULL) {
		    if (oldfp != NULL)
			fclose(oldfp); /* close previous file */
		    oldpage = 0; /* to force a showpage() */
		    newpage = savpage;
		    savpage = 1;
		    strcpy(newfile, altfile);
		    strcpy(oldfile, newfile);
		} else {
		    strcpy(newfile, oldfile);
		    fp = oldfp;
		}
	    }
	}

	if (oldpage != newpage) {
	    if (showpage(fp, newpage, oldpage) > 0) {
		if (arrowup) {
		    cur = nlinks - 1;
		    arrowup = FALSE;
		} else
		    cur = savcur;
		savcur = 0;
		oldpage = newpage;
	    } else {
		newpage = oldpage;
	    }
	}

	if (!show_help) {
	    if (more)
		statusline(MORE);
	    else
		statusline((char *) NULL);
	}

	highlight(ON, cur);	/* highlight current link */

	c=mygetch();

	if (show_help) {
	    if (more)
		statusline(MORE);
	    else
		statusline((char *) NULL);
	    show_help = FALSE;
	}

	switch(c) {
	case 'q':	/* quit */
	case 'Q':
	case 4:
	    return;
	case ' ':	/* next page */
	case '+':
	    newpage++;
	    break;
	case 'b':	/* prev page */
	case '-':
	    newpage--;
	    break;
	case UPARROW:
	case 'k':
	    if (cur>0) {		/* previous link */
		highlight(OFF, cur);
		cur--;
	    } else if (oldpage > 1) {	/* previous page */
		newpage--;
		arrowup = TRUE;
	    }
	    break;
	case DNARROW:
	case 'j':
	    if (cur<nlinks-1) {		/* next link */
		highlight(OFF, cur);
		cur++;
	    } else {			/* next page */
		newpage++;
	    }
	    break;
	case LTARROW:			/* back up a level */
	case 'h':
	    if (nhist>0)
		strcpy(newfile, pop(&savcur, &savpage));
	    break;
	case RTARROW:			/* follow a link */
	case 'l':
	case '\n':
	case '\r':
	    if (nlinks > 0) {
		if (links[cur].tcmd == NULL) {	/* it's a link  */
		    getlinkname(newfile, cur);
		    if (*newfile != ' ') { /* very simple error checking */
			push(oldfile, cur, newpage);
		    }
		} else {			/* it's a telnet command */
		    sprintf(tncommand, "%s %s %s",
			    links[cur].lname, links[cur].taddr,
			    "Proceed (y/n)?");
		    statusline(tncommand);
		    c=mygetch();
		    if (c == 'y' || c == '\n' || c == '\r') {
			addstr(" Ok...");
			refresh();
			sprintf(tncommand, "%s %s",
				links[cur].tcmd, links[cur].taddr);
			stop_curses();
			system(tncommand);
			start_curses();
			rewind(fp);
			oldpage = 0;		/* force a redraw */
			savcur = cur;
		    }
		    statusline((char *) NULL);
		}
	    }
	    break;
	case '?':			/* show help text */
	    strcpy (newfile, HELPFILE);
	    push(oldfile, cur, newpage);
	    break;
	case 'm':	/* return to main screen */
	case 'M':
	    strcpy (newfile, startfile);
	    cur = 0;
	    nhist = 0;
	    break;
	default:
	    if (more)
		statusline(MOREHELP);
	    else
		statusline(HELP);
	    show_help = TRUE;
#ifdef DEBUG
	    printw("%d", c);
#endif
	    break;
	}
    }
}


/*
 * display one screen of text
 *
 * Read & display one screenfull of text.
 * Looks for (and remembers) links, and converts IBM PC line drawing
 * characters to standard ascii
 *
 * Pre-reads one line from the next page, to ensure that the "- more -"
 * message is only displayed when there really is more.
 */
int
showpage(fp, page, oldpage)
FILE *fp;
int page, oldpage;
{
    int lineno, col;
    static char line[82];
    char *cp, *cp2, *acp;

    if (page < 1)
	page = 1;

    if (page == oldpage) {		/* nothing to do */
	return(0);
    } else if (page < oldpage) {	/* have to back up */
	rewind(fp);
	oldpage = 0;
    }

    if (page == 1 || page != oldpage+1) {
	more = FALSE;
	lineno = oldpage*(LINES-1);
	while (lineno<(page-1)*(LINES-1) && fgets(line, 81, fp) != NULL) {
	    lineno++;
	}
    }

    lineno = 0;
    while (lineno<(LINES-1) && (more || fgets(line, 81, fp) != NULL)) {
	more = FALSE;
	cp = line+strlen(line)-1;
	if (*cp == 13)
	    *cp = '\0';		/* remove trailing <return> */

	if (lineno == 0) {
	    nlinks = 0;
	    clear();
	}
	col = 0;
	for (cp=line; *cp != '\0'; cp++) {
	    if (!printable(*cp)) {
		if      (*cp == HLINE)	*cp = '-';
		else if (*cp == VLINE)	*cp = '|';
		else if (*cp == VLINE2)	*cp = '|';
		else if (*cp == UL)	*cp = '+';
		else if (*cp == UR)	*cp = '+';
		else if (*cp == LL)	*cp = '+';
		else if (*cp == LR)	*cp = '+';
		else if (*cp == UL2)	*cp = '+';
		else if (*cp == UR2)	*cp = '+';
		else if (*cp == LL2)	*cp = '+';
		else if (*cp == LR2)	*cp = '+';
		else if (*cp == BULLET)	*cp = '*';
		else if (*cp == BLOT1)	*cp = '*';
		else if (*cp == BLOT2)	*cp = '*';
		else if (*cp == BLOT3)	*cp = '*';
		else {
#ifdef DEBUG
		    /* shouldn't happen - see what we've missed */
		    if (*cp != 13) {
			printw("%d", *cp);
			refresh();
		    }
#endif
		    *cp = ' ';
		}
	    } else if (*cp == '<') {	/* start of link? */
		for (cp2 = cp+1;
		    *cp2 != '>' && *cp2 != '<' && *cp2 != '\0' &&
		    cp2-cp+1 < MAXFNAME; cp2++)
		    ; /* NULL BODY */
		if (*cp2 == '>') { /* it's a link */
		    links[nlinks].lx = col;
		    links[nlinks].ly = lineno;
		    links[nlinks].tcmd = NULL;
		    mystrncpy(links[nlinks].lname, cp, cp2-cp+1);
		    nlinks++;
		}
	    } else if (*cp == '\t') {	/* expand tabs */
		    col += 7 - (col % 8);
	    } else if (*cp == 't' || *cp == 'T') {	/* telnet command? */
		if (telnet_ok &&
		    (col == 0 || isspace(*(cp-1))) &&
		    ((TELNET_COMMAND &&
		      (STRNEQ(cp,"telnet ",7) ||
		       STRNEQ(cp,"Telnet ",7) ||
		       STRNEQ(cp,"TELNET ",7)) ) ||
		     (TN3270_COMMAND &&
		      (STRNEQ(cp,"tn3270 ",7) ||
		       STRNEQ(cp,"TN3270 ",7)) ) ) ) {

		    acp=interaddr(cp+7,links[nlinks].taddr);
		    while (acp != NULL) {
			mystrncpy(links[nlinks].lname, cp, 6);
			if (*(cp+5) == '0')
			    links[nlinks].tcmd = TN3270_COMMAND;
			else
			    links[nlinks].tcmd = TELNET_COMMAND;
			links[nlinks].lx = col;
			links[nlinks].tx = col + acp - cp;
			links[nlinks].ly = lineno;

			nlinks++;
			acp=interaddr(nextword(acp),links[nlinks].taddr);
		    }
		}
	    }
	    addch(*cp);
	    col++;
	}
	lineno++;
#ifdef DEBUG
refresh();
#endif
    }
    if (lineno==(LINES-1) && fgets(line, 81, fp) != NULL)
	more = TRUE;
    refresh();
    return(lineno);
}


/*
 * Search through a string for the first valid internet address.
 * We define an internet address as a word consisting of alpha-
 * numeric characters and dots (and with at least one embedded dot).
 *
 * This is not a general purpose routine.
 *
 * Returns an pointer to the address if one is found, NULL otherwise.
 */
char *
interaddr(cp, buf)
char *cp, *buf;
{
    int isaddr, isname;
    char *word;

    while (*cp != '\0') {

	isaddr = FALSE;
	isname = FALSE;

	while (*cp == ' ') cp++;		/* skip spaces */

	word = cp;				/* start of word? */
	while (*cp == '"' || *cp == '-' || *cp == '.' || isalnum(*cp)) {
	    if (*cp == '.' && isalnum(*(cp+1)) )
		isaddr = TRUE;
	    if (isalpha(*cp))
		isname = TRUE;
	    cp++;
	}

	if (isaddr) {			/* it's an internet address */
	    if ((telnet_by_name && isname) ||
		(telnet_by_number && !isname) ) {
		while (!isalnum(*cp)) cp--;	/* skip trailing punctuation */
		mystrncpy(buf,word,cp-word+1);
		/* printw("interaddr: %s\n", buf); refresh(); */
		return(word);
	    }
	} else {  /* if it's the word "or", try again, otherwise give up */
	    if (strncmp(word, "or", 2) != 0)
		return(NULL);
	}
    }
    return(NULL);
}

/*
 * Return pointer to the second word in a string
 */
char *
nextword(cp)
char *cp;
{
    while (*cp != ' ' && *cp != '\0') cp++;	/* skip non-spaces */
    while (*cp == ' ') cp++;			/* skip spaces */
    return(cp);
}


/*
 * my strncpy() terminates strings with a null byte.
 * Writes a null byte into the n+1 byte of dst.
 */
char *
mystrncpy(dst, src, n)
char *dst, *src;
int n;
{
    char *val;

    val = strncpy(dst, src, n);
    *(dst+n) = '\0';
    return val;
}


/*
 * my getch() translates some escape sequences and may fake noecho
 */
mygetch()
{
    int a, b, c;

    c = getch();
    if (c == 27) {	/* handle escape sequence */
	b = getch();
	if (b == '[' || b == 'O')
	    a = getch();
	else
	    a = b;

	switch (a) {
	case 'A': c = UPARROW; break;
	case 'B': c = DNARROW; break;
	case 'C': c = RTARROW; break;
	case 'D': c = LTARROW; break;
	case '5':			/* vt 300 prev. screen */
	    if (b == '[' && getch() == '~')
		c = '-';
	    break;
	case '6':			/* vt 300 next screen */
	    if (b == '[' && getch() == '~')
		c = '+';
	    break;
	}
    }
    return(c);
}


/*
 * highlight (or unhighlight) a given link
 */
highlight(flag, cur)
int flag;
int cur;
{
#ifdef VMS
    int savex, savey;
    int curx, cury;
#endif

    if (nlinks > 0) {
	move(links[cur].ly, links[cur].lx);
	if (flag == ON) standout();
	addstr(links[cur].lname);
	if (links[cur].tcmd != NULL) {
	    move(links[cur].ly, links[cur].tx);
	    addstr(links[cur].taddr);
	}
	if (flag == ON) standend();
#ifdef VMS
	mvcur(savey, savex, cury, curx);
#endif
	refresh();
    }
}


/*
 * display (or hide) the status line
 */
statusline(text)
char *text;
{
#ifdef VMS
    int savex, savey;
    int curx, cury;
#endif

    move(LINES-1,0);
    clrtoeol();
    if (text != NULL) {
	standout();
	addstr(text);
	standend();
    }
#ifdef VMS
	mvcur(savey, savex, cury, curx);
#endif
    refresh();
}


/*
 * extract the filename portion of a link <filename[ ...]>
 */
getlinkname(buf, cur)
int cur;
char *buf;
{
    char *cp;

    if (nlinks > 0) {
	for (cp=links[cur].lname+1;
	     *cp != '>' && *cp != '\0' && *cp != ' ';
	     cp++) {
	    *buf = *cp;
	    if (isupper(*buf))
		*buf = tolower(*buf);
	    buf++;
	}
	*buf = '\0';
    }
}


/*
 * push the current filename, link and page number onto the history list
 */
push(fname, cur, page)
char *fname;
int cur, page;
{
    if (nhist<MAXHIST)  {
	strcpy(history[nhist].hfname, fname);
	history[nhist].hlinkno = cur;
	history[nhist].hpageno = page;
	nhist++;
    }
}


/*
 * pop the previous filename, link and page number from the history list
 */
char *
pop(cur, page)
int *cur, *page;
{
    if (nhist>0) {
	nhist--;
	*cur = history[nhist].hlinkno;
	*page = history[nhist].hpageno;
	return(history[nhist].hfname);
    } else {
	return(" ");
    }
}

