/* NET/ROM level 7 processing (user command interpreter) */

#include <stdio.h>
#include <ctype.h>
#include "global.h"
#include "mbuf.h"
#include "iface.h"
#include "timer.h"
#include "ax25.h"
#include "lapb.h"
#include "netrom.h"
#include "cmdparse.h"
#include "netuser.h"

static struct mbuf *nr7id();
static struct nr_user *find_nr_axuser();
static void del_nr_user(),nr7_send(),nr7_execute();
static void nr7_rxn(),nr7_stnr(),nr7_crx(),nr7_ctx(),nr7_rxa(),nr7_state();

static struct nr_user *nr_users;	/* users connected to the NET/ROM */
static struct nr_user *nr_curusr;	/* requesting user */

extern struct mbuf *recv_ax25();
extern char hostname[],version[];

static char nrlog[] = "%s %s - %s";
static char nrconlog[] = "Connected NET/ROM";
static char nrdwnlog[] = "NET/ROM downlink %s";
static char nrcirlog[] = "NET/ROM circuit ok";
static char nrciflog[] = "NET/ROM circuit ab";
static char nrconnect[] = "Connected to %s\r";
static char nrfailure[] = "Failure with %s\r";
static char nrbusy[] = "Busy from %s\r";
static char nrifunknown[] = "Interface unknown\r";
static char nrnodebusy[] = "Node busy\r";
static char nrinvalid[] = "Invalid callsign\r";

/* This is the default receive upcall function, used when
 * someone else connects to us and sends a pid=Text packet.
 * This will start a session with the NET/ROM command interpreter.
 */
void
ax_incom(axp,cnt)
register struct ax25_cb *axp;
int16 cnt;
{
	register struct nr_user *nru;

	/* check if NET/ROM handling is supported here */
	if(axp->interface->nriface == NULLNRIFACE ||	/* not NET/ROM iface */
	   !axp->interface->nriface->uplink ||		/* no uplinks here */
	   !ismycall(&axp->addr.source)){		/* not proper callsign */
#if 0
	    tnc_state(axp,DISCONNECTED,CONNECTED,LAPBCONN);/* try interactive */
#else
	    disc_ax25(axp);				/* throw him out */
#endif
	    return;
	}

	/* register new NET/ROM user */
	if((nru = find_nr_axuser(axp,NRU_UP)) != NULLNRUSER ||
	   (nru = (struct nr_user *) calloc(1,sizeof(struct nr_user))) == NULLNRUSER) {
	    disc_ax25(axp);
	    return;
	}

	if((nru->next = nr_users) != NULLNRUSER)
	    nru->next->prev = nru;

	nr_users = nru;

	nru->ax25_cb[NRU_UP] = axp;
	nru->iface = axp->interface;

	axp->paclen = 256;		/* NET/ROM always has paclen=256 */
	axp->window = 1;		/* use tight flow control */
	axp->r_upcall = nr7_rxa;
	axp->s_upcall = nr7_state;

	log_ax(axp,nrconlog);

	nr7_rxa(axp,cnt);		/* pass first packet to interpreter */
}

/* receive upcall handler for AX.25 Text packets incoming on interface call */

static void
nr7_rxa(axp,cnt)
struct ax25_cb *axp;
int16 cnt;
{
	register struct nr_user *nru;
	struct mbuf *bp;
	char inbuf[80];
	struct mbuf *recvl_ax25();

	if ((nru = find_nr_axuser(axp,NRU_UP)) == NULLNRUSER) {
	    disc_ax25(axp);		/* who are you? */
	    return;
	}

	while((bp = recvl_ax25(axp,0)) != NULLBUF){ /* get a line of text */
	    inbuf[pullup(&bp,inbuf,sizeof(inbuf) - 1)] = '\0';
	    free_p(bp);
	    nr7_execute(nru,inbuf);	/* execute the command */
	}
}

/* state-change upcall handler to be used once Text packets for the NET/ROM
 * command interpreter have been received.
 * it is also used as a DM-sender for the AX.25 port for downlinks
 */

static void
nr7_state(axp,old,new,msg)
struct ax25_cb *axp;
int old,new,msg;
{
	register struct nr_user *nru;

	switch (new)
	{
	case CONNECTED:
	    if (msg == LAPBCONN)	/* can only be the AX.25 port */
		axp->state = DISCONNECTED; /* refuse incoming connections */
					/* also do processing for DISCONNECTED */
	case DISCONNECTED:
	    if ((nru = find_nr_axuser(axp,NRU_UP)) == NULLNRUSER)
		break;

	    del_nr_user(nru);
	    break;
	}
}

#ifdef NETROM4
/* receive upcall hand;
	c for NET/ROM Info */

void
nr7_rxnr(circ,pkcnt)
register struct nr_circ *circ;
int16 pkcnt;
{
	register struct nr_user *nru;
	char tmp1[10],tmp2[10];

	/* register a new user */

	if ((nru = (struct nr_user *) circ->user) != NULLNRUSER ||
	    (nru = (struct nr_user *) calloc(1,sizeof(struct nr_user))) == NULLNRUSER) {
	    nr4_close(circ);
	    return;
	}

	if ((nru->next = nr_users) != NULLNRUSER)
	    nru->next->prev = nru;

	nr_users = nru;

	nru->nrcirc[NRU_UP] = circ;
	nru->iface = nr_lap;

	circ->r_upcall = nr7_rxn;
	circ->s_upcall = nr7_stnr;
	circ->user = (char *) nru;

	pax25(tmp1,&circ->dnode);
	pax25(tmp2,&circ->suser);
	log_msg(nrlog,tmp1,tmp2,nrconlog);

	nr7_rxn(circ,pkcnt);		/* first packet to interpreter */
}

/* receive upcall handler for NET/ROM info after first packet received */

static void
nr7_rxn (circ,pkcnt)
register struct nr_circ *circ;
int16 pkcnt;
{
	register struct nr_user *nru;
	struct mbuf *bp;
	char inbuf[80];
	struct mbuf *pullline();

	if ((nru = (struct nr_user *) circ->user) == NULLNRUSER) {
	    nr4_close(circ);
	    return;
	}

	circ->rxq = nr4_recv(circ,pkcnt); /* receive all packets and append */

	while((bp = pullline(&circ->rxq,0)) != NULLBUF){ /* get a line of text */
	    inbuf[pullup(&bp,inbuf,sizeof(inbuf) - 1)] = '\0';
	    free_p(bp);
	    nr7_execute(nru,inbuf);	/* execute the command */
	}
}

/* NET/ROM state-change upcall hand;er to be used once packets received over
   the NET/ROM level 4 circuit */

static void
nr7_stnr(circ,old,new)
struct nr_circ *circ;
int old,new;
{
	register struct nr_user *nru;

	switch (new)
	{
	case NR4STDISC:
	    if ((nru = (struct nr_user *) circ->user) == NULLNRUSER)
		break;

	    del_nr_user(nru);
	    break;
	}
}
#endif

/* commands available from NET/ROM */
static int nr7_conn(),nr7_info(),nr7_mheard(),nr7_nodes(),nr7_parms(),
	nr7_ports(),nr7_routes(),nr7_users();
static struct cmds netromcmds[] = {
#ifdef NETROM4
	"CONNECT",	nr7_conn,	0, NULLCHAR,  NULLCHAR,
#endif
	"INFO",		nr7_info,	0, NULLCHAR,  NULLCHAR,
	"MHEARD",	nr7_mheard,	0, NULLCHAR,  NULLCHAR,
	"NODES",	nr7_nodes,	0, NULLCHAR,  NULLCHAR,
#ifdef NR7PARMS
	"PARMS",	nr7_parms,	0, NULLCHAR,  NULLCHAR,
#endif
	"PORTS",	nr7_ports,	0, NULLCHAR,  NULLCHAR,
	"ROUTES",	nr7_routes,	0, NULLCHAR,  NULLCHAR,
#ifdef NETROM4
	"USERS",	nr7_users,	0, NULLCHAR,  NULLCHAR,
#endif
	NULLCHAR,	NULLFP,		0, NULLCHAR,  NULLCHAR
};

/* execute a NET/ROM command already in a line buffer */
static void
nr7_execute(nru,inbuf)
register struct nr_user *nru;
char inbuf[];
{
	char *p;
	struct mbuf *bp;
	register struct cmds *cm;

	/* when a command comes in, terminate any pending downlinks */
	/* first clear the "user" fields to suppress messages */

	if (nru->ax25_cb[NRU_DOWN] != NULLAX25) {
	    nru->ax25_cb[NRU_DOWN]->user = NULLCHAR;
	    disc_ax25(nru->ax25_cb[NRU_DOWN]);
	    nru->ax25_cb[NRU_DOWN] = NULLAX25;
	}

	if (nru->nrcirc[NRU_DOWN] != NULLNRCIRC) {
	    nru->nrcirc[NRU_DOWN]->user = NULLCHAR;
	    nr4_close(nru->nrcirc[NRU_DOWN]);
	    nru->nrcirc[NRU_DOWN] = NULLNRCIRC;
	}

	rip(inbuf);			/* strip CR and LF chars */
	for(p = inbuf; *p != '\0'; p++) /* convert rest to UPPER case */
	    *p = toupper(*p);

	nr_curusr = nru;		/* set current user */

	if (inbuf[0] != '\0' &&		/* don't parse empty lines */
	    (index(inbuf,'$') != NULLCHAR || /* don't parse lines with $ */
	     cmdparse(netromcmds,inbuf) < 0)) /* parse a NET/ROM cmd */
	{
	    bp = nr7id();
	    append(&bp,qstring("Invalid command ("));

	    for (cm = netromcmds; cm->name != NULLCHAR; cm++) {
		append(&bp,qstring(cm->name));
		append(&bp,qstring(((cm+1)->name != NULLCHAR)? " ":")\r"));
	    }

	    nr7_send(bp);		/* send the error message */

	    if (++(nru->errors) >= 3) { /* too many errors? */
		if (nru->ax25_cb[NRU_UP] != NULLAX25)
		    close_ax25(nru->ax25_cb[NRU_UP]);

		if (nru->nrcirc[NRU_UP] != NULLNRCIRC)
		    nr4_close(nru->nrcirc[NRU_UP]);
	    }
	} else {
	    nru->errors = 0;		/* reset errorcnt on good command */
	}

	nr_curusr = NULLNRUSER;		/* end current user */
}

#ifdef NETROM4
/* AX.25 receive upcall hand;
	c */

static void
nr7_arx(axp,cnt)
register struct ax25_cb *axp;
int16 cnt;
{
	register struct nr_user *nru;
	struct ax25_cb *axp2;
	struct nr_circ *circ2;
	int ud;

	if (cnt == 0)
	    return;

	/* see if we know this user */

	if ((nru = find_nr_axuser(axp,NRU_UP)) != NULLNRUSER)
	    ud = NRU_DOWN;
	else {
	    if ((nru = find_nr_axuser(axp,NRU_DOWN)) != NULLNRUSER)
		ud = NRU_UP;
	    else {
		close_ax25(axp);
		return;
	    }
	}

	if ((axp2 = nru->ax25_cb[ud]) != NULLAX25) { /* AX.25 link? */
	    if (len_q(axp2->txq) >= nr_trbacklog) /* tx queue full? */
		return;				/* hold incoming data */

	    /* send it on AX.25 circuit (multi-packet if necessary) */
	    sendp_ax25(axp2,recv_ax25(axp,cnt),PID_FIRST|PID_LAST|PID_NO_L3);
	    return;
	}

	if ((circ2 = nru->nrcirc[ud]) != NULLNRCIRC) { /* NET/ROM link? */
	    if (len_q(circ2->txq) >= nr_trbacklog) /* tx queue full? */
		return;				/* hold incoming data */

	    nr4_send(circ2,recv_ax25(axp,cnt)); /* send it on NR4 circuit */
	    return;
	}

	/* "unreachable" */
	close_ax25(axp);
	return;
}

/* AX.25 transmit upcall hand;er */

static void
nr7_atx(axp,cnt)
register struct ax25_cb *axp;
int16 cnt;
{
	register struct nr_user *nru;
	int ud;

	/* see if we know this user */

	if ((nru = find_nr_axuser(axp,NRU_UP)) != NULLNRUSER)
	    ud = NRU_DOWN;
	else {
	    if ((nru = find_nr_axuser(axp,NRU_DOWN)) != NULLNRUSER)
		ud = NRU_UP;
	    else {
		close_ax25(axp);
		return;
	    }
	}

	if (nru->ax25_cb[ud] != NULLAX25)
	    nr7_arx(nru->ax25_cb[ud],len_mbuf(nru->ax25_cb[ud]->rxq));

	if (nru->nrcirc[ud] != NULLNRCIRC)
	    nr7_crx(nru->nrcirc[ud],len_q(nru->nrcirc[ud]->rxq));
}

/* AX.25 state-change upcall hand;er to be used for user-initated circuits */

static void
nr7_cstax(axp,old,new,msg)
struct ax25_cb *axp;
int old,new,msg;
{
	register struct nr_user *nru;
	struct mbuf *bp;
	char buf[40],tmp[10];

	switch (new)
	{
	case CONNECTED:
	    if ((nru = (struct nr_user *) axp->user) == NULLAX25 ||
		nru->ax25_cb[NRU_DOWN] != axp) {
		disc_ax25(axp);
		break;
	    }

	    /* set proper upcall hand;ers for data transfer */

	    if (nru->ax25_cb[NRU_UP] != NULLAX25) {
		nru->ax25_cb[NRU_UP]->r_upcall = nr7_arx;
		nru->ax25_cb[NRU_UP]->t_upcall = nr7_atx;
	    }

	    if (nru->nrcirc[NRU_UP] != NULLNRCIRC) {
		nru->nrcirc[NRU_UP]->r_upcall = nr7_crx;
		nru->nrcirc[NRU_UP]->t_upcall = nr7_ctx;
	    }

	    axp->r_upcall = nr7_arx;
	    axp->t_upcall = nr7_atx;

	    pax25(tmp,&axp->addr.dest);
	    sprintf(buf,nrconnect,tmp);

	    nr_curusr = nru;
	    bp = nr7id();
	    append(&bp,qstring(buf));
	    nr7_send(bp);
	    nr_curusr = NULLNRUSER;

	    log_ax(axp,nrdwnlog,"ok");
	    break;

	case DISCONNECTED:
	    if ((nru = (struct nr_user *) axp->user) == NULLAX25 ||
		nru->ax25_cb[NRU_DOWN] != axp)
		break;

	    if (axp->r_upcall == NULLVFP) {
		pax25(tmp,&axp->addr.dest);
		sprintf(buf,((msg == LAPBBUSY)? nrbusy : nrfailure),tmp);

		nr_curusr = nru;
		bp = nr7id();
		append(&bp,qstring(buf));
		nr7_send(bp);
		nr_curusr = NULLNRUSER;

		log_ax(axp,nrdwnlog,((msg == LAPBBUSY)? "bs":"ab"));
	    } else {
		if (nru->ax25_cb[NRU_UP] != NULLAX25)
		    close_ax25(nru->ax25_cb[NRU_UP]);

		if (nru->nrcirc[NRU_UP] != NULLNRCIRC)
		    nr4_close(nru->nrcirc[NRU_UP]);
	    }

	    nru->ax25_cb[NRU_DOWN] = NULLAX25;
	    break;
	}
}

/* NET/ROM receive upcall hand;er */

static void
nr7_crx(circ,pkcnt)
register struct nr_circ *circ;
int16 pkcnt;
{
	register struct nr_user *nru;
	struct ax25_cb *axp2;
	struct nr_circ *circ2;
	int ud,again;

	if (pkcnt == 0)
	    return;

	/* see if we know this user */

	if ((nru = (struct nr_user *) circ->user) != NULLNRUSER) {
	    if (nru->nrcirc[NRU_UP] == circ)
		ud = NRU_DOWN;
	    else
		if (nru->nrcirc[NRU_DOWN] == circ)
		    ud = NRU_UP;
		else {
		    nr4_close(circ);
		    return;
		}
	} else {
	    nr4_close(circ);
	    return;
	}

	again = 0;

	while (pkcnt--) {			/* process all packets */
	    if ((axp2 = nru->ax25_cb[ud]) != NULLAX25) { /* AX.25 link? */
		if (!again++ &&			/* first time through? */
		    axp2->txq != NULLBUF)	/* tx queue not empty? */
		    return;			/* hold incoming data */

		/* send it on AX.25 circuit (multi-packet if necessary) */
		sendp_ax25(axp2,nr4_recv(circ,1),PID_FIRST|PID_LAST|PID_NO_L3);
		continue;
	    }

	    if ((circ2 = nru->nrcirc[ud]) != NULLNRCIRC) { /* NET/ROM link? */
		if (!again++ &&			/* first time through? */
		    circ2->txq != NULLBUF)	/* tx queue not empty? */
		    return;			/* hold incoming data */

		nr4_send(circ2,nr4_recv(circ,1)); /* simply send it */
		continue;
	    }

	    /* "unreachable" */
	    nr4_close(circ);
	    return;
	}
}

/* NET/ROM transmit upcall handler */

static void
nr7_ctx(circ,pkcnt)
register struct nr_circ *circ;
int16 pkcnt;
{
	register struct nr_user *nru;
	int ud;

	/* see if we know this user */

	if ((nru = (struct nr_user *) circ->user) != NULLNRUSER) {
	    if (nru->nrcirc[NRU_UP] == circ)
		ud = NRU_DOWN;
	    else
		if (nru->nrcirc[NRU_DOWN] == circ)
		    ud = NRU_UP;
		else {
		    nr4_close(circ);
		    return;
		}
	} else {
	    nr4_close(circ);
	    return;
	}

	if (nru->ax25_cb[ud] != NULLAX25)
	    nr7_arx(nru->ax25_cb[ud],len_mbuf(nru->ax25_cb[ud]->rxq));

	if (nru->nrcirc[ud] != NULLNRCIRC)
	    nr7_crx(nru->nrcirc[ud],len_q(nru->nrcirc[ud]->rxq));
}

/* NET/ROM state-change upcall handler to be used for user-initated circuits */

static void
nr7_cstnr(circ,old,new)
struct nr_circ *circ;
int old,new;
{
	register struct nr_user *nru;
	struct mbuf *bp;
	struct nrroute_tab *rp;
	char *p;
	char buf[40],tmp[10];

	switch (new)
	{
	case NR4STCON:
	    if ((nru = (struct nr_user *) circ->user) == NULLNRUSER ||
		nru->nrcirc[NRU_DOWN] != circ) {
		nr4_close(circ);
		break;
	    }

	    if ((rp = find_nrroute(&circ->dnode)) == NULLNRRTAB) {
		nr4_close(circ);
		break;
	    }

	    /* set proper upcall hand;ers for data transfer */

	    if (nru->ax25_cb[NRU_UP] != NULLAX25) {
		nru->ax25_cb[NRU_UP]->r_upcall = nr7_arx;
		nru->ax25_cb[NRU_UP]->t_upcall = nr7_atx;
	    }

	    if (nru->nrcirc[NRU_UP] != NULLNRCIRC) {
		nru->nrcirc[NRU_UP]->r_upcall = nr7_crx;
		nru->nrcirc[NRU_UP]->t_upcall = nr7_ctx;
	    }

	    circ->r_upcall = nr7_crx;
	    circ->t_upcall = nr7_ctx;

	    sprintf(buf,nrconnect,nr_fmtcall(rp));

	    nr_curusr = nru;
	    bp = nr7id();
	    append(&bp,qstring(buf));
	    nr7_send(bp);
	    nr_curusr = NULLNRUSER;

	    pax25(buf,&circ->dnode);
	    pax25(tmp,&circ->suser);
	    log_msg(nrlog,buf,tmp,nrcirlog);
	    break;

	case NR4STDISC:
	    if ((nru = (struct nr_user *) circ->user) == NULLNRUSER ||
		nru->nrcirc[NRU_DOWN] != circ)
		break;

	    if (circ->r_upcall == NULLVFP) {
		if ((rp = find_nrroute(&circ->dnode)) != NULLNRRTAB)
		    p = nr_fmtcall(rp);
		else
		    pax25(p = tmp,&circ->dnode);

		sprintf(buf,((circ->d_reason == NR4REBUSY)? nrbusy : nrfailure),p);

		nr_curusr = nru;
		bp = nr7id();
		append(&bp,qstring(buf));
		nr7_send(bp);
		nr_curusr = NULLNRUSER;

		pax25(buf,&circ->dnode);
		pax25(tmp,&circ->suser);
		log_msg(nrlog,buf,tmp,nrciflog);
	    } else {
		if (nru->ax25_cb[NRU_UP] != NULLAX25)
		    close_ax25(nru->ax25_cb[NRU_UP]);

		if (nru->nrcirc[NRU_UP] != NULLNRCIRC)
		    nr4_close(nru->nrcirc[NRU_UP]);
	    }

	    nru->nrcirc[NRU_DOWN] = NULLNRCIRC;
	    break;
	}
}

/* check validity of a callsign (NET/ROM style) */

static int
nr7_validcall (addr)
struct ax25_addr *addr;

{
	register char *p;
	int digits = 0;

	if (valid_addr(addr) < 0)	/* checks for ctrl chars etc */
	    return -1;			/* it fails... so it's invalid! */

	for (p = addr->call; p < (addr->call + ALEN) && *p != (' ' << 1); p++)
	    if (isdigit(uchar(*p) >> 1))
		digits++;		/* count digits in call */

	/* 'it must contain 1 or 2 digits, must be 4..6 characters and
	   the last character must not be a digit' */

	if (digits == 0 || digits > 2 || (p - addr->call) < 4 ||
	    isdigit(uchar(p[-1]) >> 1))
	    return -1;

	return 0;
}

/* the NET/ROM CONNECT command */
static int
nr7_conn(argc,argv)
int argc;
char *argv[];
{
	register struct nr_user *nru = nr_curusr;
	struct nrroute_tab *rp;
	struct mbuf *bp;
	struct ax25_call *axc;
	struct ax25_addr *node;
	struct ax25 addr;
	char alias[7];
	struct ax25_cb *open_ax25(),*find_ax25();
	extern int16 t1init,t2init;

	memset(&addr,0,sizeof(addr));		/* clean the AX.25 addresses */

	/* define source user (either the AX.25 connectee or the cicuit user) */

	if (nru->ax25_cb[NRU_UP] != NULLAX25) {
	    ASSIGN(addr.source,nru->ax25_cb[NRU_UP]->addr.dest);
	} else {
	    ASSIGN(addr.source,nru->nrcirc[NRU_UP]->suser);
	}

	/* check if CONNECTed if destination is an alias */

	if (argc == 2 && strlen(argv[1]) <= 6) {
	    sprintf(alias,"%-6s",argv[1]);	/* 6-char space-padded */

	    if ((rp = find_nralias(alias)) != NULLNRRTAB) {
		memcpy(&addr.dest,&rp->call,sizeof(addr.dest));
		goto circuit;			/* hand;e as a circuit */
	    }
	    /* not found, it could be a callsign so continue */
	}

	/* get and check the destination call (AX.25 user or NODE) */

	if (setcall(&addr.dest,argv[1]) < 0 || nr7_validcall(&addr.dest) < 0) {
	    bp = nr7id();
	    append(&bp,qstring(nrinvalid));
	    nr7_send(bp);
	    return 0;
	}

	if (ismycall(&addr.dest)) {
	    bp = nr7id();
	    append(&bp,qstring(nrnodebusy));
	    nr7_send(bp);
	    return 0;
	}

	/* check if it's a NET/ROM NODE (= it appears in our table) */

	if ((rp = find_nrroute(&addr.dest)) != NULLNRRTAB) {
circuit:
	    /* setup a NET/ROM circuit and wait for connection */
	    if ((nru->nrcirc[NRU_DOWN] =
			nr4_conn(&addr.dest,nr_scall(nru->iface),&addr.source,
				 NULLVFP,NULLVFP,nr7_cstnr,nru)) == NULLNRCIRC) {
		bp = nr7id();
		append(&bp,qstring("Circuit table full\r"));
		nr7_send(bp);
	    }

	    return 0;			/* done for NET/ROM NODES */
	}

	/* when we are here, it appears to be an AX.25 downlink request */

	if (nru->iface == NULLIF) {	/* now we need an interface */
	    bp = nr7id();
	    append(&bp,qstring(nrifunknown));
	    nr7_send(bp);
	    return 0;
	}

	/* check if already connected to that station */

	if (find_ax25(&addr.dest,&addr.source) != NULLAX25) {
	    bp = nr7id();
	    append(&bp,qstring("Already connected or connecting to that station\r"));
	    nr7_send(bp);
	    return 0;
	}

	/* first digipeater is our nodecall */

	node = nr_scall(nru->iface);
	ASSIGN(addr.digis[0],*node);
	addr.digis[0].ssid |= REPEATED; /* fake the "repeated" bit */
	addr.ndigis = 1;

	/* set digipeater path */

	if (argc > 2) {
	    if (!strncmp(argv[2],"VIA",strlen(argv[2]))) {
		argv++;			/* skip the (optional) V VI VIA */
		argc--;
	    }

	    while (argc > 2 && addr.ndigis < MAXDIGIS) {
		if (setcall(&addr.digis[addr.ndigis++],argv[2]) < 0 ||
		    valid_addr(&addr) < 0) {
		    bp = nr7id();
		    append(&bp,qstring(nrinvalid));
		    nr7_send(bp);
		    return 0;
		}

		argv++;
		argc--;
	    }
	}

	/* create a "hot" digipeater to hand;e the downlink */

	if ((axc = cr_axcall(ax25_digi,node)) == NULLAXCALL) {
	    bp = nr7id();
	    append(&bp,qstring(nrnodebusy));
	    nr7_send(bp);
	    return 0;
	}

	axc->mode = DIGICONNECT;
	axc->flags = DIGIGATEWAY;
	axc->port = 255;		/* fake value to make it a port */
	axc->s_upcall = nr7_state;	/* refuse incoming connections */

	/* and now, finally, we can open the AX.25 connection... */

	if ((nru->ax25_cb[NRU_DOWN] =
		open_ax25(&addr,1,NULLVFP,NULLVFP,nr7_cstax,
			  nru->iface,(char *)nru)) == NULLAX25){
	    char fail[30],tmp[11];

	    pax25(tmp,&addr.dest);
	    sprintf(fail,nrfailure,tmp);
	    bp = nr7id();
	    append(&bp,qstring(fail));
	    nr7_send(bp);
	    return 0;
	}

	nru->ax25_cb[NRU_DOWN]->paclen = 256;	/* always paclen=256 */

	/* compute timer values depending on real digipeaters */

	nru->ax25_cb[NRU_DOWN]->t1.start = t1init * addr.ndigis;
	nru->ax25_cb[NRU_DOWN]->t2.start = t2init * addr.ndigis;

	return 0;
}
#endif

/* the NET/ROM INFO (aka IDENT) command */
static int
nr7_info(argc,argv)
int argc;
char *argv[];
{
	struct mbuf *bp;
	FILE *fp;
	char buf[128];

	bp = nr7id();
	sprintf(buf,"%s [%s]\r",hostname,inet_ntoa(ip_addr));
	append(&bp,qstring(buf));

	/* when available, send information from a file */

	if (nr_infname[0] != '\0' && (fp = fopen(nr_infname,"r")) != NULLFILE) {
	    while (fgets(buf,sizeof(buf),fp) != NULLCHAR) {
		rip(buf);
		strcat(buf,"\r");
		append(&bp,qstring(buf));
	    }

	    fclose(fp);
	}

	nr7_send(bp);
	return 0;
}

/* the NET/ROM MHEARD command */
static int
nr7_mheard(argc,argv)
int argc;
char *argv[];
{
	struct mbuf *bp;
	struct interface *ifp;
	char *p;
	char buf[40];

	bp = nr7id();

	if (argc > 1) {
	    for (p = argv[1]; *p != '\0'; p++)	/* arghh!! ifaces are lowercase */
		*p = tolower(*p);

	    ifp = if_lookup(argv[1]);		/* specified an interface */
	} else {
	    ifp = nr_curusr->iface;		/* else default to downlink iface */
	}

	if (ifp == NULLIF || ifp->nriface == NULLNRIFACE)
	    append(&bp,qstring(nrifunknown));
	else {
	    sprintf(buf,"MHEARD on %s:\r",ifp->name);
	    append(&bp,qstring(buf));
	    showmheard(&bp,ifp);
	}

	nr7_send(bp);
	return 0;
}

/* the NET/ROM NODES command */
static int
nr7_nodes(argc,argv)
int argc;
char *argv[];
{
	struct mbuf *bp;
	struct nrroute_tab *rp = NULLNRRTAB;
	int hide = 1,ext = 0;
	struct ax25_addr dest;
	char alias[7];

	bp = nr7id();			/* prepare own id */

	if(argc > 1) {
	    if (argv[1][0] == '*')	/* nodes * */
		hide = 0;		/* gives full listing */
	    else {			/* nodes <call> */
		if (strlen(argv[1]) <= 6) {
		    sprintf(alias,"%-6s",argv[1]); /* 6-char space-padded */

		    rp = find_nralias(alias);
		}
		if (rp != NULLNRRTAB ||
		    (setcall(&dest,argv[1]) == 0 &&
		     (rp = find_nrroute(&dest)) != NULLNRRTAB)){
		    append(&bp,qstring("Routes to "));
		    nrrouteinfo(&bp,rp); /* show detailed info */
		    nr7_send(bp);
		    return 0;
		}
	    }
	}

	if(argc > 2)			/* nodes * * */
	    ext = 1;			/* gives extended listing */

	append(&bp,qstring("Nodes:\r"));
	doroutedump(&bp,hide,ext);
	nr7_send(bp);
	return 0;
}

#ifdef NR7PARMS
/* the NET/ROM PARMS command */
static int
nr7_parms(argc,argv)
int argc;
char *argv[];
{
	struct mbuf *bp;
	char *p,buf[160];
	unsigned q = 0;
	extern struct timer nodetimer;
	extern int16 t1init,maxframe,n2,t2init,t3init;

	bp = nr7id();

	if (nr_curusr->iface != NULLIF)
	    q = nr_curusr->iface->nriface->quality;

	sprintf(buf," 400 %u %u 255 %u %u %lu %u %u %u %u %u %u %u %u 0 %lu %u %u %lu %lu 0 0 0\r",
	    nr_autofloor,q,
	    obso_init,obso_minbc,TICK2SEC(nodetimer.start),nr_ttl,
#ifdef NETROM4
	    nr_trtimeout,nr_trtries,nr_trackdelay,nr_trbusdelay,
	    nr_trwindow,nr_trbacklog,nr_noactive,
#else
	    0,0,0,0,0,0,0,
#endif
	    TICK2SEC(t1init),maxframe,n2,
	    TICK2MS(t2init) /10 ,TICK2MS(t3init) / 10
	);

	if ((len_mbuf(bp) + strlen(buf)) > 80) {
	    for (p = &buf[72 - len_mbuf(bp)]; *p != ' '; p--)
		;
	    *p = '\r';
	}
	append(&bp,qstring(buf));
	nr7_send(bp);
	return 0;
}
#endif

/* the NET/ROM PORTS command */
static int
nr7_ports(argc,argv)
int argc;
char *argv[];
{
	struct mbuf *bp;
	register struct interface *ifp;
	char *p;
	char buf[40];

	bp = nr7id();

	if (argc > 1) {
	    for (p = argv[1]; *p != '\0'; p++)	/* arghh!! ifaces are lowercase */
		*p = tolower(*p);

	    if ((ifp = if_lookup(argv[1])) == NULLIF ||
		ifp->nriface == NULLNRIFACE || !ifp->nriface->uplink) {
		append(&bp,qstring(nrifunknown));
		nr7_send(bp);
		return 0;
	    } else {
		nr_curusr->iface = ifp;
	    }
	} else {				/* no args, list ports */
	    append(&bp,qstring("Interfaces:"));

	    for (ifp = ifaces; ifp != NULLIF; ifp = ifp->next){
		if (ifp->nriface != NULLNRIFACE && ifp->nriface->uplink){
		    append(&bp,qstring(" "));
		    append(&bp,qstring(ifp->name));
		}
	    }

	    append(&bp,qstring("  "));
	}

	sprintf(buf,"Downlink: %s\r",
		((nr_curusr->iface != NULLIF)? nr_curusr->iface->name :
					       "[NONE]"));
	append(&bp,qstring(buf));
	nr7_send(bp);
	return 0;
}

/* the NET/ROM ROUTES command */
static int
nr7_routes(argc,argv)
int argc;
char *argv[];
{
	struct mbuf *bp;

	bp = nr7id();
	append(&bp,qstring("Routes:\r"));
	nrrtsdump(&bp);				/* show neigbors */
	nr7_send(bp);
	return 0;
}

#ifdef NETROM4
/* the NET/ROM USERS command */
static int
nr7_users(argc,argv)
int argc;
char *argv[];
{
	struct mbuf *bp;
	char line[80];

	bp = nr7id();
	sprintf(line,"NET/ROM in KA9Q NET v%s\r",version);
	append(&bp,qstring(line));
	nruserdump(&bp);		/* show users (to mbuf) */
	nr7_send(bp);

	return 0;
}

/* dump current users, also called from "netrom" subcommand interpreter */
void
nruserdump (bpp)
struct mbuf **bpp;
{
	register struct nr_user *nru;
	char *p;
	struct nrroute_tab *rp;
	int ud;
	char line[80],buf[40],tmp1[10],tmp2[10];

	for (nru = nr_users; nru != NULLNRUSER; nru = nru->next) {
	    memset(line,' ',sizeof(line));

	    for (ud = NRU_UP; ud <= NRU_DOWN; ud++) {
		if (nru->ax25_cb[ud] != NULLAX25) {
		    pax25(tmp1,&nru->ax25_cb[ud]->addr.dest);
		    sprintf(buf,"%slink(%s %s)",((ud == NRU_UP)? "Up" : "Down"),
				nru->ax25_cb[ud]->interface->name,tmp1);
		    strncpy(line + 43 * ud,buf,strlen(buf));
		} else {
		    if (nru->nrcirc[ud] != NULLNRCIRC) {
			if ((rp = find_nrroute(&nru->nrcirc[ud]->dnode)) != NULLNRRTAB)
			    p = nr_fmtcall(rp);
			else
			    pax25(p = tmp1,&nru->nrcirc[ud]->dnode);

			pax25(tmp2,&nru->nrcirc[ud]->suser);
			sprintf(buf,"Circuit(%s %s)",p,tmp2);
			strncpy(line + 43 * ud,buf,strlen(buf));
		    }
		}
	    }

	    /* put a <--> symbol in between two link ends */

	    if (line[0] != ' ' && line[43] != ' ')
		strncpy(line + 43 - 6,"<-->",4);

	    /* trim all trailing spaces and add a CR char */

	    for (p = line + sizeof(line) - 2; p >= line; p--)
		if (*p != ' ') {
		    p++;
		    break;
		}

	    if (bpp != NULLBUFP) {
		*p = '\r';
		append(bpp,qdata(line,(unsigned int) (p - line) + 1));
	    } else {
		*p = '\0';
		printf("%s\n",line);
	    }
	}
}
#endif


/* find nr_user struct for specified axp (uplink or downlink) */

static struct nr_user *
find_nr_axuser(axp,ud)
register struct ax25_cb *axp;
int ud;
{
	register struct nr_user *nru;

	for (nru = nr_users; nru != NULLNRUSER; nru = nru->next)
	    if (nru->ax25_cb[ud] == axp)
		break;

	return nru;
}

static void
del_nr_user (nru)
register struct nr_user *nru;

{
    if (nru->prev != NULLNRUSER)
	nru->prev->next = nru->next;
    else
	nr_users = nru->next;

    if (nru->next != NULLNRUSER)
	nru->next->prev = nru->prev;

    if (nru->ax25_cb[NRU_DOWN] != NULLAX25) {
	nru->ax25_cb[NRU_DOWN]->user = NULLCHAR;
	close_ax25(nru->ax25_cb[NRU_DOWN]);
    }

    if (nru->nrcirc[NRU_DOWN] != NULLNRCIRC) {
	nru->nrcirc[NRU_DOWN]->user = NULLCHAR;
	nr4_close(nru->nrcirc[NRU_DOWN]);
    }

    free(nru);
}

/* send output to an end user connected to NET/ROM via AX.25 or N/R l4	*/
/* when output to a level 4 NET/ROM connection (end user via a NET/ROM) */

static void
nr7_send(bp)
struct mbuf *bp;
{
#ifdef NETROM4
	if (nr_curusr->nrcirc[NRU_UP] != NULLNRCIRC) {
# ifdef NR_CHOP
	    struct mbuf *bp1;

	    /* it has to be chopped-up in managable pieces, or else the output of a */
	    /* single command will be sent as a single NET/ROM chained packet...    */

	    while (len_mbuf(bp) > NR4ILEN) {
		if ((bp1 = alloc_mbuf(NR4ILEN)) == NULLBUF) {
		    free_p(bp);
		    return;
		}

		bp1->cnt = pullup(&bp,bp1->data,NR4ILEN);
		nr4_send(nr_curusr->nrcirc[NRU_UP],bp1);
	    }

	    if (bp != NULLBUF)
# endif
		nr4_send(nr_curusr->nrcirc[NRU_UP],bp);

	    return;
	}
#endif

	if (nr_curusr->ax25_cb[NRU_UP] != NULLAX25) {
	    sendp_ax25(nr_curusr->ax25_cb[NRU_UP],bp,PID_FIRST|PID_LAST|PID_NO_L3);
	    return;
	}

	/* "unreachable" */
	free_p(bp);
	return;
}

/* return an MBUF containing the id string of this NET/ROM node */
/* layout: "ALIAS:CALL} " */
static struct mbuf *
nr7id()
{
	char *p;
	char buf[25],tmp1[10],tmp2[10];

	tmp1[0] = '\0';

	if (nr_curusr->iface != NULLIF && nr_curusr->iface->nriface != NULLNRIFACE) {
	    strcpy(tmp1,nr_curusr->iface->nriface->alias);
	    if((p = index(tmp1,' ')) != NULLCHAR)
		*p = '\0';
	    strcat(tmp1,":");
	}

	pax25(tmp2,nr_scall(nr_curusr->iface));
	sprintf(buf,"%s%s} ",tmp1,tmp2);
	return qstring(buf);
}