
/*
 * DUDISCDEV.C	- program to manage a single SLIP connection
 *
 * (c)Copyright 1994, Matthew Dillon, All rights reserved.  This code may
 *    be used in any project, commercial or otherwise, as long as I am given
 *    appropriate credit and as long as this copyright remains intact in all
 *    source and document files.
 *
 * DUDISCDEV -r rep -i interface -d ttydevice -s speed [-e expect] [-t encap]
 *
 * -r <rep>		0 to report failures, 1 to not report failures
 * -i <interface>	interface base, default is 'sl'
 * -d <ttydevice>	tty device, default is '/dev/cua0'
 * -s <speed>		baud rate to set the port to, default 38400
 * -e <script>		script file to run after tty modes are set (optional)
 *
 *			The script file is responsible for connecting to 
 *			the remote side.  It must return with an exit code
 *			of 2 for DUDISCDEV to continue.
 *
 * -t <encap>		encapsulation.  If not specified, the encapsulation
 *			is not set, indicating the kernel-compiled-in default
 *			should be used.
 *
 * -m mtu		can be 0, indicating no change, or left out, indicating
 *			no change.
 *
 * DUDISCDEV returns the detached monitor program's pid which the master
 * script can monitor.
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <signal.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <termios.h>

#define MS 1000

void eprintf(const char *ctl, ...);
void sigTerm(int signo);
void iwait(long uS);

short ReportFailures = 1;
short RadioMode = 0;
int CLocal = 0;
int ScriptPid = 0;
volatile int TermSignalOccured = 0;
volatile int Lockout = 0;

main(ac, av)
char *av[];
{
    char *ifcName = "sl";
    char *devName = "/dev/cua0";
    char *expectFile = NULL;
    int speed = 38400;
    int i;
    int ifcNo = 0;
    int fd;
    int ttyFd;
    int pid;
    int encap = -1;
    int mtu = 0;


    for (i = 1; i < ac; ++i) {
    	char *ptr = av[i];

    	if (*ptr != '-') {
    	    if (ReportFailures)
		printf("0:unexpected argument: %s\n", ptr);
    	    exit(0);
    	}
    	ptr += 2;
    	switch(ptr[-1]) {
    	case 'r':
    	    ReportFailures = !strtol((*ptr ? ptr : av[++i]), NULL, 0);
    	    break;
	case 'm':
    	    mtu = strtol((*ptr ? ptr : av[++i]), NULL, 0);
    	    break;
    	case 't':
    	    encap = strtol((*ptr ? ptr : av[++i]), NULL, 0);
    	    break;
    	case 'i':
    	    ifcName = (*ptr) ? ptr : av[++i];
    	    break;
    	case 'd':
    	    devName = (*ptr) ? ptr : av[++i];
    	    break;
    	case 's':
    	    /*
    	     * can be 0, indicating that the speed should not be changed
    	     */
    	    speed = strtol((*ptr ? ptr : av[++i]), NULL, 0);
    	    break;
    	case 'e':
    	    expectFile = (*ptr) ? ptr : av[++i];
    	    break;
	case 'c':
	    CLocal = CLOCAL;
	    break;
	case 'R':
	    RadioMode = 1;
	    break;
	default:
    	    if (ReportFailures)
		eprintf("unexpected option\n", ptr - 2);
	    printf("0\n");
	    exit(0);
	}
    }

    /*
     * attempt to open tty
     */

    fd = open(devName, O_RDWR | O_NDELAY);
    if (fd <= 0) {
        if (ReportFailures)
	    eprintf("couldn't open %s\n", devName);
	printf("0\n");
        exit(0);
    }
    fcntl(fd, F_SETFL, O_RDWR);

    /*
     * If radio mode, turn off DTR
     */

    if (RadioMode) {
        int v;

        if (ioctl(fd, TIOCMGET, &v) == 0) {
            v &= ~TIOCM_DTR;
            ioctl(fd, TIOCMSET, &v);
        }
    }

    /*
     * fork, return child pid
     */

    signal(SIGCHLD, SIG_IGN);

    pid = fork();
    if (pid > 0) {
    	printf("%d\n", pid);
    	exit(0);
    }
    if (pid < 0) {
        if (ReportFailures)
	    eprintf("couldn't fork\n");
	printf("0\n");
    	exit(0);
    }
    fclose(stdin);
    fclose(stdout);
/*    fclose(stderr); */

    /*
     * CHILD
     *
     * - disconnect from tty, open new tty to make controlling terminal
     */

    {
	int ttyfd = open("/dev/tty", O_RDWR);
	if (ttyfd >= 0) {
	    ioctl(ttyfd, TIOCNOTTY, 0);
	    close(ttyfd);
	}
	ttyfd = open(devName, O_RDWR | O_NDELAY);
	if (ttyfd < 0) {
	    if (ReportFailures)
		eprintf("couldn't reopen %s\n", devName);
	    exit(0);
	}
	fcntl(ttyfd, F_SETFL, O_RDWR);
	{
	    int n = N_TTY;
	    ioctl(ttyfd, TIOCSETD, &n);
	}
	close(fd);
	if (ttyfd != 0)
	    dup2(ttyfd, 0);
	if (ttyfd != 1)
	    dup2(ttyfd, 1);
/*
	if (ttyfd != 2)
	    dup2(ttyfd, 2);
*/
	if (ttyfd > 2)
	    close(ttyfd);
    }

    signal(SIGTERM, sigTerm);

    /*
     * terminal modes
     */

    {
        struct termios tios;
        int baud = tios.c_cflag & CBAUD;

        switch(speed) {
        case 0:
            break;
	case 50:
	    baud = B50;
	    break;
	case 75:
	    baud = B75;
	    break;
	case 110:
	    baud = B110;
	    break;
	case 134:
	    baud = B134;
	    break;
	case 150:
	    baud = B150;
	    break;
	case 200:
	    baud = B200;
	    break;
	case 300:
	    baud = B300;
	    break;
	case 600:
	    baud = B600;
	    break;
	case 1200:
	    baud = B1200;
	    break;
	case 1800:
	    baud = B1800;
	    break;
	case 2400:
	    baud = B2400;
	    break;
	case 4800:
	    baud = B4800;
	    break;
	case 9600:
	    baud = B9600;
	    break;
	case 19200:
	    baud = B19200;
	    break;
	case 38400:
	    baud = B38400;
	    break;
	default:
	    if (ReportFailures)
		eprintf("bad baud baud rate\n");
	    exit(0);
	}

        if (ioctl(0, TCGETS, &tios) != 0) {
            perror("TCGETS");
            exit(0);
        }
        bzero(tios.c_cc, sizeof(tios.c_cc));
        tios.c_iflag = IGNBRK|IGNPAR;
        tios.c_oflag = 0;
        tios.c_cflag = CRTSCTS|CS8|CREAD|HUPCL|baud|CLocal;
        tios.c_lflag = 0;

        if (ioctl(0, TCSETS, &tios) != 0) {
            perror("TCSETS");
            exit(0);
        }
    }

    /*
     * I/O error (nothing connected?)
     */

    sleep(1);
    if (CLocal == 0) {
	write(1, "\r", 1);
	sleep(1);
	if (write(1, "\r", 1) != 1) {
	    if (ReportFailures)
		eprintf("I/O error on port\n");
	    exit(0);
	}
    }

    /*
     * run expect script
     */

    if (expectFile) {
        int rc = 0;
        char *tmp = malloc(strlen(expectFile) + 8);

        sprintf(tmp, "exec %s", expectFile);

        /*
         * There is a window of opportunity when a SIGTERM occurs just
         * as we are forking.  The lockout ensures that we kill any
         * child process we have created.
         */

        ++Lockout;

        if ((ScriptPid = fork()) == 0) {
            execl("/bin/sh", "/bin/sh", "-c", tmp, NULL, NULL);
            exit(0);
        }

        --Lockout;

        free(tmp);

        if (TermSignalOccured)
            sigTerm(SIGTERM);

        if (ScriptPid > 0) {
            int st;

	    while (wait4(ScriptPid, &st, 0, NULL) != ScriptPid)
		;
	    rc = WEXITSTATUS(st);
	}
	ScriptPid = 0;
	if (rc != 2) {
	    if (ReportFailures)
		eprintf("expect script failed\n");
	    exit(0);
	}
    }

    /*
     * check carrier
     */

    sleep(1);

    if (CLocal == 0) {
	int n = 0;

	ioctl(0, TIOCMGET, &n);
	if ((n & TIOCM_CAR) == 0) {
	    if (ReportFailures)
		eprintf("expected carrier\n");
	    exit(0);
	}
    }

    /*
     * line discipline
     */

    {
    	int n = N_SLIP;

	if ((ifcNo = ioctl(0, TIOCSETD, &n)) < 0) {
	    perror("set line discipline");
	    exit(0);
	}
	if (ioctl(0, TIOCGETD, &n) || n != N_SLIP) {
	    perror("verify line discipline");
	    exit(0);
	}
	if (mtu) {
	    int fd;
	    struct ifreq ifr;

	    if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
	        perror("socket/mtu");
	        exit(0);
	    }
	    sprintf(ifr.ifr_name, "sl%d", ifcNo);
	    if (ioctl(fd, SIOCGIFFLAGS, &ifr) != 0) {
	        perror("gifflags");
	        exit(0);
	    }
	    ifr.ifr_mtu = mtu;
	    if (ioctl(fd, SIOCSIFMTU, &ifr) != 0) {
	        perror("sifmtu");
	        exit(0);
	    }
	    close(fd);
	}
	if (encap >= 0) {
	    n = encap;
	    if (ioctl(0, SIOCSIFENCAP, &n) != 0) {
		if (ReportFailures)
		    eprintf("error setting encapsulation\n");
	    }
	}
    }

    sprintf(av[0], "%s%d", ifcName, ifcNo);

    /*
     * wait for carrier lost
     */

    if (CLocal == 0) {
    	/*
    	 * CD terminates call
    	 */
	for (;;) {
	    int n = 0;

	    ioctl(0, TIOCMGET, &n);
	    if ((n & TIOCM_CAR) == 0)
		break;
	    sleep(5);
	}
    } else if (RadioMode) {
    	/*
    	 * We loop once a second checking for an attempt to transmit.
    	 * When we detect a transmit attempt, we use CSMA with CD
    	 * and assert DTR when there is no carrier detected.
    	 *
    	 * When DTR is asserted,
    	 */

	for (;;) {
	    int tx = 0;
	    int stat = 0;

	    iwait(500 * MS);
	    if (ioctl(0, TIOCOUTQ, &tx) != 0)
	        break;
	    if (ioctl(0, TIOCMGET, &stat) != 0)
	        break;
	    if (tx) {
	        {
	            short cnt;

	    	    if ((stat & TIOCM_CAR) == 0) {
	    	    	/*
	    	    	 * no carrier, ok to transmit.  Assert DTR and
	    	    	 * wait for transmission to complete
	    	    	 */
			stat |= TIOCM_DTR;
			if (ioctl(0, TIOCMSET, &stat) == 0) {
			    for (cnt = 0; cnt < 100; ++cnt) {
				iwait(10 * MS);
				tx = 0;
				if (ioctl(0, TIOCOUTQ, &tx) != 0 || tx == 0)
				    break;
				if (tx == 0)
				    break;
			    }
			}
			iwait(10 * MS);

			/*
			 * release DTR when transmission is complete
			 */
			stat &= ~TIOCM_DTR;
			ioctl(0, TIOCMSET, &stat);
	    	    }
	    	}
	    } else {
		stat &= ~TIOCM_DTR;
		ioctl(0, TIOCMSET, &stat);
	    }
	}
    } else {
    	/*
    	 * Ignore CD
    	 */
        for (;;) 
            sleep(3600);
    }

    eprintf("carrier lost\n");

    /*
     * carrier lost, switch back to TTY line disc.  Must close descriptors
     * first
     */

    {
        int n = N_TTY;

        close(0);
        close(1);
        close(2);
        ioctl(0, TIOCSETD, &n);
    }
    exit(0);
}

void
eprintf(const char *ctl, ...)
{
    va_list va;
    char buf[64];
    time_t t = time(NULL);
    struct tm *tm = localtime(&t);

    strftime(buf, sizeof(buf), "%a %b %d %X %Y", tm);
    fprintf(stderr, "%s ", buf);
    va_start(va, ctl);
    vfprintf(stderr, ctl, va);
    va_end(va);
}

void 
sigTerm(int signo)
{
    struct termios tios;
    struct termios orig;

    if (Lockout) {
    	TermSignalOccured = 1;
    	return;
    }

    if (ScriptPid > 0) {
        kill(ScriptPid, SIGTERM);
        sleep(1);
        kill(ScriptPid, SIGKILL);
    }

    {
        int n = N_TTY;
        ioctl(0, TIOCSETD, &n);
    }
    if (ioctl(0, TCGETS, &orig) == 0) {
        tios = orig;
    	tios.c_cflag &= ~CBAUD;
    	tios.c_cflag |= B0;
        ioctl(0, TCSETS, &tios);
        sleep(2);
        ioctl(0, TCSETS, &orig);
    }
    exit(0);
}

void
idone(int sig)
{
}

void 
iwait(long uS)
{
    struct itimerval itv;
    void *old;

    old = signal(SIGALRM, idone);
    itv.it_value.tv_sec = 0;
    itv.it_value.tv_usec = uS;
    itv.it_interval.tv_sec = 0;
    itv.it_interval.tv_usec = 0;
    setitimer(ITIMER_REAL, &itv, NULL);
    pause();
    signal(SIGALRM, old);
}

