/*
 *   Copyright (c) 2000-2002 David Parsons. All rights reserved.
 *   
 *   Redistribution and use in source and binary forms, with or without
 *   modification, are permitted provided that the following conditions
 *   are met:
 *  1. Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *  2. Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in
 *     the documentation and/or other materials provided with the
 *     distribution.
 *  3. All advertising materials mentioning features or use of this
 *     software must display the following acknowledgement:
 *     
 *   This product includes software developed by David Parsons
 *   (orc@pell.chi.il.us)
 *
 *  4. My name may not be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *     
 *  THIS SOFTWARE IS PROVIDED BY DAVID PARSONS ``AS IS'' AND ANY
 *  EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 *  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 *  PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL DAVID
 *  PARSONS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 *  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
 *  TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 *  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 *  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
 *  IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 *  THE POSSIBILITY OF SUCH DAMAGE.
 */
/*
 * message: logging for gatekeeper.
 *
 * if BE_FE is defined, gatekeeper forks into a frontend and a backend,
 * with the frontend processing messages before spitting them at syslog,
 * otherwise message() does name resolution inline.
 */
#include <stdio.h>
#include <netdb.h>
#include <unistd.h>
#include <syslog.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <fcntl.h>
#include <errno.h>
#include <signal.h>

#include "firewall.h"
#include "policies.h"
#include "parser.h"


#ifdef BE_FE
typedef struct _ipc_msg {
    int magic;
#define MAGIC 0x31071960
    connection from;	/* where the connection is coming from */
    connection to;	/* where it's going to */
    int action;		/* what we did with the connection */
    int dir;		/* incoming or outgoing traffic? */
} IPC;

static int messageq = EOF;
static pid_t messagepid = 0;
#endif

static void domessage(int, int, connection *, connection *);

/*
 * GetServByPort() does getservbyname(), because this doesn't seem to
 * work in libc 5.0.9.
 */
char*
GetServByPort(short port)
{
    struct servent *s;
    int p;

    setservent(1);
    while ((s = getservent()) != 0)
	if ((port == s->s_port) && (s->s_proto[0] == 't'))
	    return s->s_name;
    return 0;
} /* GetServByPort */


/*
 * init_message() prepares the universe for passing messages around
 */
void
init_message()
{
#ifdef BE_FE
    int sp[2];
    int ret;
    IPC message;

    syslog(LOG_ERR, "BE_FE defined");
    if (pipe(sp) == 0) {
	if ((messagepid = fork()) == 0) {
	    /* fork succeeded; we are the child, so we'll sit and
	     * read messages.
	     */
	    closelog();
	    openlog("gatekeeper-log", LOG_DAEMON, LOG_PID);

	    close(sp[1]);
	    fcntl(sp[0], F_SETFL, fcntl(sp[0], F_GETFL, 0) & ~O_NONBLOCK);
	    while (1) {
		ret = read(sp[0], &message, sizeof message);
		if (ret == sizeof message)
		    domessage(message.action, message.dir,
				&message.from, &message.to);
		else if (errno != EAGAIN && errno != EINTR) {
		    if (errno != 0)
			syslog(LOG_INFO, "%m");
		    break;
		}
	    }
	    exit(0);
	}
	else if (messagepid > 0) {
	    /* Make certain that gatekeeper never jams if the message
	     * process dies or is stuck behind a slow name server.
	     */
	    close(sp[0]);
	    fcntl(sp[1], F_SETFL, fcntl(sp[1], F_GETFL, 0) | O_NONBLOCK);
	    messageq = sp[1];
	    return;
	}
	close(sp[0]);
	close(sp[1]);
	syslog(LOG_ERR, "cannot start logger child");
    }
    else
	syslog(LOG_ERR, "cannot create FE/BE IPC: %m");
    syslog(LOG_INFO, "falling back to inline message handling");
#endif
    setservent(1);
    sethostent(1);
} /* init_message */


/*
 * end_message() pulls all the message handling scaffolding down.
 */
void
end_message()
{
#ifdef BE_FE
    int status;

    kill(messagepid, SIGINT);
    close(messageq);
    messageq = EOF;
    wait(status);
#endif
} /* end_message */


/*
 * domessage() does the nitty-gritty of the message handling. Before
 *             printing out the message it attempts to resolve all
 *             address and service numbers, which can take a while.
 *             If BE_FE is defined, this is done in a separate process
 *             that communicates with gatekeeper via a message queue,
 *             otherwise it's all done inline.
 */
static void
domessage(int action, int dir, connection *server, connection *client)
{
    char *service;
    struct hostent *host;
    char sport[10], cport[10];
    char shost[80], chost[80];
    short x;

    if ((service = GetServByPort(server->port)) != 0)
	strncpy(sport, service, sizeof cport);
    else
	sprintf(sport, "%d", ntohs(server->port));

    if ((service = GetServByPort(client->port)) != 0)
	strncpy(cport, service, sizeof cport);
    else
	sprintf(cport, "%d", ntohs(client->port));

    if ((host = gethostbyaddr(&server->ip, sizeof server->ip, AF_INET)) != 0)
	strncpy(shost, host->h_name, sizeof shost);
    else
	sprintf(shost, "---");

    if ((host = gethostbyaddr(&client->ip, sizeof client->ip, AF_INET)) != 0)
	strncpy(chost, host->h_name, sizeof chost);
    else
	sprintf(shost, "---");

    if (dir == IN) {
	syslog(LOG_INFO, "%s %s,%s <== %s,%s", (action==DENY)?"REFUSE":"ACCEPT",
			    shost, sport, chost, cport);
    }
    else {
	syslog(LOG_INFO, "%s %s,%s ==> %s,%s", (action==DENY)?"REFUSE":"ACCEPT",
			    chost, cport, shost, sport);
    }
} /* domessage */


/*
 * message() is what gatekeeper calls to emit ACCEPT or REFUSE information.
 */
void
message(int action, int dir, connection *server, connection *client)
{
#if BE_FE
    /* the actual logger is sitting at the other side of a socketpair,
     * so we'll just pack off a message to the logger.  If we can't
     * write to the pipe due to EPIPE or EINVAL, we'll assume that
     * the child died or is ill, so we'll kill it and revert to inline
     * processing.
     */
    int ret;

    if (messageq != EOF) {
	IPC message;

	message.from   = *client;
	message.to     = *server;
	message.dir    = dir;
	message.action = action;

	ret = write(messageq, &message, sizeof message);

	if (ret < 0 && (errno = EINVAL || errno == EPIPE)) {
	    if (kill(messagepid, 0) < 0)
		syslog(LOG_ERR, "logging process died: %m");
	    else
		syslog(LOG_ERR, "error logging connection: %m");
	    end_message();
	    syslog(LOG_INFO, "falling back to inline message handling");
	}
	else
	    return;
    }
#endif
    domessage(action, dir, server, client);
} /* message */
