#include <unistd.h>
#include <errno.h>
#include <stdio.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/time.h>
#include <sys/wait.h>

#define pidfile "/var/run/tunneld.pid"

#define dbg if(1) printf

static int fd0[2], fd1[2], ssh, fetchm, refetch, nodelay, broken, timeout;

static void fork_fetchm()
{
    refetch = 0;
    dbg("Running fetchmail.\n");
    if (!(fetchm = fork())) {
	execlp("fetchmail", "fetchmail", "-f", "/etc/fetchmailrc", 0);
	printf("Cannot exec fetchmail.\n");
	exit(1);
    }
}

static void killem()
{
    if (ssh)
	kill(ssh, SIGTERM);
    refetch = 0;
    if (fetchm)
	kill(fetchm, SIGTERM);
    while (ssh || fetchm)
	pause();
}

static void sigterm(int sig __attribute__((unused)))
{
    killem();
    unlink(pidfile);
    exit(0);
}

static void sigchld(int sig __attribute__((unused)))
{
    pid_t pid;

    pid = wait(0);
    if (pid == ssh) {
	dbg("SSH exited.\n");
	ssh = 0;
    }
    if (pid == fetchm) {
	dbg("Fetchmail exited.\n");
	if (refetch)
	    fork_fetchm();
	else
	    fetchm = 0;
    }
}

static void sighup(int sig __attribute__((unused)))
{
    dbg("SIGHUP received.\n");
    nodelay++;
    broken++;
}

static void sigalrm(int sig __attribute__((unused)))
{
    if (++timeout >= 2) {
	printf("Ping timeout.\n");
	broken++;
    } else {
	alarm(60);
	write(fd1[1], "D", 1);
    }
}

static void sigpipe(int sig __attribute__((unused)))
{
    dbg("SIGPIPE received.\n");
    broken++;
}

static void Signal (int sig, void (*sh)(int))
{
    struct sigaction sa;

    sa.sa_handler = sh;
    sigemptyset (&sa.sa_mask);
    sa.sa_flags = 0;
    (void) sigaction (sig, &sa, 0);
}

int main(int argc __attribute__((unused)), char **argv)
{
    char buf[1], **argp = argv + 1, *logf = "/dev/tty11";
    int quiet = 0, rv;
    FILE *f;

    if (fork())
	exit(0);
    setsid();
    while (**argp == '-') {
	if (*(*argp + 1) == 'q')
	    quiet++;
	else if (*(*argp + 1) == 'l')
	    logf = *++argp;
	else
	    break;
	argp++;
    }
    f = fopen(pidfile, "w");
    if (f) {
	fprintf(f, "%d\n", getpid());
	fclose(f);
    }
    close(0); open("/dev/null", O_RDONLY);
    close(1); open(logf, O_WRONLY | O_CREAT | O_APPEND); dup2(1, 2);

    Signal(SIGTERM, sigterm);
    Signal(SIGCHLD, sigchld);
    Signal(SIGHUP, sighup);
    Signal(SIGPIPE, sigpipe);
    Signal(SIGALRM, sigalrm);
    for (;;) {
	pipe(fd0);
	pipe(fd1);
	if ((ssh = fork()) > 0) {
	    close(fd0[1]);
	    close(fd1[0]);
	    broken = 0;
	    timeout = 0;
	    sigalrm(SIGALRM);
	    for (;;) {
		errno = 0;
		rv = read(fd0[0], buf, 1);
		if (broken)
		    break;
		if (rv > 0) {
		    if (buf[0] == 7) {
			if (!quiet)
			    write(1, buf, 1);
			refetch++;
			if (!fetchm)
			    fork_fetchm();
		    }
		    timeout = 0;
		} else
		    if (errno != EINTR)
			break;
	    }
	    alarm(0);
	    close(fd0[0]);
	    close(fd1[1]);
	    killem();
	    if (nodelay)
		nodelay--;
	    else {
		if (!broken)
		    printf("SSH exited unexpectedly.\n");
		printf("Sleeping 10 minutes ..."); fflush(stdout);
		sleep(600);
		printf(" done.\n");
	    }
	} else {
	    dbg("Running SSH.\n");
	    dup2(fd0[1], 1); close(fd0[0]); close(fd0[1]);
	    dup2(fd1[0], 0); close(fd1[0]); close(fd1[1]);
	    execvp(*argp, argp);
	    printf("Cannot exec '%s'.\n", *argp);
	    exit(1);
	}
    }
    return 0;
}

