/* net/rom user command processing
 * Copyright 1989 by Daniel M. Frank, W9NK.  Permission granted for
 * non-commercial distribution only.
 */
/* Mods by G1EMM, PA0GRI and WG7J */

#include "global.h"
#ifdef NETROM
#include "ctype.h"
#include "commands.h"
#include "mbuf.h"
#include "ax25.h"
#include "mailbox.h"
#include "netrom.h"
#include "pktdrvr.h"
#include "files.h"
#ifndef MSDOS
#include "session.h"
#endif

#if !defined(_lint)
static char rcsid[] OPTIONAL = "$Id: nrcmd.c,v 1.24 2001/05/06 16:32:58 brian Exp $";
#endif

/*lint -esym(534,pax25) */
static int Nr_hidden = 1;
static unsigned Nr_sorttype = 1;
char Nr4user[AXALEN];
extern unsigned Nr_sorttype;
extern int Nr_derate;
extern int Nr_promisc;
extern unsigned Nr_timertype;


const char *Nr4states[] =
{
	"Disconnected",
	"Conn Pending",
	"Connected",
	"Disc Pending",
	"Listening"
};

const char *Nr4reasons[] =
{
	"Normal",
	"By Peer",
	"Timeout",
	"Reset",
	"Refused"
};

static void nodetickproc (int, void *, void *);
static int dobcnodes (int argc, char *argv[], void *p);
static int dobcpoll (int argc, char *argv[], void *p);
static int dointerface (int argc, char *argv[], void *p);
static int donfadd (int argc, char *argv[], void *p);
static int donfdrop (int argc, char *argv[], void *p);
static int donfdump (void);
static int donfmode (int argc, char *argv[], void *p);
static int donodefilter (int argc, char *argv[], void *p);
static int donodetimer (int argc, char *argv[], void *p);
static int donracktime (int argc, char *argv[], void *p);
static int donrmycall (int argc, char *argv[], void *p);
static int donrchoketime (int argc, char *argv[], void *p);
static int donrconnect (int argc, char *argv[], void *p);
static int donrirtt (int argc, char *argv[], void *p);
static int donrkick (int argc, char *argv[], void *p);
static int dorouteadd (int argc, char *argv[], void *p);
static int doroutedrop (int argc, char *argv[], void *p);
static int donrqlimit (int argc, char *argv[], void *p);
static int donrreset (int argc, char *argv[], void *p);
static int donrretries (int argc, char *argv[], void *p);
int donrroute (int argc, char *argv[], void *p);
int donrstatus (int argc, char *argv[], void *p);
static int donrsave (int argc, char *argv[], void *p);
static int donrload (int argc, char *argv[], void *p);
static int donrttl (int argc, char *argv[], void *p);
static int donruser (int argc, char *argv[], void *p);
static int donrwindow (int argc, char *argv[], void *p);
void doobsotick (void);
static int doobsotimer (int argc, char *argv[], void *p);
static int dominquality (int argc, char *argv[], void *p);
static int donrtype (int argc, char *argv[], void *p);
static int donrpromisc (int argc, char *argv[], void *p);
static int donrderate (int argc, char *argv[], void *p);
static int doroutesort (int argc, char *argv[], void *p);
static int donrhidden (int argc, char *argv[], void *p);
static void doallinfo (void);
static struct iface *FindNrIface (char *name);

int donrneighbour (int argc, char *argv[], void *p);

extern int donr4tdisc (int argc, char *argv[], void *p);
extern struct nr_bind *find_best (struct nr_bind * list, unsigned obso);
extern void nrresetlinks (struct nrroute_tab *rp);

static struct cmds Nrcmds[] =
{
	{ "acktime",	 donracktime,	0,	0, NULLCHAR },
	{ "alias",	 donralias,	0,	0, NULLCHAR },
	{ "bcnodes",	 dobcnodes,	0,	2, "netrom bcnodes <iface>" },
	{ "bcpoll",	 dobcpoll,	0,	2, "netrom bcpoll <iface>" },
#ifdef ALLSESSIONS
	{ "connect",	 donrconnect,	1024,	2, "netrom connect <node>" },
#endif
	{ "call",	 donrmycall,	0,	0, NULLCHAR },
	{ "choketime",	 donrchoketime,	0,	0, NULLCHAR },
	{ "derate",	 donrderate,	0,	0, NULLCHAR },
	{ "hidden",	 donrhidden,	0,	0, NULLCHAR },
	{ "interface",	 dointerface,	0,	0, NULLCHAR },
	{ "irtt",	 donrirtt,	0,	0, NULLCHAR },
	{ "kick",	 donrkick,	0,	2, "netrom kick <&nrcb>" },
	{ "load",	 donrload,	0,	0, NULLCHAR },
	{ "minquality",	 dominquality,	0,	0, NULLCHAR },
	{ "neighbour",	 donrneighbour,	0,	0, NULLCHAR },
	{ "nodefilter",	 donodefilter,	0,	0, NULLCHAR },
	{ "nodetimer",	 donodetimer,	0,	0, NULLCHAR },
	{ "obsotimer",	 doobsotimer,	0,	0, NULLCHAR },
	{ "promiscuous", donrpromisc,	0,	0, NULLCHAR },
	{ "qlimit",	 donrqlimit,	0,	0, NULLCHAR },
	{ "route",	 donrroute,	0,	0, NULLCHAR },
	{ "reset",	 donrreset,	0,	2, "netrom reset <&nrcb>" },
	{ "retries",	 donrretries,	0,	0, NULLCHAR },
	{ "status",	 donrstatus,	0,	0, NULLCHAR },
	{ "save",	 donrsave,	0,	0, NULLCHAR },
#if defined(ALLSESSIONS) && defined(ALLSERV)
	{ "split",	 donrconnect,	1024,	2, "netrom split <node>" },
#endif
	{ "timertype",	 donrtype,	0,	0, NULLCHAR },
	{ "ttl",	 donrttl,	0,	0, NULLCHAR },
#ifdef NR4TDISC
	{ "tdisc",	 donr4tdisc,	0,	0, NULLCHAR },
#endif
	{ "user",	 donruser,	0,	0, NULLCHAR },
	{ "window",	 donrwindow,	0,	0, NULLCHAR },
	{ NULLCHAR,	 NULL,		0,	0, NULLCHAR }
};


struct timer Nodetimer;		/* timer for nodes broadcasts */
struct timer Obsotimer;		/* timer for aging routes */


/* Command multiplexer */
int
donetrom (int argc, char *argv[], void *p)
{
	return subcmd (Nrcmds, argc, argv, p);
}


static struct cmds Routecmds[] =
{
	{ "add",	dorouteadd,	0, 6, "netrom route add <alias> <destination> <interface> <quality> <neighbor>" },
	{ "drop",	doroutedrop,	0, 4, "netrom route drop <destination> <neighbor> <interface>" },
	{ "info",	dorouteinfo,	0, 0, NULLCHAR },
	{ "sort",	doroutesort,	0, 1, NULLCHAR },
	{ NULLCHAR,	NULL,		0, 0, NULLCHAR }
};


/* Route command multiplexer */
int
donrroute (int argc, char *argv[], void *p)
{
	if (argc < 2) {
		(void) doroutedump ();
		return 0;
	}
	return subcmd (Routecmds, argc, argv, p);
}


/* Code to sort Netrom node listing
 * D. Crompton  2/92
 * Dump a list of known netrom routes in
 * sorted order determined by sort
 * flag - default = sort by alias
 */

int
doroutedump ()
{
register struct nrroute_tab *rp;
register int i, j, k, column;
char buf[17];
char *cp, *temp;

	column = 1;

	for (i = 0, j = 0; i < NRNUMCHAINS; i++)
		for (rp = Nrroute_tab[i]; rp != NULLNRRTAB; j++, rp = rp->next) ;

#define RTSIZE 17

	if (j) {
		/* Allocate maximum size */
		temp = mallocw ((unsigned) j * RTSIZE);

		for (i = 0, j = 0, k = 0; i < NRNUMCHAINS; i++)
			for (rp = Nrroute_tab[i]; rp != NULLNRRTAB; rp = rp->next) {
				if (!Nr_hidden && *rp->alias == '#')
					continue;
				if (Nr_sorttype) {
					strncpy (buf, rp->alias, 17);
					/* remove trailing spaces */
					if ((cp = strchr (buf, ' ')) == NULLCHAR)
						cp = &buf[strlen (buf)];
					if (cp != buf)	/* don't include colon for null alias */
						*cp++ = ':';
					pax25 (cp, rp->call);
				} else {
					pax25 (buf, rp->call);
					cp = &buf[strlen (buf)];
					*cp++ = ':';
					strcpy (cp, rp->alias);
				}
				sprintf (&temp[k], "%-16.16s", buf);
				k += RTSIZE;
				j++;	/* number actually shown */
			}

		qsort (temp, (size_t) j, RTSIZE, (int (*)(const void *, const void *)) strcmp);

		for (i = 0, k = 0; i < j; i++, k += RTSIZE) {
			tprintf ("%-16s  ", &temp[k]);
			if (column++ == 4) {
				if (tputc ('\n') == EOF) {
					free (temp);
					return 0;
				}
				column = 1;
			}
		}

		if (column != 1)
			tputc ('\n');
		free (temp);
	}
	return 0;
}


/* netrom Route Dump Sort - ALIAS or CALL first */
static int
doroutesort (int argc, char *argv[], void *p OPTIONAL)
{
	if (argc < 2) {
		tprintf ("Netrom Sort by %s\n", Nr_sorttype ? "Alias" : "Call");
		return 0;
	}
	switch (argv[1][0]) {
		case 'A':
		case 'a':
			Nr_sorttype = 1;
			break;
		case 'C':
		case 'c':
			Nr_sorttype = 0;
			break;
		default:
			tputs ("usage: netrom sort [alias|call]\n");
			return -1;
	}

	return 0;
}


/* Print detailed information on  ALL routes  (sorted) */
/*  D. Crompton */
static void
doallinfo ()
{
register struct nrroute_tab *rp;
register struct nr_bind *bp;
register struct nrnbr_tab *np;
char neighbor[AXBUF];
char buf[17];
char *cp, *temp;
int i, j, k, flow_tmp;

	flow_tmp = Current->flowmode;
	Current->flowmode = 1;

	for (i = 0, j = 0; i < NRNUMCHAINS; i++)
		for (rp = Nrroute_tab[i]; rp != NULLNRRTAB; rp = rp->next)
			for (bp = rp->routes; bp != NULLNRBIND; bp = bp->next, j++) ;

#define STRSIZE 72

	if (j) {

		temp = mallocw ((unsigned) j * STRSIZE);

		for (i = 0, k = 0; i < NRNUMCHAINS; i++)
			for (rp = Nrroute_tab[i]; rp != NULLNRRTAB; rp = rp->next)
				for (bp = rp->routes; bp != NULLNRBIND; bp = bp->next, k += STRSIZE) {
					np = bp->via;
					if (Nr_sorttype) {

						strncpy (buf, rp->alias, 17);
						if ((cp = strchr (buf, ' ')) == NULLCHAR)
							cp = &buf[strlen (buf)];
						if (cp != buf)
							*cp++ = ':';
						pax25 (cp, rp->call);

					} else {

						pax25 (buf, rp->call);
						cp = &buf[strlen (buf)];
						*cp++ = ':';
						strcpy (cp, rp->alias);

					}

					sprintf (&temp[k], "%-16s %3d %3d %-8s %-9s %c\n", buf,
						 bp->quality, bp->obsocnt,
						 np->iface->name,
						 pax25 (neighbor, np->call),
					  (bp->flags & NRB_PERMANENT ? 'P' :
					   bp->flags & NRB_RECORDED ? 'R' : 'B'));

				}
		qsort (temp, (size_t) j, STRSIZE, (int (*)(const void *, const void *)) strcmp);

		for (i = 0, k = 0; i < j; i++, k += STRSIZE)
			if (tputs (&temp[k]) == EOF)
				break;
		free (temp);
		Current->flowmode = flow_tmp;
	}
}


/* Find the interface, and check if it is active for netrom */
static struct iface *
FindNrIface (char *name)
{
struct iface *ifp;

	if ((ifp = if_lookup (name)) == NULLIF) {
		tprintf (Badinterface, name);
		return NULLIF;
	}
	if (!(ifp->flags & IS_NR_IFACE)) {
		tprintf ("%s is not active for netrom\n", name);
		return NULLIF;
	}
	return ifp;
}


/* print detailed information on an individual route
 * Shows alias as well - WG7J
 */
int
dorouteinfo (int argc, char *argv[], void *p OPTIONAL)
{
char *cp;
register struct nrroute_tab *rp;
register struct nrroute_tab *npp;
struct nr_bind *bp;
struct nrnbr_tab *np;
char destbuf[AXBUF];
char neighbor[AXBUF];
char alias[AXALEN];
char buf[AXALEN];
char nb_alias[AXALEN];
int print_header = 1;
int16 rhash;

	if (argc == 1) {
		doallinfo ();
		return 0;
	}
	(void) putalias (alias, argv[1], 0);
	(void) strupr (argv[1]);	/*make sure it's upper case*/
	if ((rp = find_nrboth (alias, argv[1])) == NULLNRRTAB) {
		/*no such call or node alias*/
		tputs ("no such node\n\n");
		return 0;
	}
	/*copy the real alias*/
	strncpy (buf, rp->alias, AXALEN);
	if ((cp = strchr (buf, ' ')) != NULLCHAR)
		*cp = '\0';

	for (bp = rp->routes; bp != NULLNRBIND; bp = bp->next) {
		np = bp->via;
		/* now we have to find the alias of the neighbour used
		 * so we can print that as well!
		 */
		rhash = nrhash (np->call);
		for (npp = Nrroute_tab[rhash]; npp != NULLNRRTAB; npp = npp->next)
			if (addreq (npp->call, np->call))
				break;
		if (npp == NULLNRRTAB)
			continue;
		/* found, now remove trailing spaces */
		strcpy (nb_alias, npp->alias);
		if ((cp = strchr (nb_alias, ' ')) != NULLCHAR)
			*cp = '\0';
		if (print_header) {
			print_header = 0;
			tputs ("      Node          Neighbour       Port  PQual Obsocnt Type\n");
			tprintf ("%6s:%-9s  ", buf, pax25 (destbuf, rp->call));
		} else
			tputs ("                  ");
		tprintf ("%6s:%-9s  %-5s  %3d      %d     %c\n",
			 nb_alias, pax25 (neighbor, np->call),
			 np->iface->name,
			 bp->quality, bp->obsocnt,
			 bp->flags & NRB_PERMANENT ? 'P' : \
			 (bp->flags & NRB_RECORDED ? 'R' : 'B'));
	}
	tputc ('\n');
	return 0;
}


int
donrneighbour (int argc OPTIONAL, char *argv[] OPTIONAL, void *p OPTIONAL)
{
int i, printheader = 1;
struct nrnbr_tab *np;
struct nrroute_tab *rp;
struct nr_bind *thebind;
int16 rhash;
int justused;
char tmp[AXBUF];
char alias[AXALEN];
char *cp;
int percentage = 0;
int quality = 0;
int obsocnt = 0;

	for (i = 0; i < NRNUMCHAINS; i++)	/*loop through all chains of neighbours*/
		for (np = Nrnbr_tab[i]; np != NULLNTAB; np = np->next) {
			/* If the interface is hidden, then do not show the route */
			/*
			    if(np->iface->flags & HIDE_PORT && Curproc->input != Command->input)
				continue;
			 */
			if (printheader) {
				tputs ("Routes :\n   Neighbour       Port  PQual Obsocnt Dest  Tries  Retries Perc\n");
				printheader = 0;
			}
			/* has this one been used recently ? */
			if ((secclock () - np->lastsent) < 60)
				justused = 1;
			else
				justused = 0;

			/* now we have to find the alias of this neighbour
			 * so we can print that as well!
			 */
			rhash = nrhash (np->call);
			for (rp = Nrroute_tab[rhash]; rp != NULLNRRTAB; rp = rp->next)
				if (addreq (rp->call, np->call))
					break;

			if (rp != NULLNRRTAB) {
				/* found, now remove trailing spaces */
				strcpy (alias, rp->alias);
				if ((cp = strchr (alias, ' ')) != NULLCHAR)
					*cp = '\0';
				/*find the quality for this neighbour*/
				thebind = find_best (rp->routes, 1);
				if (thebind) {
					quality = (int) thebind->quality;
					obsocnt = (int) thebind->obsocnt;
				} else	/* this shouldn't happen */
					quality = obsocnt = 0;
			} else
				strcpy (alias, "##temp");
		        if (np->tries)  /* Filched from JNOS */
				    percentage = ((np->tries *100L) / (np->tries + np->retries));
		        else
				    percentage = 0;

			/* print it all out */
			tprintf ("%s %6s:%-9s  %-5s %4d     %1d    %3d  %-5u  %-5u   %2d %%\n",
				 (justused) ? ">" : " ",
				 alias, pax25 (tmp, np->call),
				 np->iface->name,
				 quality, obsocnt, np->refcnt,
				 np->tries, np->retries, percentage);
		}
	if (!printheader)
		tputc ('\n');
	return 0;
}


/* define the netrom call,
 * this simply changes the interface linkaddress!
 * but is a little easier to use...
 */
static int
donrmycall (int argc, char *argv[], void *p OPTIONAL)
{
char tmp[AXBUF];

	if (Nr_iface == NULLIF) {
		tputs ("Attach netrom interface first\n");
		return 1;
	}
	if (argc < 2) {
		if (Nr_iface->hwaddr == NULLCHAR)
			tputs ("not set\n");
		else
			tprintf ("%s\n", pax25 (tmp, Nr_iface->hwaddr));
	} else {
		if (strlen (argv[1]) > (AXBUF - 1)) {
			tputs ("too long\n");
			return 1;
		}
		if (Nr_iface->hwaddr != NULLCHAR)
			free (Nr_iface->hwaddr);
		Nr_iface->hwaddr = mallocw ((unsigned) Nr_iface->iftype->hwalen);
		(void) (*Nr_iface->iftype->scan) (Nr_iface->hwaddr, argv[1]);
#ifdef MAILBOX
		setmbnrid ();
#endif
	}
	return 0;
}


/* make an interface available to net/rom */
/* arguments are:
 * argv[0] - "interface"
 * argv[1] - "iface" , the interface name
 * argv[2] - "quality", the interface broadcast quality
 * argv[3] - "n" or "v", to override the default (verbose)
 *            n = never broadcast verbose
 *            v = always broadcast verbose
 */
static
int
dointerface (int argc, char *argv[], void *p OPTIONAL)
{
register struct iface *ifp;
int i, mtu;

	if (Nr_iface == NULLIF) {
		tputs ("Attach netrom interface first\n");
		return 1;
	}
	if (argc < 3) {
		i = 0;
		for (ifp = Ifaces; ifp; ifp = ifp->next) {
			if (ifp->flags & IS_NR_IFACE) {
				if (!i) {
					i = 1;
					tputs ("Iface  Qual Verbose\n");
				}
				tprintf ("%-6s %-3d     %c\n",
					 ifp->name, ifp->quality,
				     (ifp->flags & NR_VERBOSE) ? 'Y' : 'N');
			}
		}
		return 0;
	}
	if ((ifp = if_lookup (argv[1])) == NULLIF) {
		tprintf (Badinterface, argv[1]);
		return 1;
	}
	if (ifp->type != CL_AX25) {
		tprintf ("Interface %s is not NETROM compatible\n", argv[1]);
		return 1;
	}

	if (!strcasecmp (argv[2], "off"))	{
		ifp->flags &= ~IS_NR_IFACE;
		return 0;
	}

	/* activate the interface */
	ifp->flags |= IS_NR_IFACE;

	/* set quality */
	if ((ifp->quality = atoi (argv[2])) > 255)	/*Maximum quality possible*/
		ifp->quality = 255;
	/*check to see if quality is not 0 */
	if (ifp->quality <= 0)
		ifp->quality = 1;

	/* default is none-verbose */
	ifp->flags &= ~NR_VERBOSE;
	if (argc > 3)
		if (*argv[3] == 'v')
			ifp->flags |= NR_VERBOSE;

	/* Check, and set the NETROM MTU - WG7J */
	if ((mtu = ifp->ax25->paclen - 20) < Nr_iface->mtu)
		Nr_iface->mtu = (int16) mtu;

	/* Poll other nodes on this interface */
	nr_bcpoll (ifp);

	return 0;
}


/* convert a null-terminated alias name to a blank-filled, upcased */
/* version.  Return -1 on failure. */
int
putalias (register char *to, register char *from, int complain)
{
int len, i;

	if (!to || !from) {
		if (complain)
			tputs ("alias or call invalid\n");
		return -1;
	}
	if ((len = (int) strlen (from)) > ALEN) {
		if (complain)
			tputs ("alias too long - six characters max\n");
		return -1;
	}
	for (i = 0; i < ALEN; i++) {
		if (i < len) {
			if (islower (*from))
				*to++ = (char) toupper (*from++);
			else
				*to++ = *from++;
		} else
			*to++ = ' ';
	}

	*to = '\0';
	return 0;
}


/* Add a route */
static int
dorouteadd (int argc, char *argv[], void *p OPTIONAL)
{
char alias[AXALEN];
char dest[AXALEN];
unsigned quality;
char neighbor[AXALEN];
struct iface *ifp;
int naddr;

	/* format alias (putalias prints error message if necessary) */
	if (putalias (alias, argv[1], 1) == -1)
		return -1;

	/* format destination callsign */
	if (setcall (dest, argv[2]) == -1) {
		tputs ("bad destination callsign\n");
		return -1;
	}
	/* find interface */
	if ((ifp = FindNrIface (argv[3])) == NULLIF) {
		return -1;
	}
	/* get and check quality value */
	if ((quality = (unsigned int) atoi (argv[4])) > 255) {
		tputs ("maximum route quality is 255\n");
		return -1;
	}
	/* Change from 871225 -- no digis in net/rom table */
	naddr = argc - 5;
	if (naddr > 1) {
		tputs ("Use the ax25 route command to specify digipeaters\n");
		return -1;
	}
	/* format neighbor address string */
	(void) setcall (neighbor, argv[5]);

	return nr_routeadd (alias, dest, ifp, quality, neighbor, 1, 0);
}


/* drop a route */
static int
doroutedrop (int argc OPTIONAL, char *argv[], void *p OPTIONAL)
{
char dest[AXALEN], neighbor[AXALEN];
struct iface *ifp;

	/* format destination and neighbor callsigns */
	if (setcall (dest, argv[1]) == -1) {
		tputs ("bad destination callsign\n");
		return -1;
	}
	if (setcall (neighbor, argv[2]) == -1) {
		tputs ("bad neighbor callsign\n");
		return -1;
	}
	/* find interface */
	if ((ifp = FindNrIface (argv[3])) == NULLIF)
		return -1;

	return nr_routedrop (dest, neighbor, ifp);
}


/* Broadcast nodes list on named interface. */
static int
dobcnodes (int argc OPTIONAL, char *argv[], void *p OPTIONAL)
{
struct iface *ifp;

	/* find interface */
	if ((ifp = FindNrIface (argv[1])) == NULLIF)
		return -1;

	nr_bcnodes (ifp);
	return 0;
}


/* Poll nodes for routes on named interface. - WG7J */
static int
dobcpoll (int argc OPTIONAL, char *argv[], void *p OPTIONAL)
{
struct iface *ifp;

	/* find interface */
	if ((ifp = FindNrIface (argv[1])) == NULLIF)
		return -1;

	nr_bcpoll (ifp);
	return 0;
}


/* Set outbound node broadcast interval */
static int
donodetimer (int argc, char *argv[], void *p OPTIONAL)
{
	if (argc < 2) {
		tprintf ("Nodetimer %lu/%lu seconds\n",
			 read_timer (&Nodetimer) / 1000L,
			 dur_timer (&Nodetimer) / 1000L);
		return 0;
	}
	stop_timer (&Nodetimer);/* in case it's already running */
	Nodetimer.func = (void (*)(void *)) donodetick;	/* what to call on timeout */
	Nodetimer.arg = NULLCHAR;	/* dummy value */
	set_timer (&Nodetimer, atoi (argv[1]) * 1000L);	/* set timer duration */
	start_detached_timer (&Nodetimer);	/* and fire it up */
	return 0;
}


/* nodetickproc is the old donodetick.   But, since it can call kpause, which is
 * very very bad for timer functions :-( this has been converted to a server.
 */
void
donodetick ()
{
	if (newproc ("NETROM broadcast", 2048, nodetickproc, 0, NULL, NULL, 0) == NULLPROC)
		log (-1, "Couldn't start NETROM broadcast process");

}

static void
nodetickproc (int i OPTIONAL, void *v1 OPTIONAL, void *v2 OPTIONAL)
{
struct iface *ifp;

	for (ifp = Ifaces; ifp; ifp = ifp->next) {
		if (ifp->flags & IS_NR_IFACE)
			nr_bcnodes (ifp);
		/* we want to delay after VERBOSE NETROM interfaces (except the last one) */
		if ((ifp->flags & NR_VERBOSE) && (ifp->next))
			(void) kpause (5000);	/* this is needed to keep things hopping */
	}

	/* Restart timer */
	start_detached_timer (&Nodetimer);
}


/* Set timer for aging routes */
static int
doobsotimer (int argc, char *argv[], void *p OPTIONAL)
{
	if (argc < 2) {
		tprintf ("Obsotimer %lu/%lu seconds\n",
			 read_timer (&Obsotimer) / 1000L,
			 dur_timer (&Obsotimer) / 1000L);
		return 0;
	}
	stop_timer (&Obsotimer);/* just in case it's already running */
	Obsotimer.func = (void (*)(void *)) doobsotick;	/* what to call on timeout */
	Obsotimer.arg = NULLCHAR;	/* dummy value */
	set_timer (&Obsotimer, atoi (argv[1]) * 1000L);	/* set timer duration */
	start_detached_timer (&Obsotimer);	/* and fire it up */
	return 0;
}


/* Go through the routing table, reducing the obsolescence count of
 * non-permanent routes, and purging them if the count reaches 0
 */
void
doobsotick ()
{
register struct nrnbr_tab *np;
register struct nrroute_tab *rp, *rpnext;
register struct nr_bind *bp, *bpnext;
int i;

	for (i = 0; i < NRNUMCHAINS; i++) {
		for (rp = Nrroute_tab[i]; rp != NULLNRRTAB; rp = rpnext) {
			rpnext = rp->next;	/* save in case we free this route */
			/* Check all bindings for this route */
			for (bp = rp->routes; bp != NULLNRBIND; bp = bpnext) {
				bpnext = bp->next;	/* in case we free this binding */
				if (bp->flags & NRB_PERMANENT)	/* don't age these */
					continue;
				if (--bp->obsocnt == 0) {	/* time's up! */
					if (bp->next != NULLNRBIND)
						bp->next->prev = bp->prev;
					if (bp->prev != NULLNRBIND)
						bp->prev->next = bp->next;
					else
						rp->routes = bp->next;
					rp->num_routes--;	/* one less binding */
					np = bp->via;	/* find the neighbor */
					free ((char *) bp);	/* now we can free the bind */
					/* Check to see if we can free the neighbor */
					if (--np->refcnt == 0) {
						if (np->next != NULLNTAB)
							np->next->prev = np->prev;
						if (np->prev != NULLNTAB)
							np->prev->next = np->next;
						else {
							Nrnbr_tab[nrhash (np->call)] = np->next;
						}
						free ((char *) np);	/* free the storage */
					}
				}
			}
			if (rp->num_routes == 0) {	/* did we free them all? */
				if (rp->next != NULLNRRTAB)
					rp->next->prev = rp->prev;
				if (rp->prev != NULLNRRTAB)
					rp->prev->next = rp->next;
				else
					Nrroute_tab[i] = rp->next;
				/* No more routes left !
				 * We should close/reset any netrom connections
				 * still idling for this route ! - WG7J
				 */
				nrresetlinks (rp);
				free ((char *) rp);
			}
		}
	}

	start_detached_timer (&Obsotimer);
}


static struct cmds Nfcmds[] =
{
	{ "add",	donfadd,			0, 3, "netrom nodefilter add <neighbor> <interface> [quality]" },
	{ "drop",	donfdrop,			0, 3, "netrom nodefilter drop <neighbor> <interface>" },
	{ "mode",	donfmode,			0, 0, NULLCHAR },
	{ NULLCHAR,	NULLFP ((int, char **, void *)),0, 0, "nodefilter subcommands: add drop mode" }
};


/* nodefilter command multiplexer */
static int
donodefilter (int argc, char *argv[], void *p)
{
	if (argc < 2) {
		(void) donfdump ();
		return 0;
	}
	return subcmd (Nfcmds, argc, argv, p);
}


/* display a list of <callsign,interface> pairs from the filter
 * list.
 */
static int
donfdump ()
{
int i, column = 1;
struct nrnf_tab *fp;
char buf[AXBUF];

	for (i = 0; i < NRNUMCHAINS; i++)
		for (fp = Nrnf_tab[i]; fp != NULLNRNFTAB; fp = fp->next) {
			pax25 (buf, fp->neighbor);
			tprintf ("%-7s  %-8s  %-3d   ",
				 buf, fp->iface->name, fp->quality);
			if (column++ == 3) {
				if (tputc ('\n') == EOF)
					return 0;
				column = 1;
			}
		}

	if (column != 1)
		tputc ('\n');

	return 0;
}


/* add an entry to the filter table */
static int
donfadd (int argc, char *argv[], void *p OPTIONAL)
{
struct iface *ifp;
unsigned qual;
char neighbor[AXALEN];

	/* format callsign */
	if (setcall (neighbor, argv[1]) == -1) {
		tputs ("bad neighbor callsign\n");
		return -1;
	}
	/* find interface */
	if ((ifp = FindNrIface (argv[2])) == NULLIF)
		return -1;

	qual = (unsigned int) ifp->quality;	/* set default quality */

	if (argc > 3)
		qual = (unsigned int) atoi (argv[3]);

	return nr_nfadd (neighbor, ifp, qual);
}


/* drop an entry from the filter table */
static int
donfdrop (int argc OPTIONAL, char *argv[], void *p OPTIONAL)
{
struct iface *ifp;
char neighbor[AXALEN];

	/* format neighbor callsign */
	if (setcall (neighbor, argv[1]) == -1) {
		tputs ("bad neighbor callsign\n");
		return -1;
	}
	/* find interface */
	if ((ifp = FindNrIface (argv[2])) == NULLIF)
		return -1;

	return nr_nfdrop (neighbor, ifp);
}


/* nodefilter mode subcommand */
static int
donfmode (int argc, char *argv[], void *p OPTIONAL)
{
	if (argc < 2) {
		tputs ("filter mode is ");
		switch (Nr_nfmode) {
			case NRNF_NOFILTER:
				tputs ("none\n");
				break;
			case NRNF_ACCEPT:
				tputs ("accept\n");
				break;
			case NRNF_REJECT:
				tputs ("reject\n");
				break;
			default:
				tputs ("some strange, unknown value\n");
		}
		return 0;
	}
	switch (argv[1][0]) {
		case 'n':
		case 'N':
			Nr_nfmode = NRNF_NOFILTER;
			break;
		case 'a':
		case 'A':
			Nr_nfmode = NRNF_ACCEPT;
			break;
		case 'r':
		case 'R':
			Nr_nfmode = NRNF_REJECT;
			break;
		default:
			tputs ("modes are: none accept reject\n");
			return -1;
	}

	return 0;
}


/* netrom network packet time-to-live initializer */
static int
donrttl (int argc, char *argv[], void *p OPTIONAL)
{
	return setshort (&Nr_ttl, "Time to live", argc, argv);
}


/* show hidden (ie '#...') nodes or not */
static int
donrhidden (int argc, char *argv[], void *p OPTIONAL)
{
	return setbool (&Nr_hidden, "Hidden nodes", argc, argv);
}


/* allow automatic derating of netrom routes on link failure */
static int
donrderate (int argc, char *argv[], void *p OPTIONAL)
{
	return setbool (&Nr_derate, "Derate flag", argc, argv);
}


/* promiscuous acceptance of broadcasts */
static int
donrpromisc (int argc, char *argv[], void *p OPTIONAL)
{
	return setbool (&Nr_promisc, "Promiscuous flag", argc, argv);
}


#ifdef ALLSESSIONS
/* Initiate a NET/ROM transport connection */
static int
donrconnect (int argc OPTIONAL, char *argv[], void *p OPTIONAL)
{
struct nrroute_tab *np;
struct sockaddr_nr lsocket, fsocket;
char alias[AXBUF];
struct session *sp;
int split = 0;

	/*Make sure this comes from console - WG7J*/
	if (Curproc->input != Command->input)
		return 0;

#ifdef ALLSERV
	if (argv[0][0] == 's')
		split = 1;
#endif

	/* Get a session descriptor */
	if ((sp = newsession (argv[1], NRSESSION, split)) == NULLSESSION) {
		tputs (TooManySessions);
		return 1;
	}
	if ((sp->s = socket (AF_NETROM, SOCK_SEQPACKET, 0)) == -1) {
		tputs (Nosock);
		(void) keywait (NULLCHAR, 1);
		freesession (sp);
		return 1;
	}
	/* See if the requested destination is a known alias or call,
	 * use it if it is.  Otherwize give an error message. - WG7J
	 */
	(void) putalias (alias, argv[1], 0);
	(void) strupr (argv[1]);	/*make sure it's upper case*/
	if ((np = find_nrboth (alias, argv[1])) == NULLNRRTAB) {
		/*no such call or node alias*/
		tputs ("no such node\n\n");
		(void) keywait (NULLCHAR, 1);
		freesession (sp);
		return 1;
	}
	/* Setup the local side of the connection */
	lsocket.nr_family = AF_NETROM;

	/* Set up our local username, bind would use Mycall instead */
	memcpy (lsocket.nr_addr.user, Nr4user, AXALEN);

	/* Putting anything else than Nr_iface->hwaddr here will not work ! */
	memcpy (lsocket.nr_addr.node, Nr_iface->hwaddr, AXALEN);

	/* Now bind the socket to this */
	(void) bind (sp->s, (char *) &lsocket, sizeof (struct sockaddr_nr));


	/* Set up the remote side of the connection */
	fsocket.nr_family = AF_NETROM;
	memcpy (fsocket.nr_addr.user, np->call, AXALEN);
	memcpy (fsocket.nr_addr.node, np->call, AXALEN);
	fsocket.nr_family = AF_NETROM;

	return tel_connect (sp, (char *) &fsocket, sizeof (struct sockaddr_nr));
}

#endif


/* Reset a net/rom connection abruptly */
static int
donrreset (int argc OPTIONAL, char *argv[], void *p OPTIONAL)
{
struct nr4cb *cb;

	cb = (struct nr4cb *) htoi (argv[1]);
	if (!nr4valcb (cb)) {
		tputs (Notval);
		return 1;
	}
	reset_nr4 (cb);
	return 0;
}


/* Force retransmission on a net/rom connection */

static int
donrkick (int argc OPTIONAL, char *argv[], void *p OPTIONAL)
{
struct nr4cb *cb;

	cb = (struct nr4cb *) htoi (argv[1]);
	if (kick_nr4 (cb) == -1) {
		tputs (Notval);
		return 1;
	} else
		return 0;
}


/* netrom transport ACK delay timer */
static int
donracktime (int argc, char *argv[], void *p OPTIONAL)
{
	return setlong (&Nr4acktime, "Ack delay time (ms)", argc, argv);
}


/* netrom transport choke timeout */
static int
donrchoketime (int argc, char *argv[], void *p OPTIONAL)
{
	return setlong (&Nr4choketime, "Choke timeout (ms)", argc, argv);
}


/* netrom transport initial round trip time */

static int
donrirtt (int argc, char *argv[], void *p OPTIONAL)
{
	return setlong (&Nr4irtt, "Initial RTT (ms)", argc, argv);
}


/* netrom transport receive queue length limit.  This is the */
/* threshhold at which we will CHOKE the sender. */

static int
donrqlimit (int argc, char *argv[], void *p OPTIONAL)
{
	return setshort (&Nr4qlimit, "Queue limit (bytes)", argc, argv);
}


/* Display or change our NET/ROM username */
static int
donruser (int argc, char *argv[], void *p OPTIONAL)
{
char buf[AXBUF];

	if (argc < 2) {
		pax25 (buf, Nr4user);
		tprintf ("%s\n", buf);
		return 0;
	}
	if (setcall (Nr4user, argv[1]) == -1)
		return -1;
	Nr4user[ALEN] |= E;
	return 0;
}


/* netrom transport maximum window.  This is the largest send and */
/* receive window we may negotiate */

static int
donrwindow (int argc, char *argv[], void *p OPTIONAL)
{
	return setshort (&Nr4window, "Window (frames)", argc, argv);
}


/* netrom transport maximum retries.  This is used in connect and */
/* disconnect attempts; I haven't decided what to do about actual */
/* data retries yet. */

static int
donrretries (int argc, char *argv[], void *p OPTIONAL)
{
	return setshort (&Nr4retries, "Retry limit", argc, argv);
}


/* Display the status of NET/ROM connections */

int
donrstatus (int argc, char *argv[], void *p OPTIONAL)
{
int i;
struct nr4cb *cb;
char luser[AXBUF], ruser[AXBUF], node[AXBUF];

	if (argc < 2) {
#ifdef UNIX
		tputs ("&NCB     Snd-W Snd-Q Rcv-Q     LUser      RUser @Node     State\n");
#else
		tputs ("&NCB Snd-W Snd-Q Rcv-Q     LUser      RUser @Node     State\n");
#endif
		for (i = 0; i < NR4MAXCIRC; i++) {
			if ((cb = Nr4circuits[i].ccb) == NULLNR4CB)
				continue;
			pax25 (luser, cb->local.user);
			pax25 (ruser, cb->remote.user);
			pax25 (node, cb->remote.node);
#ifdef UNIX
			if (tprintf ("%8.8lx   %3d %5d %5d %9s  %9s %-9s %s\n",
#else
			if (tprintf ("%4.4x   %3d %5d %5d %9s  %9s %-9s %s\n",
#endif
				(uint32) cb, cb->nbuffered, len_q (cb->txq),
				     len_p (cb->rxq), luser, ruser, node,
				     Nr4states[cb->state]) == EOF)
				break;
		}
		return 0;
	}
	cb = (struct nr4cb *) htoi (argv[1]);
	if (!nr4valcb (cb)) {
		tputs (Notval);
		return 1;
	}
	donrdump (cb);
	return 0;
}


/* Dump one control block */

void
donrdump (struct nr4cb *cb)
{
char luser[AXBUF], ruser[AXBUF], node[AXBUF];
unsigned seq;
struct nr4txbuf *b;
struct timer *t;

	pax25 (luser, cb->local.user);
	pax25 (ruser, cb->remote.user);
	pax25 (node, cb->remote.node);

	tprintf ("Local: %s %d/%d Remote: %s @ %s %d/%d State: %s\n",
		 luser, cb->mynum, cb->myid, ruser, node,
		 cb->yournum, cb->yourid, Nr4states[cb->state]);

	tprintf ("Window: %-5u Rxpect: %-5u RxNext: %-5u RxQ: %-5d %s\n",
		 cb->window, uchar (cb->rxpected), uchar (cb->rxpastwin),
		 len_p (cb->rxq), cb->qfull ? "RxCHOKED" : "");

	tprintf (" Unack: %-5u Txpect: %-5u TxNext: %-5u TxQ: %-5d %s\n",
	       cb->nbuffered, uchar (cb->ackxpected), uchar (cb->nextosend),
		 len_q (cb->txq), cb->choked ? "TxCHOKED" : "");

	tputs ("TACK: ");
	if (run_timer (&cb->tack))
		tprintf ("%lu", read_timer (&cb->tack));
	else
		tputs ("stop");
	tprintf ("/%lu ms; ", dur_timer (&cb->tack));

	tputs ("TChoke: ");
	if (run_timer (&cb->tchoke))
		tprintf ("%lu", read_timer (&cb->tchoke));
	else
		tputs ("stop");
	tprintf ("/%lu ms; ", dur_timer (&cb->tchoke));

	tputs ("TCD: ");
	if (run_timer (&cb->tcd))
		tprintf ("%lu", read_timer (&cb->tcd));
	else
#ifndef NR4TDISC
		tputs ("stop");
	tprintf ("/%lu ms", dur_timer (&cb->tcd));
#else
		tputs ("stop");
	tprintf ("/%lu ms; ", dur_timer (&cb->tcd));

	tputs ("TDisc: ");
	if (run_timer (&cb->tdisc))
		tprintf ("%lu", (read_timer (&cb->tdisc) / 1000L));
	else
		tputs ("stop");
	tprintf ("/%lu", (dur_timer (&cb->tdisc) / 1000L));
#endif

	if (run_timer (&cb->tcd))
		tprintf ("; Tries: %u\n", cb->cdtries);
	else
		tputc ('\n');

	tprintf ("Backoff Level %u SRTT %ld ms Mean dev %ld ms\n",
		 cb->blevel, cb->srtt, cb->mdev);

	/* If we are connected and the send window is open, display */
	/* the status of all the buffers and their timers */

	if (cb->state == NR4STCON && cb->nextosend != cb->ackxpected) {

		tputs ("TxBuffers:  Seq  Size  Tries  Timer\n");

		for (seq = cb->ackxpected;
		     nr4between ((unsigned) cb->ackxpected, seq, (unsigned) cb->nextosend);
		     seq = (seq + 1) & NR4SEQMASK) {

			b = &cb->txbufs[seq % cb->window];
			t = &b->tretry;

			if (tprintf ("            %3u   %3d  %5d  %lu/%lu\n",
				     seq, len_p (b->data), b->retries + 1,
				     read_timer (t), dur_timer (t))
			    == EOF)
				break;
		}

	}
}


/* netrom timers type - linear v exponential */
static int
donrtype (int argc, char *argv[], void *p OPTIONAL)
{
	if (argc < 2) {
		tprintf ("Netrom timer type is %s\n", Nr_timertype ? "linear" : "exponential");
		return 0;
	}
	switch (argv[1][0]) {
		case 'l':
		case 'L':
			Nr_timertype = 1;
			break;
		case 'e':
		case 'E':
			Nr_timertype = 0;
			break;
		default:
			tputs ("use: netrom timertype [linear|exponential]\n");
			return -1;
	}

	return 0;
}


static int
dominquality (int argc, char *argv[], void *p OPTIONAL)
{
unsigned val;

	if (argc < 2) {
		tprintf ("%u\n", Nr_autofloor);
		return 0;
	}
	val = (unsigned int) atoi (argv[1]);

	if (val == 0 || val > 255) {
		tputs ("The minimum acceptable quality must be 1 to 255\n");
		return 1;
	}
	Nr_autofloor = val;

	return 0;
}


/* Fixed and now functional, 920317 WG7J */
static int
donrload (int argc OPTIONAL, char *argv[] OPTIONAL, void *p OPTIONAL)
{
char buff[255];
FILE *fn;
time_t now, prev;
#ifdef notdef
long t1, t2;
int j;
#endif
int quality, obso;
int permanent, record;
struct iface *ifp;
char alias[12], dest[12], iface[12], neighbor[12], type[3], *ptr;
char destalias[ALEN + 1];	/*alias in 'alias form'*/
char destcall[AXALEN];	/*in callsign (ie shifted) form */
char destneighbor[AXALEN];

	if (Nr_iface == NULLIF) {
		tputs ("Attach netrom interface first\n");
		return 1;
	}
	if ((fn = fopen (Netromfile, READ_TEXT)) == NULLFILE) {
		tputs("Can't open netrom save file!\n");
		return 1;
	}
	if (fgets (buff, sizeof (buff), fn) == NULLCHAR) {	/* read the timestamp */
		(void) fclose (fn);
		return 1;
	}
	if ((strncmp (buff, "time = ", 7)) != 0) {
		tputs("Wrong node file content\n");
		(void) fclose (fn);
		return 1;
	}
	(void) time (&now);
	sscanf (buff, "time =%ld", &prev);
	if (prev >= now) {
		(void) fclose (fn);
		return 1;
	}
#ifdef notdef
	t1 = now - prev;
	t2 = dur_timer (&Obsotimer) / 1000L;
	j = t1 / t2;		/* recalculate obsolete count */
	tprintf ("%ld seconds are past ( %d obsolete scans)\n", t1, j);
#endif

	while (fgets (buff, sizeof (buff), fn) != NULLCHAR) {
		if ((ptr = strchr (buff, ':')) == 0) {
			sscanf (buff, "%s%s%i%i%s%s"
			     ,dest, type, &quality, &obso, iface, neighbor);
			alias[0] = '\0';
		} else {
			*ptr = ' ';
			sscanf (buff, "%s%s%s%i%i%s%s"
				,alias, dest, type, &quality, &obso, iface, neighbor);
		}
		/*Set and check calls / alias - WG7J */
		if (setcall (destcall, dest) == -1)
			continue;

		if (setcall (destneighbor, neighbor) == -1)
			continue;

		if (putalias (destalias, alias, 1) == -1)
			continue;

		/* find interface */
		if ((ifp = if_lookup (iface)) == NULLIF)
			continue;

		/* Is it a netrom interface ? */
		if (!(ifp->flags & IS_NR_IFACE))
			continue;

		/* get and check quality value */
		if (quality > 255 || (unsigned) quality < Nr_autofloor)
			continue;

		/* Check the type of route - WG7J */
		permanent = record = 0;
		if (strchr (type, 'P') != NULLCHAR)
			permanent = 1;
		else {
			if (strchr (type, 'R') != NULLCHAR)
				record = 1;
		}
		(void) nr_routeadd (destalias, destcall, ifp, (unsigned) quality, destneighbor, \
			     (unsigned) permanent, (unsigned) record);
	}
	(void) fclose (fn);
	return 0;
}


static int
donrsave (int argc OPTIONAL, char *argv[] OPTIONAL, void *p OPTIONAL)
{
register struct nrroute_tab *rp;
register struct nr_bind *bp;
register struct nrnbr_tab *np;
char neighbor[AXBUF];
register int i;
char buf[16];
char *cp;
FILE *fn;
time_t now;

	if ((fn = fopen (Netromfile, "w+")) == NULLFILE) {
		tputs ("Can't write netrom save file!\n");
		return 1;
	}
	(void) time (&now);
	fprintf (fn, "time = %ld\n", now);
	for (i = 0; i < NRNUMCHAINS; i++) {
		for (rp = Nrroute_tab[i]; rp != NULLNRRTAB; rp = rp->next) {
			strncpy (buf, rp->alias, 16);
			/* remove trailing spaces */
			if ((cp = strchr (buf, ' ')) == NULLCHAR)
				cp = &buf[strlen (buf)];
			if (cp != buf)	/* don't include colon for null alias */
				*cp++ = ':';
			for (bp = rp->routes; bp != NULLNRBIND; bp = bp->next) {
				pax25 (cp, rp->call);
				fprintf (fn, "%-16s  ", buf);
				np = bp->via;
				if (fprintf (fn, "%1s %3d  %3d  %-8s  %s\n",
					  (bp->flags & NRB_PERMANENT ? "P" :
				      bp->flags & NRB_RECORDED ? "R" : "X"),
					     bp->quality, bp->obsocnt,
					     np->iface->name,
					 pax25 (neighbor, np->call)) == EOF)
					break;
			}
		}
	}
	(void) fclose (fn);
	return 0;
}

#endif /* NETROM */
