/* TNC-2 emulator for use by RLI or MBL bbs (or other programs) */

#include <ctype.h>
#include <time.h>
#include "global.h"
#include "mbuf.h"
#include "iface.h"
#include "timer.h"
#include "ax25.h"
#include "lapb.h"
#include "kiss.h"
#include "slip.h"
#include "tnc2.h"

#undef	ALLCMDS		1		/* include unsupported cmds? */
#define BEACON		1		/* include BEACON possibility? */

struct tnc *tnc2s = NULLTNC;		/* TNC controlblocks */
static void tnc2_recv();		/* upcall handlers */

/* TNC commands supported by the emulator */
static void dotnconmode(),dotnconnect(),dotnconverse(),
	dotncstatus(),dotndaytime(),dotndiscon(),dotndisplay(),
	dotnmycall(),dotnrestart(),dotntrans(),dotnunproto();

void dotnmheard(),dotnreset();

static struct tnccommand tnccommands[] =
{
    "8BITCONV", TC_ON_OFF,	0,	NULLVFP,
#ifdef ALLCMDS
    "AX25L2V2", TC_ON_OFF,	1,	NULLVFP,	/* not used */
#endif
    "AUTOLF",	TC_ON_OFF,	2,	NULLVFP,
#ifdef ALLCMDS
    "AWLEN",	TC_DECVAL,	3,	NULLVFP,	/* not used */
    "AXDELAY",	TC_DECVAL,	4,	NULLVFP,	/* not used */
    "AXHANG",	TC_DECVAL,	5,	NULLVFP,	/* not used */
#endif
#if (defined(BEACON) || defined(ALLCMDS))
    "BEACON",	TC_TIMER,	0,	(void (*)()) 10000,
#endif
    "BBSMSGS",	TC_ON_OFF,	55,	NULLVFP,
    "BKONDEL",	TC_ON_OFF,	6,	NULLVFP,
#if (defined(BEACON) || defined(ALLCMDS))
    "BTEXT",	TC_TEXT,	0,	NULLVFP,
#endif
#ifdef ALLCMDS
    "BUDLIST",	TC_ON_OFF,	7,	NULLVFP,	/* not used */
#endif
    "CONNECT",	TC_EXEC,	0,	dotnconnect,
    "CBELL",	TC_ON_OFF,	8,	NULLVFP,
#ifdef ALLCMDS
    "CONPERM",	TC_ON_OFF,	9,	NULLVFP,	/* not used */
    "CHECK",	TC_DECVAL,	10,	NULLVFP,	/* not used */
    "CLKADJ",	TC_DECVAL,	11,	NULLVFP,	/* not used */
#endif
    "CMDTIME",	TC_DECVAL,	12,	NULLVFP,
    "CMSG",	TC_ON_OFF,	13,	NULLVFP,
    "CMSGDISC", TC_ON_OFF,	14,	NULLVFP,
    "CPACTIME", TC_ON_OFF,	15,	NULLVFP,
    "CR",	TC_ON_OFF,	16,	NULLVFP,
    "CSTATUS",	TC_EXEC,	0,	dotncstatus,
    "CTEXT",	TC_TEXT,	1,	NULLVFP,
    "CANLINE",	TC_HEXVAL,	17,	NULLVFP,
    "COMMAND",	TC_HEXVAL,	18,	NULLVFP,
#ifdef ALLCMDS
    "CALSET",	TC_DECVAL,	19,	NULLVFP,	/* not used */
#endif
    "CANPAC",	TC_HEXVAL,	20,	NULLVFP,
    "CONOK",	TC_ON_OFF,	21,	NULLVFP,
    "CONMODE",	TC_MISC,	0,	dotnconmode,
    "CONSTAMP", TC_ON_OFF,	22,	NULLVFP,
    "CONVERSE", TC_EXEC,	0,	dotnconverse,
    "DISCONNE", TC_EXEC,	0,	dotndiscon,
    "DAYTIME",	TC_EXEC,	0,	dotndaytime,
    "DAYUSA",	TC_ON_OFF,	23,	NULLVFP,
    "DELETE",	TC_ON_OFF,	24,	NULLVFP,
#ifdef ALLCMDS
    "DWAIT",	TC_DECVAL,	25,	NULLVFP,	/* not used */
    "DIGIPEAT", TC_ON_OFF,	26,	NULLVFP,	/* not used */
#endif
    "DISPLAY",	TC_EXEC,	0,	dotndisplay,
    "ECHO",	TC_ON_OFF,	27,	NULLVFP,
#ifdef ALLCMDS
    "ESCAPE",	TC_ON_OFF,	28,	NULLVFP,	/* not used */
#endif
    "FLOW",	TC_ON_OFF,	29,	NULLVFP,
#ifdef ALLCMDS
    "FRACK",	TC_DECVAL,	30,	NULLVFP,	/* not used */
    "FULLDUP",	TC_ON_OFF,	31,	NULLVFP,	/* not used */
    "HEADERLN", TC_ON_OFF,	32,	NULLVFP,	/* not used */
    "HEALLED",	TC_ON_OFF,	33,	NULLVFP,	/* not used */
    "HID",	TC_ON_OFF,	34,	NULLVFP,	/* not used */
    "ID",	TC_EXEC,	0,	NULLVFP,
#endif
    "K",	TC_EXEC,	0,	dotnconverse,	/* ALIAS for CONVERSE */
    "KISS",	TC_ON_OFF,	76,	NULLVFP,
    "KISSRX",	TC_DECVAL,	77,	NULLVFP,
#ifdef ALLCMDS
    "LCOK",	TC_ON_OFF,	35,	NULLVFP,	/* not used */
#endif
    "LFADD",	TC_ON_OFF,	36,	NULLVFP,
    "LFIGNORE", TC_ON_OFF,	37,	NULLVFP,
#ifdef ALLCMDS
    "LCALLS",	TC_TEXT,	2,	NULLVFP,	/* not used */
    "LCSTREAM", TC_ON_OFF,	38,	NULLVFP,	/* not used */
#endif
    "MONITOR",	TC_ON_OFF,	39,	NULLVFP,	/* not used, incl for BBS */
#ifdef ALLCMDS
    "MALL",	TC_ON_OFF,	40,	NULLVFP,	/* not used */
    "MCON",	TC_ON_OFF,	41,	NULLVFP,	/* not used */
    "MFILTER",	TC_MISC,	1,	NULLVFP,	/* not used */
#endif
    "MHEARD",	TC_EXEC,	0,	dotnmheard,
#ifdef ALLCMDS
    "MHCLEAR",	TC_EXEC,	0,	NULLVFP,
    "MRPT",	TC_ON_OFF,	42,	NULLVFP,	/* not used */
    "MSTAMP",	TC_ON_OFF,	43,	NULLVFP,	/* not used */
#endif
    "MYCALL",	TC_EXEC,	0,	dotnmycall,
#ifdef ALLCMDS
    "MYALIAS",	TC_TEXT,	3,	NULLVFP,	/* not used */
    "MAXFRAME", TC_DECVAL,	44,	NULLVFP,	/* not used */
    "MCOM",	TC_ON_OFF,	45,	NULLVFP,	/* not used */
#endif
    "NEWMODE",	TC_ON_OFF,	46,	NULLVFP,
    "NOMODE",	TC_ON_OFF,	47,	NULLVFP,
#ifdef ALLCMDS
    "NUCR",	TC_ON_OFF,	48,	NULLVFP,	/* not used */
    "NULF",	TC_ON_OFF,	49,	NULLVFP,	/* not used */
    "NULLS",	TC_DECVAL,	50,	NULLVFP,	/* not used */
#endif
    "PACLEN",	TC_DECVAL,	51,	NULLVFP,
#ifdef ALLCMDS
    "PARITY",	TC_DECVAL,	52,	NULLVFP,	/* not used */
#endif
    "PASS",	TC_HEXVAL,	53,	NULLVFP,
#ifdef ALLCMDS
    "PASSALL",	TC_ON_OFF,	54,	NULLVFP,	/* not used */
#endif
    "PACTIME",	TC_TIMER,	1,	(void (*)()) 100,
#ifdef ALLCMDS
    "RETRY",	TC_DECVAL,	56,	NULLVFP,	/* not used */
#endif
    "REDISPLA", TC_HEXVAL,	57,	NULLVFP,
#ifdef ALLCMDS
    "RECONNEC", TC_EXEC,	0,	NULLVFP,
    "RESPTIME", TC_DECVAL,	58,	NULLVFP,	/* not used */
#endif
    "RESTART",	TC_EXEC,	0,	dotnrestart,
    "RESET",	TC_EXEC,	0,	dotnreset,
    "RXBLOCK",	TC_ON_OFF,	59,	NULLVFP,
#ifdef ALLCMDS
    "SCREENLN", TC_DECVAL,	60,	NULLVFP,	/* not used */
#endif
    "SENDPAC",	TC_HEXVAL,	61,	NULLVFP,
    "START",	TC_HEXVAL,	62,	NULLVFP,
    "STOP",	TC_HEXVAL,	63,	NULLVFP,
#ifdef ALLCMDS
    "STREAMSW", TC_HEXVAL,	64,	NULLVFP,	/* not used */
    "STREAMCA", TC_ON_OFF,	65,	NULLVFP,	/* not used */
    "STREAMDB", TC_ON_OFF,	66,	NULLVFP,	/* not used */
#endif
    "TRANS",	TC_EXEC,	0,	dotntrans,
#ifdef ALLCMDS
    "TRIES",	TC_EXEC,	0,	NULLVFP,	/* not used */
#endif
    "TRFLOW",	TC_ON_OFF,	67,	NULLVFP,
#ifdef ALLCMDS
    "TRACE",	TC_ON_OFF,	68,	NULLVFP,	/* not used */
    "TXDELAY",	TC_DECVAL,	69,	NULLVFP,	/* not used */
#endif
    "TXFLOW",	TC_ON_OFF,	70,	NULLVFP,
    "TXUIFRAM", TC_ON_OFF,	78,	NULLVFP,
    "UNPROTO",	TC_EXEC,	0,	dotnunproto,
#ifdef ALLCMDS
    "USERS",	TC_DECVAL,	71,	NULLVFP,	/* not used */
#endif
    "XFLOW",	TC_ON_OFF,	72,	NULLVFP,
#ifdef ALLCMDS
    "XMITOK",	TC_ON_OFF,	73,	NULLVFP,	/* not used */
#endif
    "XOFF",	TC_HEXVAL,	74,	NULLVFP,
    "XON",	TC_HEXVAL,	75,	NULLVFP,
    "",		0,		0,	NULLVFP
};

static char def_params[] = {
    0, 1, 1, 7, 0, 0, 1, 0, 0, 0, 30, 0, 1, 0, 0, 0,
    1, 0x18, 0x03, 0, 0x19, /*1*/ 0, 0, 1, 0, 16, 1, 1, 0, 1, 3, 0,
    0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 2, 1, 0, 0,
    0, 0, 0, 128, 3, 0x16, 0, 0, 10, 0x12, 0, 0, 80, 0x0d, 0x11, 0x13,
    0x7c, 0, 0, 0, 0, 30, 0, 1, 1, 1, 0x13, 0x11, 0, 1, 0
};

char tnc2_prompt[] = "cmd:";
static char tnc2_bad[] = "?bad\r";
static char tnc2_call[] = "?call\r";
static char tnc2_notenough[] = "?not enough\r";
static char tnc2_toomany[] = "?too many\r";
static char tnc2_was[] = "was ";
static char tnc2_conrq[] = "*** connect request:";
static char tnc2_bslcr[] = "\\\r";
static char tnc2_cr[] = "\r";
extern char *ax25mesgs[];

/* send message to client (BBS) */
void
tnc2_mesg (tnc2,s)
register struct tnc *tnc2;
register unsigned char *s;

{
    while (*s) {
	if (!tnc2_ochar(tnc2,*s))
	    break;

	if (*s++ == '\r' && tnc2->param[2])	/* AUTOLF */
	    if (!tnc2_ochar(tnc2,'\n'))
		break;
    }
}

/* process character input in cooked mode, return 1 if line complete */
int
tnc2_line (tnc2,c)
register struct tnc *tnc2;
register char c;

{
	if (tnc2->passc) {			/* prev was PASS char? */
	    tnc2->passc = 0;			/* clear the flag */
	    tnc2->linebuf[tnc2->linepos++] = c; /* store the passed char */
	    tnc2->linebuf[tnc2->linepos] = '\0';

	    if (tnc2->param[27])		/* ECHO ON */
		tnc2_mesg(tnc2,tnc2->linebuf + tnc2->linepos - 1); /* echo it */

	    goto checkbuf;
	}

	if (tnc2->param[17] && c == tnc2->param[17]) { /* CANLINE character */
	    while (tnc2->linepos > 0)
		if (tnc2->linebuf[--(tnc2->linepos)] == '\r') {/* up to CR */
		    tnc2->linepos++;
		    break;
		}

	    tnc2_mesg(tnc2,tnc2_bslcr);
	    return TL_CANCEL;
	}

	if (tnc2->param[18] && c == tnc2->param[18]) /* COMMAND character */
	    return TL_COMMAND;

	if (tnc2->param[20] && c == tnc2->param[20]) { /* CANPAC character */
	    tnc2->linepos = 0;			/* clear entire line (packet) */
	    tnc2_mesg(tnc2,tnc2_bslcr);
	    return TL_CANCEL;
	}

	if ((tnc2->param[24] && c == 0x7f) ||	/* DELETE ON, DEL */
	    (!tnc2->param[24] && c == '\b')) {	/* DELETE OFF, BS */
	    if (tnc2->linepos > 0) {
		tnc2->linepos--;
		if (tnc2->param[27])		/* ECHO ON */
		    tnc2_mesg(tnc2,tnc2->param[6]? /* BKONDEL */
					"\b \b" : "\\");
	    }
	    return TL_INCOMPLETE;
	}

	if (tnc2->param[37] && c == '\n')	/* LFIGNORE */
	    return TL_INCOMPLETE;

	if (tnc2->param[53] && c == tnc2->param[53]) { /* PASS character */
	    tnc2->passc = 1;			/* set a flag */
	    return TL_INCOMPLETE;
	}

	if (tnc2->param[57] && c == tnc2->param[57]) { /* REDISPLAY character */
	    tnc2_mesg(tnc2,tnc2_bslcr);

	    if (tnc2->mode == TM_CMD)		/* dirty, but... */
		tnc2_mesg(tnc2,tnc2_prompt);

	    tnc2->linebuf[tnc2->linepos] = '\0';
	    tnc2_mesg(tnc2,tnc2->linebuf);

	    return TL_INCOMPLETE;
	}

	if (tnc2->mode != TM_CMD && c == tnc2->param[61]) { /* SENDPAC character */
	    if (tnc2->param[16])		/* CR ON? */
		tnc2->linebuf[tnc2->linepos++] = c;

	    if (tnc2->param[36] && c == '\r')	/* LFADD */
		tnc2->linebuf[tnc2->linepos++] = '\n';

	    tnc2->linebuf[tnc2->linepos] = '\0';

	    if (tnc2->param[27] && tnc2->param[16]) /* ECHO ON, CR ON? */
		tnc2_mesg(tnc2,tnc2->linebuf + tnc2->linepos - 1);

	    return TL_PACKET;
	}

	if ((tnc2->param[62] && c == tnc2->param[62]) || /* START */
	    (tnc2->param[63] && c == tnc2->param[63])) /* STOP */
	    return TL_INCOMPLETE;		/* ignore them */

	tnc2->linebuf[tnc2->linepos++] = c;	/* store it */
	tnc2->linebuf[tnc2->linepos] = '\0';

	if (tnc2->param[27])			/* ECHO ON */
	    tnc2_mesg(tnc2,tnc2->linebuf + tnc2->linepos - 1); /* echo it */

	if (c == '\r') {			/* CR is end of line */
	    if (tnc2->mode == TM_CMD)
		return TL_COMPLETE;
	    else
		if (tnc2->param[36])		/* LFADD */
		    tnc2->linebuf[tnc2->linepos++] = '\n';
	}

checkbuf:
	if (tnc2->linepos >= sizeof(tnc2->linebuf) - 3) /* buffer full */
	    return TL_COMPLETE;

	return TL_INCOMPLETE;			/* await more characters */
}

/* get a word from the command line and convert to uppercase */
static char *
getword (cpp)
register char **cpp;

{
    register char c;
    char *rv;

    if (*cpp == NULLCHAR)			/* already at end? */
	return NULLCHAR;

    while ((c = **cpp) == ' ' || c == '\t')	/* skip leading whitespace */
	(*cpp)++;

    if (c == '\0' || c == '\r')			/* now at end of line? */
	return NULLCHAR;

    rv = *cpp;

    while ((c = **cpp) != '\0' && c != '\r' &&	/* collect it */
	   c != ' ' && c != '\t' && c != ',') {
	if (islower(c))
	    **cpp = toupper(c);

	(*cpp)++;
    }

    *(*cpp)++ = '\0';				/* terminate word */

    if (c == '\0' || c == '\r')			/* now at end? */
	*cpp = NULLCHAR;
    else
	if (c == ',')				/* skip comma */
	    (*cpp)++;

    return rv;
}

/* return date and time in a string */
char *
tnc2_daytime (tnc2,tloc)
struct tnc *tnc2;
time_t tloc;

{
    static char dt_string[20];			/* static area for result */
    struct tm *ltime;

    ltime = localtime(&tloc);			/* convert time to tm_t */

    if (tnc2->param[23])			/* DAYUSA */
	sprintf(dt_string,"%02d/%02d/%02d %02d:%02d:%02d",
			  ltime->tm_mon + 1,ltime->tm_mday,ltime->tm_year,
			  ltime->tm_hour,ltime->tm_min,ltime->tm_sec);
    else
	sprintf(dt_string,"%02d-%02d-%02d %02d:%02d:%02d",
			  ltime->tm_mday,ltime->tm_mon + 1,ltime->tm_year,
			  ltime->tm_hour,ltime->tm_min,ltime->tm_sec);

    return dt_string;
}

/* show parameter value, with optional "was" */
static void
showparm (tnc2,tcmd,was)
register struct tnc *tnc2;
struct tnccommand *tcmd;
char was;

{
    char buf[40],*p;

    sprintf(buf,"%-9s%s",tcmd->name,was? tnc2_was : "");
    p = buf + strlen(buf);

    switch (tcmd->type)
    {
    case TC_ON_OFF:
	strcpy(p,tnc2->param[tcmd->paramnum]? "ON" : "OFF");
	break;

    case TC_DECVAL:
	sprintf(p,"%d",uchar(tnc2->param[tcmd->paramnum]));
	break;

    case TC_HEXVAL:
	sprintf(p,"$%02x",uchar(tnc2->param[tcmd->paramnum]));
	while (*++p)
	    if (islower(*p))
		*p = toupper(*p);
	break;

    case TC_TEXT:
	tnc2_mesg(tnc2,buf);
	tnc2_mesg(tnc2,(tnc2->text[tcmd->paramnum] != NULLCHAR)?
				tnc2->text[tcmd->paramnum] : tnc2_cr);
	return;

    case TC_TIMER:
	sprintf(p,"%s %d",tnc2->every[tcmd->paramnum]? "EVERY" : "AFTER",
		(int) (TICK2MS(tnc2->timer[tcmd->paramnum].start) / (long) tcmd->func));
	break;

    case TC_MISC:
	switch (tcmd->paramnum)
	{
	case 0:					/* CONMODE */
	    strcat(p,(tnc2->conmode == TM_CONV)? "CONVERSE" : "TRANS");
	    break;

	case 1:					/* MFILTER */
	    break;
	}
    }

    strcat(p,tnc2_cr);
    tnc2_mesg(tnc2,buf);
}

/* execute command in the linebuffer */
static void
tnc2_exec (tnc2)
register struct tnc *tnc2;

{
    register char *word;
    register struct tnccommand *tcmd = tnccommands;
    char *p = tnc2->linebuf;
    register int paramnum;
    int len,v;

    if ((word = getword(&p)) == NULLCHAR)	/* get keyword */
	return;					/* empty line, ignore it */

    if ((len = strlen(word)) == 0)		/* take keyword len */
	goto eh;				/* garbage, print ?EH */

    while (tcmd->name[0] != '\0') {		/* check the command names */
	if (!strncmp(tcmd->name,word,len)) {
	    paramnum = tcmd->paramnum;		/* have it at easy access */

	    switch (tcmd->type)
	    {
	    case TC_EXEC:			/* executable command */
	    case TC_MISC:			/* misc setting command */
		if (tcmd->func != NULLVFP)
		    (*tcmd->func)(tnc2,p);

		return;

	    case TC_ON_OFF:			/* On/Off switch */
		if ((word = getword(&p)) == NULLCHAR)
		    showparm(tnc2,tcmd,0);	/* show value */
		else {
		    if (word[0] == 'Y' || (word[0] == 'O' && word[1] == 'N'))
			v = 1;
		    else
			if (word[0] == 'N' || (word[0] == 'O' && word[1] == 'F'))
			    v = 0;
			else {
			    tnc2_mesg(tnc2,tnc2_bad);
			    return;
			}

		    showparm(tnc2,tcmd,1);	/* show old value */
		    tnc2->param[paramnum] = v;	/* set new */
		    tnc2_flow(tnc2,2 - tnc2->param[72]); /* flowctrl based on XFLOW */
		}

		return;

	    case TC_DECVAL:			/* decimal value */
	    case TC_HEXVAL:			/* hexadecimal value */
		if ((word = getword(&p)) == NULLCHAR)
		    showparm(tnc2,tcmd,0);	/* show value */
		else {
		    showparm(tnc2,tcmd,1);	/* show old value */

		    if (*word == '$')		/* new in HEX? */
			tnc2->param[paramnum] = htoi(++word);
		    else
			tnc2->param[paramnum] = atoi(word);
		}

		return;

	    case TC_TEXT:			/* a text */
		if (p != NULLCHAR) {
		    while (*p == ' ' || *p == '\t') /* skip whitespace */
			p++;

		    if (*p == '\0' || *p == '\r')
			p = NULLCHAR;
		}

		if (p == NULLCHAR)
		    showparm(tnc2,tcmd,0);	/* show value */
		else {
		    showparm(tnc2,tcmd,1);	/* show old value */

		    if (tnc2->text[paramnum] != NULLCHAR)
			free(tnc2->text[paramnum]);

		    if ((p[0] == '%' || p[0] == '&') &&
			(p[1] == '\0' || p[1] == '\r'))
			tnc2->text[paramnum] = NULLCHAR;
		    else
			if ((tnc2->text[paramnum] = malloc(strlen(p) + 1)) != NULLCHAR)
			    strcpy(tnc2->text[paramnum],p);
		}
		return;

	    case TC_TIMER:			/* Every/After timer */
		if ((word = getword(&p)) == NULLCHAR)
		    showparm(tnc2,tcmd,0);	/* show value */
		else {
		    if (word[0] == 'E')
			v = 1;
		    else
			if (word[0] == 'A')
			    v = 0;
			else {
			    tnc2_mesg(tnc2,tnc2_bad);
			    return;
			}

		    if ((word = getword(&p)) == NULLCHAR) {
			tnc2_mesg(tnc2,tnc2_notenough);
			return;
		    }

		    showparm(tnc2,tcmd,1);	/* show old value */
		    tnc2->every[paramnum] = v;	/* set new */
		    stop_timer(&tnc2->timer[paramnum]);
		    if ((tnc2->timer[paramnum].start = MS2TICK((long) tcmd->func) * atoi(word)) != 0)
			start_timer(&tnc2->timer[paramnum]);
		}

		return;
	    }
	}

	tcmd++;					/* try to match next cmd */
    }

eh:
    tnc2_mesg(tnc2,"?EH\r");			/* command not found */
}

/* send any data present in the linebuffer */
static void
tnc2_send (tnc2)
register struct tnc *tnc2;

{
    switch (tnc2->mode)
    {
    case TM_CMD:
	return;					/* command mode, don't send */

    case TM_TRANS:				/* trans mode, add cmdchars */
	if (tnc2->passc) {			/* buffered cmdchars? */
	    if (run_timer(&tnc2->timer[2])) {	/* still timing? */
		start_timer(&tnc2->timer[1]);	/* then restart PACTIME */
		return;				/* and try again later */
	    }
	    while (tnc2->passc) {
		tnc2->linebuf[tnc2->linepos++] = tnc2->param[18]; /* add them */
		tnc2->passc--;
	    }
	}
	break;
    }

    if (tnc2->linepos != 0) {
	if (tnc2->axp != NULLAX25)		/* connection? */
	    sendp_ax25(tnc2->axp,qdata(tnc2->linebuf,tnc2->linepos),
		       PID_FIRST|PID_LAST|PID_NO_L3);
	else
	    if (tnc2->param[78])		/* TXUIFRAM */
		sendui_ax25(tnc2->interface,&tnc2->unproto,
			    qdata(tnc2->linebuf,tnc2->linepos),
			    PID_FIRST|PID_LAST|PID_NO_L3);

	tnc2->linepos = 0;			/* clear the buffer */
    }

    if (tnc2->every[1] &&			/* PACTIME EVERY ... */
	!run_timer(&tnc2->timer[1]) &&
	(tnc2->mode == TM_TRANS || tnc2->param[15])) /* TRANS or CPACTIME */
	start_timer(&tnc2->timer[1]);
}

/* go to command mode */
static void tnc2_cmd (tnc2)
register struct tnc *tnc2;

{
    tnc2_flow(tnc2,2 - tnc2->param[72]);	/* flowctrl based on XFLOW */
    tnc2_mesg(tnc2,tnc2_cr);
    tnc2_mesg(tnc2,tnc2_prompt);
    tnc2->mode = TM_CMD;
    tnc2->linepos = 0;
    tnc2->timer[2].func = NULLVFP;		/* no CMDTIME func */
    tnc2->passc = 0;
}

/* this routine gets called by main() each loop, to process incoming data */
/* for the emulated TNC2 */
void
tnc2_serv (tnc2)
register struct tnc *tnc2;

{
    int c;
    struct mbuf *bp,*slip_decode();
    char gotcha = 0;

    /* check if we should block input (packet queue too long) if not, */
    /* read characters from client and process them		       */

    while ((tnc2->mode == TM_CMD || tnc2->axp == NULLAX25 || len_q(tnc2->axp->txq) <= tnc2->axp->maxframe) &&
	   (c = tnc2_ichar(tnc2)) >= 0)
    {
	switch (tnc2->mode)
	{
	case TM_CMD:				/* command mode */
	    if (c == TCH_BREAK)			/* a BREAK received? */
		break;				/* ignore it */

	    switch (tnc2_line(tnc2,c & 0x7f))	/* edit an input line */
	    {
	    case TL_COMPLETE:			/* done entering command */
		tnc2_exec(tnc2);		/* execute command */
		tnc2->linepos = 0;		/* clear the buffer */
		if (tnc2->mode != TM_CMD)	/* changed mode? */
		    break;

	    case TL_CANCEL:			/* cancelled */
		tnc2_mesg(tnc2,tnc2_prompt);	/* new prompt */
		break;
	    }
	    break;

	case TM_CONV:				/* converse mode */
	    if (c == TCH_BREAK)			/* a BREAK received? */
		c = tnc2->param[18];		/* make command-mode escape */

	    if (!tnc2->param[0])		/* 8BITCONV OFF? */
		c &= ~0x80;			/* strip high bit */

	    switch (tnc2_line(tnc2,c))		/* edit an input line */
	    {
	    case TL_COMPLETE:			/* entered a line */
	    case TL_PACKET:			/* entered a packet */
		tnc2_send(tnc2);		/* send it */
		break;

	    case TL_COMMAND:			/* to command mode */
		tnc2_cmd(tnc2);
		break;
	    }
	    break;

	case TM_TRANS:				/* transparent mode */
	    if (tnc2->param[67] &&		/* TRFLOW */
		((tnc2->param[62] && c == tnc2->param[62]) || /* START */
		 (tnc2->param[63] && c == tnc2->param[63]))) /* STOP */
		continue;			/* ignore them */

	    if (c == TCH_BREAK) {		/* a BREAK? */
		tnc2_cmd(tnc2);			/* to command mode */
		break;
	    }

	    if (tnc2->param[18] &&		/* command char enabled? */
		c == uchar(tnc2->param[18]) &&	/* command-mode escape? */
		tnc2->passc < 3 &&		/* less than 3 before? */
		/* check if (within cmdtime && already in sequence) ||
			    (after cmdtime && first in sequence) */
		((run_timer(&tnc2->timer[2]) | gotcha) ^ !tnc2->passc))
	    {
		if (++tnc2->passc == 3){	/* 3rd command-mode char? */
		    tnc2->timer[2].func = tnc2_cmd; /* goto cmdmode after CMDTIME */
		    tnc2->timer[2].arg = (char *) tnc2;
		}
		break;
	    }
	    else
	    {
		while (tnc2->passc) {		/* another, add cmdmode chars */
		    tnc2->linebuf[tnc2->linepos++] = tnc2->param[18];
		    tnc2->passc--;
		}
		tnc2->timer[2].func = NULLVFP;
	    }

	    tnc2->linebuf[tnc2->linepos++] = c;

	    if (tnc2->linepos >= 256 || tnc2->linepos >= uchar(tnc2->param[51]))
		tnc2_send(tnc2);		/* send complete packets */

	    break;

	case TM_KISS:				/* KISS mode */
	    if (c == TCH_BREAK)			/* a BREAK? */
		break;				/* ignore it */

	    if ((bp = slip_decode(&tnc2->slip,c)) != NULLBUF) { /* decode it */
		switch (uchar(pullchar(&bp)))
		{
		case KISS_DATA:
		    if (bp != NULLBUF) {
			if(tnc2->interface->forw != NULLIF)
			    (*tnc2->interface->forw->raw)(tnc2->interface->forw,bp);
			else
			    (*tnc2->interface->raw)(tnc2->interface,bp);

			bp = NULLBUF;
		    }
		    break;

		case KISS_SPEC:			/* TNC-specific ctrl */
		    tnc2->param[77] = pullchar(&bp); /* set KISSRX */
		    break;

		case KISS_QUIT:
		    tnc2->param[76] = 0;	/* KISS OFF */
		    tnc2->mode = TM_CMD;
		    dotnrestart(tnc2);
		    tnc2_mesg(tnc2,tnc2_prompt);
		    break;
		}

		free_p(bp);
	    }
	    break;
	}

	gotcha = 1;
    }

    if (tnc2->axp != NULLAX25)			/* connection? */
	tnc2_recv(tnc2->axp,0);			/* flush receive queue */

    if (gotcha) {
	if (tnc2->mode == TM_TRANS) {
	    tnc2->timer[2].start = SEC2TICK(uchar(tnc2->param[12])); /* CMDTIME */
	    start_timer(&tnc2->timer[2]);
	}

	if ((tnc2->linepos != 0 || tnc2->passc != 0) && /* something buffered */
	    (tnc2->mode == TM_TRANS ||
	     (tnc2->mode == TM_CONV && tnc2->param[15])) && /* CPACTIME */
	    (!tnc2->every[1] || !run_timer(&tnc2->timer[1])))
	    start_timer(&tnc2->timer[1]);	/* start PACTIME timer */
    }
}

/* find the tnc controlblock for a connection */
static struct tnc *
find_tnc (axp)
register struct ax25_cb *axp;

{
    register struct tnc *tnc2;
    register struct tnc *rv = NULLTNC;

    for (tnc2 = tnc2s; tnc2 != NULLTNC; tnc2 = tnc2->next) {
	if (tnc2->interface == axp->interface &&
	    tnc2->axcall == (struct ax25_call *) axp->user) {
	    if (tnc2->axp == axp)
		return tnc2;			/* this one is connected */

	    if (tnc2->axp == NULLAX25)
		rv = tnc2;			/* this one is free */
	}
    }

    return rv;
}

/* state-change upcall handler for TNC2 */
void
tnc2_state(axp,old,new,msg)
register struct ax25_cb *axp;
int old,new,msg;

{
    register struct tnc *tnc2;
    struct mbuf *bp;
    char tmp[100];
    extern void disc_ax25();

    switch (new)				/* check new status */
    {
    case CONNECTED:
	if ((tnc2 = find_tnc(axp)) == NULLTNC) {
	    axp->state = DISCONNECTED;		/* refuse it, send DM */

	    /* send *** connect request to TNC's that could accept the connect */
	    for (tnc2 = tnc2s; tnc2 != NULLTNC; tnc2 = tnc2->next) {
		if (tnc2->interface == axp->interface &&
		    tnc2->axcall == (struct ax25_call *) axp->user &&
		    tnc2->mode < TM_TRANS &&
		    !tnc2->param[55])		/* BBSMSGS */
		{
		    tnc2_mesg(tnc2,tnc2_conrq);
		    pdax25(tmp,&axp->addr);
		    tnc2_mesg(tnc2,tmp);
		    tnc2_mesg(tnc2,tnc2_cr);
		}
	    }

	    return;
	}

	if (tnc2->axp == NULLAX25) {
	    /* new, incoming connection */
	    if (!tnc2->param[21]) {		/* CONOK ON? */
		axp->state = DISCONNECTED;	/* no, refuse it, send DM */

		if (tnc2->mode < TM_TRANS &&	/* *** connect request when allowed */
		    !tnc2->param[55]) {		/* BBSMSGS */
		    tnc2_mesg(tnc2,tnc2_conrq);
		    pdax25(tmp,&axp->addr);
		    tnc2_mesg(tnc2,tmp);
		    tnc2_mesg(tnc2,tnc2_cr);
		}

		return;
	    }

	    tnc2->axp = axp;
	    tnc2->status |= TS_DCD;		/* set DCD on */

	    if (tnc2->param[13] && tnc2->text[1] != NULLCHAR) {/* CMSG, CTEXT */
		if ((bp = alloc_mbuf(1)) != NULLBUF) {
		    *bp->data = PID_FIRST|PID_LAST|PID_NO_L3;
		    bp->cnt = 1;
		    bp->next = qstring(tnc2->text[1]);
		    enqueue(&axp->txq,bp);
		}
	    }

	    if (tnc2->param[14])		/* CMSGDISC */
		axp->r_upcall = axp->t_upcall = disc_ax25;
	    else
		axp->r_upcall = tnc2_recv;

	    if (tnc2->param[8])			/* CBELL */
		tnc2_ochar(tnc2,'\007');
	}

	if (tnc2->mode == TM_CMD && tnc2->linepos == 0 &&
	    !tnc2->param[47] && !tnc2->param[46]) /* NOMODE OFF, NEWMODE OFF */
	    tnc2->mode = tnc2->conmode;

	break;

    case DISCONNECTED:
	if ((tnc2 = find_tnc(axp)) == NULLTNC)
	    return;

	tnc2->axp = NULLAX25;
	break;
    }

    if (msg != LAPBNOMS && msg != LAPBDISC &&
	(tnc2->mode < TM_TRANS || msg < LAPBRESF)) {
	sprintf(tmp,"*** %s: ",ax25mesgs[msg]);
	tnc2_mesg(tnc2,tmp);
	pdax25(tmp,&axp->addr);
	tnc2_mesg(tnc2,tmp);

	if (msg == LAPBCONN && tnc2->param[22]){	/* CONSTAMP */
	    time_t tloc;

	    time(&tloc);
	    sprintf(tmp," [%s]",tnc2_daytime(tnc2,tloc));
	    tnc2_mesg(tnc2,tmp);
	}
	tnc2_mesg(tnc2,tnc2_cr);
    }

    if (new == DISCONNECTED) {
	tnc2_mesg(tnc2,"*** DISCONNECTED\r");

	tnc2->status &= ~TS_DCD;			/* DCD off */

	if (tnc2->mode != TM_CMD &&
	    !tnc2->param[47] && tnc2->param[46]){ /* NOMODE OFF, NEWMODE ON */
	    tnc2->mode = TM_CMD;
	    tnc2->linepos = 0;
	    tnc2_flow(tnc2,2 - tnc2->param[72]);	/* flowctrl based on XFLOW */
	    tnc2_mesg(tnc2,tnc2_prompt);
	}
    }
}

/* receive upcall handler for TNC2 */
static void
tnc2_recv (axp,cnt)
struct ax25_cb *axp;
int16 cnt;

{
    register struct tnc *tnc2;
    struct mbuf *bp,*recv_ax25();
    unsigned char c;

    if (axp->rxq == NULLBUF)			/* nothing queued, */
	return;					/* nothing to do */

    if ((tnc2 = find_tnc(axp)) == NULLTNC) {
	disc_ax25(axp);
	return;
    }

    switch (tnc2->mode)
    {
    case TM_CMD:				/* command mode */
	return;

    case TM_CONV:				/* converse */
	if (tnc2->param[29] && tnc2->linepos != 0) /* FLOW ON */
	    return;

    case TM_TRANS:				/* transparent */
	if (!tnc2_ochar(tnc2,-1))		/* test for blocked queue */
	    return;

	if (tnc2->param[59] && (cnt == 0 || cnt > 255))
	    cnt = 255;				/* upper limit for RXBLOCK fmt */

	if ((bp = recv_ax25(axp,cnt)) == NULLBUF)
	    return;

	if (tnc2->param[59]) {			/* RXBLOCK */
	    tnc2_ochar(tnc2,0xff);
	    cnt = len_mbuf(bp);			/* bytes in received packet */
	    tnc2_ochar(tnc2,0xf0 | (cnt >> 4));
	    tnc2_ochar(tnc2,0xf0 | cnt);
	    tnc2_ochar(tnc2,PID_FIRST|PID_LAST|PID_NO_L3);
	}

	while (pullup(&bp,&c,1) == 1) {
	    if (!tnc2_ochar(tnc2,c))
		break;

	    if (c == '\r' && tnc2->mode == TM_CONV && tnc2->param[2]) /* AUTOLF */
		if (!tnc2_ochar(tnc2,'\n'))
		    break;
	}

	free_p(bp);
	break;
    }

#ifdef BEACON
    if (!tnc2->every[0])			/* BEACON AFTER */
	start_timer(&tnc2->timer[0]);
#endif
}

/* handle incoming packets in KISS mode */
/* when a TNC2 on this interface is in KISS mode, the packet is duplicated */
/* and sent to the client.  return value is 0 if no TNC found in KISS mode */
int
tnc2_kissrcv (interface,hdr,bp,kissrx)
struct interface *interface;
struct ax25 *hdr;
struct mbuf *bp;
int kissrx;

{
    register struct tnc *tnc2;
    struct mbuf *hbp,*dbp,*htonax25(),*slip_encode();
    int rv = 0;
    unsigned char c;

    for (tnc2 = tnc2s; tnc2 != NULLTNC; tnc2 = tnc2->next) {
	if (tnc2->interface == interface && tnc2->mode == TM_KISS) {
	    /* only receive packets at or above KISSRX level */
	    if (tnc2->param[77] >= kissrx) {		/* KISSRX */
		if (tnc2_ochar(tnc2,-1) &&		/* test for blocked queue */
		    dup_p(&dbp,bp,0,(unsigned int) MAXINT16) != 0) {
		    if ((hbp = htonax25(hdr,dbp)) != NULLBUF) {
			/* Put type field for KISS TNC on front */
			if ((dbp = pushdown(hbp,1)) != NULLBUF) {
			    hbp = NULLBUF;
			    dbp->data[0] = KISS_DATA;
			    dbp = slip_encode(dbp);
			    while (pullup(&dbp,&c,1) == 1)
				if (!tnc2_ochar(tnc2,c))
				    break;
			}
		    }

		    /* when out of memory or fifo full, free everything */
		    free_p(dbp);
		    free_p(hbp);
		}
	    }

	    rv = 1;
	}
    }

    return rv;
}

#ifdef BEACON
/* send a beacon (arghh!!) */
static void
tnc2_beacon (tnc2)
register struct tnc *tnc2;

{
    struct ax25 beacon;

    if (tnc2->text[0] != NULLCHAR) {		/* BTEXT */
	memcpy(&beacon,&tnc2->unproto,sizeof(struct ax25));
	setcall(&beacon.dest,"BEACON");		/* send it to BEACON */

	sendui_ax25(tnc2->interface,&beacon,qstring(tnc2->text[0]),
		    PID_FIRST|PID_LAST|PID_NO_L3);
    }

    if (tnc2->every[0])				/* BEACON EVERY */
	start_timer(&tnc2->timer[0]);
}
#endif

static void
dotnconmode (tnc2,args)
struct tnc *tnc2;
char *args;

{
    char *word;

    tnc2_mesg(tnc2,"CONMODE  ");
    if ((word = getword(&args)) != NULLCHAR)
	tnc2_mesg(tnc2,tnc2_was);

    tnc2_mesg(tnc2,(tnc2->conmode == TM_CONV)? "CONVERSE\r" : "TRANS\r");

    if (word != NULLCHAR)
	tnc2->conmode = (*word != 'T')? TM_CONV : TM_TRANS;
}

/* get callsign and digi string from commandline */
static int
tnc2_path (tnc2,addr,args)
register struct tnc *tnc2;
register struct ax25 *addr;			/* where to store it */
char **args;					/* ptr to command tail ptr */

{
    register char *word;

    if ((word = getword(args)) == NULLCHAR)
	return 1;				/* nothing, return 1 */

    memset(addr,0,sizeof(*addr));

    if(setcall(&addr->dest,word) < 0){
	tnc2_mesg(tnc2,tnc2_call);
	return -1;
    }

    ASSIGN(addr->source,tnc2->axcall->addr);	/* Set sourcecall = our own call */

    if ((word = getword(args)) != NULLCHAR) {	/* Set digipeater path */
	if (strncmp(word,"VIA",strlen(word))) {
	    tnc2_mesg(tnc2,"?VIA\r");
	    return -1;
	}

	while ((word = getword(args)) != NULLCHAR) {
	    if (++(addr->ndigis) > MAXDIGIS){
		tnc2_mesg(tnc2,tnc2_toomany);
		return -1;
	    }

	    if(setcall(&addr->digis[addr->ndigis - 1],word) < 0){
		tnc2_mesg(tnc2,tnc2_call);
		return -1;
	    }
	}
    }

    return 0;					/* all ok, return 0 */
}

static void
dotnconnect (tnc2,args)
struct tnc *tnc2;
char *args;

{
    struct ax25 addr;
    struct ax25_cb *open_ax25();
    int rv;
    extern int16 axwindow;

    if ((rv = tnc2_path(tnc2,&addr,&args)) < 0)
	return;

    if (rv != 0 || tnc2->axp != NULLAX25) {
	dotncstatus(tnc2);
	return;
    }

    if ((tnc2->axp = open_ax25(&addr,axwindow,tnc2_recv,NULLFP,tnc2_state,
			       tnc2->interface,(char *) tnc2->axcall)) == NULLAX25) {
	tnc2_mesg(tnc2,"*** FAILED\r");
	return;
    }

    if (!tnc2->param[47] && tnc2->param[46])	/* NOMODE OFF, NEWMODE ON */
	if ((tnc2->mode = tnc2->conmode) == TM_TRANS)
	    dotntrans(tnc2);
}

static void
dotnconverse (tnc2)
struct tnc *tnc2;

{
    tnc2_flow(tnc2,2 - tnc2->param[72]);	/* flowctrl based on XFLOW */
    tnc2->mode = TM_CONV;
}

static void
dotncstatus (tnc2)
register struct tnc *tnc2;

{
    int state;
    char tmp[100];

    tnc2_mesg(tnc2,"Link state is: ");

    if (tnc2->axp == NULLAX25)
	state = DISCONNECTED;
    else
	state = tnc2->axp->state;

    switch (state)
    {
    case DISCONNECTED:
	tnc2_mesg(tnc2,"DISCONNECTED");
	break;

    case SETUP:
	tnc2_mesg(tnc2,"CONNECT in progress");
	break;

    case CONNECTED:
	tnc2_mesg(tnc2,"CONNECTED to ");
	pdax25(tmp,&tnc2->axp->addr);
	tnc2_mesg(tnc2,tmp);
	break;

    case DISCPENDING:
	tnc2_mesg(tnc2,"DISCONNECT in progress");
	break;

    case FRAMEREJECT:
	tnc2_mesg(tnc2,"FRMR in progress");
	break;
    }

    tnc2_mesg(tnc2,tnc2_cr);
}

static void
dotndaytime (tnc2,args)
struct tnc *tnc2;
char *args;

{
    time_t tloc;

    if (getword(&args) != NULLCHAR)		/* attempt to set clock */
	return;					/* ignore it */

    time(&tloc);				/* read current time */
    tnc2_mesg(tnc2,tnc2_daytime(tnc2,tloc));	/* show current time */
    tnc2_mesg(tnc2,tnc2_cr);
}

static void
dotndiscon (tnc2)
struct tnc *tnc2;

{
    if (tnc2->axp == NULLAX25)
	dotncstatus(tnc2);
    else
	disc_ax25(tnc2->axp);
}

static void
dotndisplay (tnc2)
struct tnc *tnc2;

{
    register struct tnccommand *tcmd;

    for (tcmd = tnccommands; tcmd->name[0] != '\0'; tcmd++)
	if (tcmd->type != TC_EXEC)
	    showparm(tnc2,tcmd,0);
	else
	    if (tcmd->func == dotnmycall)
		dotnmycall(tnc2);
}

static void
dotnmycall (tnc2)
struct tnc *tnc2;

{
    char buf[10];

    tnc2_mesg(tnc2,"MYCALL   ");
    pax25(buf,&tnc2->axcall->addr);
    tnc2_mesg(tnc2,buf);
    tnc2_mesg(tnc2,tnc2_cr);
}

void dotnreset (tnc2)
struct tnc *tnc2;

{
    int i;

    for (i = 0; i < TNCTIMERS; i++)		/* stop timers */
	stop_timer(&tnc2->timer[i]);

    tnc2->conmode = TM_CONV;			/* CONMODE CONVERSE */
    memcpy(tnc2->param,def_params,sizeof(tnc2->param));
    for (i = 0; i < TNCTEXTS; i++) {		/* all texts empty */
	if (tnc2->text[i] != NULLCHAR)
	    free(tnc2->text[i]);
	tnc2->text[i] = NULLCHAR;
    }
    tnc2->timer[0].start = 0;			/* BEACON EVERY 0 */
#ifdef BEACON
    tnc2->timer[0].func = tnc2_beacon;
    tnc2->timer[0].arg = (char *) tnc2;
#endif
    tnc2->every[0] = 1;
    tnc2->timer[1].start = SEC2TICK(1);		/* PACTIME AFTER 10 */
    tnc2->timer[1].func = tnc2_send;
    tnc2->timer[1].arg = (char *) tnc2;
    tnc2->every[1] = 0;
    memset(&tnc2->unproto,0,sizeof(tnc2->unproto));
    setcall(&tnc2->unproto.dest,"QST");		/* UNPROTO QST */
    ASSIGN(tnc2->unproto.source,tnc2->axcall->addr);

    dotnrestart(tnc2);
}

static void dotnrestart (tnc2)
struct tnc *tnc2;

{
    int i;
    extern char version[];

    for (i = 0; i < TNCTIMERS; i++)		/* stop timers */
	stop_timer(&tnc2->timer[i]);

    if (tnc2->axp != NULLAX25) {
	del_ax25(tnc2->axp);
	tnc2->axp = NULLAX25;
    }

    if (tnc2->param[76]) {			/* KISS */
	tnc2_flow(tnc2,0);			/* no flowctrl at all */
	tnc2->mode = TM_KISS;

	tnc2_mesg(tnc2,"[KISS]\300");		/* a hint for the user */
	return;
    }

    tnc2_flow(tnc2,2 - tnc2->param[72]);	/* flowctrl based on XFLOW */

    tnc2_mesg(tnc2,"\r\rAX.25 TNC2 emulator in KA9Q NET\rWritten by R.E. Janssen, PE1CHL\rRelease ");
    tnc2_mesg(tnc2,version);
    tnc2_mesg(tnc2,"\rInterface: ");
    tnc2_mesg(tnc2,tnc2->interface->name);
    tnc2_mesg(tnc2,tnc2_cr);
}

static void
dotntrans (tnc2)
struct tnc *tnc2;

{
    tnc2_flow(tnc2,2 - (tnc2->param[70] & tnc2->param[72])); /* TXFLOW and XFLOW */
    tnc2->mode = TM_TRANS;
}

static void
dotnunproto (tnc2,args)
struct tnc *tnc2;
char *args;

{
    struct ax25 addr;
    int rv;
    char buf[100];

    if ((rv = tnc2_path(tnc2,&addr,&args)) < 0)
	return;

    tnc2_mesg(tnc2,"UNPROTO  ");
    if (!rv)
	tnc2_mesg(tnc2,tnc2_was);

    pdax25(buf,&tnc2->unproto);
    tnc2_mesg(tnc2,buf);
    tnc2_mesg(tnc2,tnc2_cr);

    if (!rv)
	memcpy(&tnc2->unproto,&addr,sizeof(addr));
}
