#include "rm.h"
#include <xfbuf.h>
#include <stdio.h>
#include <ctype.h>
#include <malloc.h>
#include <process.h>
#include <errno.h>

/*

ReadMail -- offine message reader/privacy shell for MSDOS/FidoNet
copyright Tom Jennings 1992
Box 77731, San Francisco, CA 94107, USA, 1:125/111@fidonet


REVISION HISTORY:

17 Sep 92
	Original release.

20 Sep 92
	Small bug fixes, etc.

23 Sep 92
	Fixed mem buffer overflow when reading a message .GE. buff size.
	Changed to "char far *" MSDOSisms to move buffer out of local
	heap to get around 8086 small-model. (Buffer gets it's own 64K
	segment.) Max. msg size is now about 64K. This involved changing
	MSG.C and CYPHER.C to use readf() and writef() which were added
	to RM.C.

31 Oct 92
	Fixes: in AREAS.BBS ignore lines beginning with "-".
	Added "RMFN" spec for edited filename. Allow "M" command to 'move'
	a message to the same area (ie. dup it as highest message.)


This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or (at
your option) any later version.

This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

*/

extern int errno;			/* Microsoft library */

#define PGMIDENT "9 November 1992"



/* The screen is broken into three parts: the top contains the message
header. Next comes the message body, and the bottom line is the
command/status line. */

WINDOW header = {0,1,0,80,A_NORMAL,W_NONE};		/* message header */
WINDOW body =   {1,23,0,80,A_NORMAL,W_NONE};		/* message body */
WINDOW status = {24,1,0,80,A_REVERSE,W_NONE};		/* command line */
static WINDOW errorbox = {6,10,15,50,A_NORMAL,W_DOUBLEBOX};

WINDOW help = {2,19,6,68,A_NORMAL,W_DOUBLEBOX,		/* help message */
    "ReadMail HELP screen\001"
    PGMIDENT
    " \r"
    " \r"
    " \004E\005nter new message\r"
    " \004R\005eply to message                                \004Home\005\r"
    " \004C\005hange this message                            \004Page Up\005\r"
    "                                                   \004\014\030\005\r"
    " \004W\005rite message to file                  \004P\005rev   \004\014\033\005Ä     Ä\004\014\032\005   \004N\005ext\r"
    " \004K\005ill (delete) message                  \004<\005first     \004\014\031\005       \004>\005last\r"
    " \004M\005ove message to another area                   \004Page Dn\005\r"
    " \004A\005rea select                                      \004End\005\r"
    "\r",
    " \004D\005ecrypt this message                      \004?\005 this help screen\r"
    " Encr\004y\005pt (with addressee's public key)     \004*\005 DOS shell\r"
    " \004S\005ign (with your private key)              \004U\005ser option\r"
    " \004B\005oth encrypt and sign                     \004G\005oodbye\r"
    "\r"
    " World Power Systems,  Box 77731,  San Francisco CA 94107  USA"
};

/* Stuff gleaned from CONFIG.DOG and AREAS.BBS */

char user_name[80];			/* default FROM name */
struct _node id;			/* fidonet node number */
struct _area area[AREAS];		/* list of message areas */
char tagline[60];			/* msg tagline */

int areano;				/* current area selection */

/* UGH! MSDOS! */
char far *textptr;			/* ptr to display text */
unsigned textsize;			/* how much is in it */
char far *mem;				/* big work buffer */
unsigned memsize;			/* how big it is */

/* Junk from the DOS environment. */

char rmpath[80];			/* ptr to BBS area */
char editor[80];			/* editor invokation */
char rmuser[80];			/* optional special features! */
char settz[80];				/* TZ var PGP needs */
char pgppath[80];			/* PGP pathname */
char textfile[80] = "MESSAGE.TXT";	/* specified edited pathname */
char pgppass[80] = "";			/* PGP password (see pgp()) */

/* Option flags set by RMOPT environment variable. */

char secret_prompt = 0;			/* 1 == handle secret pass phrase */
char initial_help = 1;			/* 1 == display help screen */

int brkflg;				/* where break state is saved */

/*

The functions below copy, compare, and otherwise process _node
structures. Comparisons uses only zone, net and number; point is
generally not used, since it's handling is limited to replying, entering
and packeting for points to pickup; there is no routing of points.


fix_node(&node)
	Fixes negative net and node numbers to 0.

cpy_node(&dest,&src)
	Copies one node into another, including point.

char *str_node(&node)
	Generates a static string expression of the node, and returns a 
pointer to it. Through a kludge it can be called up to 4 times without
overwriting a previous invokation. The point field will be expressed if
it is non-zero.

set_nn(string,&node)

	Parse the string into various zone:net/node numbers. This accepts
"zone:net/node" format. If zone or net isnt specified, it is left untouched.
This will parse points.

*/


main(argc,argv)
int argc;
char **argv;
{
int i,f;
char *cp,buff[256];

	printf("\r\nReadMail, "); printf(PGMIDENT); printf(", copyright Tom Jennings 1992\r\n\n");
	printf("Available from: World Power Systems, Box 77731, San Francisco CA 94107 USA\r\n");
	printf("Filerequest \"RM\" or download from 1:125/111@FidoNet. On diskette from WPS $25.\r\n\n");

	fastfile(5);				/* start the file system */
/*	while (--argc) {
		++argv;
		strip_switch(buff,*argv);
		switch (*buff) {
		}
	}
*/
	newdelim(" ,\t\r\n");			/* our delimiters */
	loadinfo();				/* read init files */
	if (!*rmpath || !*editor) {
		printf("Environment variable(s) RMPATH or RMEDIT not set\r\n");
		exit(1);
	}
	if (! *area[0].path) {
		printf("No message areas found! (CONFIG.DOG or AREAS.BBS)\r\n");
		exit(1);
	}
	if (! id.net) {
		printf("No node address found! (COMFIG.DOG)\r\n");
		exit(1);
	}
	if (! *user_name) {
		printf("No user name found! (CONFIG.DOG)\r\n");
		exit(1);
	}
	brkflg= _break(0,0);			/* get break, */
	_break(1,0);				/* turn it off */
	init_window(&status);			/* init the status line */
	init_window(&header);
	init_window(&body);
	bigloop();				/* main forever loop */
}

/* Allocate as much memory as we can. Complain. */

static get_mem() {

	for (memsize= 65000; memsize > 1000; memsize -= 100) {
		mem= (char far *) _fmalloc(memsize);/* get a big chunk */
		if (mem != NULL) break;		/* big as possible */
	}
	if ((memsize < 5000) || (mem == NULL)) { /* oops... */
		free_mem();			/* not enough to run */
		error("NOT ENOUGH AVAILABLE MEMORY: It is recommended that you "
		    "quit this program now.");
	}
}

/* Free up the allocated memory. */

static free_mem() {

	if (mem != NUL) _ffree(mem);		/* give it up */
	mem= NULL;				/* mark unused */
	memsize= 0;
}

/* This is the forever loop. */

static bigloop() {
int i,n,c;
char lastc,buff[80];

	get_mem();					/* get memory for editing */
	prompt("Wait...");
	select_area(areano);				/* setup for message area */
	display_message();				/* display initial msg */
	if (initial_help) {
		w_dim(&header);				/* overlay screen */
		w_dim(&body);				/* with help box */
		init_window(&help);			/* display (temporary) hello box */
	}
	lastc= 'n';					/* CR == N)ext */

	while(1) {
		prompt(" Command (?=help): ");		/* display a prompt */
get:		c= keyhit();				/* get a character */
		if (! c) goto get;

		if (active(&help)) {			/* if HELP turned on */
			removewindow(&help);		/* turn it off */
			w_unhighlight(&body);		/* restore brightness */
			w_unhighlight(&header);
		}
		text_motion(c);				/* do cursor motion */

again:		switch (tolower(c)) {

/* Editing commands. */

		case 'c':
/*			new_message(1);		*/	/* make image for editing */
			if (! write_text()) break;	/* disk full! */
c_edit:			msgedit();
			prompt("Wait...");
			if (save_message(0)) goto c_edit; /* replace existing msg */
			display_message();
			break;

		case 'r':
			new_message(2);			/* 2 == reply */
			if (! write_text()) break;
			goto e_edit;

		case 'e':
			new_message(0);			/* 0 == new message */
			if (! write_text()) break;	/* (write error) */
e_edit:			msgedit(); 			/* enter new */
			prompt("Wait...");		/* may be slow */
			if (save_message(1)) goto e_edit; /* !0 == create new */
			display_message();		/* redisplay current */
			break;

/* Message read/positioning commands. */

		case 'n': case KEY_RIGHT: next_message(); lastc= 'n'; break;
		case 'p': case KEY_LEFT: previous_message(); lastc= 'p'; break;
default_move:	case CR: c= lastc; goto again;		/* EEEWW GROSS! */
		case '>': last_message(); break;
		case '<': first_message(); break;

		case '?':
			w_dim(&header);
			w_dim(&body);
			init_window(&help);		/* display help */
			break;

		case ' ': break;			/* standard do-nothing */
		case 'w': writefile(); break;		/* write to disk */
		case 'k': kill_message(); goto default_move; /* kill */
		case 'a': getarea(); break;		/* select area */
		case 'd': decrypt(); break;		/* decrypt */
		case 'y': encrypt("-ea"); break;	/* encrypt */
		case 's': encrypt("-sa"); break;	/* sign */
		case 'b': encrypt("-esa"); break;	/* encrypt/sign */
		case 'm': movemsg(); break;		/* move */

		case 'u':
			if (!write_text()) break;	/* special command */
			doopt();			/* execute it */
			save_message(0);		/* replace message */
			display_message();		/* redisplay it */
			break;

		case '*': 
			write_text();			/* it's always handy! */
			pushdos(); break;

		case 'g':
			unlink(textfile);		/* kill this */
			for (i= 0; i < sizeof(pgppass); i++)
				pgppass[i]= NUL;	/* zap pass phrase */

			w_unhighlight(&header);
			w_unhighlight(&status);

			w_restore(&body);		/* restore brightness */
			w_restore(&header);
			w_restore(&status);

			w_pos(&body,body.lines - 1,body.cols - 1);
			w_cursor(&body);
			_break(1,brkflg);		/* restore break */
			free_mem();
			exit(0);			/* goodbye */
			break;
		}
	}
}

/* Execute the optional feature. Hail discordia. */

static doopt() {
int e;

	if (! *rmuser) {
		error("RMUSER not set. Read the docs.");
		return;
	}
	free_mem();				/* free up memory */
	e= spawnlp(P_WAIT,rmuser,rmuser,textfile,NULL);
	if (e == -1) exec_result(rmuser);	/* oops */
	get_mem();
	return(e);
}

/* Move a message from one area to another. */

static movemsg() {
int a;

	if (! write_text()) return;			/* write to disk */

	a= areano;					/* remember current area */
	getarea();					/* pick a new one */
	prompt("Wait...");
	save_message(1);				/* save it */
	select_area(a);					/* reselect orig. area */
	display_message();				/* redisplay current message */
}

/* Write the current message out to a disk file, appending if it already
exists. */

static writefile() {
char buff[64],c;
int i,f;

again:	prompt("Write to file: ");
	i= 0; while (1) {
		while (!(c= keyhit()));			/* get a key */
		if (c == ESC) goto again;
		if (c == CR) break;
		if ((c == BS) && i) {
			w_string(&status,"\010 \010");
			--i;
		}
		if ((c > ' ') && (c < 127) && (i < sizeof(buff) - 1)) {
			buff[i++]= c;
			w_char(&status,c); w_cursor(&status);
		}
	}
	buff[i]= NUL;
	if (! i) return;				/* nothing to do */

	f= open(buff,OPEN_RW);				/* open (for append) */
	if (f == -1) f= creat(buff,CREAT_RW);		/* or create */
	if (f == -1) return(error("Can't create file!"));
	lseek(f,0L,2);					/* seek to EOF */
	i= writef(f,mem,textsize);			/* write it out */
	close(f);
	if (i != textsize) error("Disk full!");
}

/* Pick a message area. */

static getarea() {
int v,n,i;
char c;

	n= list_areas();		/* generate the area list */
error:	prompt("Area Number: ");
	v= 0;				/* new area number */
	while (n) {			/* (assuming there's at least one) */
		switch (c= keyhit()) {
			case ESC: case BS: goto error;

			case '0': case '1': case '2':
			case '3': case '4': case '5':
			case '6': case '7': case '8':
			case '9':
				w_char(&status,c); /* echo */
				v *= 10; v += c - '0'; break;

			case CR: if (v == 0) n= 0;
				else if (--v >= n) goto error;
				else areano= v; n= 0;
				break;
		}
		text_motion(c);		/* (scroll text) */
	}
	prompt("Wait...");		/* may be slow... */
	select_area(areano);		/* (re)select the area */
	display_message();		/* display some message */
}

/* Generate the status line display. */

static prompt(s)
char *s;
{

	init_window(&status);
	w_string(&status,s);
	w_cursor(&status);
}

/* Push to COMMAND.COM */

pushdos() {
char *cp;
int e;
char *getenv();

	free_mem();			/* free up memory */

	cp= getenv("COMSPEC"); 		/* find COMSPEC, */
	if ((int)cp == 0) {		/* no COMSPEC??? */
		e= -1;

	} else {
		init_window(&status);		/* clear this */
		w_string(&status,"\016");	/* reverse it */
		w_pos(&status,0,0);
		printf("Pushing to DOS; enter \"EXIT\" to return to FidoTerm\r\n");
		e= spawnl(P_WAIT,cp,cp,NULL);	/* exec, remember error code */
	}
	if (e == -1) exec_result(cp);	/* error */
	get_mem();			/* get memory back */
	display_message();
}

static msgedit() {
int e;

	free_mem();				/* free up memory */
	e= spawnlp(P_WAIT,editor,editor,textfile,NULL);	/* exec the editor */
	if (e == -1) exec_result(editor);	/* oops */
	get_mem();
	display_message();			/* redisplay current msg */
	return(e);
}

/* Invoke PGP. The fancy arg names are for encrypt only. Decrypt only needs a 
filename. I know this is ugly. So sue me. PGP is annoying to fork.

We use spawnlpe() and pass the relevant environment vars to PGP explicitly.
This allows the major convenience of having RM ask for the users secret
key the first time it is needed, and store it in a local array for subsequent
uses. The memory it occupies gets cleared upon exit to DOS. If the var
PGPPASS is set we don't have to ask at all. */

int pgp(mode,fn,id,sigid,redir)
char *mode;	/* - switches */
char *fn;	/* filename */
char *id;	/* user ID */
char *sigid;	/* signator ID */
char *redir;	/* redirection file */
{
int e;
char *cp;
char *args[4];	/* [0]= PGPPATH [1]= PGPPASS [2]= TZ [3]= NULL */
char guck[200];	/* built environment itself */


	if (secret_prompt) get_secret();	/* handle it ourselves or */
	else *pgppass= NUL;			/* let PGP do it */

	sprintf(guck,"PGPPATH=%s",pgppath);	/* build an environment */
	args[0]= guck;				/* point arg[0] to it */
	for (cp= guck; *cp; ++cp);		/* find it's end */
	sprintf(++cp,"PGPPASS=%s",pgppass);
	args[1]= cp;
	while (*cp) ++cp;
	sprintf(++cp,"TZ=%s",settz);
	args[2]= cp;
	while (*cp) ++cp;
	*++cp= NUL;				/* pointer to a single NUL */
	args[3]= cp;				/* point to that */

	free_mem();				/* free up memory */

	init_window(&body);			/* clear screen */
	init_window(&status);			/* and prompt */
	w_string(&status,"\016");		/* reverse it */
	w_pos(&body,body.lines - 1,body.cols - 1);
	w_cursor(&body);

	e= spawnlpe(P_WAIT,"PGP","PGP",mode,fn,id,sigid,redir,NULL,args);
	if (e == -1) exec_result("PGP");	/* oops */

/* PGP errorlevel result codes. The damn docs are useless. It doesn't
tell you shit about what codes are what. */

	if (e == 20) *pgppass= NUL;		/* bad passphrase */

	get_mem();
	return(e);
}

/* If we don't have it already, ask for the secret pass phrase. */

static get_secret() {
char c;
int i;

	if (*pgppass) return;

	w_dim(&header);
	w_dim(&body);
	init_window(&errorbox);		/* open a window */
	errorbox.line= 1;		/* 2nd line */
	w_string(&errorbox,"Please enter your secret pass phrase. It will\rbe "
	    "stored in-memory only, and wiped upon exit. CR when done, BS to "
	    "edit, ESC to reenter from start. Keystrokes will not be displayed. "
	    "Entering CR only (no pass phrase) avoids this feature.");

	cursoroff();
	for (i= 0; i < sizeof(pgppass);) {		/* get pass phrase */
		while (!(c= keyhit()));			/* get a key */
		if (c == CR) break;			/* done */
		if (c == ESC) i= 0;			/* restart */
		if (c == BS) if (i) --i;		/* silent backspace */
		if ((c >= ' ') && (c < 127)) pgppass[i++]= c;
	}
	pgppass[i]= NUL;				/* NUL terminate */

	removewindow(&errorbox);
	w_unhighlight(&header);
	w_unhighlight(&body);
}

/* See what happened after an exec; if error, make a nasty message box. */

static exec_result() {

	switch (errno) {
		case ENOMEM: error("    Not enough memory!"); break;
		case ENOENT: error("    The program was not found!"); break;
		case ENOEXEC:error("    The program specified isn't executable!"); break;
		case 99:     error("    No COMSPEC environment variable set!"); break;
		default:     error("    Some damn thing went wrong!"); break;
	}
}

/* Generate an error-message box, require a keystroke to continue. Return 
1 if "Y" or CR is entered. */

int error(errmsg)
char *errmsg;
{
char c;

	w_dim(&header);
	w_dim(&body);
	init_window(&errorbox);		/* open a window */
	errorbox.line= 1;		/* 2nd line */
	w_string(&errorbox,errmsg);	/* display the error message */
	w_pos(&errorbox,errorbox.lines - 1,0);

	for (; *errmsg; ++errmsg)	/* check if a question */
		if (*errmsg == '?') break;
	if (*errmsg != '?') 		/* if not... */
		w_string(&errorbox,"Type any key to continue");
	while (!(c= keyhit()));
	removewindow(&errorbox);
	w_unhighlight(&header);
	w_unhighlight(&body);
	return((c == CR) || (tolower(c) == 'y'));
}

/* Load info from CONFIG.DOG and AREAS.BBS. CONFIG.DOG unambiguously
identifies the netmail area, node address and sysop name; do that first.
AREAS.BBS may contain sysop name. It may also contain a path identical to 
the netmail area; if so, drop it from the list. */

static loadinfo() {
int f,i;
char *cp,ln[256],word[5];

/* Mandatory */

	cp= getenv("RMPATH");			/* get RMPATH= */
	if ((int)cp == NULL) return;
	strcpy(rmpath,cp);

	cp= getenv("RMEDIT");			/* get RMEDIT= */
	if ((int)cp == NULL) return;
	strcpy(editor,cp);


/* Optional. */

	cp= getenv("RMUSER");			/* get RMUSER= */
	if ((int)cp != NULL) strcpy(rmuser,cp);

	cp= getenv("PGPPATH");			/* get PGPPATH= */
	if ((int)cp != NULL) strcpy(pgppath,cp);

	cp= getenv("RMFN");
	if ((int)cp != NULL) strcpy(textfile,cp);

	cp= getenv("PGPPASS");			/* get PGPPASS= */
	if ((int)cp != NULL) strcpy(pgppass,cp);

	cp= getenv("TZ");			/* get TZ= */
	if ((int)cp != NULL) strcpy(settz,cp);

	cp= getenv("RMOPT");			/* get options */
	if ((int)cp != NULL) for ( ; *cp; ++cp) {
		switch (tolower(*cp)) {
			case 's': secret_prompt= 1; break;
			case 'h': initial_help= 0; break;
		}
	}

	makefn(ln,rmpath,"config.dog");		/* full filename */
	f= open(ln,OPEN_RO);
	if (f != -1) {
		while (rline(f,ln,sizeof(ln))) {
			cp= skip_delim(ln);	/* skip leading space */
			if (! *cp) continue;	/*    skip blank lines */
			if (*cp == ';') continue; /*  skip comments */

			strncpy(word,cp,4);	/* (sufficiently unique) */
			word[4]= NUL;
			stolower(word);

			cp= next_arg(cp);	/* point to next */
			cp[64]= NUL;		/* paranoid safety truncate */

			if (same(word,"name")) strcpy(user_name,cp);
			else if (same(word,"node")) set_nn(cp,&id);
			else if (same(word,"mail")) strcpy(area[0],cp);
		}
		close(f);
	}

	makefn(ln,rmpath,"areas.bbs");
	f= open(ln,OPEN_RO);
	if (f != -1) {
		rline(f,ln,sizeof(ln));		/* first line is system name */
		for (i= 0, cp= ln; i < sizeof(tagline) && *cp; ++cp) {
			if (*cp == '!') break;
			tagline[i++]= *cp;
		}
		tagline[i]= 0;

/* Make a list of all the areas. If [0] is set (netmail area found in
COMFIG.DOG) add to the list starting at 1. Check each path read for ==
to area[0]; this will catch duplicates (netmail area within
AREAS.BBS). However, if we run into a pathname in AREAS.BBS that
matches the one from CONFIG.DOG, copy in the area name, since that
info is not contained in CONFIG.DOG. */

		i= 0; if (*area[0].path) i= 1;
		while (rline(f,ln,sizeof(ln))) {
			cp= skip_delim(ln);	/* skip leading blanks */
			if (! *cp) continue;	/*   skip blank lines */
			if (*cp == ';') continue; /* skip comments */
			if (*cp == '-') continue; /* funny options */

			cpyarg(area[i].path,cp);/* copy in first atom */
			stolower(area[i].path);	/* lower case */
			cp= next_arg(cp);	/* point to areaname */
			cpyarg(area[i].name,cp);
			stoupper(area[i].name);	/* UPPER CASE */
			if (i && same(area[i].path,area[0].path)) {
				strcpy(area[0].name,area[i].name);
				continue;
			}
			if (i < AREAS) ++i;
		}
		close(f);
	}
}

/* Create a pathname from the RMPATH variable and the given string. Mainly
this consists of concatenating the two strings, making sure there's a
path delimiter character between them. */

void makefn(d,p,fn)
char *d,*p,*fn;
{
	strcpy(d,p);
	if (p[strlen(p) - 1] != '\\') strcat(d,"\\");
	strcat(d,fn);
}

/* Fixed length string compare. Returns 0 if a match */

int fcomp(s1,s2,n)
char *s1,*s2;
int n;
{
	while (n) {
		if (*s1++ != *s2++) break;
		--n;
	}
	return(n);
}
/* If a character from the keyboard is present, return it, else
return 0. This function should not poll the ring buffer, nor get
characters from the script, since it is used to check for aborts,
etc during file transfers. */

int keyhit() {

int c;		/* int not char */

	c= iskey();			/* sample keyboard, */
	if (c != -1) {			/* if one there, */
		if (c == 0) {		/* if IBM PC F-key leadin */
			while ((c= iskey()) == -1);
			c += 128;	/* get 2nd key, */
		}			/* set MSB */
	} else c= 0;			/* no key hit */
	return(c);
}


/* Return true if the two nodes are members of the same net. */

int same_zone(n1,n2)
struct _node *n1,*n2;
{
	return(n1-> zone == n2-> zone);
}

/* Return true if the two nodes are members of the same net. */

int same_net(n1,n2)
struct _node *n1,*n2;
{
	return(
		(n1-> net == n2-> net) &&
		(n1-> zone == n2-> zone)
	);
}

/* Return true if the two nodes are the same. */

int same_node(n1,n2)
struct _node *n1,*n2;
{
	return(
		(n1-> number == n2-> number) &&
		(n1-> net == n2-> net) &&
		(n1-> zone == n2-> zone)
	);
}

/* Parse a string into net and node number. zone and net are untouched if
not found in the input string. This parses as follows: (xx means unchanged)

string			output

99:88/77		99:88/77.0
88/77			xx:88/88.0
77			xx:xx/77.0
99:88			99:88/00.0
88/			xx:88/00.0
99:88/77.3		99:88/77.3

*/

void set_nn(cp,n)
char *cp;
struct _node *n;
{
int x;

	if (isdigit(*cp)) {
		n-> number= atoi(cp);		/* assume its node only */
		while (isdigit(*cp)) ++cp;	/* skip the number, */
	}
	if (*cp == ':') {			/* if it was a zone, */
		n-> zone= n-> number;		/* that was a zone number, */
		n-> net= atoi(++cp);		/* net must follow */
		while (isdigit(*cp)) ++cp;	/* skip the net number */
		if (*cp == '/') {		/* if that was a net number */
			n-> number= atoi(++cp);	/* set node number (or zero) */
			while (isdigit(*cp)) ++cp; /* skip node number */
		}

	} else if (*cp == '/') {		/* if that was a net number, */
		n-> net= n-> number;		/* copy it up, */
		n-> number= atoi(++cp);		/* now set node number */
		while (isdigit(*cp)) ++cp;	/* skip node number */
	}
	if (*cp == '.') {			/* if a dot */
		n-> point= atoi(++cp);		/* parse point */
	}
}

/* Copy one node structure into another. IMPORTANT NOTE: Notice that the
structures are treated as char * pointers. */

void cpy_node(d,s)
struct _node *d,*s;
{
	d-> zone= s-> zone;
	d-> net= s-> net;
	d-> number= s-> number;
	d-> point= s-> point;
}

/* Return a pointer to a string of the zone:net/node. */

char *str_node(n)
struct _node *n;
{
#define KLUDGE 2			/* strings we can make at once */
static char *cp,work[KLUDGE][40];	/* where we keep them */
static int k;				/* which one as are on */

	k = ++k % KLUDGE;	/* select next ... */
	cp= work[k];		/* easier faster shorter */
	*cp= NUL;		/* empty it */

	if (n-> point) sprintf(cp,"%u:%u/%u.%u",n-> zone,n-> net,n-> number,n-> point);
	else sprintf(cp,"%u:%u/%u",n-> zone,n-> net,n-> number);
	return(cp);
}

/* Level 1 read/write routines with explicit far pointers. */

unsigned writef(h,p,count)
int h;
char far *p;
unsigned count;
{
char buff[512];
char *cp;
unsigned i,n,c;

#pragma loop_opt(on)			/* optimize this small loop */

	for (c= count; c;) {
		n= (c > sizeof(buff) ? sizeof(buff) : c); /* calc count */
		for (i= n, cp= buff; i--;) *cp++= *p++;	/* far to near */
		if (write(h,buff,n) != n) break;	/* write, error check */
		c -= n;					/* did this much more */
	}

#pragma loop_opt()					/* revert to default */

	return(count - c);				/* amt written */
}

unsigned readf(h,p,count)
int h;
char far *p;
unsigned count;
{
char buff[512];
char *cp;
unsigned c,i,n;

#pragma loop_opt(on)

	for (c= count; c;) {
		n= (c > sizeof(buff) ? sizeof(buff) : c);
		if (! (n= read(h,buff,n))) break;	/* read to buff */
		c -= n;					/* tally */
		for (cp= buff; n--;) *p++= *cp++;	/* copy data out */
	}

#pragma loop_opt()					/* revert */
	return(count - c);
}

