#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <syslog.h>
#include <netdb.h>

#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/ioctl.h>
#include <sys/socketio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <linux/if.h>

#include "firewall.h"


int debug = 0;
struct ifreq bridge_if;

/*
 * if_down() brings down the bridging interface, using the parameters
 *           in bridge_if.
 */
void
if_down(int signo)
{
    int s = socket(AF_INET, SOCK_DGRAM, 0);

    if (signo != 0)
	syslog(LOG_INFO, "got signal %d, stopping bridging on %s",
			signo, bridge_if.ifr_name);

    if (s < 0)
	syslog(LOG_ERR, "Couldn't stop TCP bridging: %m");
    else {
	memset(bridge_if.ifr_slave, 0, sizeof bridge_if.ifr_slave);

	if (ioctl(s, SIOCSIFBR, &bridge_if) < 0)
	    syslog(LOG_ERR, "Couldn't stop TCP bridging: %m");
	close(s);
    }
    end_message();
    exit(signo);
} /* if_down */


/*
 * if_up() brings up the bridging interface, using the parameters
 *         in bridge_if.
 */
int
if_up(char *inside, char *outside)
{
    int s = socket(AF_INET, SOCK_DGRAM, 0);

    if (s < 0) {
	syslog(LOG_ERR, "Couldn't start TCP bridging: %m");
	return 0;
    }

    memset(&bridge_if, 0, sizeof bridge_if);
    strncpy(bridge_if.ifr_name, inside, sizeof bridge_if.ifr_name);
    strncpy(bridge_if.ifr_slave, outside, sizeof bridge_if.ifr_slave);

    if (ioctl(s, SIOCSIFBR, &bridge_if) < 0) {
	syslog(LOG_ERR, "Couldn't start TCP bridging: %m");
	return 0;
    }
    close(s);

    syslog(LOG_INFO, "now bridging from %s to %s", outside, inside);
    /* after bringing the bridge up, try to arrange things
     * so that the bridging will do down when the gatekeeper
     * goes to the happy hunting grounds
     */
    signal(SIGKILL, if_down);
    signal(SIGQUIT, if_down);
    signal(SIGTERM, if_down);
    signal(SIGINT,  if_down);
    return 1;
} /* if_up */


/*
 * debug_switch() turns debugging on or off, depending on the signal
 */
void
debug_switch(int signo)
{
    debug = (signo == SIGUSR1) ? 1 : 0;

    syslog(LOG_INFO, "debugging output now %s", debug ? "ON" : "OFF");
} /* debug_switch */


/*
 * aaiiee() complains bitterly if something crashes the firewall
 */
void
aaiiee(int signo)
{
    syslog(LOG_ERR, "** CRASH ON SIGNAL %d **", signo);
    sleep(1);
    if_down(signo);
} /* aaiiee */


/*
 * authorize() checks to see if the traffic is okay.
 */
int
authorize(int client)
{
    int rc, server, service, len;
    struct sockaddr_in server_in;
    struct sockaddr_in client_in;
    connection server_auth, client_auth;
    int direction;

    if ((server = ioctl(client, SIOCSCANPAIR)) < 0) {
	syslog(LOG_ALERT, "Can't bridge connection: %m");
	return 0;
    }

    len = sizeof server_in;
    if (getsockname(server, (struct sockaddr*)&server_in, &len) < 0) {
	syslog(LOG_ALERT, "Can't get server information: %m");
	close(server);
	return 0;
    }

    len = sizeof client_in;
    if (getsockname(client, (struct sockaddr*)&client_in, &len) < 0) {
	syslog(LOG_ALERT, "Can't get client information: %m");
	close(server);
	return 0;
    }

    server_auth.ip = server_in.sin_addr.s_addr;
    server_auth.port = server_in.sin_port;
    client_auth.ip = client_in.sin_addr.s_addr;
    client_auth.port = client_in.sin_port;

    direction = ioctl(server, SIOCSINTERNAL) ? IN : OUT;
    rc = allow(&client_auth, &server_auth, direction);

    if (rc == DEFAULT)
	rc = (direction == IN) ? DENY : ALLOW;

    message(rc, direction, &client_auth, &server_auth);
    if (rc == ALLOW)
	ioctl(client, SIOCSDONTCARE);
    close(server);
    return 1;
} /* authenticate */


/*
 * The gatekeeper
 */
int
main(int argc, char **argv)
{
    struct sockaddr junk;
    fd_set reader;
    fd_set sel_r;
    fd_set sel_err;
    int sel_nr;
    int listener;
    int len, client, server;
    int rc, max;

    openlog("gatekeeper", LOG_DAEMON, LOG_PID);

    if (argc < 3) {
	syslog(LOG_ERR, "usage: gatekeeper <internal-if> <external-if>");
	exit(1);
    }

    init_message();
    if (if_up(argv[1], argv[2]) == 0) {
	end_message();
	exit(1);
    }

    readcfg();

    signal(SIGUSR1, debug_switch);
    signal(SIGUSR2, debug_switch);

    signal(SIGBUS,  aaiiee);
    signal(SIGILL,  aaiiee);

    if ((listener = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
	syslog(LOG_ALERT, "socket(): %m");
	exit(1);
    }

    if ((rc = listen(listener, 128)) < 0) {
	syslog(LOG_ALERT, "listen(): %m");
	exit(1);
    }

    if ((rc = ioctl(listener, SIOCSLISTEN)) < 0) {
	syslog(LOG_ALERT, "ioctl(): %m");
	exit(1);
    }

    FD_ZERO(&reader);
    FD_SET(listener, &reader);

    sel_nr = listener;

    syslog(LOG_INFO, "waiting for traffic");
    /* after all this setup, drop into an infinite loop to
     * process requests
     */
    while (1) {
	sel_r = reader;
	sel_err = reader;

	if (select(1+sel_nr, &sel_r, 0, &sel_err, 0) < 0) {
	    if (errno != EAGAIN && errno != EINTR)
		syslog(LOG_ERR, "select(): %m");
	    continue;
	}

	if (FD_ISSET(listener, &sel_r)) {
	    len = sizeof junk;
	    if ((client = accept(listener, &junk, &len)) >= 0) {
		(void)authorize(client);
		close(client);
	    }
	}
    }
    if_down(0);	/* will never happen */
} /* main */
