/*
 * tcpreen.cpp - TCP connection reverse engineering tool.
 * $Id: tcpreen.cpp,v 1.34 2005/03/09 10:08:30 rdenisc Exp $
 */

/***********************************************************************
 *  Copyright (C) 2002-2005 Remi Denis-Courmont.                       *
 *  This program is free software; you can redistribute and/or modify  *
 *  it under the terms of the GNU General Public License as published  *
 *  by the Free Software Foundation; version 2 of the license.         *
 *                                                                     *
 *  This program is distributed in the hope that it will be useful,    *
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of     *
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.               *
 *  See the GNU General Public License for more details.               *
 *                                                                     *
 *  You should have received a copy of the GNU General Public License  *
 *  along with this program; if not, you can get it from:              *
 *  http://www.gnu.org/copyleft/gpl.html                               *
 ***********************************************************************/

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

#include <stdio.h>
#include <stdlib.h> // exit()
#include <string.h> // strerror(), strsignal()
#include <stdarg.h>
#include <signal.h> // signal()
#include <limits.h> // INT_MAX

#include <sys/types.h> // uid_t, pid_t
#include <sys/time.h>
#include <unistd.h> // close(), fork(), sleep()
#if HAVE_SYS_WAIT_H
# include <sys/wait.h> // wait()
#endif
#if HAVE_SYS_SOCKET_H
# include <sys/socket.h>
#endif
#include <errno.h> // errno, EINTR
#include <fcntl.h> // fcntl()
#if HAVE_SYSLOG_H
# include <syslog.h> // FIXME: move
#endif
#include "gettext.h"

#include "tcpreen.h"
#include "bridge.h"
#include "log.h"
#include "libsolve/sockprot.h"

static int mode;
static uid_t user;
static pid_t mainpid;
static int niflags;

static void
bridge_printf (int level, const char *fmt, ...)
{
#if HAVE_VSYSLOG
	if (mode & tcpreen_daemon)
	{
		va_list ap;

		va_start (ap, fmt);
		vsyslog (level, fmt, ap);
		va_end (ap);
	}
#endif

	if (mode & tcpreen_verbose)
	{
		va_list ap;

		va_start (ap, fmt);
		vfprintf (stderr, fmt, ap);
		va_end (ap);
	}
}


static void
bridge_perror (const char *str, int level = LOG_ERR)
{
	bridge_printf (level, "%s: %s\n", str, strerror (errno));
}


inline void
bridge_fatal (void)
{
	bridge_perror (_("Fatal error"), LOG_CRIT);
}


static void
bridge_hosterror (const SocketAddress& host, int l = LOG_ERR)
{
	if (*(host.Node ()))
		bridge_printf (l, _("%s port %s: %s\n"), host.Node (),
				host.Service (), host.StrError ());
	else
		bridge_printf (l, _("port %s: %s\n"), host.Service (),
				host.StrError ());
}


/*
 * Accepts a socket passive connection.
 */
static int
bridge_accept (int listenfd)
{
	int fd = accept (listenfd, NULL, 0);
	if (fd != -1)
	{
		const int val = 1;

		setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof (val));
#ifdef O_NONBLOCK
		int flags = fcntl (fd, F_GETFL);
		fcntl (fd, F_SETFL, flags | O_NONBLOCK);
#endif
	}

	return fd;
}


static int
bridge_listen (const SocketAddress& iface)
{
	int fd;
	SocketAddress real = iface;

	seteuid (0);
	fd = real.Bind ();
	seteuid (user);

	if (fd == -1)
	{
		bridge_hosterror (real);
		return -1;
	}

	if (real.GetSockName (fd, niflags)) // very strange error
	{
		bridge_hosterror (real);
		close (fd);
		return -1;
	}

	if (listen (fd, INT_MAX) == 0)
	{
		bridge_printf (LOG_NOTICE, _("Listening on: %s port %s\n"),
				real.Node (), real.Service ());
		return fd;
	}

	real.SetError ();
	close (fd);
	bridge_hosterror (real);
	return -1;
}


/*
 * SIGNAL HANDLING
 */
static void
bridge_child_signal_handler (int signum)
{
	/* This is not like SIG_IGN: it still interrupts select() & sleep()
	 * which is what matters */
	signal (signum, bridge_child_signal_handler);
}

static volatile int bridge_signaled = 0;

static void
bridge_parent_signal_handler (int signum)
{
	if (!bridge_signaled)
		bridge_signaled = signum;

	if (getpid () == mainpid)
		signal (signum, bridge_parent_signal_handler);
}

static void
bridge_wait (int& numclients)
{
#ifndef WINSOCK
	sleep (30000);

	if (bridge_signaled)
		return;

	int val;
	pid_t child = wait (&val);

	if (child != (pid_t)(-1))
	{
		if (WIFEXITED (val))
		{
			val = WEXITSTATUS (val);
			if (val)
				bridge_printf (LOG_WARNING, _(
					"Child %d returned an error (%d)\n"),
						(int)child, val);
		}
		else
		if (WIFSIGNALED (val))
		{
			val = WTERMSIG (val);

			bridge_printf (LOG_WARNING, _(
					"Child %d killed by signal %d (%s)\n"),
					(int)child, val,
#if HAVE_STRSIGNAL
					strsignal(val)
#else
					""
#endif
					);
		}
		numclients--;
	}
#endif
}



/*
 * Establishes a bridge between a TCP listener and a TCP active
 * connection.
 * The listening socket is dropped as soon as the first incoming
 * connection is established.
 *
 * Returns 0 on success, true on error.
 */
#ifdef WINSOCK
# define signal( num, hd ) (0)
# define fork( ) (0)
# define exit( val ) continue
# define kill( pid, sig ) (0)
# define SIGHUP SIGBREAK
#endif
int
bridge_main (const struct bridgeconf *conf)
{
	/* explicitly bufferized settings */
	mode = conf->mode; // static global variable
	user = conf->user;
	mainpid = getpid ();
	niflags = (mode & tcpreen_numeric) ? NI_NUMERICHOST|NI_NUMERICSERV : 0;

	// Socket addresses
	SocketAddress svrif, cltif;
	if (svrif.SetByName (conf->servername, conf->serverservice,
			(mode & tcpreen_listen_server) ? AI_PASSIVE : 0,
			conf->serveraf, SOCK_STREAM))
	{
		bridge_hosterror (svrif);
		return -1;
	}

	if (cltif.SetByName (conf->bridgename, conf->bridgeservice,
			(mode & tcpreen_listen_client) ? AI_PASSIVE : 0,
			conf->bridgeaf, SOCK_STREAM))
	{
		bridge_hosterror (cltif);
		return -1;
	}

	// Captures deadly signal
	// (done after name resolution, which is blocking)
	signal (SIGHUP, bridge_parent_signal_handler);
	signal (SIGINT, bridge_parent_signal_handler);
	signal (SIGTERM, bridge_parent_signal_handler);
#ifndef WINSOCK
	signal (SIGQUIT, bridge_parent_signal_handler);
	signal (SIGUSR1, SIG_IGN);
	signal (SIGUSR2, SIG_IGN);
	signal (SIGCHLD, bridge_child_signal_handler);
#endif


	// Things that must not be goto-bypassed
	int in = -1, out = -1, numclients = 0, retval = -1,
		maxclients = conf->maxclients;
	long countdown = conf->totalclients;

	// Sets up server socket(s)
	if (mode & tcpreen_listen_client)
	{
		in = bridge_listen (cltif);
		if (in == -1)
			goto bridge_abort;
	}

	if (mode & tcpreen_listen_server)
	{
		out = bridge_listen (svrif);
		if (out == -1)
			goto bridge_abort;
	}

	/* Definitely drops priviledges */
	if (seteuid (user) || setuid (user))
	{
		bridge_perror (_("UID setting"), LOG_CRIT);
		goto bridge_abort;
	}

#if HAVE_DAEMON
	// Goes in the background
	if ((mode & tcpreen_daemon) && daemon (0, 0))
	{
		bridge_perror (_("Background mode"), LOG_CRIT);
		goto bridge_abort;
	}
#endif

	/*
	 * SERVER LOOP
	 */
	retval = 0;

	while ((!bridge_signaled) && countdown)
	{
		/*
		 * Simple pre-forking server model: there are always a fixed
		 * predefined number of processes running. This will not work
		 * fine with a broken OS.
		 */
		if (numclients >= maxclients)
		{
			bridge_wait (numclients);
			continue;
		}

		if (countdown > 0)
			countdown--; /* one connection attempt */

		pid_t pid = fork ();
		if (pid == (pid_t)(-1))
		{
			bridge_perror (_("Process creation"));
			sleep (5); // prevent CPU usage from bursting
			continue;
		}
		else
		if (pid != 0)
		{
			numclients++;
			continue; // back to server loop
		}

		/*
		 * CHILD PROCESS
		 */
		// some blocking calls follow, so die upon signal
		signal (SIGHUP, SIG_DFL);
		signal (SIGINT, SIG_DFL);
		signal (SIGTERM, SIG_DFL);
		signal (SIGQUIT, SIG_DFL);
		signal (SIGUSR1, SIG_IGN);
		signal (SIGUSR2, SIG_IGN);
		if (bridge_signaled)
			exit (EXIT_SUCCESS);

		int fd[2];
		/* settinp up client socket... */

		if (in != -1)
		{
			fd[0] = bridge_accept (in);
#ifndef WINSOCK
			close (in);
#endif
		}
		else
			fd[0] = cltif.Connect ();

		if (fd[0] == -1)
		{
			#ifdef EINTR
			if (errno == EINTR)
				exit (EXIT_SUCCESS);
			#endif
			bridge_hosterror (cltif);
			exit (1);
		}

		/* Computes client address */
		SocketAddress clt;
		clt.GetPeerName (fd[0], niflags);

		if (!clt)
			bridge_hosterror (clt);
		if (mode & tcpreen_daemon)
			syslog (LOG_INFO, _("Connection from: %s port %s\n"),
				clt.Node (), clt.Service ());

		/* setting up server socket... */
		if (out != -1)
		{
			fd[1] = bridge_accept (out);
#ifndef WINSOCK
			close (out);
#endif
		}
		else
			fd[1] = svrif.Connect ();

		if (fd[1] == -1)
		{
			#ifdef EINTR
			if (errno == EINTR)
			{
				close (fd[0]);
				exit (0);
			}
			#endif

			bridge_hosterror (svrif);
			close (fd[0]);
			exit (2);
		}

		/* Computes server address */
		SocketAddress svr;
		svr.GetPeerName (fd[1], niflags);

		if (!svr)
			bridge_hosterror (svr);
		if (mode & tcpreen_daemon)
			syslog (LOG_INFO, _("Connection to: %s port %s\n"),
				svr.Node (), svr.Service ());

		/* Both sides of the bridge are now properly connected! */
		// from then on, do not exit when receiving a signal
		signal (SIGHUP, bridge_child_signal_handler);
		signal (SIGINT, bridge_child_signal_handler);
		signal (SIGTERM, bridge_child_signal_handler);
		signal (SIGQUIT, bridge_child_signal_handler);

		DataLogList *logs = conf->logsmaker->MakeLogList (clt.Node (),
							clt.Service ());
		if (logs == NULL)
		{
			bridge_perror (_("Log file error"));
			close (fd[1]);
			close (fd[0]);
			exit (3);
		}

		if (!!(*conf->logsmaker))
			logs->Connect (svr.Node (), svr.Service(),
					clt.Node (), clt.Service ());
		retval = monitor_bridge (fd, logs, conf->bytelimit);

		close (fd[1]);
		close (fd[0]);
		delete logs;
		exit (retval ? EXIT_FAILURE : EXIT_SUCCESS);
		/* END OF CHILD PROCESS */
	}

	/* Waits for pending clients */
	while (numclients && !bridge_signaled)
		bridge_wait (numclients);

bridge_abort:
	if (bridge_signaled)
	{
		kill (-getpid (), bridge_signaled); // kill children

#if HAVE_STRSIGNAL
		bridge_printf (LOG_NOTICE, "%s: %s\n", _("Caught signal"),
				strsignal (bridge_signaled));
#else
		bridge_printf (LOG_NOTICE, "%s %d\n", _("Caught signal"),
				bridge_signaled);
#endif
	}

	if (mode & tcpreen_listen_client)
		cltif.CleanUp ();
	if (mode & tcpreen_listen_server)
		svrif.CleanUp ();

	if (out != -1)
		close (out);
	if (in != -1)
		close (in);

	return retval;
}


syntax highlighted by Code2HTML, v. 0.9.1