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

/*

23 Oct 92
	Changed MSGID string to be compatible with the world.

31 Oct 92
	Changed hard-coded "MESSAGE.TXT" to externally specified.

9 Nov 92
	Added colons to MSGID and PID keywords (!)


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

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.

*/
/*
	Message base support for .MSG files.


PUBLIC FUNCTIONS

select_area(n)
	Count the messages in the selected area, and determine the
	highest message number. 

next_message()
	Determine the next available message.

previous_message()
	Determine the previous message.

first_message()
last_message()
	Select the first (oldest) or last (newest) message, respectively.

display_message()
	Display the currently selected message.

save_message(new)
	Convert the edited text file MESSAGE.TXT into a .MSG file.
	'new' indicates whether to add a new message to the end of the
	list or to update and existing one.



LOCAL FUNCTIONS:

display_text()
	Display the in-memory text in the window 'body'.

text_motion(key)
	Handles basic cursor motion, up, down, page-up, page-down,
	hone and end. 

write_text()
	Write out the text in memory to a disk file, for editing.
	Returns zero if error.

*/

static int msgno;			/* current message number */
static int message_highest;		/* current highest message number */
static int message_total;		/* current count of messages */
static unsigned message_lines;		/* number of lines in message */

static int msgfile;			/* currently open file */
static struct _msg msg;			/* message header */
static struct _node msg_orig;		/* KLUDGE for backwards compatibility */
static struct _node msg_dest;		/* KLUDGE for backwards compatibility */



/* Save a text file as a .MSG file. */

int save_message(new)
int new;
{
char *cp,*sp;		/* junk */
int i,f,o;		/* junk */
char ln[256];		/* line of text read from file */
char keyword[80];	/* header line keyword */
char state;		/* message header completeness state (see below) */
struct _node s,d;	/* junk */
long x;			/* junk */

/* Message-header-completeness bits. 'state' is built up when keywords
are processed, and checked when done with the header portion. */

#define TO 1
#define FROM 2
#define SUBJ 4

#define DONE (TO | FROM | SUBJ)

	f= open(textfile,OPEN_RO);
	if (f == -1) {
		sprintf(ln,"The file \"%s\" disappeared! Want to re-edit and try again?",textfile);
		return(error(ln));
	}
	for (cp= (char *) &msg, i= sizeof(struct _msg); i--;)
		*cp++= 0;				/* clear the msg header */
	cpy_node(&msg_orig,&id);			/* default node address */
	cpy_node(&msg_dest,&id);
	gtod(msg.date);					/* set creation date */
	msg.attr |= MSGLOCAL;

	state= 0;					/* just started */
	while (1) {
		if (!rline(f,ln,sizeof(ln))) break;	/*   (EOF) */
		cp= skip_delim(ln);
		if (*cp == ';') continue;		/* and comments */

		cpyarg(keyword,cp);			/* line type keyword */
		stolower(keyword);			/* consistency is for machinery */
		cp= next_arg(cp);			/* skip to text following */

		if (! *keyword) break;			/* end of msg header */

		else if (same(keyword,"from:")) {	/* FROM */
			cvtname(cp,&msg_orig);		/* cleanup, get node */
			cp[36]= NUL;			/* truncate */
			strcpy(msg.from,cp);
			state |= FROM;

		} else if (same(keyword,"to:")) {	/* TO */
			cvtname(cp,&msg_dest);
			cp[36]= NUL;
			strcpy(msg.to,cp);
			state |= TO;

		} else if (same(keyword,"subject:")) {	/* SUBJECT */
subj:			cp[72]= NUL;
			strcpy(msg.subj,cp);
			state |= SUBJ;

		} else if (same(keyword,"file-send:")) { /* FILE-SEND */
			msg.attr |= MSGFILE;
			goto subj;

		} else if (same(keyword,"file-req:")) {	/* FILE-REQ */
			msg.attr |= MSGFREQ;
			goto subj;

		} else if (same(keyword,"options:")) {	/* OPTIONS */
			stolower(cp);
			while (*cp) {
				cpyarg(keyword,cp);	/* process subcommands */
				cp= next_arg(cp);
				if (same(keyword,"kill-sent")) msg.attr |= MSGKILL;
				else if (same(keyword,"private")) msg.attr |= MSGPRIVATE;
				else if (same(keyword,"hold")) msg.attr |= MSGHFP;
				else if (same(keyword,"Receipt-requested")) msg.attr |= MSGRRR;
				else if (same(keyword,"Audit-request")) msg.attr |= MSGAR;
				else if (same(keyword,"File-update-req")) msg.attr |= MSGFUR;
			}

		} else {				/* nothing we understand */
			close(f);
			sprintf(ln,"Don't know what the line beginning with \"%s\" is supposed to be; want to re-edit?",keyword);
			return(error(ln));
		}
	}

/* Now see how far we got. Generate a useful error message. */

	if (state != DONE) {
		close(f);
		strcpy(ln,"Missing or ill-formed ");
		i= 0;
		if ((state & TO) == 0) { strcat(ln,"\"To:\" "); ++i;}
		if ((state & FROM) == 0) { strcat(ln,"\"From:\" "); ++i;}
		if ((state & SUBJ) == 0) { strcat(ln,"\"Subject:\" "); ++i;}
		if (i == 1) strcat(ln,"field"); else strcat(ln,"fields");
		strcat(ln,"; want to re-edit?");
		return(error(ln));
	}

/* If 'new', find the highest message number, add one, and create a
new message. */

	if (new) {
		select_area(areano);		/* (select recounts messages) */
		sprintf(keyword,"%d.MSG",message_highest + 1);

	} else sprintf(keyword,"%u.MSG",msgno);
	makefn(ln,area[areano].path,keyword);	/* make full pathname */
	o= creat(ln,CREAT_RW);			/* create new file */
	if (o == -1) {
		close(f);			/* oops! */
		error("Can't create new message file");
		return(0);
	}

/* Generate the IFNA kludge lines, add them to the message. */

	cpy_node(&d,&msg_dest);			/* the INTL line cannot contain */
	cpy_node(&s,&msg_orig);			/* point addresses */
	d.point= s.point= 0;			/* so we force it clear */

	msg.orig_node= id.number;		/* always from us */
	msg.orig_net= id.net;

	sprintf(ln,"\001INTL %s %s\r\n",str_node(&d),str_node(&s));
	if (!same_zone(&msg_dest,&id)) {
		msg.dest_node= msg_dest.zone;	/* IFNA kludge! */
		msg.dest_net= msg_orig.zone;

	} else {
		msg.dest_node= msg_dest.number;	/* intra-zone */
		msg.dest_net= msg_dest.net;
	}
	write(o,&msg,sizeof(struct _msg));	/* write out header */

	if (msg_orig.point) sprintf(&ln[strlen(ln)],"\001FMPT %d\r\n",msg_orig.point);
	if (msg_dest.point) sprintf(&ln[strlen(ln)],"\001TOPT %d\r\n",msg_dest.point);
	write(o,ln,strlen(ln));			/* write out kludges */

	sprintf(ln,"\001PID: ReadMail\r\n");	/* our program ID */
	write(o,ln,strlen(ln));

	time(&x);				/* mS since 1970 */
	sprintf(ln,"\001MSGID: %s %lx\r\n",str_node(&id),x);
	write(o,ln,strlen(ln));

/* Copy the text from the edited file to the .MSG file en masse. */

	while (i= readf(f,mem,memsize)) {	/* read chunks */
		if (writef(o,mem,i) != i) {	/* write chunks */
			close(f);
			close(o);		/* oops */
			return(error("Disk full writing message; want to re-edit and try to recover from there?"));
		}
	}
	close(f);
	close(o);

	select_area(areano);			/* recount messages... */
	return(0);
}

/* Local routine: given a name string of the form "name@z:n/f.p", get
the node address out and strip it off the string. */

static cvtname(cp,d)
char *cp;
struct _node *d;
{
	for ( ; *cp; ++cp) {
		if (*cp == '@') {		/* look for the marker */
			*cp++= NUL;		/* strip it off */
			set_nn(cp,d);		/* get node address */
			return;
		}
	}
}

/* Display current, next and previous messages. */

void first_message() {

	dispmsg(msgno= 1,1);
}

void last_message() {

	dispmsg(msgno= message_highest,-1);
}

void next_message() {

	dispmsg(msgno + 1,1);
}

void previous_message() {

	dispmsg(msgno - 1,-1);
}

void display_message() {

	dispmsg(msgno,0);
}

/* Display a message. */


static dispmsg(n,dir)
unsigned n;
int dir;
{
int i;
char buff[80];

	if (mem == NULL) return;		/* no-memory... */
	zaptext();				/* clear the in-memory text */

	i= pickmsg(n,dir);			/* find next/prev/current message */
	if (i != msgno) {			/* if a different message, */
		msgno= i;			/* new current-message number */
		++msg.times;			/* number of times read */
		if (same(user_name,msg.to)) msg.attr |= MSGREAD;
	}
	display_header();			/* display window header */

/* Generate the in-memory copy of the message. We generate header text
followed by the message body. We want CR/LF terminating every line.
This detects CR, CR/LF and LF as EOL markers. */

	message_lines= 0;
	listhdr(&msg,msgno);				/* load msg header */
	loadtext("",0);					/* load message body */
	settext();					/* setup text pointers */
	writemsg();					/* update/close msg */

	w_pos(&header,0,30);				/* say how many lines */
	w_printf(&header,"(%d lines, %,u bytes)",message_lines,textsize);
	if (textsize == memsize) w_printf(&header," \002too big!\003");
	display_text();					/* initial display */

/* Update the LASTREAD file. */

	makefn(buff,area[areano].path,"lastread");
	i= open(buff,OPEN_RW);				/* in case someone has */
	if (i == -1) i= creat(buff,CREAT_RW);		/* and "extended" */
	lseek(i,0L,0);
	write(i,&msgno,2);				/* LAST READ file */
	close(i);
}

/* Pick the correct message number. We've given a starting number, and
a direction, which may be 0 (choose current), 1 (next) or -1 (previous). It's
not quite totally intuitive. */

static pickmsg(n,dir)
int n,dir;
{
int i;

	if (n > message_highest) n= message_highest; /* bound it */
	if (n < 1) n= 1;

	i= findmsg(n,dir);			/* find next/prev/current msg */
	if (i) return(i);			/* no problemo */

/* OK, no message found. If the direction is 0, try to find a message
thats's readable. Try dir=-1 (so we don't skip unread messages), if
that fails, try dir=1. If all that fails, there aren't any messages;
use new_message() to create an empty one. */

	i= findmsg(n,-1);			/* backwards */
	if (i) return(i);

	i= findmsg(n,1);
	if (i) return(i);

	return(1);				/* always number 1 */
}

/* Display the top of window header. */

void display_header() {

	init_window(&header);
	w_printf(&header,"[#%u of %u in %s]  \001 %s ",
	    msgno,message_highest,		/* [#1 of 99 */
	    area[areano].name,			/* in THIS.AREA] */
	    area[areano].path);			/* pathname */
}

/* Load message text from the msgfile into the in-memory buffer. The
IFNA kludge lines are really part of the header; prefix all kludge
lines with ";" to indicate they are non-editable. INTL, FMPT, TOPT,
PID, MSGID etc all get set by ReadMail implicitly. */

static loadtext(quote,edit)
char *quote;		/* quoting indicator and quote string (both) */
int edit;		/* 1 == insert blank-line notice */
{
char lastc,c,buff[80];
int i,f;
char width;		/* line length max. */
char qlen;		/* strlen of quote */
char rptchar;		/* character repeat count (for TABs) */
char suppressed;	/* 1 == we ate a CR (when wrapping lines) */
char in_paragraph;	/* 1 == we need to wrap this paragraph */
int col;		/* "column" position */
char word[40];		/* word built from input */
int wi;			/* index into word */
int count;		/* read() count */

	qlen= strlen(quote);			/* length (may be 0) */
	width= (qlen ? 64 : 80);		/* set margins */
	*word= NUL; wi= suppressed= in_paragraph= rptchar= col= c= 0;

/* Before we process the message body, handle all the IFNA kludge lines, 
which are really part of header information. NOTE: some editors terminate
lines with CR, not LF or CR/LF! Hence we ignore LFs in the header portion. */

	while (read(msgfile,&c,1)) {
		if (c == LF) continue;		/* ignore LFs! */
		if (c != SOH) break;		/* doesn't start with ^A */
		*textptr++= ';';		/* prefix with semicolon */
		*textptr++= SOH;
		while (readf(msgfile,textptr,1)) {
			if (*textptr == CR) break; /* lines terminate on CR! */
			++textptr;		/* save that char */
		}
		*textptr= SUB;			/* (so stradd() works) */
		textsize= textptr - mem;
		stradd("\r\n");			/* CR/LF terminate each */
		++message_lines;		/* count another */
	}

/* Terminate the message header. The 'goto jump_in' ASSUMES that the
header info will be far, far less than the size of the in-memory
buffer for the message (we don't check). Everything else has been
preset to work. */

	*textptr= SUB;				/* make sure it's terminated */
	textsize= textptr - mem;

	if (edit) {
		stradd(";A blank line is required between the message header and body.\r\n");
		++message_lines;
	}
	stradd("\r\n");
	goto jump_in;

	while (1) {
		word[wi= 0]= NUL;		/* word is empty */
		while ((wi < width) && (wi < sizeof(word))) { /* word at a time */
			lastc= c;		/* remember last character */
			while (1) {
				if (rptchar) --rptchar;
				else count= read(msgfile,&c,1); /* read a character, */
				if (! count) break; /* check EOF */

jump_in:			switch (c) {
					case SOH:
						word[wi++]= ';';
						break;

					case CR:
						if (in_paragraph && !suppressed) {
							suppressed= 1;
							c= ' ';
						}
						break;

					case SUB: c= NUL; count= 0; break;
					case CR + 128: if (lastc == ' ') c= NUL;
						else c= ' '; break;

					case TAB: 
						rptchar= 8 - ((col + wi) % 8);
						c= ' ';
						continue;

					default: if (c < ' ') c= NUL; break;
				}
				if (c) break;	/* got a real one or CR */
			}
			if (count == 0) break;	/* end of file, word too */
			if (c == CR) break;	/* end of (the) wor(l)d */
			word[wi++]= c;		/* build the word */
			word[wi]= NUL;		/* for output */
 			if (c == ' ') break;
			suppressed= 0;		/* definitely not a CR */
		}

/* Now we have a word, plus the reason for word termination in 'c'.
We append words to the current line until we get a CR or line length
would be exceeded. If CR, we watch for two in a row, indicating end of
paragraph (ie. hard CR). Single CRs get ignored (in a paragraph).
Lines are wrapped to fit within the specified width. */

		if (suppressed && (c == CR)) {	/* two CRs in a row */
			stradd("\r\n");		/* end paragraph */
			++message_lines;
			col= 0;			/* go to next line */
		}
		if (col + wi >= width) {	/* ... if it wont fit this line */
			in_paragraph= 1;	/* needin' tuh wrap */
			stradd("\r\n");
			++message_lines;
			col= 0;
		}
		if ((col == 0) && qlen) {	/* add the quote indicator */
			stradd(quote);
			col= qlen;		/* current column... */
		}
		stradd(word);			/* add the text, */
		col += wi;			/* recalc column */
		wi= 0;				/* empty what we processed */

		if (c == CR) {			/* if we had a CR */
			in_paragraph= 0;	/* paragraph over */
			stradd("\r\n");
			++message_lines;
			col= 0;			/* go to next line */
		}
		if (! count) break;		/* we had hit EOF */
	}
	if (col != 0) {
		stradd("\r\n");			/* always ends with CR/LF */
		++message_lines;
	}
}

/* Kill (delete) a message. */

void kill_message() {
char buff[80],fn[80];

	sprintf(fn,"%u.msg",msgno);		/* .MSG filename */
	makefn(buff,area[areano].path,fn);	/* make full pathname */
	unlink(buff);				/* kill it */
	select_area(areano);			/* recount messages, etc */
}

/* Create a new or reply-to in-memory message. */

void new_message(type)
int type;	/* 0 == new message */
		/* 1 == edit existing message (add ";...blank line..." mainly) */
		/* 2 == reply message (quotes, etc) */
{
#define NEW 0
#define EDIT 1
#define REPLY 2
char buff[256],buffx[30],*cp,*sp;
int i;

	if (mem == NULL) return;			/* no-memory... */

	zaptext();					/* clear the in-memory text */

	gtod(buffx);					/* set creation date */
	sprintf(buff,";Date      %s\r\n",buffx);
	stradd(buff);

	if ((type != NEW) && !findmsg(msgno,0)) {	/* re-open msg file */
		error("Message disappeared?!");
		return;
	}

/* Reply generates a message "to" the "from" name, unless we're
replying to a message we entered, then we reply "to" the "to" name. */

	if (type == REPLY) {
		if (same_node(&msg_orig,&id)) {		/* if already from us */
			/* use same dest & name */

		} else {
			strcpy(msg.to,msg.from);	/* do reply */
			cpy_node(&msg_dest,&msg_orig);
		}
		strcpy(buff,"re: ");
		strcat(buff,msg.subj);			/* make default "re:" */
		buff[72]= NUL;				/* subject */
		strcpy(msg.subj,buff);
		cpy_node(&msg_orig,&id);		/* always from us */
		strcpy(msg.from,user_name);
		msg.attr |= MSGKILL | MSGLOCAL;		/* default attrib */

	} else if (type == NEW) {			/* else clear it out */
		for (cp= (char *) &msg, i= sizeof(struct _msg); i--;)
			*cp++= 0;			/* clear the msg header */
		cpy_node(&msg_dest,&id);		/* default to us */
		cpy_node(&msg_orig,&id);		/* always from us */
		strcpy(msg.from,user_name);
		msg.attr |= MSGKILL | MSGLOCAL;		/* default attrib */
	}

	sprintf(buff,"From:      %s@%s\r\n",msg.from,str_node(&msg_orig));
	stradd(buff);
	sprintf(buff,"To:        %s@%s\r\n",msg.to,str_node(&msg_dest));
	stradd(buff);

	i= msg.attr;				/* make a copy, we change it */
	if (type == REPLY) i &= ~(MSGFILE | MSGFREQ);

	if (i & MSGFILE) sprintf(buff,"File-Send: %s\r\n",msg.subj);
	else if (i & MSGFREQ) sprintf(buff,"File-Req:  %s\r\n",msg.subj);
	else sprintf(buff,"Subject:   %s\r\n",msg.subj);
	stradd(buff);

	stradd("Options:   ");
	if (i & MSGKILL) stradd("kill-sent ");
	if (i & MSGPRIVATE) stradd("private ");
	stradd("\r\n");

	stradd(";Status:   ");
	if (i & MSGORPHAN) stradd("ORPHAN ");
	if (i & MSGSENT) stradd("sent ");
	if (i & MSGREAD) stradd("recv'd ");
	sprintf(buff,"(read %d times)\r\n",msg.times);
	stradd(buff);

	*buffx= NUL;				/* assume not quoting */
	if (type == REPLY) {
		cp= skip_delim(msg.to);		/* build quote prefix */
		sp= buffx;
		*sp++= ' ';			/* leading space */
		if (*cp) *sp++= toupper(*cp);	/* first initial */
		cp= next_arg(cp);		/* possibly second */
		if (*cp) *sp++= toupper(*cp);
		*sp++= '>';			/* the traditional angle bracket */
		*sp++= ' ';			/* trailing space */
		*sp= NUL;
	}
	loadtext(buffx,1);			/* load'n'quote */

	closemsg();
	if (tagline[0] != NUL) {
		sprintf(buff,"\r\n--- ReadMail\r\n * Origin: %s (%s)\r\n",tagline,str_node(&id));
		stradd(buff);
	}
	settext();					/* setup text pointers */
}

/* Write the in-memory message to disk file MESSAGE.TXT. */

int write_text() {
char *cp,buff[80];
int f;

	f= creat(textfile,CREAT_RW);
	if (f == -1) {
		sprintf(buff,"Can't create file \"%s\"",textfile);
		error(buff);
		return(0);
	}
	if (writef(f,mem,textsize) != textsize) {
		sprintf(buff,"Disk full writing \"%s\"!",textfile);
		error(buff);
		return(0);
	}
	close(f);
	return(1);
}

/* List message stats in the header window, add other message header text
to the in-memory text. */

static listhdr(m,n)
struct _msg *m;
int n;
{
char buff[100];			/* longest field 72 + prefix... */

	sprintf(buff,";Date      %s\r\n",msg.date);
	stradd(buff);

/*	if (m-> reply) p_printf("In-reply-to #%u    ",m-> reply);
	if (m-> up) p_printf("See also #%u",m-> up);
*/

	sprintf(buff,"From:      %s@%s\r\n",m-> from,str_node(&msg_orig));
	stradd(buff);
	sprintf(buff,"To:        %s@%s\r\n",m-> to,str_node(&msg_dest));
	stradd(buff);

	if (m-> attr & MSGFILE)	sprintf(buff,"File-Send: %s\r\n",m-> subj);
	else if (m-> attr & MSGFREQ) sprintf(buff,"File-Req:  %s\r\n",m-> subj);
	else sprintf(buff,"Subject:   %s\r\n",m-> subj);
	stradd(buff);

	stradd("Options:   ");
	if (m-> attr & MSGKILL) stradd("kill-sent ");
	if (m-> attr & MSGPRIVATE) stradd("private ");
	stradd("\r\n");

	stradd(";Status:   ");
	if ((!same_node(&id,&msg_orig) && !same_node(&id,&msg_dest)) || 
	    (m-> attr & MSGFWD))
		stradd("IN-TRANSIT ");
	if (m-> attr & MSGORPHAN) stradd("ORPHAN ");
	if (m-> attr & MSGSENT) stradd("sent ");
	if (m-> attr & MSGREAD) stradd("recv'd ");

	sprintf(buff,"(read %d times)\r\n",msg.times);
	stradd(buff);

	message_lines += 10;			/* ahh, it's easier */
}

/* Generate a list of message areas; return the highest area number. */

int list_areas() {
int i;
char buff[80];

	if (mem == NULL) return;			/* no-memory... */

	zaptext();					/* clear the in-memory text */
	stradd("\r\n");
	for (i= 0; *area[i].path; i++) {		/* in memory */
		sprintf(buff,"  %32s  %d  %s\r\n",
		    area[i].name,i + 1,area[i].path);
		stradd(buff);
	}
	message_lines= i;				/* number of lines */
	settext();
	return(i);
}

/* Select a message area as current. Load LASTREAD, if available, from the
area and set the current message number. */

void select_area(n)
int n;
{
char buff[80];
struct _xfbuf xfbuf;
int i,f;

	if ((n < AREAS) && *area[n].path)
		areano= n;			/* in range */

	message_highest= message_total= 0;	/* reset */
	xfbuf.s_attrib= 0;			/* normal files only */
	i= 0;
	makefn(buff,area[areano].path,"*.MSG");	/* make full pathname */
	while (_find(buff,message_total,&xfbuf)) {
		++message_total;		/* another found */
		n= atoi(xfbuf.name);
		if (n > message_highest) message_highest= n;

	}
	msgno= 1;				/* assume none found */
	makefn(buff,area[areano].path,"lastread");
	f= open(buff,OPEN_RO);
	if (f != -1) read(f,&msgno,2);		/* KLUDGE! */
	if (f != -1) close(f);

	closemsg();				/* mark no msg file open */
}

/* Find a message by number. This will loop up or down, or not at all,
depending on the flag passed. The file, if found, is left open (global
'msgfile' so that it can be worked on. This returns "not found" or the 
message is skipped if it doesnt meet various criteria, such as privacy,
being forwarded, etc. best to look at the code. 

KLUDGE: This looks for the IFNA Kludge in the text immediately following 
the message header. If it finds it, it sets msg_dest to that. */

static findmsg(num,loop)
int num,loop;
{
char *cp,lastc,name[80];
char work[256];			/* rummage for ^A lines */
int i;

	do {
		if ((num > message_highest) || (num < 1)) return(0);

		sprintf(work,"%d.MSG",num);
		makefn(name,area[areano].path,work);
		msgfile= open(name,OPEN_RW);

		if (msgfile != -1) {
			read(msgfile,&msg,sizeof(struct _msg));

/* Generate the msg_orig and msg_dest structures, used globally, from the
piecemeal elements in the message header. */

			msg_orig.number= msg.orig_node;
			msg_orig.net= msg.orig_net;

			msg_dest.number= msg.dest_node;
			msg_dest.net= msg.dest_net;

			msg_orig.zone= id.zone; 	/* in case no */
			msg_dest.zone= id.zone;		/* IFNA Kludge */

			msg_orig.point= 0;		/* assume no points */
			msg_dest.point= 0;

/* Check for the presence of an IFNA Kludge; if there is one, set the 
addresses to that. Also looks for FMPT and TOPT, and fills in the rest of
the _node structures. ^AWORD must be at the start of the line; either the
very first thing following the header, or following a CR/LF. */

			i= read(msgfile,work,sizeof(work));
			work[i]= NUL;
			lseek(msgfile,(0L + sizeof(struct _msg)),0);

			lastc= CR;				/* for first time */
			for (cp= work; *cp;) {
				if (*cp != SOH) {		/* look for SOH */
					lastc= *cp++ & 0x7f;	/* but remember prev. */
					continue;
				}
				++cp;
				if ((lastc != CR) && (lastc != LF)) {
					continue;		/* must be at start of line */
				}
				if (fcomp(cp,"INTL",4) == 0) {
					cp= next_arg(cp);
					set_nn(cp,&msg_dest);
					cp= next_arg(cp);
					set_nn(cp,&msg_orig);

				} else if (fcomp(cp,"FMPT",4) == 0) {
					msg_orig.point= atoi(next_arg(cp));

				} else if (fcomp(cp,"TOPT",4) == 0) {
					msg_dest.point= atoi(next_arg(cp));
				}
			}

/* The number-of-times-read counter is supposed to run 1 - N. If it is 0,
it was probably generated outside Fido. In order for the [NOTE: Modified..]
check to work, it must start at 1. Fix it. */

			if (! msg.times) ++msg.times;

			return(num);
		}
		num += loop;
	} while (loop);

	return(0);
}

/* Close the message file, if open. */

static closemsg() {

	if (msgfile != -1) close(msgfile);
	msgfile= -1;
}

/* Write out the message header and close the message file. */

static writemsg() {

	lseek(msgfile,0L,0);
	write(msgfile,&msg,sizeof(struct _msg));
	closemsg();
}

/* Display the in-memory message text. */

void display_text() {
int i,d;
char far *cp;

	w_pos(&body,0,0);				/* start at top */
	cp= textptr;					/* ptr to text */
	while (1) {
		if (*cp == CR) {			/* EOL marker */
			w_clreol(&body);		/* clear to EOL */
			body.col= 0;			/* CR action */

		} else if (*cp == LF) {			/* next line... */
			w_char(&body,LF);		/* LF action */
			if (body.line == 0) break;	/*   it wrapped */

		} else if (*cp == SUB) {		/* end of file */
			w_clreol(&body);		/* clear to EOL */
			body.col= 0;
			while (++body.line < body.lines)
				w_clreol(&body);	/* clear rest of window */
			break;

		} else {
			w_char(&body,*cp);		/* display it */

			/* This detects when writing a character in the
			last cell on the last line wraps to the start of
			the first line. (w_char() wraps rather than scrolls.) */

			if (body.col == 0) if (body.line == 0) break;
		}
		++cp;
	}
}

/* Do text display motion. This consists of positioning the text display
pointer up and down through the in-memory text, using LFs as the EOL
marker. */

void text_motion(key)
char key;	/* IBM keyboard char */
{
char *cp;
int d,i;

	switch (key) {
		/* HOME is simple. */
		case KEY_HOME:
			textptr= mem; break;

		/* END moves to the end of the file minus (body.lines - 1)
		lines. */
		case KEY_END:
			while (*textptr != SUB) ++textptr;
			for (i= body.lines - 1; textptr > mem && i;) {
				if (*--textptr == LF) --i;
			}
			break;

		/* DOWN moves the origin down one line. */
		case KEY_DN:			/* scroll up */
			d= 1; goto down;	/* down one line */

		/* PAGE DOWN moves down (body.lines - 1) lines down. If
		we reach the bottom, scroll up (always display one line.) */
		case KEY_PGDN:
			d= body.lines - 2;
down:			while (*textptr != SUB) {
				if (*textptr++ == LF) if (--d == 0) break;
			}
			if (*textptr == SUB) {		/* if EOF */
				d= 1;			/* go up one */
				goto up;
			}
			break;

		/* UP moves the origin up one line. */
		case KEY_UP:
			d= 1; goto up;

		/* PAGE UP moves up (body.lines - 2) lines. */
		case KEY_PGUP:
			d= body.lines - 2;
up:			--textptr;
			while (textptr > mem) {
				if (*--textptr == LF) if (--d == 0) break;
			}
			if (textptr != mem) ++textptr;
			break;
	}
	display_text();
}

/* Create a standard FSC-0001 date string. This is MSC specific. */

static char _months[12][4] = {
	"Jan",
	"Feb",
	"Mar",
	"Apr",
	"May",
	"Jun",
	"Jul",
	"Aug",
	"Sep",
	"Oct",
	"Nov",
	"Dec"
};

static gtod(s)
char *s;
{
long ltime;
struct tm *t,*gmtime();

	time(&ltime);				/* mS since 1980 etc */
	t= gmtime(&ltime);			/* convert */
	sprintf(s,"%02d %s %02d  %02d:%02d:%02d", /* format */
	    t-> tm_mday,_months[t-> tm_mon],t-> tm_year,
	    t-> tm_hour,t-> tm_min,t-> tm_sec);
}

/* Add text to the in-memory copy of the message. SUB marks the end
of the text. */

void stradd(s)
char *s;
{
	while ((textsize < (memsize - 1)) && *s) {
		*textptr++= *s++;
		++textsize;
	}
	*textptr= SUB;
}

/* Zap the in-memory text buffer. */

void zaptext() {

	*(textptr= mem)= SUB;
	textsize= 0;
}

/* Set the final in-memory text pointer and text size. */

void settext() {

	textsize= textptr - mem;		/* final, added text */
	textptr= mem;
}
