/* 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
 *	11 Mar 92 Giles Todd
 *		Add "configure:" and "execute:" sections and new commands.
 *	19 Mar 92 Giles Todd
 *		Fix failure string processing.
 *	31 May 92 Giles Todd
 *		Fix empty configure string problems.
 *	02 Jun 92 Giles Todd
 *		Fix dial session cancel bug.
 *
 $Id: dialer.c 1.13 94/01/04 14:09:02 ROOT_DOS Exp $
 *
 *	04 Aug 92	1.6	GT	No change.
 *	09 Aug 92	1.7	GT	Inline dialing.
 *	17 Aug 92	1.8	GT	Allow escape from inline dial.
 *	13 Sep 92	1.9	CMS	Ignore script lines consisting only of a newline.
 *	03 Apr 93	1.11	GT	Fix order of calls.
 *	08 May 93	1.12	GT	Fix warnings.
 *							Improve dialer interruption.
 *	10 Dec 93	1.13	GT	Don't obscure modem messages.
 *
 * ATARI version by David Nash - dnash@chaos.demon.co.uk
 *
 *		Include st_asy.h in place of 8250.h
 *
 * 04.11.94 DFN dodial_status correct call to iostatus function
 *
 */
 
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include "global.h"
#include "config.h"
#include "mbuf.h"
#include "timer.h"
#include "proc.h"
#include "iface.h"
#include "netuser.h"
#ifdef ATARI
#include "st_asy.h"
#else
#include "8250.h"
#endif
#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"
#include "hardware.h"
#include "commands.h"

extern struct cmds far Cmds[];

#define MIN_INTERVAL	5L

typedef struct item ITEM;

struct item
	{
	char *data;							/* some text						*/
	ITEM *next;							/* -> next in list					*/
	};


static int redial __ARGS((struct iface *ifp, char *file));

static int cfg_init			__ARGS((int argc, char *argv[], void *p));
static int cfg_dial_cmd		__ARGS((int argc, char *argv[], void *p));
static int cfg_ld_code		__ARGS((int argc, char *argv[], void *p));
static int cfg_number		__ARGS((int argc, char *argv[], void *p));
static int cfg_retries		__ARGS((int argc, char *argv[], void *p));

static int dodial_control	__ARGS((int argc, char *argv[], void *p));
static int dodial_dial		__ARGS((int argc, char *argv[], void *p));
static int dodial_init		__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 int dodial_cwait		__ARGS((int argc, char *argv[], void *p));

static int __stdargs dodialer		__ARGS((int argc, char *argv[], void *p));
static int __stdargs do_inline	__ARGS((int argc, char *argv[], void *p));

static void add_cmd __ARGS((ITEM **hdr, ITEM **ptr, char *buf));
static char *next_cmd __ARGS((char *buf, ITEM **ptr));
static void clean_up __ARGS((void));
static void clean __ARGS((ITEM **hdr));
static int internal_send __ARGS((char *ptr, void *p));
static int asy_kb_poll __ARGS((int dev));

static struct cmds dial_cmds[] =
	{
	{ "control", 	dodial_control, 	0, 2, "control up | down" },
	{ "dial",		dodial_dial,		0, 0, "dial" },
	{ "init",		dodial_init,		0, 0, "init" },
	{ "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] ]" }, 
	{ "cwait", 		dodial_cwait, 	0, 2, 
	"cwait <milliseconds> [ \"success string\" \"failure string\" [, \"failure string\" ...] [speed] ]" }, 
	{ NULLCHAR, 	NULLFP, 		0, 0, "Unknown command" }, 
	};

static struct cmds cfg_cmds[] =
	{
	{ "init",		cfg_init,			0, 2, "init \"string\"" },
	{ "dial_cmd",	cfg_dial_cmd,		0, 2, "dial_cmd \"string\"" },
	{ "ld_code",	cfg_ld_code,		0, 2, "ld_code \"string\"" },
	{ "number",	cfg_number,			0, 2, "number \"string\"" },
	{ "retries",	cfg_retries,		0, 2, "retries <count>" },
	{ NULLCHAR, 	NULLFP, 		0, 0, "Unknown command" }, 
	};

static char cfg_intro[] = "configure:";
static char exe_intro[] = "execute:";
static int configuring = 0;				/* nz - found configuration section	*/
	
static char *init = NULL;				/* initialization string			*/
static char *dial_cmd = NULL;			/* modem dial command				*/
static char *ld_code = NULL;			/* long distance code				*/
static ITEM *number = NULL;				/* telephone numbers				*/
static ITEM *nr_ptr = NULL;				/* -> current number				*/
static unsigned retries = 1;			/* number of dial retries			*/
static ITEM *cfg_cmd = NULL;			/* configuration commands			*/
static ITEM *exe_cmd = NULL;			/* script commands					*/
static ITEM *cmd_ptr = NULL;			/* -> current script command		*/

static int dial_inline = 0;				/* nz - dial inline					*/
static int interrupted;					/* nz - dial interrupted			*/


/****************************************************************************
*	do_inline																*
*	Toggles the "inline dialing" flag and sets and resets the dialer task's	*
*	stack size.																*
****************************************************************************/

#ifndef ATARI
#define	DIALER_STACK	512
#else
#define  DIALER_STACK   2512
#endif

int __stdargs do_inline (argc, argv, p)
int argc;
char *argv[];
void *p;
	{
	int rc;									/* return value					*/
	struct cmds *cmd_ptr;					/* -> command structure			*/
	
	/* Set / reset / report flag value. */
	
	rc = setbool (&dial_inline, "Inline dialing", argc, argv);

	/* Set the dialer stack size. */

	cmd_ptr = Cmds;
	while (cmd_ptr->name != NULLCHAR)
		{
		if (strcmp (cmd_ptr->name, "dialer") == 0)
			break;

		cmd_ptr++;
		}
		
	if (dial_inline == 0)
		cmd_ptr->stksize = DIALER_STACK;	/* asynch process				*/
	else
		cmd_ptr->stksize = 0;				/* inline process				*/

	return (rc);
	}	/* int do_inline (argc, argv, p) */
	
		
/* 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>.
 *				(if 0 then demand dial)
 *	<pings> 	number of missed pings before redial.
 *	<hostid>	interface to ping.
 */

int __stdargs dodialer (argc, argv, p)
int argc;
char *argv[];
void *p;
	{
	struct iface *ifp;
	struct asy *ap;
	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;
	int exit_on_fail;

	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);
#ifdef ATARI
	if (!access(argv[2], 0))								/* check if file exists	*/
		filename = argv[2];
	else
#endif
	filename = rootdircat (argv[2]);

	ap = &Asy[ ifp->dev ];

	if (argc == 4 && strcmp(argv[3], "failexit") == 0)
		exit_on_fail = 1;
	else exit_on_fail = 0;

	/* handle minimal command (just thru filename) */
	
	if (argc < 4 || exit_on_fail)
		{
		/* just dialing */
		ifp->supv = Curproc;				/* so that it can be cancelled	*/
		result = redial (ifp, filename);

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

		ifp->supv = NULLPROC;
		
		if (result != 0 && exit_on_fail == 1)
			doexit (0, NULL, NULL);

		return result;
		}
	/* get polling interval (arg 3) */
	else if (strcmp(argv[3], "demand")
			&& (interval = atol (argv[3])) <= MIN_INTERVAL)
		{
		tprintf ("interval must be > %d seconds\n", MIN_INTERVAL);
		return 1;
		}

	if (strcmp(argv[3], "demand") == 0)
		/* Interval of 0, so we're demand dialing */
		{
		int32 backoff;
		tprintf("Demand dialing enabled\n");
		backoff = 4;
		ifp->supv = Curproc;				/* so that it can be cancelled	*/
		ifp->dial_me = 0;
		while (!main_exit)
			{
			alarm (2000L);
			if (pwait (& (ifp->supv)) == EABORT)
				break;
			alarm (0L);
			if (ifp->dial_me)
				{
				if (ap->dtr_usage == FOUND_DOWN ||
					ap->dtr_usage == MOVED_DOWN ||
					ap->rlsd_line_control == MOVED_DOWN)
					/* dial has been requested _and_ the link is down */
					{	
					tprintf("About to dial\n");
					if (redial (ifp, filename) == 0) {
						tprintf("Dial succeeded\n");
						backoff = 4;
						}
					else {
						if (backoff < 90000L) /* Max backoff is day & a bit */
							backoff = backoff * 2;
						tprintf("Dial failed, backing off for %lu seconds\n",
																	backoff);
						}
					}
				alarm (backoff * 1000L);
				if (pwait (& (ifp->supv)) == EABORT)
					break;
				alarm (0L);
				ifp->dial_me = 0;
				}
			}
		ifp->supv = NULLPROC;
		tprintf("Demand dialing finished\n");
		return 0;
		}

	/* 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 (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 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)
			{
			(*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 */
			}
			
		}	/* while (!main_exit) */

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

	ifp->supv = NULLPROC;	/* We're being terminated */
	return 0;
	}	/* int dodialer (argc, argv, p) */


/* 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;
	unsigned i;							/* loop counter						*/

	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);
		(void) fclose (fp);
		return -1;
		}

	/* allocate a session descriptor */

	if (dial_inline == 0)
		{
		if ((sp = newsession (ifp->name, DIAL)) == NULLSESSION)
			{
			tprintf ("Too many sessions\n");
			(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

	inbuf = mallocw (BUFSIZ);

	/* Read the file into cfg_cmd and exe_cmd. */

	cfg_cmd = NULL;
	exe_cmd = NULL;
	cmd_ptr = cfg_cmd;					/* initialize list pointer			*/
	configuring = 0;					/* not configuring yet				*/
	while (fgets (inbuf, BUFSIZ, fp) != NULLCHAR)
		{
		if (strnicmp (inbuf, cfg_intro, strlen (cfg_intro)) == 0)
			{
			configuring = 1;			/* found config section				*/
			break;
			}

		}

	if (configuring == 0)
		{
		tprintf ("redial: no \"configure:\" section\n");
		(void) fclose (fp);
		free (inbuf);
		ifp->raw = rawsave;
		resume (ifp->rxproc);
		tprintf ("\nDial %s complete\n", ifp->name);

		/* Wait for awhile, so the user can read the screen. */
#ifndef ATARI
		Pause (10000L);
#endif
		if (dial_inline == 0)
			{
			freesession (sp);
			}
			
		Curproc->input = save_input;
		Curproc->output = save_output;
		return (-1);
		}

	while (fgets (inbuf, BUFSIZ, fp) != NULLCHAR)
		{
		if (strnicmp (inbuf, exe_intro, strlen (exe_intro)) == 0)
			{
			configuring = 0;			/* found execute section			*/
			break;
			}

		if (*inbuf != '#' && *inbuf != '\n')
			add_cmd (&cfg_cmd, &cmd_ptr, inbuf);	/* add to list			*/

		}

	if (configuring == 1)
		{
		tprintf ("redial: no \"execute:\" section\n");
		clean_up ();					/* free list						*/
		(void) fclose (fp);
		free (inbuf);
		ifp->raw = rawsave;
		resume (ifp->rxproc);
		tprintf ("\nDial %s complete\n", ifp->name);

		/* Wait for awhile, so the user can read the screen. */
#ifndef ATARI	 
		Pause (10000L);
#endif
		if (dial_inline == 0)
			{
			freesession (sp);
			}
			
		Curproc->input = save_input;
		Curproc->output = save_output;
		return (-1);
		}

	cmd_ptr = exe_cmd;					/* read in execute commands			*/
	while (fgets (inbuf, BUFSIZ, fp) != NULLCHAR)
		{
		if (*inbuf != '#' && *inbuf != '\n')
			add_cmd (&exe_cmd, &cmd_ptr, inbuf);	/* add to list			*/

		}

	(void) fclose (fp);
	intmp = mallocw (BUFSIZ);

	/* Do the configure commands. */

	cmd_ptr = cfg_cmd;
	while (next_cmd (inbuf, &cmd_ptr) != NULLCHAR)
		{
		strcpy (intmp, inbuf);
		rip (intmp);
		log (-1, "%s dialer: %s", ifp->name, intmp);
		if ((result = cmdparse (cfg_cmds, inbuf, ifp)) != 0)
			{
			tprintf ("\ninput line: %s\n", intmp);
			break;
			}
			
		}	/* while (next_cmd (inbuf, &cmd_ptr) != NULLCHAR) */

	/* Do the execute commands. */

	nr_ptr = number;					/* initialize number pointer		*/
	if (result == 0)
		{
		interrupted = 0;
		for (i = 0; i < retries; i++)
			{
			if (pwait (NULL) == EABORT)
				break;						/* we are being killed			*/

			cmd_ptr = exe_cmd;
			while (next_cmd (inbuf, &cmd_ptr) != NULLCHAR)
				{
				int c;						/* input character				*/
				
				/* Poll the keyboard for 1/10 of a second to
				 * see if <ESC> has been pressed.
				 */

				if (dial_inline != 0)
					{
					alarm (100L);
					if ((c = recvchar (Curproc->input)) != EOF)
						{
						alarm (0L);
						recv_mbuf(Curproc->input,NULLBUFP,0,NULLCHAR,0);
						if (c == 0x01b)
							{
							/* <ESC> pressed - give up. */
							
							interrupted = 1;
							result = -1;
							tprintf ("interrupted\n");
							break;
							}

						}

					alarm (0L);
					}	/* if (dial_inline != 0) */

				/* Process script command. */
				
				strcpy (intmp, inbuf);
				rip (intmp);
				log (-1, "%s dialer: %s", ifp->name, intmp);
				if ((result = cmdparse (dial_cmds, inbuf, ifp)) != 0)
					{
					tprintf ("\ninput line: %s\n", intmp);
					break;
					}
			
				}	/* while (next_cmd (inbuf, &cmd_ptr) != NULLCHAR) */

			if (result == 0 || interrupted != 0)
				break;					/* dial succeeded					*/

			}	/* for (i = 0; i < retries; i++) */

		}	/* if (result == 0) */

	clean_up ();						/* free lists						*/
	free (inbuf);
	free (intmp);

	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!
	 */
#ifndef ATARI
	Pause (10000L);
#endif
	if (dial_inline == 0)
		{
		freesession (sp);
		}
		
	Curproc->input = save_input;
	Curproc->output = save_output;
	return result;
	}	/* static int redial (ifp, file) */


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_control (argc, argv, p) */


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_send (argc, argv, p) */


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_speed (argc, argv, p) */


/*
	dodial_status - Bring interface up or down
*/
	
static int dodial_status (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]));  produces crash? */
		
	(*ifp->iostatus) (ifp, param, 0);

	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 = asy_kb_poll (ifp->dev)) != -1)
			{
			tputc (c &= 0x7F);
			tflush ();
			}

		alarm (0L);
		return 0;
		}
	else
		{
		register char *cp = argv[2];

		while (*cp != '\0' && (c = asy_kb_poll (ifp->dev)) != -1)
			{
			tputc (c &= 0x7F);
			tflush ();

			if (*cp++ != c)
				{
				cp = argv[2];
				}
				
			}	/* while (*cp != '\0' && (c = asy_kb_poll (ifp->dev)) != -1) */

		if (argc > 3)
			{
			if (stricmp (argv[3], "speed") == 0)
				{
				int16 speed = 0;

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

					if (isdigit (c))
						{
						speed *= 10;
						speed += c - '0';
						}
					else
						{
						alarm (0L);
						return asy_speed (ifp->dev, speed);
						}
						
					}	/* while ((c = asy_kb_poll (ifp->dev)) != -1) */
					
				}	/* if (stricmp (argv[3], "speed") == 0) */
			else
				{
				return -1;
				}
				
			}	/* if (argc > 3) */
			
		}	/* if (argc != 2) */
		
	alarm (0L);
	return (c == -1);
	}	/* static int dodial_wait (argc, argv, p) */


/****************************************************************************
*	cfg_init																*
*	Sets up the modem initialization string.								*
****************************************************************************/

static int cfg_init (argc, argv, p)
int argc;
char **argv;
void *p;
	{
	if (init != NULL)
		free (init);

	if (strlen (argv[1]) == 0)
		{
		init = NULL;
		return (0);
		}
		
	init = mallocw (strlen (argv[1]) + 1);
	(void) strcpy (init, argv[1]);
	return (0);
	}	/* static int cfg_init (argc, argv, p) */


/****************************************************************************
*	cfg_dial_cmd															*
*	Sets up the modem dial command.											*
****************************************************************************/

static int cfg_dial_cmd (argc, argv, p)
int argc;
char **argv;
void *p;
	{
	if (dial_cmd != NULL)
		free (dial_cmd);
		
	if (strlen (argv[1]) == 0)
		{
		dial_cmd = NULL;
		return (0);
		}
		
	dial_cmd = mallocw (strlen (argv[1]) + 1);
	(void) strcpy (dial_cmd, argv[1]);
	return (0);
	}	/* static int cfg_dial_cmd (argc, argv, p) */


/****************************************************************************
*	cfg_ld_code																*
*	Sets up the long distance code.											*
****************************************************************************/

static int cfg_ld_code (argc, argv, p)
int argc;
char **argv;
void *p;
	{
	if (ld_code != NULL)
		free (ld_code);
		
	if (strlen (argv[1]) == 0)
		{
		ld_code = NULL;
		return (0);
		}
		
	ld_code = mallocw (strlen (argv[1]) + 1);
	(void) strcpy (ld_code, argv[1]);
	return (0);
	}	/* static int cfg_ld_code (argc, argv, p) */


/****************************************************************************
*	cfg_retries																*
*	Sets up the number of dial retries.										*
****************************************************************************/

static int cfg_retries (argc, argv, p)
int argc;
char **argv;
void *p;
	{
	(void) sscanf (argv[1], "%u", &retries);
	if (retries <= 0)
		return (-1);					/* must be >= 1						*/

	return (0);
	}	/* static int cfg_retries (argc, argv, p) */


/****************************************************************************
*	cfg_number																*
*	Adds a number to the dial list.											*
****************************************************************************/

static int cfg_number (argc, argv, p)
int argc;
char **argv;
void *p;
	{
	add_cmd (&number, &nr_ptr, argv[1]);
	return (0);
	}	/* static int cfg_number (argc, argv, p) */


/****************************************************************************
*	internal_send															*
*	Sends a string to dodial_send ().										*
****************************************************************************/

static int internal_send (ptr, p)
char *ptr;
void *p;
	{
	char *argv[2];

	argv[0] = NULL;
	argv[1] = ptr;
	return (dodial_send (2, argv, p));
	}	/* static int internal_send (ptr, p) */
	

/****************************************************************************
*	dodial_init																*
*	Send the initialisation string to the modem.							*
****************************************************************************/

static int dodial_init (argc, argv, p)
int argc;
char **argv;
void *p;
	{
	if (init == NULL)
		return (0);						/* no initialization string - ok	*/

	return (internal_send (init, p));
	}	/* static int dodial_init (argc, argv, p) */


/****************************************************************************
*	dodial_dial																*
*	Send the dialer commands and the next number to the modem.				*
****************************************************************************/

static int dodial_dial (argc, argv, p)
int argc;
char **argv;
void *p;
	{
	char *buf;							/* -> number buffer					*/
	int rc;								/* result code						*/

	if (number == NULL)
		return (-1);					/* no numbers specified				*/
		
	buf = mallocw (BUFSIZ);				/* get a buffer for the number		*/
	if (dial_cmd != NULL)
		{
		/* Send the dial command. */
		
		if ((rc = internal_send (dial_cmd, p)) != 0)
			{
			free (buf);
			return (rc);
			}

		}	/* if (dial_cmd != NULL) */

	if (ld_code != NULL)
		{
		/* Send the long distance code. */
		
		if ((rc = internal_send (ld_code, p)) != 0)
			{
			free (buf);
			return (rc);
			}

		}	/* if (ld_code != NULL) */

	/* Send the next number. */

	if (next_cmd (buf, &nr_ptr) == NULLCHAR)
		{
		/* Try resetting the pointer. */

		nr_ptr = number;
		if (next_cmd (buf, &nr_ptr) == NULLCHAR)
			{
			free (buf);					/* gone badly wrong					*/
			return (-1);
			}

		}	/* if (next_cmd (buf, nr_ptr) == NULLCHAR) */

	if ((rc = internal_send (buf, p)) != 0)	/* send the number				*/
		{
		free (buf);
		return (rc);
		}

	rc = internal_send ("\r", p);
	free (buf);
	return (rc);
	}	/* static int dodial_dial (argc, argv, p) */


/****************************************************************************
*	dodial_cwait															*
*	Conditional version of dodial_wait ().									*
****************************************************************************/

static int dodial_cwait (argc, argv, p)
int argc;
char *argv[];
void *p;
	{
	struct iface *ifp = p;
	register int c = -1;
	int speedarg;						/* index of "speed" argument		*/
	int lastarg;						/* index of last argument			*/
	int i;								/* loop counter						*/
	char failed;						/* failure flag - nz means failed	*/
	
	alarm (atol (argv[1]));

	lastarg = argc - 1;
	if (stricmp (argv[lastarg], "speed") == 0)
		{
		speedarg = lastarg;
		lastarg--;
		}
	else
		speedarg = 0;					/* no "speed" argument				*/
		
	if (argc == 2)
		{
		/* Wait for duration expiry only. */
		
		while ((c = asy_kb_poll (ifp->dev)) != -1)
			{
			tputc (c &= 0x7F);
			tflush ();
			}

		alarm (0L);
		return 0;
		}
	else
		{
		register char *cp = argv[2];	/* -> success string				*/
		char **fail_ptr;				/* array of ptrs to failure strings	*/

		/* Set up the array of failure string pointers. */

		fail_ptr = (char **) mallocw ((lastarg - 2) * sizeof (char *));
		for (i = 0; i < (lastarg - 2); i++)
			fail_ptr[i] = argv[i + 3];

		/* Looking for string matches. */

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

			/* Check the success string first. */

			if (*cp == '\0')
				break;					/* we got a connect					*/
				
			if (*cp++ != c)
				{
				cp = argv[2];
				}

			/* Now check the failure strings. */

			for (i = 0; i < (lastarg - 2); i++)
				{
				if (*fail_ptr[i] == '\0')
					{
					failed = 1;
					break;				/* we lost							*/
					}

				if (*fail_ptr[i]++ != c)
					{
					fail_ptr[i] = argv[i + 3];
					}

				}

			if (failed)
				break;
				
			}	/* while ((c = asy_kb_poll (ifp->dev)) != -1) */
				
		free (fail_ptr);				/* give back heap memory			*/
		
		/* If we get here, we may have matched the "success" string. */

		if (*cp != '\0')
			{
			alarm (0L);
			return (-1);				/* we didn't						*/
			}
			
		if (speedarg != 0)
			{
			int16 speed = 0;

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

				if (isdigit (c))
					{
					speed *= 10;
					speed += c - '0';
					}
				else
					{
					alarm (0L);
					return asy_speed (ifp->dev, speed);
					}
						
				}	/* while ((c = asy_kb_poll (ifp->dev)) != -1) */
					
			}	/* if (stricmp (lastarg, "speed") == 0) */
			
		}	/* if (argc != 2) */
		
	alarm (0L);
	return (c == -1);
	}	/* static int dodial_cwait (argc, argv, p) */

	
/****************************************************************************
*	add_cmd																	*
*	Add a string to the end of the list whose header is pointed to by 		*
*	<*hdr>.  <*ptr>	(if non-NULL) points to the last item in the list.		*
*	<buf> points to the data to be added to the list and is assumed to be	*
*	an ASCIIZ string.														*
****************************************************************************/

static void add_cmd (hdr, ptr, buf)
ITEM **hdr;
ITEM **ptr;
char *buf;
	{
	if (strlen (buf) == 0)
		return;
		
	if (*hdr == NULL)
		{
		/* Empty list. */

		*hdr = (ITEM *) mallocw (sizeof (ITEM));
		*ptr = *hdr;
		}
	else
		{
		(*ptr)->next = (ITEM *) mallocw (sizeof (ITEM));
		*ptr = (*ptr)->next;
		}

	(*ptr)->data = (char *) mallocw (strlen (buf) + 1);
	(*ptr)->next = NULL;
	(void) strcpy ((*ptr)->data, buf);
	}	/* static void add_cmd (hdr, ptr, buf) */


/****************************************************************************
*	next_cmd																*
*	Copies the data part of the item pointed to by <*ptr> into the buffer	*
*	pointed to by <buf>.  This area is assumed to be large enough to hold	*
*	the data.  <*ptr> is advanced to point to the next item.  Returns <buf>	*
*	if there is another item, NULLCHAR at the end of the list.				*
****************************************************************************/
	
static char *next_cmd (buf, ptr)
char *buf;
ITEM **ptr;
	{
	if (*ptr == NULL)
		return (NULLCHAR);
		
	(void) strcpy (buf, (*ptr)->data);
	*ptr = (*ptr)->next;
	return (buf);
	}	/* static char *next_cmd (buf, ptr) */
	

/****************************************************************************
*	clean_up																*
*	Frees the heap memory used by the various lists.						*
****************************************************************************/

static void clean_up ()
	{
	clean (&number);
	clean (&cfg_cmd);
	clean (&exe_cmd);
	if (init != NULL)
		{
		free (init);
		init = NULL;
		}

	if (dial_cmd != NULL)
		{
		free (dial_cmd);
		dial_cmd = NULL;
		}
		
	if (ld_code != NULL)
		{
		free (ld_code);
		ld_code = NULL;
		}

	retries = 1;		
	}	/* static void clean_up () */


/****************************************************************************
*	clean																	*
*	Free up the list pointed to by <*hdr>.									*
****************************************************************************/

static void clean (hdr)
ITEM **hdr;
	{
	ITEM *p, *q;						/* list followers					*/

	p = *hdr;
	while (p != NULL)
		{
		q = p->next;
		free (p->data);
		free (p);
		p = q;
		}

	*hdr = NULL;
	}	/* static void clean (hdr) */


/****************************************************************************
*	asy_kb_poll																*
*	Polls the async interface for incoming characters and the keyboard for	*
*	<ESC>.  Returns the character on the async receive queue or -1 if timed	*
*	out or <ESC> detected.													*
****************************************************************************/

static int asy_kb_poll (dev)
int dev;
	{
	int c;								/* input character					*/

	for (;;)
		{
		/* Check keyboard. */
		
		if (socklen (Curproc->input,0) > 0)
			{
			c = recvchar (Curproc->input);
			if (c != EOF)
				{
				recv_mbuf (Curproc->input, NULLBUFP, 0, NULLCHAR, 0);
				if (c == 0x1b)
					{
					/* <ESC> pressed - give up. */

					interrupted = 1;
					c = -1;
					break;
					}

				}	/* if (c != EOF) */

			}	/* if (socklen (Curproc->input,0) > 0) */

		/* Check async. */

		if (asy_len (dev) > 0)
			{
			c = get_asy (dev);
			break;
			}

		/* Let someone else run. */

		if (pwait (0) != 0)
			{
			c = -1;
			break;
			}

		}	/* for (;;) */

	return (c);
	}	/* static int asy_kb_poll (dev) */
