/*
** Copyright 1992
** Orest Zoborowski, Patrick Sweeney
**
** Copyright 1993
** Stig@netcom.com (Jonathan Stigelman)
*/
#include <stdio.h>
#include <sys/types.h>
#include <termios.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/time.h>
#include <assert.h>
#include "script.h"
#include "ll2.h"

#define ESC 035			/* ^] */

static struct termios o_tios;

void gets_noecho(char *buf, int size)
{
    struct termios tios, o_tios;

    if (tcgetattr(0, &tios) < 0) {
	perror("TCGETS");
	exit(1);
    }
    o_tios = tios;
    tios.c_lflag &= ~ECHO;
    if (tcsetattr(0, TCSAFLUSH, &tios) < 0) {
	perror("TCSETS");
	exit(1);
    }
    fgets(buf, size, stdin);
    if (tcsetattr(0, TCSAFLUSH, &o_tios) < 0) {
	perror("TCSETS");
	exit(1);
    }
}

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)) {
	fprintf(stderr, "LOCAL: %s\n", buf + 6);
	system(buf + 6);
	if (tcsetattr(0, TCSAFLUSH, &tios) < 0) {
	    perror("TCSETS");
	    exit(1);
	}
    } else if (!strncmp(buf, "remote ", 7)) {
	int pid;

	fprintf(stderr, "REMOTE: %s\n", buf + 6);
	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 void substitute(char *dst, char *src, char letter, char *sub)
{
    while (*src) {
	if ((src[0] == '%') && (src[1] == letter)) {
	    src += 2;
	    while (*sub)
		*dst++ = *sub++;
	} else {
	    *dst++ = *src++;
	}
    }
    *dst = '\0';
}

static int smart_watch_fd(int fd, char **strings, int timeout, int echoflag)
/*
 * Function:    smart_watch_fd
 * Purpose:	watch a file descriptor for any of several strings
 * Parameters:	int fd		file descriptor
 *		char **strings	NULL terminated array of strings (10 max)
 *		int timeout	timeout in seconds
 *		int echoflag	echo characters read from fd to stdout
 * Returns:	the index of the string detected, -1 on timeout, -2 on error
 */
{
    struct timeval tv;
    struct timeval *tvp;
    fd_set r_fds;
    char ch, *ptr[10];
    int r, i;

    for (i = 0; (i < 10) && strings[i]; i++) {
	ptr[i] = strings[i];
    }

    tv.tv_sec = timeout;
    tvp = (timeout != 0) ? &tv : NULL;
    while (1) {
	FD_ZERO(&r_fds);
	FD_SET(fd, &r_fds);
	if ((r = select(32, &r_fds, NULL, NULL, tvp)) < 0) {
	    perror("smart_watch_fd (select)");
	    return -2;
	}
	if (r == 0) {
	    assert(timeout != 0);
	    return -1;
	}
	if (FD_ISSET(fd, &r_fds)) {
	    if (read(fd, &ch, 1) <= 0) {
		perror("smart_watch_fd (read)");
		return -2;
	    }
	    if (echoflag)
		write(1, &ch, 1);
	    for (i = 0; (i < 10) && strings[i]; i++) {

		if (ch == *ptr[i]) {	/* if this character matches the one
					expected... */
		    ptr[i]++;	/* then look for next char */
		} else {
		    ptr[i] = strings[i];	/* otherwise, reset pointer */
		}
		if (*ptr[i] == '\0')
		    return i;
	    }
	}
    }
    assert(0);
}

static int watch_fd(int fd, char *str, int timeout, int echoflag)
/*
 * Function:    watch_fd
 * Purpose:	wrapper for smart_watch_fd 
 * Parameters:	fd	  file descriptor
 *		str	  string to expect
 *		timeout	  max time to wait (in seconds)
 *		echoflag  echo data from the file descriptor?
 * Returns:	0 on success, -1 for timeout, -2 if some error occurred
 */
{
    char *strs[2];
    int r;

    strs[0] = str;
    strs[1] = (char *) 0;
    return smart_watch_fd(fd, strs, timeout, echoflag);
}

static int watch_line(int modem, char *str, int timeout)
/*
 * Function:    watch_line
 * Purpose:	reverse compatibility...wrapper for watch_fd
 * Parameters:	modem		file descriptor
 *		str		string to wait for
 *		timeout		# of seconds to wait
 * Returns:	0 on success, 1 on timeout, -1 on error
 */
{
    int r;
    r= watch_fd(modem, str, timeout, (timeout != 0));
    return ( (r==0) ? 0 : ((r==-1)? 1 : -1) );
}

char *result_strings[] =
{
    "CONNECT",
    "BUSY",
    "NO DIALTONE",
    "NO CARRIER",
    "VOICE",
    0
};

static int dial_modem(int modem, Modem * m, System * s)
{
    char dstr[MAX_MODEMSTR];
    char buf[MAX_MODEMSTR];
    char *num;
    int tries, success, result;

    write(modem, m->resetstr, strlen(m->resetstr));
    if (watch_fd(modem, m->resetstr_r, 30, 0) < 0) {
	fprintf(stderr, "Timeout:  modem not responding\n");
	exit(1);
    }
    substitute(dstr, m->dialstr, 'd', s->phone1);
    for (tries = 0, success = 0; !success && (tries < s->retries); tries++) {
	num = ((tries % 2) ? s->phone1 : s->phone2);
	fprintf(stderr, "dialing: %s    phone #:%s   ", m->device, num);
	write(modem, dstr, strlen(dstr));
	if (result = smart_watch_fd(modem, result_strings, 30, 0)) {
	    fprintf(stderr, "result: %s\n", (result < 0) ? "TIMEOUT" : result_strings[result]);
	} else {
	    fprintf(stderr, "Connection established!\n");
	    success = 1;
	}
    }
    if (!success) {
	fprintf(stderr,"Connection failed.\n");
	exit(1);
    }
    if (tries > 1)
	fprintf(stderr, "\7\7\7\7\7");
    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) {
			fprintf(stderr,"<<fet timeout>>\n");
			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;
    char *target;		/* the name of the system to dial */
    char *password[64];		/* that's one HELL of a long password! */

    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;
    target = ((ac == 2) ? av[1] : e->data);
    while (e) {
	s = (System *) e->data;
	if (strcmp(s->system_name, target) == 0)
	    break;
	e = e->next;
    }
    if (e == NULL) {
	fprintf(stderr, "undefined system: %s\n", target);
	exit(1);
    }
#if 0
    printf("Enter your password for %s: ", target);
    gets(password);
#endif
    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;

    case 19200:
	baud = B19200;
	break;

    case 38400:
	baud = B38400;
	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 at %d baud\n", m->device, s->baudrate);

    if (dial_modem(modem, m, s) != 0)
	exit(1);
    /* leave the tty settings alone until after the connection is
	   established...this way you can ^C if the line is busy */
    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 (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);
}
