
/*
 * ALLOCSLIP.C
 *
 * ALLOCSLIP <options> begscript endscript
 *
 * (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.
 */

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

void sigTerm(int signo);
void fini(void);
void system_script(char *ptr);

short DidSetD;
short DidBegin;
int   IfcNo = -1;
char *EndScript;
char *BegScript;
char  Line[32];

main(ac, av)
char *av[];
{
    int r;
    int i;
    int encap = -1;

    signal(SIGHUP, sigTerm);
    signal(SIGTERM, sigTerm);

    fprintf(stderr, "GO!\n");
    fflush(stderr);

    /*
     * find line
     */

    {
        FILE *fp;

        if ((fp = popen("/usr/bin/tty", "r")) != NULL) {
            fscanf(fp, "%s", Line);
            fclose(fp);
        }
    }

    /*
     * options
     */

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

        if (*ptr == '-') {
            ptr += 2;
            switch(ptr[-1]) {
            case 't':
                ptr = (*ptr) ? ptr : av[++i];
                encap = strtol(ptr, NULL, 0);
                break;
            default:
                i = ac;
                break;
            }
        } else {
            if (BegScript == NULL)
                BegScript = ptr;
            else if (EndScript == NULL)
                EndScript = ptr;
	}
    }

    if (i > ac || ac == 1) {
        puts("allocslip [-t encap] beginscript endscript");
        exit(0);
    }

    freopen("/dev/null", "w", stderr);
    freopen("/dev/null", "w", stdout);

    openlog("allocslip", LOG_PID, LOG_DAEMON);

    if (BegScript == NULL)
	BegScript = "echo";
    if (EndScript == NULL)
	EndScript = "echo";

    /*
     * terminal modes
     */

    {
        struct termios tios;

        if (ioctl(0, TCGETS, &tios) != 0) {
            syslog(LOG_ERR, "unable to read stty modes (%s)", strerror(errno));
            fini();
        }
        bzero(tios.c_cc, sizeof(tios.c_cc));
        tios.c_iflag = IGNBRK | IGNPAR;
        tios.c_oflag = 0;
        tios.c_cflag = CRTSCTS | CS8 | CREAD | HUPCL | (tios.c_cflag & CBAUD);
        tios.c_lflag = 0;

        if (ioctl(0, TCSETS, &tios) != 0) {
            syslog(LOG_ERR, "unable to write stty modes (%s)", strerror(errno));
            fini();
        }
    }

    /*
     * set line discipline
     */

    {
        int n = N_SLIP;

        if ((IfcNo = ioctl(0, TIOCSETD, &n)) < 0) {
            syslog(LOG_ERR, "unable to set line discipline (%s)", strerror(errno));
            fini();
        }
        DidSetD = 1;
        syslog(LOG_NOTICE, "Allocated interface sl%d encap %d on %s", IfcNo, encap, Line);
        if (ioctl(0, TIOCGETD, &n) || n != N_SLIP) {
            syslog(LOG_ERR, "unable to verify line discipline (%s)", strerror(errno));
            fini();
        }
        if (encap >= 0) {
            n = encap;
            if (ioctl(0, SIOCSIFENCAP, &n) != 0) {
		syslog(LOG_ERR, "unable to set encapsulation (%s)", strerror(errno));
		fini();
            }
        }
    }

    /*
     * run begin script
     */

    system_script(BegScript);
    DidBegin = 1;

    /*
     * wait for carrier lost, check every 10 seconds
     */

    {
        time_t t = time(NULL);

	for (;;) {
	    int n = TIOCM_CAR;

	    sleep(10);
	    if (ioctl(0, TIOCMGET, &n) < 0)
		break;
	    if ((n & TIOCM_CAR) == 0)
		break;
	}
	syslog(LOG_ERR, "carrier lost after %d seconds", time(NULL) - t);
    }

    /*
     * carrier lost, fini
     */

    fini();
}

void 
sigTerm(int signo)
{
    fini();
}

void
fini(void)
{
    signal(SIGHUP, sigTerm);

    if (IfcNo >= 0)
	syslog(LOG_NOTICE, "disconnected from interface sl%d line %s", IfcNo, Line);
    else
	syslog(LOG_NOTICE, "disconnected from interface <none> line %s", Line);

    if (DidSetD) {
        int n = N_TTY;
        if ((n = ioctl(0, TIOCSETD, &n)) != N_SLIP)
            syslog(LOG_ERR, "previous ldisc not N_SLIP: %d", n);
        if (ioctl(0, TIOCGETD, &n) != 0 || n != N_TTY);
	    syslog(LOG_ERR, "couldn't reset ldisc to N_TTY: %d", n);
    }
    fflush(stderr);
    if (DidBegin) {
        close(0);
        open("/dev/null", O_RDWR, 0666);

        if (Line[0]) {
	    int fd = open(Line, O_RDWR|O_NONBLOCK);
	    int n;

	    if (fd >= 0) {
	        n = N_TTY;
		ioctl(fd, TIOCSETD, &n);
		n = -1;
	        if (ioctl(fd, TIOCGETD, &n) != 0 || n != N_TTY)
		    syslog(LOG_ERR, "couldn't reset ldisc to N_TTY: %d line %s", n, Line);
		close(fd);
	    }
	}
    }
    if (DidBegin) {
        system_script(EndScript);
    }
    closelog();
    exit(0);
}

void
system_script(char *ptr)
{
    char buf[256];
    int r;

    if (IfcNo >= 0) {
        sprintf(buf, "%s sl%d", ptr, IfcNo);
        r = system(buf);
    }
}

