/*
**  Parse and execute dialing script.  Ancient history says this was
**  based on MMDF, but it wouldn't be recognized now...
**  Copyright (c) 1991 Bolt Beranek and Newman, Inc.
**  All rights reserved.
**
**  Redistribution and use in source and binary forms are permitted
**  provided that: (1) source distributions retain this entire copyright
**  notice and comment, and (2) distributions including binaries display
**  the following acknowledgement:  ``This product includes software
**  developed by Bolt Beranek and Newman, Inc. and CREN/CSNET'' in the
**  documentation or other materials provided with the distribution and in
**  all advertising materials mentioning features or use of this software.
**  Neither the name of Bolt Beranek and Newman nor CREN/CSNET may be used
**  to endorse or promote products derived from this software without
**  specific prior written permission.
**
**  THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
**  WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
**  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
*/
/*
 * Copyright (c) 1992 Purdue University
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms are permitted
 * provided that the above copyright notice and this paragraph are
 * duplicated in all such forms and that any documentation,
 * advertising materials, and other materials related to such
 * distribution and use acknowledge that the software was developed
 * by Purdue University.  The name of the University may not be used
 * to endorse or promote products derived * from this software without
 * specific prior written permission.
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
 * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 *
 * Note: this copyright applies to portions of this software developed
 * at Purdue beyond the software covered by the original copyright.
 */
#include <stdio.h>
#include <signal.h>
#include <errno.h>
#include <setjmp.h>
#include <fcntl.h>
#include <ctype.h>
#include <errno.h>
#include <malloc.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <sys/file.h>
#include <sys/param.h>
#include <sys/stream.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <sys/socket.h>
#include <net/if.h>

#include "dp.h"
#include "dpio.h"
#include "dpd.h"

    /* Various constants. */
#define MAXFILES	15
#define MAXLINE		256
#define MAXFIELDS	32	/* Maximum number of parameters		*/
#define	MAXRECVSTR	MAXFIELDS - 2	/* Maximum number of recieve strings */
#define MATCHLEN	128	/* Size of text to match in recv	*/
#define XMITWAIT	60	/* How long to wait on a write		*/
#define REPBUFSIZE	256	/* Size of replay buffer		*/

#define SC_SLEEP	0377	/* Char for \x, sleep			*/
#define SC_BREAK	0376	/* Char for \#, break			*/

    /* Command codes; must not overlap with D_xxx codes */
#define S_ABORT		101	/* Abort the script			*/
#define S_ALT		102	/* Alternate				*/
#define S_COMMENT	103	/* Comment				*/
#define S_GO		104	/* Start the protocol			*/
#define S_LOG		105	/* Log a message			*/
#define S_MARK		106	/* Mark the place to start a replay	*/
#define S_RECV		107	/* Watch for a string			*/
#define S_REPLAY	108	/* Reuse old input			*/
#define S_SELEND	109	/* Select end				*/
#define S_SELST		110	/* Select begin				*/
#define S_USEFILE	111	/* Read script commands from another file */
#define S_XMIT		112	/* Transmit a string			*/
#define S_EOF		113	/* EOF in file				*/

    /* Error codes return by commands */
#define D_CONTIN	 2	/* Continue with process		*/
#define D_OK		 1	/* Everything is fine			*/
#define D_NOMATCH	 0	/* Input did not match			*/
#define D_FATAL		-1	/* Fatal error				*/
#define D_NONFATAL	-2	/* Possibly-recoverable error		*/
#define D_INTRPT	-3	/* Interrupt during system call		*/

    /* Error codes returned by parsing routines */
#define ERR_NOWORD	-1
#define ERR_QUOTE	-2
#define ERR_FIELDS	-3
#define ERR_BADVAR	-4
#define ERR_TOOLONG	-5

    /* Structure that keeps track of open files. */
typedef struct _OPENFILE {
    char	*scriptname;
    FILE	*scriptfile;
    int		linenumber;
    int		nfields;
    char	*fields[MAXFIELDS];
} OPENFILE;

    /* Structure that defines a command */
typedef struct _COMMAND {
    char	*Name;
    int		Code;
    int		MinFields;
    int		MaxFields;
} COMMAND;


static char	scriptname[128];	/* Script filename		*/
static char	pushbuffer[MATCHLEN + 2]; /* Pushback buffer for matcher */
static char	*pushptr = pushbuffer;	/* Pointer into buffer		*/
static jmp_buf	timerest;
static int	linenumber;		/* Line number in script file	*/
static int	use_nfields;		/* Number of "use" fields	*/
static char	*use_fields[MAXFIELDS];	/* Current "use" fields		*/
static OPENFILE	*openfiles[MAXFILES];	/* Stack of open files		*/
static int	nopenfiles;		/* Number of open files		*/
static FILE	*scriptfile;		/* Script file			*/
static FILE	*portfile;		/* Modem file			*/
static FILE	*tranfile;		/* Transcript file descriptor	*/
static int	transtyle;		/* Transcript style		*/
static int	repcount;		/* Chars left in replay		*/
static int	repcurr;		/* Current replay char		*/
static int	repnext;		/* Where to insert next char	*/
static int	repsize;		/* Replay buffer size; constant	*/
static int	repfirst;		/* First char in replay		*/
static int	replay;			/* Doing a replay?		*/
static int	repbuff[REPBUFSIZE];	/* Replay buffer		*/
static char	WHERE[] = "dialscript";	/* Where we are, for logging	*/
static COMMAND	commands[] = {
    {	"abort",	S_ABORT,	0,	0	},
    {	"alternate",	S_ALT,		0,	0	},
    {	"go",		S_GO,		0,	0	},
    {	"log",		S_LOG,		1,	1	},
    {	"mark",		S_MARK,		0,	0	},
    {	"recv",		S_RECV,		2,	10	},
    {	"replay",	S_REPLAY,	0,	0	},
    {	"use",		S_USEFILE,	1,	10	},
    {	"xmit",		S_XMIT,		1,	1	},
    {	"{",		S_SELST,	0,	0	},
    {	"}",		S_SELEND,	0,	0	},
    { NULL }
};

extern char	*strerror();


/*
**  Report a syntax error.
*/
static void
syntax_error(problem)
    char	*problem;
{
    d_log(DLOG_GENERAL, WHERE, "Syntax error in \"%s\", line %d:\n\t%s\n",
	scriptname, linenumber, problem);
}


/*
**  Report an I/O error.
*/
static void
io_error(problem)
    char	*problem;
{
    d_log(DLOG_GENERAL, WHERE, "I/O error in \"%s\", line %d:\n\t%s\n",
	scriptname, linenumber, problem);
}


/*
**  Report a miscellaneous error with errno.
*/
static void
misc_error(format, arg)
    char	*format;
    char	*arg;
{
    char	buff[256];

    (void)sprintf(buff, "Error in \"%s\", line %d:\n\t%s, %s",
	scriptname, linenumber, format, strerror(errno));
    d_log(DLOG_GENERAL, WHERE, buff, arg);
}


/*
**  Return printable representation of char.
*/
static char *
seechar(c)
    char	c;
{
    static char	buff[8];

    if (isprint(c) || c == '\n') {
	buff[0] = c;
	buff[1] = '\0';
    }
    else
	(void)sprintf(buff, "\\%03o", c);
    return buff;
}


/*
**  Parse a C-style escape string.  Returns the length of the string
**  or -1 on error.
*/
static int
canonstring(in, out)
    register char	*in;
    register char	*out;
{
    int			count;
    char		c;
    char		*save;

    for (save = out; c = *in++; )
	if (c != '\\')
	    *out++ = c;
	else {
	    switch (c = *in++) {
	    default:	*out++ = c;		break;
	    case 'b':	*out++ = '\b';		break;
	    case 'n':	*out++ = '\n';		break;
	    case 'r':	*out++ = '\r';		break;
	    case 't':	*out++ = '\t';		break;
	    case 'x':	*out++ = SC_SLEEP;	break;
	    case '#':	*out++ = SC_BREAK;	break;
	    case '\0':
		return -1;

	    case '0': case '1': case '2': case '3':
	    case '4': case '5': case '6': case '7':
		for (c -= '0', count = 1; count < 3; count++, in++) {
		    if (*in < '0' || *in > '7')
			break;
		    c = (c << 3) | (*in - '0');
		}
		*out++ = c;
	    }
	}
    *out = '\0';
    return out - save;
}


/*
**  Snip the next work out of the buffer.
*/
static int
getword(p, start, next)
    register char	*p;
    char		**start;
    char		**next;
{
    /* Skip leading whitespace, see if there's anything left. */
    while (*p == ' ' || *p == '\t')
	p++;
    if (*p == '\0')
	return ERR_NOWORD;

    if (*p == '"') {
	for (*start = ++p; *p != '"'; p++)
	    if (*p == '\0')
		return ERR_QUOTE;
	*p++ = '\0';
    }
    else {
	for (*start = p; *p && *p != ' ' && *p != '\t'; )
	    p++;
	if (*p)
	    *p++ = '\0';
    }
    *next = p;
    return D_OK;
}


#define END	(&temp[MAXLINE - 2])


/*
**  Handle $-expansion
*/
static int
expanddollars(buff)
    char	*buff;
{
    char	temp[MAXLINE];
    char	*out;
    char	*p;
    int		i;

    for (out = temp, p = buff; *p; p++)
	if (*p != '$') {
	    if (out >= END)
		return ERR_TOOLONG;
	    *out++ = *p;
	}
	else if (*++p == '$') {
	    if (out >= END)
		return ERR_TOOLONG;
	    *out++ = *p;
	}
	else {
	    if (!isdigit(*p) || (i = *p - '0') > use_nfields)
		return ERR_BADVAR;
	    if (out + strlen(use_fields[i]) >= END)
		return ERR_TOOLONG;
	    out += strlen(strcpy(out, use_fields[i]));
	}

    *out++ = '\0';
    (void)strcpy(buff, temp);
    return D_OK;
}


/*
**  Parse a line and break it into fields.  Return the number of fields
**  or an error.
*/
static int
getfields(buff, maxfields, fields)
    char	*buff;
    int		 maxfields;
    char	*fields[];
{
    char	*p;
    char	*next;
    int		i;

    if (strchr(buff, '$') != NULL && (i = expanddollars(buff)) != D_OK)
	return i;

    for (i = 0, p = buff; i < maxfields; i++, p = next)
	switch (getword(p, &fields[i], &next)) {
	case ERR_NOWORD:
	    return i;
	case ERR_QUOTE:
	    return ERR_QUOTE;
	}
    return ERR_FIELDS;
}


/*
**  Get a line of text and parse it into a command and fields.  Return
**  the command number or an error.
*/
static int
getcommand(buff, nfields, fields)
    char		*buff;
    int			*nfields;
    char		*fields[];
{
    char		*p;
    register COMMAND	*cp;

    do {
	linenumber++;
	if (fgets(buff, MAXLINE, scriptfile) == NULL)
	    return S_EOF;
	if ((p = strchr(buff, '\n')) == NULL) {
	    syntax_error("Line too long");
	    return D_FATAL;
	}
	*p = '\0';
    } while (buff[0] == '\0' || buff[0] == '#');

    /* Get the first word. */
    switch (getword(buff, &fields[0], &p)) {
    default:
	return D_FATAL;
    case ERR_QUOTE:
	syntax_error("Missing end quote");
	return D_FATAL;
    case D_OK:
	break;
    }

    /* Find the command word. */
    for (cp = commands; cp->Name; cp++)
	if (strcmp(fields[0], cp->Name) == 0)
	    break;
    if (cp->Name == NULL) {
	syntax_error("Bad command");
	return D_FATAL;
    }

    /* Parse the rest of the line. */
    *nfields = getfields(p, MAXFIELDS - 1, &fields[1]);
    if (*nfields < 0) {
	switch (*nfields) {
	default:
	    syntax_error("Internal error in parser");
	    break;
	case ERR_FIELDS:
	    syntax_error("Too many fields");
	    break;
	case ERR_QUOTE:
	    syntax_error("Missing end quote");
	    break;
	case ERR_BADVAR:
	    syntax_error("Bad \"$\" variable");
	    break;
	case ERR_TOOLONG:
	    syntax_error("Line too long");
	    break;
	}
	return D_FATAL;
    }

    /* Make sure the right number of fields are present. */
    if (*nfields >= cp->MinFields && *nfields <= cp->MaxFields)
	return cp->Code;
    syntax_error("Wrong number of fields");
    return D_FATAL;
}


/*
**  Command dispatcher.
*/
static int
dispatch(c, nfields, fields)
    int		c;
    int		nfields;
    char	*fields[];
{
    int		i;

    switch (c) {
    default:
	syntax_error("Unknown command in dispatch");
	return D_FATAL;
    case S_MARK:
	do_mark();
	break;
    case S_REPLAY:
	do_replay();
	break;
    case S_LOG:
	d_log(DLOG_GENERAL, WHERE, "%s", fields[1]);
	break;
    case S_COMMENT:
	break;
    case S_ABORT:
	return D_FATAL;
    case S_GO:
	return D_OK;
    case S_ALT:
    case S_SELST:
    case S_SELEND:
	return c;
    case S_XMIT:
	return (i = do_xmit(fields[1])) < 0 ? i : D_CONTIN;
    case S_RECV:
	return (i = do_recv(nfields, fields)) < 0 ? i : D_CONTIN;
    case S_USEFILE:
	return do_use(fields[1], nfields, fields) < 0 ? D_FATAL : D_CONTIN;
    case S_EOF:
	/* Unexpected EOF.  Revert to previous script if possible. */
	return closescript() > 0 ? D_CONTIN : D_FATAL;
    }
    return D_CONTIN;
}


/*
**  Execute all commands in a block.
*/
static int
do_block()
{
    char	buff[MAXLINE + 2];
    char	*fields[MAXFIELDS];
    int		nfields;
    int		c;
    int		i;

    for ( ; ; ) {
	if ((c = getcommand(buff, &nfields, fields)) < 0)
	    return c;
	if ((i = dispatch(c, nfields, fields)) != D_CONTIN)
	    return i;
    }
}


/*
**  Skip forward until we hit the end of the current select block.
*/
static int
findblockend()
{
    char	buff[MAXLINE + 2];
    char	*fields[MAXFIELDS];
    int		nfields;
    int		i;

    for ( ; ; ) {
	if ((i = getcommand(buff, &nfields, fields)) < 0)
	    return i;
	switch (i) {
	case S_SELEND:
	    return S_SELEND;
	case S_SELST:
	    if ((i = findblockend()) < 0)
		return i;
	    break;
	case S_EOF:
	    /* Unexpected EOF.  Revert to previous script if possible. */
	    if (closescript() <= 0)
		return D_FATAL;
	}
    }
}


/*
**  Find next alternative in current select block.
*/
static int
findnextalt()
{
    char	buff[MAXLINE + 2];
    char	*fields[MAXFIELDS];
    int		nfields;
    int		i;

    for ( ; ; ) {
	if ((i = getcommand(buff, &nfields, fields)) < 0)
	    return i;
	switch (i) {
	case S_SELEND:
	case S_ALT:
	    return i;
	case S_SELST:
	    if ((findblockend()) < 0)
		return i;
	    break;
	case S_EOF:
	    /* Unexpected EOF.  Revert to previous script if possible. */
	    if (closescript() <= 0)
		return D_FATAL;
	}
    }
}


static int
runloop()
{
    char	buff[MAXLINE + 2];
    char	*fields[MAXFIELDS];
    int		nfields;
    int		nselect;
    int		i;

    /* Go for it. */
    for (nselect = 0; ; )
	switch (i = do_block()) {
	default:
	    if (i < 0)
		return i;
	    syntax_error("Internal error in runloop");
	    return -1;

	case D_OK:
	    return 0;
	case D_FATAL:
	    return -1;

	case D_NONFATAL:
	    /* Possibly-recoverable error.  If in a select block, try the
	     * next alternate. */
	    if (nselect <= 0 || (i = findnextalt()) < 0)
		return -1;
	    switch (i) {
	    default:
		syntax_error("Bad syntax in script");
		return -1;
	    case S_ALT:
		continue;
	    case S_SELEND:
		return -1;
	    }

	case S_SELST:
	    /* The next line had better be an alternate  */
	    if (getcommand(buff, &nfields, fields) != S_ALT) {
		syntax_error("Missing \"alternate\" after \"{\"");
		return -1;
	    }
	    nselect++;
	    continue;

	case S_SELEND:
	    /* verify that there was a select block being looked at.  If so,
	     * then the last alternate was the successful one.  Continue
	     * normally. */
	    if (nselect-- <= 0) {
		syntax_error("Bad \"}\"");
		return -1;
	    }
	    continue;

	case S_ALT:
	    /* First, make sure the context was correct  */
	    if (nselect <= 0) {
		syntax_error("Bad alternate");
		return -1;
	    }
	    /* If we get here, then an alternate within a select was
	     * completed successfully.  Move on. */
	    if ((i = findblockend()) < 0)
		return -1;
	    nselect--;
	    switch (i) {
	    default:
		syntax_error("Syntax error in alternate");
		return -1;
	    case S_SELEND:
		continue;
	    }
	}
}

static char *
copystring(p)
    char	*p;
{
    char	*new;

    if ((new = malloc((unsigned int)strlen(p) + 1)) == NULL) {
	misc_error("Malloc failed; can't copy \"%s\"", p);
	return NULL;
    }
    return strcpy(new, p);
}

char *
tr_dial(str, map)
char *str;
char *map;
{
    char *s, *m;
    unsigned int nm, n;
    if (!map || !*map)
	return str;

    nm = strlen(map) / (unsigned)2;
    for (s = str ; *s ; s++)
	for (n = nm, m = map ; n-- ; m += 2)
	    if (*s == *m) {
		*s = *(m+1);
		break;
	    }

    return str;
}

/*
**  Read lines from the script, parse them, do the actions.
*/
int
runscript(rp, fp, ms, tname, charmap)
    REMOTE	*rp;
    FILE	*fp;
    char	*ms;
    char	*tname;
    char	*charmap;
{
    int		i;
    char	*fields[MAXFIELDS];
    char	**ap;

    /* Set up globals. */
    portfile = fp;
    if (tname == NULL)
	tranfile = NULL;
    else {
	tranfile = *tname == '+' ? fopen(++tname, "a") : fopen(tname, "w");
	if (tranfile == NULL) {
	    misc_error("Can't create transcript \"%s\"", tname);
	    return -1;
	}
    }
    transtyle = rp->Transtyle;

    /*
     * Set up the modem dialing script first..
     */
    if (ms) {
	fields[0] = use_fields[0] = copystring(ms);
	use_nfields = 1;
	if (rp->Phone) {
	    fields[1] = use_fields[1] = tr_dial(copystring(rp->Phone), charmap);
	    use_nfields++;
	}
	i = do_use(ms, use_nfields, fields) < 0 ? -1 : runloop();
	while (closescript())
	    ;
	if (i < 0)
	    i = -1;
    }
    else
	i = 0;

    if (i >= 0) {
	/*
	 * If call is successful, then do the login script.
	 */
	fields[0] = use_fields[0] = copystring(rp->Script);
	use_nfields = 1;
	if (ap = rp->ScriptArgs)
	    for ( ; *ap ; ap++, use_nfields++ )
		fields[use_nfields] = use_fields[use_nfields] = copystring(*ap);

	i = do_use(rp->Script, use_nfields, fields) < 0 ? -1 : runloop();
	while (closescript())
	    ;
	if (i < 0)
	    i = -2;
    }

    if (tranfile)
	(void)fclose(tranfile);
    return i;
}


/*
**   Wait for a given string, with optional timeout.
*/
static void
scripttimeout(sig)
int sig;
{
    longjmp(timerest, 1);
}


/*
**  Write to the port, with a timeout.
*/
static int
writechar(p)
    char	*p;
{
    int		i;

    if (tranfile && transtyle != TS_LOW) {
	if (isprint(*p))
	    (void)fprintf(tranfile, "\tPUT    %c (0%03o)\n", *p, *p);
	else
	    (void)fprintf(tranfile, "\tPUT 0x%02x (0%03o)\n", *p, *p);
	(void)fflush(tranfile);
    }

    (void)signal(SIGALRM, scripttimeout);
    if (setjmp(timerest) == 0) {
	(void)alarm(XMITWAIT);
	i = write(fileno(portfile), p, 1);
	(void)alarm(0);

	/* Process the return codes. */
	if (i == 1)
	    return D_OK;
	if (i >= 0)
	    return D_NONFATAL;
	if (errno != EINTR)
	    return D_FATAL;
    }
    io_error("Write time-out");
    return D_INTRPT;
}


/*
**  Transmit a string.
*/
do_xmit(string)
    char		*string;
{
    register int	i;
    register char	*p;
    char		buff[MAXLINE];

    if (tranfile && transtyle != TS_LOW) {
	(void)fprintf(tranfile, "XMIT %s\n", string);
	(void)fflush(tranfile);
    }

    /* Make canonical. */
    if ((i = canonstring(string, buff)) < 0) {
	syntax_error("Bad transmit string format");
	return D_FATAL;
    }

    for (p = buff; --i >= 0; p++) {
	if ((unsigned char)*p == (unsigned char)SC_SLEEP) {
	    if (tranfile && transtyle != TS_LOW) {
		(void)fprintf(tranfile, "\tSLEEP\n");
		(void)fflush(tranfile);
	    }
	    (void)sleep(1);
	}
	else if ((unsigned char)*p == (unsigned char)SC_BREAK) {
	    if (tranfile && transtyle != TS_LOW) {
		(void)fprintf(tranfile, "\tBREAK\n");
		(void)fflush(tranfile);
	    }
	    tcsendbreak(fileno(portfile), 1);
	}
	else if (writechar(p) < 0)
	    return D_FATAL;
    }
    return D_OK;
}


do_recv(nrecv_str, recv_str)
int nrecv_str;
char *recv_str[];
{
    char	*timestr = recv_str[nrecv_str];
    int		length;
    int		i;
    int		err;
    int		timeout;

    if (tranfile && transtyle != TS_LOW) {
	(void)fprintf(tranfile, "RECV ");
	for (i = 1 ; i <= nrecv_str ; i++)
	    (void)fprintf(tranfile, "%s%c", recv_str[i],
			  (i == nrecv_str) ? '\n' : ' ');
	(void)fflush(tranfile);
    }

    recv_str++;
    nrecv_str--;
    /* Convert the strings. */
    for (i = 0 ; i < nrecv_str ; i++) {
	if ((length = canonstring(recv_str[i], recv_str[i])) < 0) {
	    syntax_error("Bad receive string format");
	    return D_FATAL;
	}
	if (length > MATCHLEN) {
	    syntax_error("Receive string too long");
	    return D_FATAL;
	}
    }

    /* Set up the timer */
    if ((timeout = atoi(timestr)) < 0) {
	syntax_error("Bad timeout value");
	return D_FATAL;
    }
    if (timeout > 0)
	(void)signal(SIGALRM, scripttimeout);

    if (setjmp(timerest) == 0) {
	/* Do the matching. */
	if (timeout > 0)
	    (void)alarm((unsigned int)timeout);
	for ( ; ; ) {
	    for (i = 0 ; i < nrecv_str ; i++)
		if ((err = matchtext(recv_str[i], timeout)) == D_OK)
		    break;
	    if (i < nrecv_str)
		break;
	    if (getnextchar(timeout) == D_CONTIN)
		longjmp(timerest, 2);
	}
	if (timeout > 0)
	    (void)alarm(0);

	switch (err) {
	default:
	    return D_FATAL;
	case D_OK:
	    return D_OK;
	case D_NONFATAL:
	    io_error("EOF while trying match");
	    return D_NONFATAL;
	case D_FATAL:
	    io_error("Read error while trying match");
	    return D_FATAL;
	case D_INTRPT:
	    break;
	}
    }

    d_log(DLOG_INFO, WHERE, "No match for \"%s\"%s after %d seconds",
	recv_str[0], nrecv_str > 1 ? " ..." : "", timeout);
    return D_NONFATAL;
}


#define pushbackchar(c)		(*pushptr++ = (c))


/*
**  Check for a match between the string and the input stream.
*/
int
matchtext(p, timeout)
    char	*p;
    int		timeout;
{
    int		c;
    int		i;

    if (*p == '\0')
	return D_OK;
    if ((c = getnextchar(timeout)) < D_OK)
	return c;
    
    if (c == D_CONTIN)
	return D_NOMATCH;

    if (c == *p) {
	i = matchtext(++p, timeout);
	if (i < D_OK)
	    pushbackchar(c);
	return i;
    }
    pushbackchar(c);
    return D_NOMATCH;
}


/*
**  Read characters from the port in raw mode, or the pushback buffer.
*/
int
getnextchar(timeout)
int timeout;
{
    int		i;
    char	c;

    /* Use pushback if there is any. */
    if (pushptr > pushbuffer) {
	c = *--pushptr;
	if (tranfile && transtyle != TS_LOW) {
	    if (isprint(c))
		(void)fprintf(tranfile, "\t\tGET %4c (0%03o)\n", c, c);
	    else
		(void)fprintf(tranfile, "\t\tGET 0x%02x (0%03o)\n", c, c);
	    (void)fflush(tranfile);
	}
	return c;
    }


    while ((i = readchar(timeout)) != EOF) {
	/* filter out some of the junk characters */
	c = toascii(i);
	if (tranfile && transtyle != TS_LOW) {
	    if (isprint(c))
		(void)fprintf(tranfile, "\t\tGET %4c (0%03o)\n", c, c);
	    else
		(void)fprintf(tranfile, "\t\tGET 0x%02x (0%03o)\n", c, c);
	    (void)fflush(tranfile);
	}
	if (c != '\0' && c != '\177')
	    return c;
	/* Ignore NUL and DEL because they are almost always just noise */
    }

    if (i == EOF)
	return D_CONTIN;	/* End of replay only... */
    if (feof(portfile))
	return D_NONFATAL;
    if (errno == EINTR)
	return D_INTRPT;
    return D_FATAL;
}


/*
**  Open a script.
*/
int
do_use(sname, nfields, fields)
    char *sname;
    int nfields;
    char *fields[];
{
    register int	i;
    OPENFILE		*op;
    FILE		*F;

    if (nopenfiles > MAXFILES) {
	syntax_error("Too many \"use\" commands");
	return D_FATAL;
    }

    /* Get the full filename. */
    (void)strcpy(scriptname,
		 expand_dirs_file("$DPCONF_DIR:$DPSCRIPT_DIR", sname));

    /* Open it. */
    if ((F = fopen(scriptname, "r")) == NULL) {
	misc_error("Can't open script file \"%s\"", scriptname);
	return D_FATAL;
    }
    if (tranfile && transtyle != TS_LOW) {
	(void)fprintf(tranfile, "USING %s", scriptname);
	for (i = 0; i < nfields; i++)
	    (void)fprintf(tranfile, " %s", fields[i]);
	(void)fprintf(tranfile, "\n");
	(void)fflush(tranfile);
    }

    /* If this isn't the first file, save the current state. */
    if (nopenfiles) {
	if ((op = (OPENFILE *)malloc(sizeof (OPENFILE))) == NULL) {
	    misc_error("No space for openfile \"%s\"", scriptname);
	    return D_FATAL;
	}
	op->linenumber = linenumber;
	op->scriptfile = scriptfile;
	op->scriptname = copystring(scriptname);
	for (op->nfields = use_nfields, i = 0; i < use_nfields; i++)
	    op->fields[i] = use_fields[i];
	for (use_nfields = nfields - 1, i = 0; i < nfields; i++)
	    use_fields[i] = copystring(fields[i + 1]);
	openfiles[nopenfiles - 1] = op;
    }
    nopenfiles++;

    scriptfile = F;
    linenumber = 0;
    return D_OK;
}


int
closescript()
{
    int		i;
    OPENFILE	*op;

    if (nopenfiles <= 0)
	return 0;

    if (scriptfile)
	(void)fclose(scriptfile);
    for (i = 0; i < use_nfields; i++)
	free(use_fields[i]);

    if (--nopenfiles > 0) {
	op = openfiles[nopenfiles - 1];
	scriptfile = op->scriptfile;
	(void)strcpy(scriptname, op->scriptname);
	free(op->scriptname);
	linenumber = op->linenumber;
	for (use_nfields = op->nfields, i = 0; i < use_nfields; i++)
	    use_fields[i] = op->fields[i];
	free((char *)op);
	return nopenfiles;
    }
    return 0;
}



/*
**  Read characters from the TTY line and store them in a circular
**  buffer so that they can be replayed if desired.
*/

do_replay()
{
    replay = 1;
    repcurr = repfirst;
    repcount = repsize;
    if (tranfile && transtyle != TS_LOW) {
	(void)fprintf(tranfile, "REPLAY\n");
	(void)fflush(tranfile);
    }
}


do_mark()
{
    replay = 0;
    repsize = 0;
    repfirst = 0;
    repnext = 0;
    if (tranfile && transtyle != TS_LOW) {
	(void)fprintf(tranfile, "SET MARK\n");
	(void)fflush(tranfile);
    }
}


static int
repskipline()
{
    for (; repbuff[repfirst] != '\n' && repsize > 0; repsize--)
	repfirst++;

    if (repsize <= 0)
	do_mark();
    else {
	repfirst++;
	repsize--;
    }
}


int
readchar(timeout)
int timeout;
{
    register int c;

Top:
    if (replay) {
	/* Replaying input.  Anything left? */
	if (repcount == 0) {
	    replay = 0;
	    goto Top;
	}

	c = repbuff[repcurr];
	if (++repcurr == REPBUFSIZE)
	    repcurr = 0;
	repcount--;

	/* Don't replay EOF's.  They get in there when a timeout interrupts
	 * a read. */
	if (c == EOF)
	    goto Top;
    }
    else {
	/*
	 * Don't issue a read unless a timeout is active.
	 * We could wait forever.
	 */
	if (!timeout)
	    return EOF;
	if (repsize == REPBUFSIZE)
	    /* Ran out of buffer space.  Rather then leave a partial line,
	     * delete the first line in the buffer. */
	    repskipline();
	else
	    repsize++;

	c = getc(portfile);
	if (ferror(portfile)) {
	    d_log(DLOG_GENERAL, WHERE, "getc returned error, %m\n");
	    clearerr(portfile);
	}
	if (feof(portfile)) {
	    d_log(DLOG_GENERAL, WHERE, "getc returned EOF\n");
	    clearerr(portfile);
	}
	if (c != EOF)
	    repbuff[repnext] = c &= 0177;   /* Strip parity for comparison */

	if (tranfile) {
	    if (transtyle == TS_LOW)
		(void)fprintf(tranfile, "%s", seechar(c));
	    else if (isprint(c))
		(void)fprintf(tranfile, "\tREAD    %c (0%03o)\n", c, c);
	    else
		(void)fprintf(tranfile, "\tREAD 0x%02x (0%03o)\n", c, c);
	    (void)fflush(tranfile);
	}
	if (++repnext == REPBUFSIZE)
	    repnext = 0;
    }
    return c;
}
