/*
 * Copyright (c) 1992 Purdue University
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms are permitted
 * provided that the above copyright notice and this paragraph are
 * duplicated in all such forms and that any documentation,
 * advertising materials, and other materials related to such
 * distribution and use acknowledge that the software was developed
 * by Purdue University.  The name of the University may not be used
 * to endorse or promote products derived * from this software without
 * specific prior written permission.
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
 * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 *
 */

#include <stdio.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/param.h>
#include <sys/stream.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <sys/resource.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <net/if.h>

#include "dpio.h"
#include "dpd.h"
#include "dp.h"

static int ppp_pid;
static int aux_pid;
extern void hangup();

start_ppp(rp, tty, passive, asyncmap)
REMOTE *rp;
char *tty;
int passive,
    asyncmap;
{
    static char		WHERE[] = "start_ppp";
    char srcbuf[32], dstbuf[32], addrbuf[128],
	 unitbuf[16], asyncbuf[9];
    char *cmds[24], **argv = cmds;

    (void)strcpy(srcbuf, inet_ntoa(rp->Address));
    (void)strcpy(dstbuf, inet_ntoa(rp->DstAddress));

    (void)sprintf(unitbuf, "%d", rp->Unit);
    (void)sprintf(addrbuf, "%s:%s", srcbuf, dstbuf);
    (void)sprintf(asyncbuf,"%x", asyncmap);

    *argv++ = expand_dir_file("$DPBIN_DIR", PPP_PROG);
    if (log_level >= DLOG_DIAG)
	*argv++ = "-d";
    *argv++ = "-as";
    *argv++ = asyncbuf;
    if (rp->PppArgs) {
	char **ap;
	for (ap = rp->PppArgs ; *ap ; )
	    *argv++ = *ap++;
    }
    if (passive)
	*argv++ = "-p";
    *argv++ = "dialup";
    *argv++ = "unit";
    *argv++ = unitbuf;
    if (tty)
	*argv++ = tty;
    *argv++ = addrbuf;
    *argv++ = (char *)0;
    {
	char cmdbuf[512], *c = cmdbuf;
	*c++ = '"';
	for (argv = cmds ; *argv ; argv++) {
	    (void)sprintf(c, "%s%c", *argv, *(argv+1) ? ' ' : '"');
	    c += strlen(c);
	}
	d_log(DLOG_GENERAL, WHERE, "Executing: %s", cmdbuf);
    }

    if ((ppp_pid = fork()) == 0) {
	(void)execv(cmds[0], cmds);
	exit(128);
    }
    record_pid(rp->Device, getpid(), ppp_pid);

    (void)free(cmds[0]);

    start_aux(rp);

    return ppp_pid;
}

void
collect_child(sig)
int sig;
{
    int wstatus;
    extern int errno, sys_nerr;
    extern char *sys_errlist[];
    int pid;
    static char		WHERE[] = "collect_child";
    char *pnm;
    int lev = DLOG_DIAG;
    char cnm[32];

    errno = 0;
    if (ppp_pid <= 0 && aux_pid <= 0)
	return;

    for ( ; ; ) {
	pid = waitpid(-1, &wstatus, WNOHANG);
	if (pid < 0)
	    switch (errno) {
	     case ECHILD:
		d_log(DLOG_GENERAL, WHERE, "Spurious signal %d", sig);
		return;
	     case EINTR:
		continue;
	     default:
		if (errno > 0 && errno <= sys_nerr)
		    d_log(DLOG_GENERAL, WHERE, "Waitpid: %s",
			  sys_errlist[errno]);
		else
		    d_log(DLOG_GENERAL, WHERE, "Waitpid: %d", errno);
		break;
	    }

	if (pid == ppp_pid) {
	    pnm = "PPP";
	    ppp_pid = 0;
	}
	else if (pid == aux_pid) {
	    pnm = "Aux Program";
	    aux_pid = 0;
	    sig = 0;
	}
	else {
	    (void)sprintf(cnm, "Unknown Child Process %d", pid);
	    pnm = cnm;
	    lev = DLOG_GENERAL;
	    sig = 0;
	}
	if (WIFEXITED(wstatus))
	    d_log(lev, WHERE, "%s exited with status %d", pnm,
		  WEXITSTATUS(wstatus));
	else if (WIFSIGNALED(wstatus))
	    d_log(lev, WHERE, "%s exited due to signal %d", pnm,
		  WTERMSIG(wstatus));
	if (sig)
	    hangup(sig);
	else
	    (void)signal(SIGCHLD, collect_child);
	return;
    }
}

void
collect_ppp()
{
    int wstatus;
    extern int errno, sys_nerr;
    extern char *sys_errlist[];
    static char		WHERE[] = "collect_ppp";

    errno = 0;
    if (ppp_pid <= 0)
	return;

    for ( ; ; ) {
	if (waitpid(ppp_pid, &wstatus, 0) < 0)
	    switch (errno) {
	     case ECHILD:
		d_log(DLOG_GENERAL, WHERE, "PPP already collected");
		return;
	     case EINTR:
		continue;
	     default:
		if (errno > 0 && errno <= sys_nerr)
		    d_log(DLOG_GENERAL, WHERE, "Waitpid: %s",
			  sys_errlist[errno]);
		else
		    d_log(DLOG_GENERAL, WHERE, "Waitpid: %d", errno);
		break;
	    }
	if (WIFEXITED(wstatus))
	    d_log(DLOG_DIAG, WHERE, "PPP exited with status %d",
		  WEXITSTATUS(wstatus));
	else if (WIFSIGNALED(wstatus))
	    d_log(DLOG_DIAG, WHERE, "PPP exited due to signal %d",
		  WTERMSIG(wstatus));
	ppp_pid = 0;
	return;
    }
}

kill_ppp(sig)
int sig;
{
    if (ppp_pid > 0)
	(void)kill(ppp_pid, sig);
}

/*
 * Start an auxilliary program that will take advantage of the fact that
 * we have an open connection to a given host.
 */
start_aux(rp)
REMOTE *rp;
{
    static char		WHERE[] = "start_aux";
    char *auxpath;
    char *cmds[24], **argv = cmds;
    int f;
    struct rlimit	rl;

    aux_pid = -1;

    if (!rp->AuxProgram)
	return;

    auxpath = expand_dir_file("$DPAUX_DIR", rp->AuxProgram);
    *argv++ = auxpath;

    if (rp->AuxArgs) {
	char **ap;
	for (ap = rp->AuxArgs ; *ap ; )
	    *argv++ = *ap++;
    }
    *argv++ = rp->Sitename;
    *argv++ = (char *)0;
    {
	char cmdbuf[512], *c = cmdbuf;
	*c++ = '"';
	for (argv = cmds ; *argv ; argv++) {
	    (void)sprintf(c, "%s%c", *argv, *(argv+1) ? ' ' : '"');
	    c += strlen(c);
	}
	d_log(DLOG_GENERAL, WHERE, "Executing: %s", cmdbuf);
    }

    if ((aux_pid = fork()) == 0) {
	if (getrlimit(RLIMIT_NOFILE, &rl) == 0)
	    for (f = rl.rlim_cur - 1 ; f >= 0 ; f--)
		(void)close(f);
	(void)open("/", O_RDONLY);
	(void)dup2(0, 1);
	(void)dup2(0, 2);
	(void)setsid();
	(void)execv(cmds[0], cmds);
	exit(128);
    }

    (void)free(auxpath);
}
