/* 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
 *	Mar '92	Phil Karn
 *		autosense modem control stuff removed
 *		Largely rewritten to do demand dialing
 */
#include <stdio.h>
#include <ctype.h>
#include "global.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 "socket.h"
#include "cmdparse.h"
#include "devparam.h"
#include "files.h"
#include "main.h"
#include "trace.h"
#include "commands.h"

static int redial __ARGS((struct iface *ifp,char *file));
static void dropline __ARGS((void *));
static void dropit(int,void *,void *);

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_status	__ARGS((int argc,char *argv[],void *p));
static int dodial_wait		__ARGS((int argc,char *argv[],void *p));

static struct cmds dial_cmds[] = {
	"",		donothing,	0, 0, "",
	"control",	dodial_control,	0, 2, "control up | down",
	"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,		0, 0, "Unknown command",
};
/* 
 * dial <iface> <seconds> <raisefile> <dropfile>
 * dial <iface>
 * dial <iface> 0
 */
int
dodialer(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	struct iface *ifp;
	struct asy *ap;
	int32 timeout;

	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;
	}
	ap = &Asy[ifp->dev];
	if(argc < 3){
		tprintf("%s: %s",ifp->name,(ap->msr & MSR_RLSD) ? "UP":"DOWN");
		tprintf(", idle timer %ld/%ld",read_timer(&ap->idle)/1000L,
		  dur_timer(&ap->idle)/1000L);
		if(ap->actfile != NULLCHAR)
			tprintf(", up script: %s",ap->actfile);
		if(ap->dropfile != NULLCHAR)
			tprintf(", down script: %s\n",ap->dropfile);
		else
			tprintf("\n");

		tprintf("Calls originated %ld, carrier up transitions %ld\n",
			ap->originates,ap->answers);
		tprintf("Calls timed out %ld, carrier down transitions %ld\n",
			ap->localdrops,ap->remdrops);
		return 0;
	}
	timeout = atol(argv[2]) * 1000L;
	if(timeout != 0 && argc < 5){
		tprintf("Usage: dial <iface> <timeout> <raisefile> <dropfile>\n");
		tprintf("       dial <iface> 0\n");
		return 1;
	}
	if(!ap->rlsd){
		tprintf("Must set 'r' flag at attach time\n");
		return 1;
	}
	stop_timer(&ap->idle);
	set_timer(&ap->idle,timeout);
	ap->idle.func = dropline;
	ap->idle.arg = ifp;
	if(ap->actfile != NULLCHAR){
		free(ap->actfile);
		ap->actfile = NULLCHAR;
	}
	if(ap->dropfile != NULLCHAR){
		free(ap->dropfile);
		ap->dropfile = NULLCHAR;
	}
	if(timeout != 0){
		ap->actfile = strdup(argv[3]);
		ap->dropfile = strdup(argv[4]);
		start_timer(&ap->idle);
	}
	return 0;
}
void
dialer_kick(asyp)
struct asy *asyp;
{
	stop_timer(&asyp->idle);
	while(asyp->rlsd && (asyp->msr & MSR_RLSD) == 0
	 && asyp->actfile != NULLCHAR){
		/* Line down, need to redial it */
		asyp->originates++;
		redial(asyp->iface,asyp->actfile);
	}
}

/* Called when idle line timer expires -- executes script to drop line */
static void
dropline(p)
void *p;
{
	/* Fork this off to prevent wedging the timer task */
	newproc("dropit",512,dropit,0,p,NULL,0);
}

static void
dropit(i,p,u)
int i;
void *p;
void *u;
{
	struct iface *ifp = p;
	struct asy *ap;

	ap = &Asy[ifp->dev];
	if(ap->msr & MSR_RLSD){
		ap->localdrops++;
		redial(ifp,ap->dropfile);	/* Drop only if still up */
	}
}

/* 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 *));
	int result = 0;
	int save_input = Curproc->input;
	int save_output = Curproc->output;

	if((fp = fopen(file,READ_TEXT)) == NULLFILE){
		if(ifp->trace & (IF_TRACE_IN|IF_TRACE_OUT))
			fprintf(ifp->trfp,"redial: can't read %s\n",file);
		return -1;
	}
	/* Save output handler and temporarily redirect output to null */
	if(ifp->raw == bitbucket){
		if(ifp->trace & (IF_TRACE_IN|IF_TRACE_OUT))
			fprintf(ifp->trfp,"redial: tip or dialer already active on %s\n",ifp->name);

		return -1;
	}

	if(ifp->trace & (IF_TRACE_IN|IF_TRACE_OUT))
		fprintf(ifp->trfp,"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);
		rip(intmp);
		log(-1,"%s dialer: %s",ifp->name,intmp);
		if((result = cmdparse(dial_cmds,inbuf,ifp)) != 0){
			if(ifp->trace & (IF_TRACE_IN|IF_TRACE_OUT))
				fprintf(ifp->trfp,"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);
	if(ifp->trace & (IF_TRACE_IN|IF_TRACE_OUT))
		fprintf(ifp->trfp,"\nDial %s complete\n",ifp->name);

	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++){
			asy_write(ifp->dev,cp,1);
			pause(cdelay);
		}
	} else {
		if (ifp->trace & IF_TRACE_RAW)
			raw_dump( ifp, IF_TRACE_OUT, bp );
		asy_write(ifp->dev,argv[1],strlen(argv[1]));
	}
	return 0;
}


static int
dodial_speed(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	struct iface *ifp = p;

	if ( argc < 2 ) {
		if(ifp->trace & (IF_TRACE_IN|IF_TRACE_OUT))
			fprintf(ifp->trfp,"current speed = %u bps\n", Asy[ifp->dev].speed);
		return 0;
	}
	return asy_speed( ifp->dev, (int16)atol( argv[1] ) );
}


static int
dodial_status(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	struct iface *ifp = p;
	int param;

	if ( ifp->iostatus == NULL )
		return -1;

	if ( (param = devparam( argv[1] )) == -1 )
		return -1;

	(*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;

	alarm(atol(argv[1]));

	if(argc == 2){
		while((c = get_asy(ifp->dev)) != -1 ){
			c &= 0x7f;
			if(ifp->trace & IF_TRACE_IN){
				fputc(c,ifp->trfp);
				fflush(ifp->trfp);
			}
		}
		alarm(0L);
		return 0;
	} else {
		register char *cp = argv[2];

		while(*cp != '\0' && (c = get_asy(ifp->dev)) != -1){
			c &= 0x7f;
			if(ifp->trace & IF_TRACE_IN){
				fputc(c,ifp->trfp);
				fflush(ifp->trfp);
			}
			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){
					c &= 0x7f;
					if(ifp->trace & IF_TRACE_IN){
						fputc(c,ifp->trfp);
						fflush(ifp->trfp);
					}
					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);
}


