#include "fido.h"
#include <ascii.h>
#include "fidomem.h"
#include "proto.h"

char *cpystr(char *, char *);

/* Message routing control. This builds the translate list,
and translates message numbers for routing.

open_route(tag,repeat)

This build the N x N routing table for the specified schedule tag; if 
repeat is true, and the files are still intact from the last open_route()
call, the previous tables are used; this speeds up FidoNet startup
immensely.



	In all of the following, a node may be specified by the new
longhand:

	ZONE:NET/NODE

	If ZONE or NET is not specified, then it defaults to the 
currently defined zone or net.


ZONE n	Change the default zone or net number to N. The default is
NET n	the current number.

ACCEPT-FROM n1,n2,n3 ...
	Accept mail from these systems to be forwarded. When a
	msg is about to be sent, it is checked against the route table. 
	If it originated here, then it is sent regardless. If not, 
	it must be found in the accept list; this prevents turkeys 
	(or mistakes) from forwarding mail on your phone bill.

ROUTE-TO n1 n2,n3,n4 ...
	Route all mail for n2,n3,n4 ... to n1. N1 is assumed to
	be an incoming host. 

ALIAS-AS n1, n2,n3,n4 ...
	Similar to ROUTE-TO, except that it cannot be overridden
	by route-to or file attach status.

IF-SCHEDULE tag
	Execute the statements starting with the next command until
	the next IF-SCHEDULE or END-IF statement or EOF, if the currently 
	running event matches 'tag'.

END-IF
	Defines the end of a block of statements.

SEND-TO n1, n2, n3, ...
	Marks all the specified nodes as being in the current
	schedule. Only allowed for new format ROUTE.tag files.

QUICK-SEND
	Same as "RUSH FIDONET" -- terminates the event early if
	there are no more calls to make.

INTERLEAVE n
	Do QUICK-SEND, reawaken this event in n minutes.


EXTERNAL-MAIL
	Only effect is to make Fido not delete the .OUT and .FLO
	work files at the end of the schedule.

CM-ONLY
	Functionally similar to "send-to (nodes with #CM)". Disables
	all non-CM nodes.

SEND-ONLY
	Causes Fido to not wait the usual one minute interval
	between outgoing calls. Assumes that the system is outgoing 
	ONLY.

RECV-ONLY
	Causes Fido to not send any mail. Same as the /X command
	line switch.

	Removes any routing (such as default host routing) to the
	list of nodes, if any. Note that though this is appears to
	the user as a "bit command", it is really unique, in that
	it has it's own flag and code. The problem is that Fido does
	default host routing, and that is not always what you want.

DIAL-TRIES n
CONNECT-TRIES n
DIAL-INTERVAL n
CONT-INTERVAL n
RINGS n
MODEM-STRING "s"
DIAL-PREFIX "s"
MDMTYPE n
IODEV n
CD-BIT n
	These have the same meaning as the ones in FIDO.INI


The following are modifiers that can substitute for all or any
node address arguments to the commands above.

ALL	All nodes in the list.
THISNET	All nodes in the default net
MYNET	All nodes in my net.
THISZONE All nodes in the default zone
MYZONE	All nodes in my zone.
HOSTS	Means all host nodes.


The routing table is an N by N matrix, implemented as a one level
recursive table, where N is the number of nodes in the list. Each
entry has a bunch of flags (routing controls, etc) and a "route-to"
node address, that reindexes into the same list to make the
matrix.

By default, the table is 1:1 except for nodes not in our net. All
route-to nodes are the same as the node address, except that nodes not
in our zone go to zone:0/0, and nodes in our zone but not in our net
go to zone:net/0.

Routing controls here consist of setting routing control bits and
modifying the route-to address.



 out_xlate()
	This returns the index into the node of the node to send 
this message to PLUS one. 0 if not to be sent, or -1 if error.

	If the node number doesnt translate, then there are
four possibilities:

(0) Wrong schedule.
	Not really an error, this is not the right schedule to
send mail to this nmap. Returns 0.

(1) Dest node not found.
	The destination node is not in the node list. This happens
when the dest node number is not in the route list nor nodelist. 
 out_xlate() returns -1 in this case.

(2) Dest node not allowed.
	The originator of this msg (not us) is not an accepted
nmap. This can happen if: (1) someone has a bad node list 
(wrong number, etc) and we ended up with a
wrong message) or (2) someone sent us a msg on purpose, hoping
we'd send it for them, saving THEM a toll call. out_xlate()
returns -1 in this case.

(3) Dest node is translatable.
	The dest node translated properly, ie. it was found
in the route table. Returns the index into node PLUS ONE.

(4) Node has no credit or not in the list
	For mail not originating on this system only. Applies
only to messages received from another node, that need to be
forwarded. If there is not enough credit left, or the node cannot
be found, -1 is returned, else the destination node number.

*/

/* Static stuff global to router. */

#define ERROR 255	/* watch out for char vs. int ... */
#define IDLE 254
#define IGNORE 253

#define ROUTE 1		/* ROUTE-TO <h> <n ...n> 3 states; */
#define ROUTE2 2	/* host, */
#define ROUTE3 3	/* nodes */

#define SCHED 4		/* IF-SCHEDULE <tag> */
#define SCHED2 5	/* keyword, <tag> */

#define NET 6		/* NET <n> 2 states; */
#define NET2 7		/* keyword, net */

/* These are "bit" commands; they all share the same 2nd state. */

#define ACCEPT 8	/* ACCEPT-FROM <n> 2 states; */
#define SEND 9		/* SEND-TO <n...n> 2 states; */
#define PICKUP 10	/* PICKUP <n...n> 2 states; */
#define POLL 11		/* POLL <n...n> 2 states; */
#define HOLD 12		/* HOLD <n...n> two states */
#define NOROUTE 13	/* NO-ROUTE <n...n> 2 states; */
#define BITS 19		/*     the 2nd state */

#define EXTMAIL 22	/* EXTERNAL-MAIL 1 state */

#define SENDONLY 23	/* SEND-ONLY one state */

#define RECVONLY 24	/* RECV-ONLY one state */

#define ZONE 25		/* ZONE n */
#define ZONE2 26

#define ZGATE 27	/* ZONEGATE n three states */
#define ZGATE2 28	/* get node address */

#define DIALTRY 29	/* DIAL-TRIES n two states */
#define DIALTRY2 30	/* get number of tries */

#define CNCTTRY 31	/* CONNECT-TRIES n two states */
#define CNCTTRY2 32	/* get number of tries */

#define MDMINI 33	/* MODEM-STRING string two states */
#define MDMINI2 34	/* get string */

#define SCHEDEND 35	/* END-IF */

#define BEGIN 36	/* BEGIN group */
#define END 37		/* END group */

#define ALIAS 38	/* ALIAS-AS <a> <n...n> 3 states */
#define ALIAS2 39	/* host */
#define ALIAS3 40	/* nodes */

#define RING1 41	/* RING n, two states */
#define RING2 42

#define DIALINT1 43	/* DIAL-INTERVAL n, two states */
#define DIALINT2 44

#define CONTINT1 45	/* CONT-INTERVAL n, two states */
#define CONTINT2 46

#define DIALPRF1 47	/* DIAL-PREFIX s, two states */
#define DIALPRF2 48

#define IODEV1 49	/* IODEV n, 2 states */
#define IODEV2 50

#define MDMTYPE1 51	/* MODEM-TYPE n, 2 states */
#define MDMTYPE2 52

#define MAXBAUD1 53	/* MAX-BAUD n, 2 states */
#define MAXBAUD2 54

#define CDBIT1 55	/* CD-BIT n, 2 states */
#define CDBIT2 56

#define MAILCOD1 57	/* MAIL-ERRORLEVEL n, 2 states */
#define MAILCOD2 58

#define FILECOD1 59	/* FILE-ERRORLEVEL n, 2 states */
#define FILECOD2 60

#define CMONLY 61	/* CM-ONLY one state */

#define FORWARD 62	/* FORWARD-TO <h> <n ...n> 3 states; */
#define FORWARD2 63	/* host, */
/* #define FORWARD3 	nodes (same as ROUTE-TO here) */ 

#define INTERLV1 64	/* CONTINUOUS <n> 2 states */
#define INTERLV2 65

#define QKSEND 66	/* QUICK-SEND 1 state */

static struct {
	char *word;
	char state;

} rct[] = {
	"begin",BEGIN,
	"end",END,
	"accept-from",ACCEPT,
	"forward-from",ACCEPT,		/* Ken Ganshirt's suggestion */
	"forward-to",FORWARD,
	"route-to",ROUTE,
	"alias-as",ALIAS,
	"if-schedule",SCHED,
	"end-if",SCHEDEND,
	"send-to",SEND,
	"zone",ZONE,
	"net",NET,
	"pickup",PICKUP,
	"poll",POLL,
	"no-route",NOROUTE,
	"send-only",SENDONLY,
	"recv-only",RECVONLY,
	"zonegate",ZGATE,
	"dial-tries",DIALTRY,
	"connect-tries",CNCTTRY,
	"modem-string",MDMINI,
	"hold",HOLD,
	"external-mail",EXTMAIL,
	"rings",RING1,
	"dial-interval",DIALINT1,
	"cont-interval",CONTINT1,
	"dial-prefix",DIALPRF1,
	"io-port",IODEV1,
	"cd-bit",CDBIT1,
	"modem-type",MDMTYPE1,
	"max-baud",MAXBAUD1,
	"file-errorlevel",FILECOD1,
	"mail-errorlevel",MAILCOD1,
	"cm-only",CMONLY,
	"interleave",INTERLV1,
	"quick-send",QKSEND,
	"a",IGNORE,		/* sched tag A */
	"b",IGNORE,		/* the parser sees them */
	"c",IGNORE,
	"d",IGNORE,
	"e",IGNORE,
	"f",IGNORE,
	"g",IGNORE,
	"h",IGNORE,
	"i",IGNORE,
	"j",IGNORE,
	"k",IGNORE,
	"l",IGNORE,
	"m",IGNORE,
	"n",IGNORE,
	"o",IGNORE,
	"p",IGNORE,
	"q",IGNORE,
	"r",IGNORE,
	"s",IGNORE,
	"t",IGNORE,
	"u",IGNORE,
	"v",IGNORE,
	"w",IGNORE,
	"",ERROR
};

#define STACKMAX 5			/* how many we can stack */
static int sp;				/* the stack pointer (index) */
static struct {
	struct _node def;		/* stack frames contain this */
} sf[STACKMAX];

static struct _rptrcd rptrcd;		/* we fill it in here */
static struct _node target;		/* working target node */
static struct _node routedest;		/* ROUTE-TO <routedest> */
static unsigned dest_recd;		/* for speed: recd # of routedest node */

static FLAG process;			/* 1 == process statements (else ignore) */

/* Modifier word tables. */

static FLAG not;			/* true if we are inverting commands */

static nr;				/* 1 == "no route"; ie. set 1:1 routing */

static unsigned flags;			/* accumulated bits, below. Flags
					gets cleared when a new keyword is
					found; this allows stacked arguments
					to all be used for one statement (ie.
					SEND-TO 1 2 3, MYZONE NOT MYNET) */

#define NOT 1				/* used only in the mct table */
#define ALL 2
#define MYZONE 4
#define THISZONE 8
#define MYNET 16
#define THISNET 32
#define HOSTS 64
#define FLAG_MAX 64			/* highest bit used */

static struct {				/* table of modifier words */
	char word[10];			/* its name, */
	char bit;			/* its bit pattern */

} mct[] = {
	"not",NOT,			/* invert everything */
	"all",ALL,			/* all nodes */
	"myzone",MYZONE,		/* all in my zone */
	"thiszone",THISZONE,		/* all in default zone */
	"mynet",MYNET,			/* all in my net, */
	"thisnet",THISNET,		/* all in default net, */
	"hosts",HOSTS,			/* all hosts (z:f/0 or /-1) */
	"",0
};

static char bitpat;			/* NMAP_ bit pattern to set */


static int liodev;			/* locally set comm. port number, or -1 */

/* Check the nodemap; if it is the correct schedule, load schedule-specific
stuff and return true. */

static sched_settings(tag)
char tag;
{
	lseek(nodefile,0L,0);			/* load the repeat record */
	if (read(nodefile,&rptrcd,sizeof(struct _rptrcd)) != sizeof(struct _rptrcd)) 
		return(0);			/* dud file */
	if (rptrcd.tag != tag) return(0);	/* wrong schedule */
	if (rptrcd.revision != NODEVERS) return(0); /* wrong revision */

/* Not all things are specified. rptrcd elements are all ints; < 0 means
unspecified, ie. use FIDO.INI values. Values are bounded elsewhere. */

	fido_settings();			/* load FIDO.INI settings */

	send_only= rptrcd.bits & RPT_SO ? 1 : 0;
	recv_only= rptrcd.bits & RPT_RO ? 1 : 0;
	cm_only=   rptrcd.bits & RPT_CM ? 1 : 0;
	ext_mail=  rptrcd.bits & RPT_EXT ? 1 : 0;

	if (*rptrcd.mdmstr) strcpy(mdmstr,rptrcd.mdmstr);
	if (*rptrcd.dial_pref) strcpy(dial_pref,rptrcd.dial_pref);

	if (rptrcd.mailcode >= 0) mailcode=rptrcd.mailcode;
	if (rptrcd.filecode >= 0) filecode=rptrcd.filecode;

	if (rptrcd.connect_tries >= 0) connect_tries=rptrcd.connect_tries;
	if (rptrcd.dial_tries >= 0) dial_tries= rptrcd.dial_tries;

	if (rptrcd.rings >= 0) rings= rptrcd.rings;

	if (rptrcd.dial_interval >= 0) dial_interval= rptrcd.dial_interval;
	if (rptrcd.cont_interval >= 0) cont_interval= rptrcd.cont_interval;

	return(1);
}

/* Build the route table and compile the route list if it exists. */

void open_route(tag,event)
char tag;
int event;
{
int i;
char bits,c,*cp;
char buff[SS];
unsigned host_recd;
FLAG ournet,ourzone;

/* See if we can avoid recompiling the route files, by checking for the
correct nodemap already on disk. NOTE: it loads the revision byte also. */

	if (sched_settings(tag)) return;		/* reload previous sched */

/* Different or new schedule. Clear out the structure, the route file
processing will possibly fill it in. -1's allow detection of specified
values. NOTE: We don't overwrite the revision byte. */

	for (cp= (char *) &rptrcd.tag, i= sizeof(struct _rptrcd) - 1; i--;) 
		*cp++= 255;				/* set to all -1's */

	rptrcd.bits= 0;					/* except these */
	*rptrcd.mdmstr= NUL;
	*rptrcd.dial_pref= NUL;

/* First make the node map of all nodes in the system. This also sets
default routing for other nets; for each node in another net it sets
the route-to to zero, the host. For regions, -1 node, it does not
set the host to route to. */

	cprintf(SM+202);
	if (fbit(FBIT_FNB)) bits= NMAP_SEND | NMAP_PU;	/* new default */
	else bits= (tag == 'A') ? NMAP_SEND : 0;	/* old default */

	host_recd= -1;					/* no host yet */
	i= 0;						/* preincrement ... */
	while (1) {
		if (!(i & 31)) 				/* every 32 only */
			if (evtabort()) return;		/* (for speed) */
		++i;
		if (get_node(i) == -1) break;		/* end of the table */
		nmap.bits= bits;			/* set the bits, */

/* If the net number changes, remember the zone:net/node for possible 
routing. If it is a different zone, then we route even for regions, because
they are in a different region:

		DEST NODE	ROUTE-TO NODE
hosts
(example: our zone == 1)
(example: our net == 100)
		1:100/222	1:100/222		in our zone/net
		1:333/1		1:333/0			not our net
		3:211/4		3:0/0			not our zone
regions
(example: our region == 20)
		1:20/3		1:20/3			our region
		1:30/5		1:30/5			still a region!
		3:29/17		3:0/0			not our zone
 */

/* Remember the record number of the most recent host; outside our zone,
we only remember zone hosts. */

		if (is_host(&nmap.node)) {
			ournet= same_net(&id,&nmap.node) || same_net(&altid,&nmap.node);
			ourzone= same_zone(&id,&nmap.node) || same_zone(&altid,&nmap.node);

			if (!ourzone) {			/* to another zone */
				if (is_zonehost(&nmap.node)) /* remember Z:0/0 */
					host_recd= i;
				/* else leave as last-set (zone) host */

			} else if (!ournet) {		/* to a net in our zone */
				if (is_nethost(&nmap.node)) /* remember Z:N/0 */
					host_recd= i;
				else host_recd= -1;	/* else its a REGION host */

			} else host_recd= -1;		/* its our net */
		}

/* If a host is specified set it, else point it to itself. (Not very 
interesting to note is the fact that if this node is a host (see above)
then we are setting a route-to; however it is itself ...) */

		if (host_recd != -1)
			nmap.route_recd= host_recd;	/* point to last host */

		else nmap.route_recd= i;		/* else 1:1 */

		put_node(i);
	}

/* Now process all route files. */

	routefile("ROUTE.DEF",tag);		/* do default routing */
	sprintf(buff,0,"ROUTE.%c",tag);		/* look for specific route list */
	if (! routefile(buff,tag,event)) 	/* if it does not exist, */
		routefile("ROUTE.BBS",tag,event); /* try the default */

/* Record some details on this session so a subsequent REPEAT schedule
can potentially skip these route file processing steps. */

	if (!evtabort()) {
		rptrcd.tag= tag;			/* which schedule */
		lseek(nodefile,0L,0);			/* write to disk */
		write(nodefile,&rptrcd,sizeof(struct _rptrcd));
		cprintf(SM+203);
	}
}

/* Process the specified route file. Return true if it exists. */

static routefile(fn,tag,event)
char *fn;
char tag;	/* schedule letter */
int event;	/* event number */
{
char line[132],atom[132];
int lineno,n,i,f,state;
char c,*cp;
FLAG clrbitpat;		/* see comments */

	if (evtabort()) return;			/* check for manual abort */
	makesname(line,fn);			/* route file in system path */
	f= open(line,0);			/* try to open it, */
	if (f == -1) return(0);			/* doesnt exist */

	stoupper(line);
	cprintf(SM+204,line);			/* say which file */

	state= IDLE;				/* idle state machine */
	process= 1;				/* do process statements */
	flags= 					/* clear everything */
	    not= 				/* invert-er off */
	    nr= 				/* no-route off */
	    bitpat= 				/* no bits set */
	    clrbitpat= 0;

	sp= 0;					/* stack is empty */
	cpy_node(&sf[sp].def,&id);		/* default to us */

	lineno= 0;				/* for error reports */
	while (rline(f,line,sizeof(line))) {
		if (evtabort()) break;		/* manually aborted */

		++lineno;
		cprintf(0,"   %2d: ",lineno);	/* pretty listing */
		for (n= 0, cp= line; *cp; ++cp) { /* parser prints "*" if active */
			if (*cp == TAB) {	/* expand tabs */
				do lconout(' ');
				while (++n % 8);

			} else {		/* everything else */
				lconout(*cp);	/* as-is */
				++n;
			}
		}
		cprintf(0,"\r");		/* note CR only */

		for (cp= line; *cp; ++cp) {	/* delete comments */
			if (*cp == ';') {	/* by marking */
				*cp= NUL;	/* as the end of the line */
				break;
			}
		}
		cp= skip_delim(line);
		while (num_args(cp)) {

/* String arguments get processed only minimally; they do not affect the
current state nor do they get any of the processing other arguements do. (Yes
it is possible to generate silly arguments.) */

			if (*cp == '"') {	/* if a quoted string, */
				cp= cpystr(atom,cp); /* copy it, */
				goto execute;	/* skip other processing */
			}
			cpyatm(atom,cp);	/* this atom, */
			cp= next_arg(cp);	/* for next time through */

			stolower(atom);		/* make it pretty, */

			cpy_node(&target,&sf[sp].def);	/* target is current operand */
			set_nn(atom,&target);	/* maybe */

/* First check for the special argument words; these set bits, and are
arguments to command words. These do not change state, so we have to check
for them first. If we find one, execute the currently set state. (Which 
hopefully will be able to use the bits just set.)  */

			for (i= 0; *mct[i].word; i++) {
				if (same(atom,mct[i].word)) {
					flags |= mct[i].bit;
					clrbitpat= 1;	/* see below */
					break;
				}
			}

/* If "NOT" just found, clear the macro bits and then wait for the next
keyword. For example: HOLD ALL NOT OURNET. HOLD ALL executes, and 'flags'
still has ALL set. NOT is found; the flags are cleared and we get the next
macro, OURNET. dobits() then executes the NOT OURNET. If we did not clear
the ACTIVE flags after a NOT, it would do HOLD NOT ALL NOT OURNET. */

			if (mct[i].bit == NOT) {
				not= 1;		/* set NOT */
				flags= 0;	/* clear the flags, */
				clrbitpat= 1;	/* see below */
				continue;
			}

/* If its not a modifier word, then its either a digit (address?) or a
command word. If its suspected of being a command word, look it up. This is 
what changes state. Numbers never change state. If we get a command that 
says "IGNORE", do just that; do not treat as a state change. This lets us 
ignore totally "words" such as the schedule tags. Note that state BITS lets
bits accumulate until we get a number or modifier, at which point they are 
all applied. (Kludge for performance.) 

There is an important special case of all this bit-cacheing biz: 

send-to, hold all
hold not ournet

...does not work. It does "(send-to hold) not ournet" because since the 
state doesn't change (BITS becomes BITS) bitpat is never cleared. So, 
whenever a modifier (OURNET, ALL, etc), NOT, or a number is found, we
set the flag clrbitpat so that the next keyword parse clears bitpat. Sheesh. */

			if (!*mct[i].word && !isdigit(*atom)) {
				not= flags= 0;		/* clear flags */

/* We clear bitpat (POLL, PICKUP, etc) and nr (NO-ROUTE) before every
command *except* when the previous command was one of the "bit" commands;
in this case, we allow them to stack up and be executed all at once. */

				if (state != BITS) clrbitpat= 1;

				for (i= 0; *rct[i].word; i++) {
					if (same(atom,rct[i].word)) break;
				}
				if (*rct[i].word) {	/* if we matched it */
					if (rct[i].state != IGNORE) state= rct[i].state;

				} else state= ERROR;	/* new state, else error */

/* At this point, before getting more args/executing the new command, we
clear bitpat and nr, unless the criteria above was all satisfied. */

				if (clrbitpat) bitpat= nr= clrbitpat= 0;
			}

execute:;		i= 0;
			if (isdigit(*atom)) {
				i= atoi(atom);		/* in case its a number */
				clrbitpat= 1;
			}

/* We process IF-SCHEDULE and SCHED-END statements specially; if 'process' 
is off we ignore all other statements; these two commands therefore control 
the execution of all other commands. */

			if (! process) {		/* if turned off, */
				if ((state != SCHED) &&	/* allow only these */
				    (state != SCHED2) && /* states to run */
				    (state != SCHEDEND)) {
					continue;
				}
			}

/* We switch states when we find a keyword, but only start executing states
until the thing following a keyword. Ie. after SEND-TO we have to wait
for a number. */

			switch (state) {

				case IDLE: break;
				case IGNORE: break;

				case QKSEND: rptrcd.bits |= RPT_RUSH;
					set_rush(event); break;

				case INTERLV1: state= INTERLV2;
				case INTERLV2: set_cont(event); 
						rptrcd.bits |= RPT_CONT;
						rptrcd.cont_interval= i; 
						break;

				case BEGIN: if (++sp >= STACKMAX) {
						clprintf(SM+205,lineno);
						sp= STACKMAX - 1;

					} else cpy_node(&sf[sp].def,&sf[sp - 1].def);
					break;

				case END: if (--sp < 0) {
						clprintf(SM+206,lineno);
						sp= 0;
					}
					break;

				case ERROR: 
					clprintf(SM+207,atom,lineno,fn); /* "unknown word" */
					break;

				case SCHED: process= 0; state= SCHED2; break;
				case SCHED2: process= (*atom == tolower(tag));
					state= IDLE; break;
				case SCHEDEND: process= 1; state= IDLE; break;

				case EXTMAIL: rptrcd.bits |= RPT_EXT; break;
				case SENDONLY: rptrcd.bits |= RPT_SO; break;
				case RECVONLY: rptrcd.bits |= RPT_RO; break;
				case CMONLY: rptrcd.bits |= RPT_CM; break;

/* These are the bit commands; they can be stacked one after another until
a modifier keyword is found, at which point they all get done at once. */

				case SEND: bitpat |= NMAP_SEND; state= BITS; break;
				case HOLD: bitpat |= NMAP_HOLD; state= BITS; break;
				case PICKUP: bitpat |= NMAP_PU; state= BITS; break;
				case ACCEPT: bitpat |= NMAP_ACCEPT; state= BITS; break;

				case POLL: bitpat |= (NMAP_POLL | NMAP_PU); 
					nr= 1; state= BITS; break;
				case NOROUTE: nr= 1; state= BITS; break;

				case BITS: if (bitpat) dobits(); 
					if (nr) doroute();
					break;

				case DIALTRY: state= DIALTRY2; break;
				case DIALTRY2: rptrcd.dial_tries= i; break;

				case CNCTTRY: state= CNCTTRY2; break;
				case CNCTTRY2: rptrcd.connect_tries= i; break;

				case MDMINI: state= MDMINI2; break;
				case MDMINI2: stoupper(atom); strcat(atom,"\r"); strcpy(rptrcd.mdmstr,atom); break;

				case RING1: state= RING2; break;
				case RING2: rptrcd.rings= i; break;

				case DIALINT1: state= DIALINT2; break;
				case DIALINT2: rptrcd.dial_interval= i; break;

				case CONTINT1: state= CONTINT2; break;
				case CONTINT2: rptrcd.cont_interval= i; break;

				case DIALPRF1: state= DIALPRF2; break;
				case DIALPRF2: stoupper(atom); strcpy(rptrcd.dial_pref,atom); break;

				case MDMTYPE1: state= MDMTYPE2; break;
				case MDMTYPE2: rptrcd.mdmtype= i; break;

				case MAXBAUD1: state= MAXBAUD2; break;
				case MAXBAUD2: rptrcd.maxbaud= i; break;

				case CDBIT1: state= CDBIT2; break;
				case CDBIT2: rptrcd.cd_bit= i; break;

				case IODEV1: state= IODEV2; break;
				case IODEV2: rptrcd.iodev= i - 1; break; /* local copy */

				case MAILCOD1: state= MAILCOD2; break;
				case MAILCOD2: if ((i > 0) && ((i < 3) || (i > 255))) 
							clprintf(SM+151);/* "must be 3 - 255" */
						else rptrcd.mailcode= i; break;

				case FILECOD1: state= FILECOD2; break;
				case FILECOD2: if ((i > 0) && ((i < 3) || (i > 255))) 
							clprintf(SM+151);/* "must be 3 - 255" */
						else rptrcd.filecode= i; break;

				case ZONE: state= ZONE2; break;
				case ZONE2: sf[sp].def.zone= i; break;

				case NET: state= NET2; break;
				case NET2: sf[sp].def.net= i; break;

				case ZGATE: state= ZGATE2; break;
				case ZGATE2: flags= THISZONE;	/* assumes prev. ZONE statement */
					cpy_node(&routedest,&target); /* we route-to this */
					dest_recd= find_node(&routedest); /* pre-locate it */
					if (dest_recd == -1) {
						clprintf(SM+208,str_node(&routedest),lineno,fn);
						state= IDLE;

					} else doroute();
					break;

				case ALIAS: state= ALIAS2; break;
				case ROUTE: state= ROUTE2; break;
				case FORWARD: state= FORWARD2; break;

				case ALIAS2:
				case ROUTE2: 			/* get <dest> */
					dest_recd= find_node(&target);
					if (dest_recd == -1) {	/* (missing dest node) */
						clprintf(SM+208,str_node(&target),lineno,fn);
						state= IDLE;
						break;
					}
					cpy_node(&routedest,&target); /* remember node to route to */
					if (state == ALIAS2) state= ALIAS3;
					else state= ROUTE3; 
					break;

/* FORWARD-TO <dest> <nodes...> is equiv. to ROUTE-TO ... once the SEND-TO
bit is set on the <dest> node. */

				case FORWARD2:
					dest_recd= find_node(&target);
					if (dest_recd == -1) {	/* (missing dest node) */
						clprintf(SM+208,str_node(&target),lineno,fn);
						state= IDLE;
						break;
					}
					cpy_node(&routedest,&target); /* remember node to route to */
					bitpat= NMAP_SEND; dobits();	/* set SEND on <dest> */
					state= ROUTE3; 
					break;

/* For ALIAS-AS, we have to make sure we have bitpat set, because doroute()
will not modify a record with the ALIAS bit set, unless the ALIAS bit is
also set in bitpat! (A kludge to prevent ROUTE-TO and NO-ROUTE etc from 
changing an ALIAS-AS.) */
				case ALIAS3: 
					bitpat= NMAP_ALIAS; dobits();
					/* fall through */
				case ROUTE3: doroute(); break;

			}
		}
		cprintf(0," %c\r\n",process ? '*' : '-');	/* flag the line */
	}
	close(f);
	return(1);
}

/* Copy a quoted string. */

static char *
cpystr(dp,sp)
char *dp;	/* dest string */
char *sp;	/* source string */
{
char lastc,c,q;

	if (*sp == '"') q= *sp++; else q= NUL;	/* optional quote stripping */
	lastc= NUL;
	while (c= *sp) {
		++sp;				/* next ... */
		if ((c == q) && (lastc != '\\')) /* if the quote char & not quoted */
			break;			/* end of argument */
		if (!q && delim(c)) break;	/* else stop if a delimiter */
		*dp++= c;			/* else part of same arg */
		lastc= c;			/* remember last char */
	}
	*dp= NUL;
	return(sp);				/* points to next ... */
}

/* Using the various flags, set or clear the bits in the specified
node entries:

	n,m		node and net, unless ALL, THISNET or MYNET
	all		1 == do all nodes
	mynet		1 == all nodes in my net
	thisnet		1 == all nodes in current net only
	hosts		1 == do all net/ region/ hosts
	not		1 == do the opposite
	bitpat		bit pattern to add

*/

static dobits() {
int b,n,i;
struct _node d;

/* If no flag bits are set, it must have been an explicit node address. */

	if (!flags) {
		i= find_node(&target);
		if (i != -1) {
			if (not) nmap.bits &= ~bitpat;
			else nmap.bits |= bitpat;
			put_node(i);

		} else clprintf(SM+210,str_node(&target));

	} else for (b= 1; b <= FLAG_MAX; b <<= 1) switch (flags & b) {
		case ALL:
			for (i= 1; get_node(i) != -1; i++) {
				if (not) nmap.bits &= ~bitpat;
				else nmap.bits |= bitpat;
				put_node(i);
			}
			break;

		case THISNET:
			cpy_node(&d,&sf[sp].def);	/* make default net/0 */
			d.number= 0;			/* find the host first */
			i= find_node(&d);		/* faster searching */
			if (i == -1) {
				clprintf(SM+211,str_node(&d));
				break;
			}
			while (get_node(i) != -1) {	/* if host found, */
				if (! same_net(&nmap.node,&d)) break;
				if (not) nmap.bits &= ~bitpat;
				else nmap.bits |= bitpat;
				put_node(i);
				++i;
			}
			break;

		case MYNET: 
			cpy_node(&d,&id);		/* make my net/0 */
			d.number= 0;			/* find the host first */
			i= find_node(&d);		/* faster searching */
			if (i == -1) {
				clprintf(SM+211,str_node(&d));
				break;
			}
			while (get_node(i) != -1) {
				if (! same_net(&nmap.node,&id)) break;
				if (not) nmap.bits &= ~bitpat;
				else nmap.bits |= bitpat;
				put_node(i);
				++i;
			}
			break;

		case THISZONE:
			cpy_node(&d,&sf[sp].def);	/* make default zone:0/0 */
			d.net= d.number= 0;		/* find the host first */
			i= find_node(&d);		/* faster searching */
			if (i == -1) {
				clprintf(SM+213,str_node(&d));
				break;
			}
			while (get_node(i) != -1) {
				if (! same_zone(&nmap.node,&d)) break;
				if (not) nmap.bits &= ~bitpat;
				else nmap.bits |= bitpat;
				put_node(i);
				++i;
			}
			break;

		case MYZONE:
			cpy_node(&d,&id);		/* make myzone:0/0 */
			d.net= d.number= 0;		/* find the host first */
			i= find_node(&d);		/* faster searching */
			if (i == -1) {
				clprintf(SM+213,str_node(&target));
				break;
			}
			while (get_node(i) != -1) {
				if (! same_zone(&nmap.node,&id)) break;
				if (not) nmap.bits &= ~bitpat;
				else nmap.bits |= bitpat;
				put_node(i);
				++i;
			}
			break;

		case HOSTS:
			for (i= 1; get_node(i) != -1; i++) {
				if (is_host(&nmap.node)) {
					if (not) nmap.bits &= ~bitpat;
					else nmap.bits |= bitpat;
					put_node(i);
				}
			}
			break;

	}
}

/* Do routing for the specified nodes:

	host.node	host to route-to
	host.net	ditto
	n,m		node,net unless ALL, THISNET or MYNET
	all		do all nodes
	mynet		all nodes in my net
	thisnet		all nodes in current net only
	hosts		all hosts
	not		do the opposite

What this really does is truly awful:

"route-to <z:n/f> ALL NOT THISNET"

actually makes two passes: first it does "route-to <z:n/f> ALL"
and then does "route-to <z:n/f> NOT thisnet", where NOT means un-do any
routing applied.

Will not modify the routing on any record marked ALIAS unless the
to-change bits in 'bitpat' is also set to ALIAS; this prevents an ALIASed
node from being changed by ROUTE, etc.

*/

static doroute() {

int b,i;
struct _node d;

	if (!flags) { 					/* if no flags set, */
		i= find_node(&target);			/* must be explicit */
		if (i != -1) {
			if (set_route(i)) put_node(i);

		} else clprintf(SM+215,str_node(&target));

	} else for (b= 1; b <= FLAG_MAX; b <<= 1) switch (flags & b) {
		case ALL:
			for (i= 1; get_node(i) != -1; i++) {
				if (set_route(i)) put_node(i);
			}
			break;

		case THISNET:
			cpy_node(&d,&sf[sp].def);	/* make default net/0 */
			d.number= 0;			/* find the host first */
			i= find_node(&d);		/* faster searching */
			if (i == -1) {
				clprintf(SM+211,str_node(&d));
				break;
			}
			while (get_node(i) != -1) {
				if (! same_net(&nmap.node,&d)) break;
				if (set_route(i)) put_node(i);
				++i;
			}
			break;

		case MYNET:
			cpy_node(&d,&id);		/* make my net/0 */
			d.number= 0;			/* find the host first */
			i= find_node(&d);		/* faster searching */
			if (i == -1) {
				clprintf(SM+211,str_node(&d));
				break;
			}
			while (get_node(i) != -1) {
				if (! same_net(&nmap.node,&d)) break;
				if (set_route(i)) put_node(i);
				++i;
			}
			break;

		case THISZONE:
			cpy_node(&d,&sf[sp].def);	/* make default zone:0/0 */
			d.net= d.number= 0;		/* find the host first */
			i= find_node(&d);		/* faster searching */
			if (i == -1) {
				clprintf(SM+213,str_node(&d));
				break;
			}
			while (get_node(i) != -1) {
				if (! same_zone(&nmap.node,&d)) break;
				if (set_route(i)) put_node(i);
				++i;
			}
			break;

		case MYZONE:
			cpy_node(&d,&id);		/* make my net/0 */
			d.net= d.number= 0;		/* find the host first */
			i= find_node(&d);		/* faster searching */
			if (i == -1) {
				clprintf(SM+213,str_node(&d));
				break;
			}
			while (get_node(i) != -1) {
				if (! same_zone(&nmap.node,&d)) break;
				if (set_route(i)) put_node(i);
				++i;
			}
			break;

		case HOSTS:
			for (i= 1; get_node(i) != -1; i++) {
				if (is_host(&nmap.node)) {
					if (set_route(i)) put_node(i);
				}
			}
			break;
	}
}

/* Mark the destination of this nmap entry to either the specified destination
(routedest) or its original destination. (ie. NOT). Returns true if the record
was changed, else 0. (ie. we can avoid writing it out if unchanged.) */

static set_route(r)
int r;		/* this nmap's recd number */
{
struct _node d;
int n;

	if (nmap.bits & NMAP_ALIAS) {			/* if recd is ALIASed */
		if (! (bitpat & NMAP_ALIAS)) return(0);	/* do not change */
	}						/* unless another ALIAS */

	if (nr) {					/* absolutely */
		n= r;					/* no routing */

	} else if (not) {
		cpy_node(&d,&nmap.node);		/* un-do routing */
		if (! same_zone(&nmap.node,&sf[sp].def)) {/* if not our zone, */
			d.net= 0;			/* route to zone host */
			d.number= 0;

		} else if (!same_net(&nmap.node,&sf[sp].def)) {/* if not our net, */
			d.number= 0;			/* route to its own host */
		}
		n= find_node(&d);			/* find recd # of route-to */
		if (n != r) get_node(r);		/* restore orig. recd */

	} else n= dest_recd;				/* else do routing */

	if (nmap.route_recd == n) return(0);		/* hasnt changed */
	nmap.route_recd= n;				/* set it */
	return(1);
}

/* This function does the high-level routing for the FidoNet routines.
Given the source node in msg_orig, and the destination node in msg_dest,
performs the necessary lookups in the nodemap to determine where (if 
anywhere) this node is going. 

Returns: 0 if not to be sent (not an error; wrong schedule, etc); -1 if
error (not in the nodelist, msg_orig not marked ACCEPT-FROM etc); or the
nodemap index PLUS ONE of the node the message is to be packeted for, and
struct nmap loaded with the nodelist record for the dest node. (Return values 
run 1 - N; 0 == no send, -1 == error). */

int out_xlate() {
int i;		/* recd number of node to packet for */
int bits;

	if (is_us(&msg_dest)) 			/* if its To: us, */
		return(0);			/* ignore it */

	i= route();				/* do routing/lookup, */
	if (i == -1) return(-1);		/* (node not in nodelist) */

/* struct nmap now contains the destination node info; 'i' is its index 
into the nodelist. Check the status of the destination node next. */

	if (! (nmap.bits & NMAP_SEND))		/* dont send (but no error) */
		return(0);			/* if not enabled (not SEND-TO) */

	/* (NOT IMPLEMENTED YET IN MAKELIST.EXE) */
	if (cm_only && !(nmap.bits & NMAP_CM))	/* CM-ONLY and not a CM node */
		return(0);			/* (same as not "SEND-TO") */


/* Now check the source node; if it is not us, make sure it's marked
ACCEPT-FROM. */

	if (is_us(&msg_orig)) 			/* if from us, its OK */
		return(i + 1);

	if (find_node(&msg_orig) == -1)		/* find source node */
		return(-1);			/* (missing from nodelist) */
	bits= nmap.bits;			/* copy of bits from source node */
	get_node(i);				/* (re-load dest node recd) */
	fix_node(&nmap.node);			/* logicalize (sic) node numbers */
	return( (bits & NMAP_ACCEPT) ? i + 1 : -1); /* check ACCEPT-FROM */
}

/* Check the routing for this message and return the record number of the
destination node, or -1 if error. */

static route() {
int i;

	i= find_node(&msg_dest);		/* locate the logical dest. */
	if (i == -1) return(i);			/* (no such node) */

	if (msg.attr & (MSGFILE | MSGFREQ)) {	/* file attach/request: no routing */
		if (! (msg.attr & MSGLOCAL) || 	/* must be local and from us */
		    !is_us(&msg_orig)) return(-1);/* else its bad */

	} else {				/* not a file attach/request */
		i= get_node(nmap.route_recd);	/* route to host */
		if (i == -1) return(-1);	/* (no such node) */
	}
	if (nmap.bits & NMAP_ALIAS) {		/* if this node aliased */
		i= get_node(nmap.route_recd);	/* route to indicated node */
	}
	fix_node(&nmap.node);			/* logicalize (sic) node numbers */
	return(i);				/* record number or -1 */
}
