/*
**  Copyright (c) 1991 Bolt Beranek and Newman, Inc.
**  All rights reserved.
**
**  Redistribution and use in source and binary forms are permitted
**  provided that: (1) source distributions retain this entire copyright
**  notice and comment, and (2) distributions including binaries display
**  the following acknowledgement:  ``This product includes software
**  developed by Bolt Beranek and Newman, Inc. and CREN/CSNET'' in the
**  documentation or other materials provided with the distribution and in
**  all advertising materials mentioning features or use of this software.
**  Neither the name of Bolt Beranek and Newman nor CREN/CSNET may 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
**  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
*/
/*
 * 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.
 *
 * Note: this copyright applies to portions of this software developed
 * at Purdue beyond the software covered by the original copyright.
 */
#include <stdio.h>
#include <signal.h>
#include <errno.h>
#include <setjmp.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <sys/file.h>
#include <sys/fcntl.h>
#include <sys/param.h>
#include <sys/stream.h>
#include <sys/resource.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <sys/socket.h>
#include <net/if.h>
#include <stropts.h>
#include <sys/dlpi.h>

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

typedef struct _TABLE {
    int		Protocol;
    char	*Name;
} TABLE;

static int	reqtrace;

static jmp_buf	context;
static int	pleaserescan;
static int	pleasequit;
static char	*WHERE = "dpd";
static TABLE	ProtocolNames[] = {
#ifdef	IPPROTO_IP
    { IPPROTO_IP, "IP Dummy" },
#endif
#ifdef	IPPROTO_ICMP
    { IPPROTO_ICMP, "ICMP" },
#endif
#ifdef	IPPROTO_GGP
    { IPPROTO_GGP, "GGP" },
#endif
#ifdef	IPPROTO_TCP
    { IPPROTO_TCP, "TCP" },
#endif
#ifdef	IPPROTO_EGP
    { IPPROTO_EGP, "EGP" },
#endif
#ifdef	IPPROTO_PUP
    { IPPROTO_PUP, "PUP" },
#endif
#ifdef	IPPROTO_UDP
    { IPPROTO_UDP, "UDP" },
#endif
#ifdef IPPROTO_IDP
    { IPPROTO_IDP, "IDP" },
#endif
#ifdef IPPROTO_RAW
    { IPPROTO_RAW, "RAW" },
#endif
    { 0, NULL }
};


void
dlsap2ph(ph, sap)
phdr_t *ph;
ulong sap;
{
    ph->ph_address  = DP_DLSAP_ADDR(sap);
    ph->ph_control  = DP_DLSAP_CTL(sap);
    ph->ph_protocol = DP_DLSAP_PROTO(sap);
}

static int
authorized(rp, dlu, dlu_len, pkt, pkt_len)
    register REMOTE		*rp;
    register dl_unitdata_ind_t	*dlu;
    int				dlu_len;
    char			*pkt;
    int				pkt_len;
{
    register int		i;
    register TABLE		*tp;
    struct timeval		tv;
    struct tm			*tm;
    ulong			dlsap;
    phdr_t			phdr;

    if (reqtrace) {
	int fd;
	pkt_trace_t pt;
	char *tf = expand_dir_file("$DPLOG_DIR", TRACE_FILE);
	if ((fd = open(tf, O_WRONLY|O_APPEND|O_CREAT, 0644)) >= 0) {
	    pt.time = time((time_t *)0);
	    pt.dlu_len = dlu_len;
	    pt.pkt_len = pkt_len;
	    (void)write(fd, (char *)&pt, sizeof(pt));
	    (void)write(fd, (char *)dlu, dlu_len);
	    (void)write(fd, (char *)pkt, pkt_len);
	    (void)close(fd);
	}
	(void)free(tf);
    }

    /*
     * Check time.
     */
    if (gettimeofday(&tv, (struct timezone *)NULL) < 0) {
	d_log(DLOG_GENERAL, WHERE, "Can't do gettimeofday, %m");
	return 0;
    }
    if ((tm = localtime(&tv.tv_sec)) == NULL) {
	d_log(DLOG_GENERAL, WHERE, "Can't do localtime, %m");
	return 0;
    }
    if ((rp->Times[tm->tm_wday] & (1L << tm->tm_hour)) == 0) {
	d_log(DLOG_INFO, WHERE, "Bad time to call");
	return 0;
    }

    /*
     * Check protocol specific fields.. Currently, we only support IP.
     */
    dlsap = *(ulong *)(((char *)dlu) + dlu->dl_dest_addr_offset);
    switch (DP_DLSAP_PROTO(dlsap) & ~DPP_DIAL_REQ) {
     case PPP_IP:
	{
	    /*
	     * Check IP Protocol (TCP, UDP, etc)
	     */
	    struct ip *ip = (struct ip *)pkt;
	    i = ip->ip_p;
	    if ((rp->Protocols[P_WORD(i)] & P_BIT(i)) == 0) {
		for (tp = ProtocolNames; tp->Name; tp++)
		    if (i == tp->Protocol)
			break;
		d_log(DLOG_INFO, WHERE, "Bad protocol %s (%d)",
		    tp ? tp->Name : "unknown");
		return 0;
	    }

	    /*
	     * Check Source & Destination Addresses.
	     */
	    if (rp->AllowCount && !hostinlist(rp->AllowTo, ip->ip_dst)) {
		d_log(DLOG_INFO, WHERE, "Bad destination address \"%s\"",
		    inet_ntoa(ip->ip_dst));
		return 0;
	    }
	    if (rp->DisallowCount && hostinlist(rp->DisallowFrom, ip->ip_src)) {
		d_log(DLOG_INFO, WHERE, "Bad source address \"%s\"",
		    inet_ntoa(ip->ip_src));
		return 0;
	    }
	}
	break;

     default:
	/*
	 * We should not get an unsupported protocol....
	 */
	dlsap2ph(&phdr, dlsap);
	d_log(DLOG_INFO, WHERE,
	      "Unknown protocol %02x:%02x:%04x",
	      phdr.ph_address,
	      phdr.ph_control,
	      ntohs(phdr.ph_protocol));
	return 0;
    }
    /*
     * Okay to make call
     */
    return 1;
}


/*ARGUSED*/
static void
catchsigterm(sig)
int sig;
{
    pleasequit++;
    longjmp(context, 1);
}


/*ARGUSED*/
static void
catchsighup(sig)
int sig;
{
    pleaserescan++;
    (void)signal(SIGHUP, catchsighup);
    longjmp(context, 1);
}


/*ARGUSED*/
static void
catchsigchld(sig)
int sig;
{
    int w;
    int stat;

    errno = 0;
    while ((w = waitpid((pid_t)-1, &stat, WNOHANG)) > 0) {
	if (WIFEXITED(stat) && WEXITSTATUS(stat) != 0)
	    d_log(DLOG_GENERAL, WHERE, "child %d exited with status %d\n",
		  w, WEXITSTATUS(stat));
	else if (WIFSIGNALED(stat))
	    d_log(DLOG_GENERAL, WHERE, "child %d terminated due to signal %d\n",
		  w, WTERMSIG(stat));
    }
    (void)signal(SIGCHLD, catchsigchld);
}



static void
usage()
{
    (void)fprintf(stderr, "usage: %s [options]\n", progname);
    d_log(DLOG_GENERAL, WHERE, "Usage error");
    exit(10);
}


void
main(argc, argv)
    int			argc;
    char		*argv[];
{
    int			fd;
    int			f;
    int			nofork;
    int			i;
    char		dlu[128];
    char		pkt[PPP_MTU];
    REMOTE		*rp;
    char		*configfile;
    struct strbuf ctlb, datab;
    int 		npfds;
    struct pollfd	*pfds,
			*pfd;
    int			sf;
    struct rlimit	rl;

    /* Set defaults. */
    setprogname(argv[0]);
    WHERE = progname;
    nofork = 0;
    configfile = DP_CONFIG;

    /* Parse flags. */
    while ((i = getopt(argc, argv, "c:d:Fft")) != EOF)
	switch (i) {
	default:
	    usage();
	    /* NOTREACHED */
	case 'c':
	    configfile = optarg;
	    break;
	case 'd':
	    log_level = atoi(optarg);
	    break;
	case 'f':
	    nofork = 1;
	    break;
	case 'F':
	    nofork = 2;
	    break;
	case 't':
	    reqtrace = 1;
	    break;
	}
    argc -= optind;
    argv += optind;
    if (argc)
	usage();

    /* Fork us off */
    if (nofork < 1) {
	if ((i = fork()) < 0) {
	    d_log(DLOG_GENERAL, WHERE, "Can't fork, %m");
	    exit(3);
	}
	if (i > 0)
	    exit(0);

    }

    /*
     * Sanitize our process state.
     */
    if (nofork < 1) {
	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();
    }
    else
	if (getrlimit(RLIMIT_NOFILE, &rl) == 0) {
	    for (f = rl.rlim_cur ; f > 2 ; f--)
		(void)close(f);
	}

    /*
     * Figure out where everything is located.
     */
    init_pathconf();

    /*
     * Read network and modem configuration.
     */
    if (!readconfig(configfile))
	exit(4);

    /*
     * Open the DP devices.
     */
    if (!(npfds = opendpdevs(&pfds)))
	exit(5);

    /* Tell the log file where are here */
    d_log(DLOG_GENERAL, WHERE, "%s started", dp_release());
    record_pid(progname, getpid(), -1);

    /*
     * Initialize timeouts.
     */
    if (!set_if_timeouts())
	exit(6);

    /* Process requests */
    (void)signal(SIGHUP, catchsighup);
    (void)signal(SIGTERM, catchsigterm);
    (void)signal(SIGCHLD, catchsigchld);
    for ( ; ; ) {
	(void)setjmp(context);
	if (pleasequit)
	    break;
	if (pleaserescan) {
	    d_log(DLOG_GENERAL, WHERE, "Rescanning config files");
#ifdef	MDEBUG
	    mem_report();
#endif
	    closedpdevs();
	    if (!readconfig(configfile)) {
		unlock_pid();
		exit(7);
	    }
	    if (!(npfds = opendpdevs(&pfds))) {
		unlock_pid();
		exit(8);
	    }
	    (void)signal(SIGHUP, catchsighup);
	    pleaserescan = 0;
	    continue;
	}
	for (fd = 0, pfd = pfds ; fd < npfds ; fd++, pfd++)
	    pfd->revents = 0;
	if (poll(pfds, npfds, -1) < 0) {
	    if (errno == EINTR)
		continue;
	    d_log(DLOG_GENERAL, WHERE,
		  "Bad poll (returned %d) from \"%s\", %m", i, DEVDP);
	    unlock_pid();
	    exit(9);
	}
	for (fd = 0, pfd = pfds ; fd < npfds ; fd++, pfd++) {
	    if (!pfd->revents)
		continue;
	    /*
	     * Find config data for this PPA.
	     */
	    if ((rp = findfdconfig(pfd->fd)) == NULL) {
		d_log(DLOG_GENERAL, WHERE, "No REMOTE for fd %d ?!?", pfd->fd);
		pfd->events = 0;
		continue;
	    }
	    
	    if (pfd->revents != POLLIN) {
		d_log(DLOG_GENERAL, WHERE,
		    "Bad poll return events 0x%x from \"%s\", %m",
		    i, rp->Device);
		pfd->events = 0;
		continue;
	    }

	    ctlb.maxlen = sizeof(dlu);
	    ctlb.buf = dlu;
	    datab.maxlen = sizeof(pkt);
	    datab.buf = pkt;
	    sf = 0;
	    if (i = getmsg(pfd->fd, &ctlb, &datab, &sf)) {
		if (errno != EINTR)
		    d_log(DLOG_GENERAL, WHERE,
			"Bad getmsg (returned %d) from \"%s\", %m",
			i, DEVDP);
		continue;
	    }
	    d_log(DLOG_ALL, WHERE, "request received");


	    /* Is this packet is authorized? */
	    if (!authorized(rp, (dl_unitdata_ind_t *)ctlb.buf, ctlb.len,
			    datab.buf, datab.len)) {
		failcall(rp, (MODEM *)0, DP_FAILURE, "authorization failure");
		continue;
	    }

	    /* Make the call. */
	    if (nofork >= 2) {
		makecall(rp, (dl_unitdata_ind_t *)ctlb.buf, datab.buf);
		continue;
	    }
	    if ((i = fork()) == 0) {
		(void)setpgrp(0, getpgrp(0));
		(void)close(f);
		if (rp->LogLevel > log_level)
		    log_level = rp->LogLevel;
		makecall(rp, (dl_unitdata_ind_t *)ctlb.buf, datab.buf);
		exit(0);
	    }
	    if (i < 0)
		d_log(DLOG_GENERAL, WHERE, "Can't fork dialing process, %m");
	}
    }

    d_log(DLOG_GENERAL, WHERE, "%s exiting", dp_release());
    unlock_pid();
    exit(0);
    /* NOTREACHED */
}

#ifdef	MDEBUG
#include <malloc.h>

mem_report()
{
    struct mallinfo ma;
    ma = mallinfo();
    d_log(DLOG_GENERAL, WHERE,
	  "arena = %5d, usmblks = %5d, uordblks = %5d",
	  ma.arena, ma.usmblks, ma.uordblks);
}
#endif
