/*
** Copyright 1992
** Orest Zoborowski, Patrick Sweeney
*/
#include <stdio.h>
#include <sys/types.h>
#include <termios.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/time.h>
#include "script.h"
#include "ll2.h"

#define ESC 035 /* ^] */

static struct termios o_tios;

int
esc(int modem)
{
	char buf[1024];
	struct termios tios;
	int n;

	fprintf(stderr, "\r>>cmd: ");
	if (tcgetattr(0, &tios) < 0)
	{
		perror("TCGETS");
		exit(1);
	}
	if (tcsetattr(0, TCSAFLUSH, &o_tios) < 0)
	{
		perror("TCSETS");
		exit(1);
	}
	fgets(buf, sizeof(buf), stdin);
	if (tcsetattr(0, TCSAFLUSH, &tios) < 0)
	{
		perror("TCSETS");
		exit(1);
	}

	if ((n = strlen(buf)) > 0 && buf[n-1] == '\n')
		buf[--n] = '\0';
	else
		buf[0] = '\0';

	if (!strcmp(buf, "quit") || !strcmp(buf, "exit"))
		return 1;
	else if (!strncmp(buf, "local ", 6))
	{
		system(buf + 6);
		if (tcsetattr(0, TCSAFLUSH, &tios) < 0)
		{
			perror("TCSETS");
			exit(1);
		}
	}
	else if (!strncmp(buf, "remote ", 7))
	{
		int pid;

		switch (pid = fork())
		{
		case -1:
			fprintf(stderr, "can't fork\n");
			break;

		case 0:
			dup2(modem, 0);
			dup2(modem, 1);
			dup2(modem, 2);
			close(modem);
			execl("/bin/sh", "sh", "-c", buf+7, NULL);
			_exit(0);

		default:
			waitpid(pid, NULL, 0);
		}
		if (tcsetattr(0, TCSAFLUSH, &tios) < 0)
		{
			perror("TCSETS");
			exit(1);
		}
	}
	else
		fprintf(stderr, "unknown line \"%s\"\n", buf);

	fputs("\r<resume...>\n", stderr);

	return 0;
}

int
cmd(int modem)
{
	fd_set r_fds, w_fds;
	char ch;

	while (1)
	{
		FD_ZERO(&r_fds);
		FD_SET(0, &r_fds);
		FD_SET(modem, &r_fds);

		if (select(32, &r_fds, NULL, NULL, NULL) <= 0)
			break;

		if (FD_ISSET(0, &r_fds))
		{
			if (read(0, &ch, 1) <= 0)
			{
				perror("read (0)");
				break;
			}
			if (ch == ESC)
			{
				if (esc(modem))
					break;
			}
			else if (write(modem, &ch, 1) <= 0)
			{
				perror("write (modem)");
				break;
			}
		}

		if (FD_ISSET(modem, &r_fds))
		{
			if (read(modem, &ch, 1) <= 0)
			{
				perror("read (modem)");
				break;
			}
			if (write(1, &ch, 1) <= 0)
			{
				perror("write (1)");
				break;
			}
		}
	}

	return 0;
}

static int
watch_line(int modem, char *str, int timeout)
{
	fd_set r_fds;
	char ch;
	char *p;
	struct timeval tv;
	struct timeval *tvp;
	int r;

	p = str;
	tv.tv_sec = timeout;
	tvp = (timeout != 0) ? &tv : NULL;
	while (1)
	{
		FD_ZERO(&r_fds);
		FD_SET(modem, &r_fds);
		if ((r = select(32, &r_fds, NULL, NULL, tvp)) < 0)
			return -1;
		if (r == 0)
		{
			if (timeout == 0)
				return -1;
			return 1;
		}
		if (FD_ISSET(modem, &r_fds))
		{
			if (read(modem, &ch, 1) <= 0)
			{
				perror("read (modem)");
				break;
			}
			if (ch == *p)
				p++;
			if (*p == '\0')
				break;
			if (timeout != 0)
				write(1, &ch, 1);
		}
	}
	return 0;
}

static int
substitute(char *dst, char *src, char letter, char *sub)
{
	while(*src)
	{
		if (*src == '%')
		{
			src++;
			if (*src == '%')
			{
				*dst = '%';
				dst++;
				src++;
			}
			else if (*src == letter)
			{
				src++;
				while(*sub)
				{
					*dst = *sub;
					dst++;
					sub++;
				}
			}
			else
			{
				*dst = '%';
				dst++;
			}
		}
		else
		{
			*dst = *src;
			dst++;
			src++;
		}
	}
	*dst = '\0';
	return 0;
}

static int
dial_modem(int modem, Modem *m, System *s)
{
	char dstr[MAX_MODEMSTR];
	char buf[MAX_MODEMSTR];
	
#ifdef DEBUG
	fprintf(stderr,"dialing: %s    phone #:%s\n", m->device, s->phone1);
#endif /* DEBUG */
	write(modem, m->resetstr, strlen(m->resetstr));
	watch_line(modem, m->resetstr_r, 0);
	substitute(dstr, m->dialstr, 'd', s->phone1);
	write(modem, dstr, strlen(dstr));
	sprintf(buf, "%d", s->baudrate);
	substitute(dstr, m->dialstr_r, 'b', buf);
	watch_line(modem, dstr, 0);
	return 0;
}

int
do_script(int modem, System *s)
{
	int i;
	Scriptentry *e;
	int r;
	int tries = 0;
	struct termios tios;

	e = s->entries;
	for(i=0; i<s->nentries; i++, e++)
	{
		if (e->type == ENTRY_LOCAL)
		{
			int pid;
			
			switch (pid = fork())
			{
			case -1:
				fprintf(stderr, "can't fork\n");
				break;
			case 0:
				close(modem);
				execl("/bin/sh", "sh", "-c", e->v.exec.cmd,
				      NULL);
				_exit(0);
			default:
				if (e->v.exec.wait)
					waitpid(pid, NULL, 0);
			}
			if (tcsetattr(0, TCSAFLUSH, &tios) < 0)
			{
				perror("TCSETS");
				return -1;
			}
			continue;
		}
		if (e->type == ENTRY_REMOTE)
		{
			int pid;
			
			switch (pid = fork())
			{
			case -1:
				fprintf(stderr, "can't fork\n");
				break;
			case 0:
				dup2(modem, 0);
				dup2(modem, 1);
				dup2(modem, 2);
				close(modem);
				execl("/bin/sh", "sh", "-c", e->v.exec.cmd,
				      NULL);
				_exit(0);
			default:
				if (e->v.exec.wait)
					waitpid(pid, NULL, 0);
			}
			if (tcsetattr(0, TCSAFLUSH, &tios) < 0)
			{
				perror("TCSETS");
				return -1;
			}
			continue;
		}
		if (e->v.expect.recv && e->v.expect.recv != '\0')
		{
			while(1)
			{
				if ((r = watch_line(modem, e->v.expect.recv,
						    s->timeout)) != 0)
				{
					if (r < 0)
						return -1;
					if (r > 0)
					{
						write(modem,
					      e->v.expect.err_send,
					      strlen(e->v.expect.err_send));
						tries++;
						if (tries == s->retries)
						{
							fprintf(stderr,
							  "timed out!\n");
							return -1;
						}
					}
				}
				else
					break;
			}
		}
		if (e->v.expect.send && e->v.expect.send != '\0')
			write(modem, e->v.expect.send,
			      strlen(e->v.expect.send));
	}
	return 0;
}

main(int ac, char **av)
{
	int n;
	static struct termios n_tios;
	int baud;
	int modem;
	Ll2e *e;
	System *s;
	Systeminfo info;
	Modem *m;

	if (ac != 2)
	{
		fprintf(stderr, "usage: %s <system name>\n", av[0]);
		exit(1);
	}

	if (script_parse(NULL, &info) != 0)
		exit(1);

	e = info.syslist->first;
	while(e)
	{
		s = (System *) e->data;
		if (strcmp(s->system_name, av[1]) == 0)
			break;
		e = e->next;
	}
	if (e == NULL)
	{
		fprintf(stderr, "undefined system: %s\n", av[1]);
		exit(1);
	}
	e = info.modems->first;
	while(e)
	{
		m = (Modem *) e->data;
		if (strcmp(m->name, s->dialer) == 0)
			break;
		e = e->next;
	}
	if (e == NULL)
	{
		fprintf(stderr, "can't find dialer: %s\n", s->dialer);
		exit(1);
	}
	switch (s->baudrate)
	{
	case 2400:
		baud = B2400;
		break;

	case 9600:
		baud = B9600;
		break;
		
	default:
		fprintf(stderr, "unknown baudrate %d\n", s->baudrate);
		exit(1);
	}
	
	if ((modem = open(m->device, O_RDWR)) < 0)
	{
		perror("main:open");
		exit(1);
	}
	
	memset(&n_tios, 0, sizeof(n_tios));
	n_tios.c_iflag = IGNBRK | IGNPAR | ISTRIP;
	switch(m->databits)
	{
	case 5: n_tios.c_cflag = CS5; break;
	case 6: n_tios.c_cflag = CS6; break;
	case 7: n_tios.c_cflag = CS7; break;
	case 8: n_tios.c_cflag = CS8; break;
	}
	n_tios.c_cflag |= baud | CREAD | HUPCL;
	if (m->stopbits == 2)
		n_tios.c_cflag |= CSTOPB;
	if (tcsetattr(modem, TCSAFLUSH, &n_tios) < 0)
	{
		perror("main:TCSETS modem");
		exit(1);
	}
	
	fprintf(stderr, "modem %s opened as %d\n", m->device, modem);

	if (tcgetattr(0, &o_tios))
	{
		perror("main:TCGETS 0");
		exit(1);
	}
	n_tios = o_tios;
	n_tios.c_iflag &= ~(ICRNL);
	n_tios.c_lflag &= ~(ICANON | ECHO | ISIG);
	n_tios.c_cc[VMIN] = 1;
	n_tios.c_cc[VTIME] = 0;
	if (tcsetattr(0, TCSAFLUSH, &n_tios) < 0)
	{
		perror("main:TCSETS 0 ");
		exit(1);
	}

	if (dial_modem(modem, m, s) != 0)
		exit(1);
	if (do_script(modem, s) != 0)
		exit(1);
	if (cmd(modem) != 0)
		exit(1);

	close(modem);

	if (tcsetattr(0, TCSAFLUSH, &o_tios) < 0)
	{
		perror("main:TCSETS 0 ");
		exit(1);
	}

	exit(0);
}
