/* Automatic SLIP/PPP line dialer.
 *
 * Copyright 1991 Phil Karn, KA9Q
 *
 *	Mar '91	Bill Simpson & Glenn McGregor
 *		completely re-written;
 *		human readable control file;
 *		includes wait for string, and speed sense;
 *		dials immediately when invoked.
 *	May '91 Bill Simpson
 *		re-ordered command line;
 *		allow dial only;
 *		allow inactivity timeout without ping.
 *	Sep '91 Bill Simpson
 *		Check known DTR & RSLD state for redial decision
 *
 * Mods by PA0GRI (newsession parameters)
 *
 * Mods by KF8NH:  iffailed, begin/end
 */
#include "global.h"
#include "ctype.h"
#include "commands.h"
#include "mbuf.h"
#include "timer.h"
#include "proc.h"
#include "iface.h"
#ifdef UNIX
#include "unixasy.h"
#else
#include "n8250.h"
#endif
#include "asy.h"
#include "tty.h"
#include "devparam.h"
#include "icmp.h"
#include "files.h"
#include "main.h"
#include "trace.h"

#if !defined(_lint)
static char rcsid[] OPTIONAL = "$Id: dialer.c,v 1.22 1997/08/19 01:19:22 root Exp $";
#endif

#define MIN_INTERVAL	5L
#define MAXDEPTH	8

static int Failmode = 0;
static char Failed;
static char Depth;
static char Skip[MAXDEPTH];
static char SkipOverride;
static char OverrideDepth;

static int redial (struct iface * ifp, const char *file);
static int dodial_nothing (int argc, char *argv[], void *p);
static int dodial_begin (int argc, char *argv[], void *p);
static int dodial_control (int argc, char *argv[], void *p);
static int dodial_end (int argc, char *argv[], void *p);
static int dodial_exit (int argc, char *argv[], void *p);
static int dodial_failmode (int argc, char *argv[], void *p);
static int dodial_iffail (int argc, char *argv[], void *p);
static int dodial_ifok (int argc, char *argv[], void *p);
static int dodial_send (int argc, char *argv[], void *p);
static int dodial_speed (int argc, char *argv[], void *p);
static int dodial_status (int argc, char *argv[], void *p);
static int dodial_wait (int argc, char *argv[], void *p);


static struct cmds dial_cmds[] =
{
	{ "",		dodial_nothing,		0, 0, ""},
	{ "begin",	dodial_begin,		0, 1, "begin"},
	{ "control",	dodial_control,		0, 2, "control up | down"},
	{ "end",	dodial_end,		0, 1, "end"},
	{ "exit",	dodial_exit,		0, 1, "exit"},
	{ "failmode",	dodial_failmode,	0, 2, "failmode on | off"},
	{ "iffail",	dodial_iffail,		0, 2, "iffail \"command\""},
	{ "ifok",	dodial_ifok,		0, 2, "ifok \"command\""},
	{ "send",	dodial_send, 		0, 2, "send \"string\" [<milliseconds>]"},
	{ "speed",	dodial_speed,		0, 2, "speed <bps>"},
	{ "status",	dodial_status,		0, 2, "status up | down"},
	{ "wait",	dodial_wait,		0, 2, "wait <milliseconds> [ \"string\" [speed] ]"},
	{ NULLCHAR,	NULLFP ((int, char **, void *)), 0, 0, "Unknown command"},
};


/* dial <iface> <filename> [ <seconds> [ <pings> [<hostid>] ] ]
 *	<iface>		must be asy type
 *	<filename>	contains commands which are executed.
 *			missing: kill outstanding dialer.
 *	<seconds>	interval to check for activity on <iface>.
 *	<pings> 	number of missed pings before redial.
 *	<hostid>	interface to ping.
 */
int
dodialer (int argc, char **argv, void *p OPTIONAL)
{
struct iface *ifp;
#ifndef UNIX
struct asy *ap;
#endif
int32 interval = 0L;	/* in seconds */
int32 last_wait = 0L;
uint32 target = 0L;
int pings = 0;
int countdown;
char const *filename;
char *ifn;
int result;
int s;

	if ((ifp = if_lookup (argv[1])) == NULLIF) {
		tprintf (Badinterface, argv[1]);
		return 1;
	}
	if (ifp->dev >= ASY_MAX || Asy[ifp->dev].iface != ifp) {
		tprintf ("Interface %s not asy port\n", argv[1]);
		return 1;
	}
	if (ifp->supv != NULLPROC) {
		while (ifp->supv != NULLPROC) {
			alert (ifp->supv, EABORT);
			kwait (NULL);
		}
		tprintf ("dialer terminated on %s\n", argv[1]);
	}
	if (argc < 3) {
		/* just terminating */
		return 0;
	}
	chname (Curproc, ifn = if_name (ifp, " dialer"));
	free (ifn);
	filename = argv[2];
	if ((argv[2][0] != '/') && (argv[2][0] != '\\'))
		rootdircat (&filename);

	/* handle minimal command (just thru filename) */
	if (argc < 4) {
		/* just dialing */
		result = redial (ifp, filename);

		if (filename != argv[2])
			free (filename);
		return result;

		/* get polling interval (arg 3) */
	} else if ((interval = atol (argv[3])) <= MIN_INTERVAL) {
		tprintf ("interval must be > %ld seconds\n", MIN_INTERVAL);
		return 1;
	}
	/* get the number of pings before redialing (arg 4) */
	if (argc >= 5 && (pings = atoi (argv[4])) <= 0) {
		tputs ("pings must be > 0\n");
		return 1;
	}
	/* retrieve the host name (arg 5) */
	if (argc >= 6 && (target = resolve (argv[5])) == 0L) {
		tprintf (Badhost, argv[5]);
		return 1;
	}
	countdown = pings;
	ifp->supv = Curproc;
	while (!main_exit) {
		int32 wait_for = interval;

		/*
		 * N.B. Eventually, when the rest of this is stable, I will
		 * look into supporting DTR and RTS.  For now, it's enough to
		 * just have the code work.
		 */
#ifndef UNIX
		ap = &Asy[ifp->dev];
		if (ap->dtr_usage == FOUND_DOWN || ap->dtr_usage == MOVED_DOWN
		    || ap->rlsd_line_control == MOVED_DOWN) {
			/* definitely down */
			if (redial (ifp, filename) < 0)
				break;
		} else
#endif
		if (ifp->lastrecv >= last_wait) {
			/* got something recently */
			wait_for -= secclock () - ifp->lastrecv;
			countdown = pings;
		} else if (countdown < 1) {
			/* we're down, or host we ping is down */
			if (redial (ifp, filename) < 0)
				break;
			countdown = pings;
		} else if (target != 0L && (s = socket (AF_INET, SOCK_RAW, ICMP_PTCL)) != -1) {
			pingem (s, target, 0, (int16) s, 0);
			close_s (s);
			countdown--;
		} else if (ifp->echo != NULLFP ((struct iface *, struct mbuf *))) {
			(void) (*ifp->echo) (ifp, NULLBUF);
			countdown--;
		}
		last_wait = secclock ();
		if (wait_for != 0L) {
			kalarm (wait_for * 1000L);
			if (kwait (&(ifp->supv)) == EABORT)
				break;
			kalarm (0L);	/* clear alarm */
		}
	}

	if (filename != argv[2])
		free (filename);
	ifp->supv = NULLPROC;	/* We're being terminated */
	return 0;
}


/* execute dialer commands
 * returns: -1 fatal error, 0 OK, 1 try again
 */
static int
redial (ifp, file)
struct iface *ifp;
const char *file;
{
char *inbuff, *intmp;
FILE *fp;
int (*rawsave) (struct iface *, struct mbuf *);
struct session *sp;
int result = 0;
int LineCounter = 0;
int save_input = Curproc->input;
int save_output = Curproc->output;

	/* Save output handler and temporarily redirect output to null */
	if (ifp->raw == bitbucket) {
		tprintf ("redial: tip or dialer already active on %s\n", ifp->name);
		return -1;
	}
	if ((fp = fopen (file, READ_TEXT)) == NULLFILE) {
		tprintf ("redial: can't read %s\n", file);
		return -1;	/* Causes dialer proc to terminate */
	}
	/* allocate a session descriptor */
	if ((sp = newsession (ifp->name, DIAL, 0)) == NULLSESSION) {
		tputs (TooManySessions);
		(void) fclose (fp);
		return 1;
	}
	tprintf ("Dialing on %s\n\n", ifp->name);

	/* Save output handler and temporarily redirect output to null */
	rawsave = ifp->raw;
	ifp->raw = bitbucket;

	/* Suspend the packet input driver. Note that the transmit driver
	 * is left running since we use it to send buffers to the line.
	 */
	suspend (ifp->rxproc);

#ifdef notdef
	tprintf ("rlsd: 0x%02x, dtr: 0x%02x\n",
		 Asy[ifp->dev].rlsd_line_control,
		 Asy[ifp->dev].dtr_usage);
#endif

	Failed = 0;
	Depth = 0;
	Skip[0] = 0;
	SkipOverride = -1;

	inbuff = mallocw (BUFSIZ);
	intmp = mallocw (BUFSIZ);
	while (fgets (inbuff, BUFSIZ, fp) != NULLCHAR) {
		LineCounter++;
		strncpy (intmp, inbuff, BUFSIZ);
		rip (intmp);
#ifdef DIALDEBUG
		log (-1, "%s dialer: %s", ifp->name, intmp);
#endif
		if ((result = cmdparse (dial_cmds, inbuff, ifp)) != 0) {
			if (Failmode)
				Failed = 1;
			else {
				tprintf ("input line #%d: %s", LineCounter, intmp);
#ifndef DIALDEBUG
				log (-1, "%s dialer: Failure at line %d: %s", ifp->name, LineCounter, intmp);
#endif
				break;
			}
		} else
			Failed = 0;
		if (Depth == -1)
			break;
		if (SkipOverride != -1) {
			Skip[(int) OverrideDepth] = SkipOverride;
			SkipOverride = -1;
		}
	}
	if (Depth > 0)
		tprintf ("Warning: %d unmatched `begin's in command file\n", Depth);
	free (inbuff);
	free (intmp);
	(void) fclose (fp);

	if (result == 0)
		ifp->lastsent = ifp->lastrecv = secclock ();

	ifp->raw = rawsave;
	resume (ifp->rxproc);
	tprintf ("\nDial %s complete\n", ifp->name);

	/* Wait for awhile, so the user can read the screen,
	 * AND to give it time to send some packets on the new connection!
	 */
	(void) kpause (10000L);
	freesession (sp);
	Curproc->input = save_input;
	Curproc->output = save_output;
	return result;
}


static int
dodial_control (argc, argv, p)
int argc OPTIONAL;
char *argv[];
void *p;
{
struct iface *ifp = p;
int param;

	if (Skip[(int) Depth])
		return 0;

	tprintf ("control %s %ld\n", argv[1], atol (argv[2]));
	if (ifp->ioctl == NULL)
		return -1;

	if ((param = devparam (argv[1])) == -1)
		return -1;

	(void) (*ifp->ioctl) (ifp, param, TRUE, atol (argv[2]));
	return 0;
}


static int
dodial_send (argc, argv, p)
int argc;
char *argv[];
void *p;
{
struct iface *ifp = p;
struct mbuf *bp;

	if (Skip[(int) Depth])
		return 0;

	tprintf ("send <%s>\n", argv[1]);
	if (argc > 2) {
		/* Send characters with inter-character delay
		 * (for dealing with prehistoric Micom switches that
		 * can't take back-to-back characters...yes, they
		 * still exist.)
		 */
		char *cp;
		int32 cdelay = atol (argv[2]);

		for (cp = argv[1]; *cp != '\0'; cp++) {
			bp = qdata ((unsigned char *) cp, 1);
			(void) asy_send (ifp->dev, bp);
			(void) kpause (cdelay);
		}
	} else {
		bp = qdata ((unsigned char *) argv[1], (int16) strlen (argv[1]));

#ifdef TRACE
		if (ifp->trace & IF_TRACE_RAW)
			raw_dump (ifp, IF_TRACE_OUT, bp);
#endif
		(void) asy_send (ifp->dev, bp);
	}
	return 0;
}


static int
dodial_speed (argc, argv, p)
int argc;
char *argv[];
void *p;
{
struct iface *ifp = p;

	if (Skip[(int) Depth])
		return 0;

	if (argc < 2) {
		tprintf ("current speed = %lu bps\n", Asy[ifp->dev].speed);
		return 0;
	}
	tprintf ("speed %ld\n", atol (argv[1]));
	return asy_speed (ifp->dev, (int16) atol (argv[1]));
}


static int
dodial_status (argc, argv, p)
int argc OPTIONAL;
char *argv[];
void *p;
{
struct iface *ifp = p;
int param;

	if (Skip[(int) Depth])
		return 0;

	if (ifp->iostatus == NULL)
		return -1;

	if ((param = devparam (argv[1])) == -1)
		return -1;

	(void) (*ifp->iostatus) (ifp, param, atol (argv[2]));
	return 0;
}


static int
dodial_wait (argc, argv, p)
int argc;
char *argv[];
void *p;
{
struct iface *ifp = p;
register int c = -1;

	if (Skip[(int) Depth])
		return 0;

	kalarm (atol (argv[1]));

	if (argc == 2) {
		tprintf ("wait %ld\nimsg <", atol (argv[1]));
		while ((c = get_asy (ifp->dev)) != -1) {
			tputc (c &= 0x7F);
			tflush ();
		}
		kalarm (0L);
		tputs (">\n");
		return 0;
	} else {
		register char *cp = argv[2];

		tprintf ("waitfor <%s>\nimsg <", argv[2]);
		tflush ();
		while (*cp != '\0' && (c = get_asy (ifp->dev)) != -1) {
			tputc (c &= 0x7F);
			tflush ();

			if (*cp++ != c) {
				cp = argv[2];
			}
		}

		if (argc > 3 && c != -1) {
			if (stricmp (argv[3], "speed") == 0) {
				int16 speed = 0;

				while ((c = get_asy (ifp->dev)) != -1) {
					tputc (c &= 0x7F);
					tflush ();

					if (isdigit (c)) {
						speed *= 10;
						speed += (int16) (c - '0');
					} else {
						kalarm (0L);
						tputs ("> ok\n");
						return asy_speed (ifp->dev, speed);
					}
				}
			} else {
				tputs ("> bad command\n");
				return -1;
			}
		}
	}
	kalarm (0L);
	tprintf ("> %s\n", (c == -1 ? "failed" : "ok"));
	return (c == -1);
}


static int
dodial_failmode (argc, argv, p)
int argc;
char **argv;
void *p OPTIONAL;
{
	if (Skip[(int) Depth])
		return 0;
	tprintf ("failmode %s\n", argv[1]);
	return setbool (&Failmode, "Continue on dial command failure", argc, argv);
}


static int
dodial_iffail (argc, argv, p)
int argc OPTIONAL;
char **argv;
void *p;
{
	if (!Skip[(int) Depth]) {
		if (SkipOverride == -1) {
			SkipOverride = Skip[(int) Depth];
			OverrideDepth = Depth;
		}
		Skip[(int) Depth] = !Failed;
	}
	return cmdparse (dial_cmds, argv[1], p);
}


static int
dodial_ifok (argc, argv, p)
int argc OPTIONAL;
char **argv;
void *p;
{
	if (!Skip[(int) Depth]) {
		if (SkipOverride == -1) {
			SkipOverride = Skip[(int) Depth];
			OverrideDepth = Depth;
		}
		Skip[(int) Depth] = Failed;
	}
	return cmdparse (dial_cmds, argv[1], p);
}


static int
dodial_begin (argc, argv, p)
int argc OPTIONAL;
char **argv OPTIONAL;
void *p OPTIONAL;
{
	if (Depth == MAXDEPTH) {
		Depth = -1;
		tputs ("Blocks nested too deep\n");
		return -1;
	}
	if (!Skip[(int) Depth])
		tputs ("begin\n");
	Skip[Depth + 1] = Skip[(int) Depth];
	Depth++;
	return 0;
}


static int
dodial_end (argc, argv, p)
int argc OPTIONAL;
char **argv OPTIONAL;
void *p OPTIONAL;
{
	if (!Skip[(int) Depth])
		tputs ("end\n");
	if (Depth-- == 0) {
		tputs ("`end' without `begin'\n");
		return -1;
	}
	return 0;
}


static int
dodial_exit (argc, argv, p)
int argc;
char **argv;
void *p OPTIONAL;
{
	if (Skip[(int) Depth])
		return 0;
	if (argc > 1)
		tprintf ("exit %d\n", atoi (argv[1]));
	else
		tputs ("exit\n");
	Depth = -1;
	return (argc > 1 ? atoi (argv[1]) : Failed);
}


/*
 * cmdparse sends blank lines to the first command, sigh
 */

static int
dodial_nothing (argc, argv, p)
int argc OPTIONAL;
char **argv OPTIONAL;
void *p OPTIONAL;
{
	return 0;
}
