/* 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.
 */
/* mods by PA0GRI */
#include <stdio.h>
#include <ctype.h>
#include "global.h"
#include "config.h"
#include "mbuf.h"
#include "timer.h"
#include "proc.h"
#include "iface.h"
#include "netuser.h"
#include "8250.h"
#include "asy.h"
#include "tty.h"
#include "session.h"
#include "socket.h"
#include "cmdparse.h"
#include "devparam.h"
#include "icmp.h"
#include "files.h"
#include "main.h"
#include "trace.h"

#define MIN_INTERVAL	5L

static int redial __ARGS((struct iface *ifp,char *file));

static int dodial_control	__ARGS((int argc,char *argv[],void *p));
static int dodial_send		__ARGS((int argc,char *argv[],void *p));
static int dodial_speed		__ARGS((int argc,char *argv[],void *p));
static int dodial_wait		__ARGS((int argc,char *argv[],void *p));


static struct cmds dial_cmds[] = {
	"control",	dodial_control,	0, 2, "control up | down",
	"send",		dodial_send,	0, 2,
	"send \"string\" [<milliseconds>]",
	"speed",	dodial_speed,	0, 2, "speed <bps>",
	"wait",		dodial_wait,	0, 2,
	"wait <milliseconds> [ \"string\" [speed] ]",
	NULLCHAR,	NULLFP,		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(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	struct iface *ifp;
	int32 interval = 0L;		/* in seconds */
	int32 last_wait = 0L;
	int32 target = 0L;
	int pings = 0;
	int countdown;
	char *filename;
	char *ifn;
	int result;
	int s;

	if((ifp = if_lookup(argv[1])) == NULLIF){
		tprintf("Interface %s unknown\n",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);
			pwait(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 = rootdircat(argv[2]);

	/* 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 > %d seconds\n", MIN_INTERVAL);
		return 1;
	}

	/* get the number of pings before redialing (arg 4) */
	if ( argc < 5 ) {
	} else if ( (pings = atoi(argv[4])) <= 0 ){
		tprintf("pings must be > 0\n");
		return 1;
	}

	/* retrieve the host name (arg 5) */
	if ( argc < 6 ) {
	} else if ( (target = resolve(argv[5])) == 0L ) {
		tprintf(Badhost,argv[5]);
		return 1;
	}

	countdown = pings;
	ifp->supv = Curproc;

	while ( !main_exit ) {
		int32 wait_for = interval;

		if ( Asy[ ifp->dev ].rlsd_line_control == RLSD_DOWN ) {
			/* definitely down */
			if ( redial(ifp,filename) < 0 )
				break;
		} else 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;
		} else if ( target != 0L
		   && (s = socket(AF_INET,SOCK_RAW,IPPROTO_ICMP)) != -1 ) {
			pingem(s,target,0,(int16)s,0);
			close_s(s);
			countdown--;
		} else if ( ifp->echo != NULLFP ) {
			(*ifp->echo)(ifp,NULLBUF);
			countdown--;
		}

		last_wait = secclock();
		if ( wait_for != 0L ) {
			alarm( wait_for * 1000L );
			if ( pwait( &(ifp->supv) ) == EABORT )
				break;
			alarm(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;
char *file;
{
	char *inbuf, *intmp;
	FILE *fp;
	int (*rawsave) __ARGS((struct iface *,struct mbuf *));
	struct session *sp;
	int result = 0;
	int save_input = Curproc->input;
	int save_output = Curproc->output;

	if((fp = fopen(file,READ_TEXT)) == NULLFILE){
		tprintf("redial: can't read %s\n",file);
		return -1;	/* Causes dialer proc to terminate */
	}
	/* 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;
	}

	/* allocate a session descriptor */
	if ( (sp = newsession( ifp->name, DIAL, 0 )) == NULLSESSION ) {
		tprintf( "Too many sessions\n" );
		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);

	inbuf = mallocw(BUFSIZ);
	intmp = mallocw(BUFSIZ);
	while ( fgets( inbuf, BUFSIZ, fp ) != NULLCHAR ) {
		strcpy(intmp,inbuf);
		if( (result = cmdparse(dial_cmds,inbuf,ifp)) != 0 ){
			tprintf("input line: %s",intmp);
			break;
		}
	}
	free(inbuf);
	free(intmp);
	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!
	 */
	pause( 10000L );
	freesession( sp );
	Curproc->input = save_input;
	Curproc->output = save_output;
	return result;
}


static int
dodial_control(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	struct iface *ifp = p;
	int param;

	if ( ifp->ioctl == NULL )
		return -1;

	if ( (param = devparam( argv[1] )) == -1 )
		return -1;

	(*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(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(cp,1);
			asy_send(ifp->dev,bp);
			pause(cdelay);
		}
	} else {
		bp = qdata( argv[1], strlen(argv[1]) );

		if (ifp->trace & IF_TRACE_RAW)
			raw_dump( ifp, IF_TRACE_OUT, bp );
		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 ( argc < 2 ) {
		tprintf( "current speed = %u bps\n", Asy[ifp->dev].speed );
		return 0;
	}
	return asy_speed( ifp->dev, (int16)atol( argv[1] ) );
}


static int
dodial_wait(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	struct iface *ifp = p;
	register int c = -1;

	alarm( atol( argv[1] ) );

	if ( argc == 2 ) {
		while ( (c = get_asy(ifp->dev)) != -1 ) {
			tputc( c &= 0x7F );
			tflush();
		}
		alarm( 0L );
		return 0;
	} else {
		register char *cp = argv[2];

		while ( *cp != '\0'  &&  (c = get_asy(ifp->dev)) != -1 ) {
			tputc( c &= 0x7F );
			tflush();

			if (*cp++ != c) {
				cp = argv[2];
			}
		}

		if ( argc > 3 ) {
			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 += c - '0';
					} else {
						alarm( 0L );
						return asy_speed( ifp->dev, speed );
					}
				}
			} else {
				return -1;
			}
		}
	}
	alarm( 0L );
	return ( c == -1 );
}


