/*
 *  main.c - main module
 * 
 *  nc6 - an advanced netcat clone
 *  Copyright (C) 2001-2006 Mauro Tortonesi <mauro _at_ deepspace6.net>
 *  Copyright (C) 2002-2006 Chris Leishman <chris _at_ leishman.org>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  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, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */  
#include "system.h"
#include "misc.h"
#include "connection.h"
#include "io_stream.h"
#include "parser.h"
#include "network.h"
#include "readwrite.h"

#include <unistd.h>
#include <signal.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <assert.h>
#ifdef HAVE_LOCALE_H
#include <locale.h>
#endif
 

RCSID("@(#) $Header: /ds6/cvs/nc6/src/main.c,v 1.42 2006/01/19 22:46:23 chris Exp $");

/* program name */
static char *program_name  = NULL;


/* function prototypes */
static int establish_connections(const connection_attributes_t *attrs);
static void established_callback(const connection_attributes_t *attrs,
		int fd, int socktype, void *cdata);
static int connection_main(const connection_attributes_t *attrs,
		int fd, int socktype);
static void setup_local_stream(const connection_attributes_t *attrs,
                io_stream_t *local, circ_buf_t *remote_buffer,
                circ_buf_t *local_buffer);
static void setup_remote_stream(const connection_attributes_t *attrs,
                int fd, int socktype, io_stream_t *remote,
                circ_buf_t *remote_buffer, circ_buf_t *local_buffer);
static int run_transfer(const connection_attributes_t *attrs,
                io_stream_t *remote_stream, io_stream_t *local_stream);
static void i18n_init(void);
static void sigchld_handler(int signum);



int main(int argc, char **argv)
{
	connection_attributes_t connection_attrs;
	char *ptr;
	int retval;

	i18n_init();

	/* initialise connection attributes */
	ca_init(&connection_attrs);

	/* save the program name in a static variable */
	if ((ptr = strrchr(argv[0], '/')) != NULL) {
		program_name = ++ptr;
	} else {
		program_name = argv[0];
	}
	
	/* SIGPIPE and SIGURG must be ignored */
	signal(SIGURG, SIG_IGN);
	signal(SIGPIPE, SIG_IGN);

	/* catch SIGCHLD and cleanup the child processes */
	signal(SIGCHLD, sigchld_handler);

	/* set flags and fill out the addresses and connection attributes */
	parse_arguments(argc, argv, &connection_attrs);

	/* establish the connections */
	retval = establish_connections(&connection_attrs);

	/* cleanup */
	ca_destroy(&connection_attrs);

	return (retval)? EXIT_FAILURE : EXIT_SUCCESS;
}



static int establish_connections(const connection_attributes_t *attrs)
{
	int err, result = 0;

	/* establish connection and callback when connected */
	err = net_establish(attrs, established_callback, &result);
	if (err)
		return err;

	/* if only a single connection was established, result will
	 * contain any error code from that connection handler */
	return result;
}



static void established_callback(const connection_attributes_t *attrs,
		int fd, int socktype, void *cdata)
{
	/* a connection has been established */
	int do_fork = 0;
	int result;

	/* check if multiple connections will be established,
	 * in which case a child should be forked to handle this connection */
	if (ca_is_flag_set(attrs, CA_LISTEN_MODE) &&
	    ca_is_flag_set(attrs, CA_CONTINUOUS_ACCEPT))
	{
		do_fork = 1;
	}

	if (do_fork) {
		/* fork and let the parent return immediately */
		int pid;
		int size;
		char *new_name;

		pid = fork();
		if (pid < 0) {
			fatal("fork failed: %s", strerror(errno));
		} else if (pid > 0) {
			/* parent */
			return;
		}

		/* setup program_name */
		size = strlen(program_name) + 10;
		new_name = (char *) xmalloc(size * sizeof(char));
		snprintf(new_name, size, "%s[%d]", program_name, (int)getpid());
		program_name = new_name;
	}

	/* invoke main connection handler */
	result = connection_main(attrs, fd, socktype);

	/* if this is a forked child, then exit with an error code */
	if (do_fork)
		exit((result)? EXIT_FAILURE : EXIT_SUCCESS);

	/* otherwise write the result code to the cdata before returning */
	*((int *)cdata) = result;
}



static int connection_main(const connection_attributes_t *attrs,
		int fd, int socktype)
{
	circ_buf_t remote_buffer, local_buffer;
	io_stream_t remote_stream, local_stream;
	int retval;

	assert(attrs != NULL);
	assert(fd >= 0);
	assert(socktype >= 0);

	/* initialise buffers */
	cb_init(&remote_buffer, ca_buffer_size(attrs));
	cb_init(&local_buffer, ca_buffer_size(attrs));

	/* setup remote stream */
	setup_remote_stream(attrs, fd, socktype, &remote_stream,
	                    &remote_buffer, &local_buffer);

	/* setup local stream */
	setup_local_stream(attrs, &local_stream, &remote_buffer, &local_buffer);
	
	/* set remote mtu & nru */
	ios_set_mtu(&remote_stream, ca_remote_MTU(attrs));
	ios_set_nru(&remote_stream, ca_remote_NRU(attrs));

	/* set idle timeouts - only on remote ios */
	ios_set_idle_timeout(&remote_stream, ca_idle_timeout(attrs));

	/* set stream hold timeouts */
	ios_set_hold_timeout(&remote_stream, ca_remote_hold_timeout(attrs));
	ios_set_hold_timeout(&local_stream, ca_local_hold_timeout(attrs));

	/* set stream half close suppression */
	ios_suppress_half_close(&remote_stream,
		ca_remote_half_close_suppress(attrs));
	ios_suppress_half_close(&local_stream,
		ca_local_half_close_suppress(attrs));

	/* give information about the connection in very verbose mode */
	if (very_verbose_mode()) {
		warning(_("using buffer size of %d"), remote_buffer.buf_size);
		if (remote_stream.nru > 0)
			warning(_("using remote receive nru of %d"),
			     remote_stream.nru);
		if (remote_stream.mtu > 0)
			warning(_("using remote send mtu of %d"),
			     remote_stream.mtu);
	}

	/* transfer data between endpoints */
	retval = run_transfer(attrs, &remote_stream, &local_stream);

	/* cleanup */
	io_stream_destroy(&local_stream);
	io_stream_destroy(&remote_stream);
	cb_destroy(&local_buffer);
	cb_destroy(&remote_buffer);

	return retval;
}



static void setup_local_stream(const connection_attributes_t *attrs,
		io_stream_t *stream, circ_buf_t *remote_buffer,
		circ_buf_t *local_buffer)
{
	const char *cmd;
	assert(attrs != NULL);
	assert(stream != NULL);

	cmd = ca_local_exec(attrs);
	if (cmd != NULL) {
		int in, out;
		if (very_verbose_mode())
			warning(_("executing '%s'"), cmd);
		if (open3(cmd, &in, &out, NULL) < 0) {
			fatal(_("failed to exec '%s': %s"),
			      cmd, strerror(errno));
		}
		ios_init(stream, "local", out, in, SOCK_STREAM,
		         local_buffer, remote_buffer);
	}
	else {
		ios_init_stdio(stream, "local", local_buffer, remote_buffer);
	}
}



static void setup_remote_stream(const connection_attributes_t *attrs,
		int fd, int socktype, io_stream_t *stream,
		circ_buf_t *remote_buffer, circ_buf_t *local_buffer)
{
	/* suppress unused attrs warning */
	while (0&&attrs);
	assert(attrs != NULL);
	assert(fd >= 0);
	assert(socktype >= 0);
	assert(stream != NULL);

	ios_init_socket(stream, "remote", fd, socktype,
	                remote_buffer, local_buffer);
}



static int run_transfer(const connection_attributes_t *attrs,
		io_stream_t *remote_stream, io_stream_t *local_stream)
{
	int retval;

	assert(remote_stream != NULL);
	assert(local_stream != NULL);

	/* setup unidirectional data transfers (if requested) */
	assert(!ca_is_flag_set(attrs, CA_RECV_DATA_ONLY) ||
	       !ca_is_flag_set(attrs, CA_SEND_DATA_ONLY));

	if (ca_is_flag_set(attrs, CA_RECV_DATA_ONLY)) {
		/* reading only from the remote stream */

		/* close the remote stream for writing */
		ios_shutdown(remote_stream, SHUT_WR);
		/* close the local stream for reading */
		ios_shutdown(local_stream, SHUT_RD);
		/* disable all hold timeouts */
		ios_set_hold_timeout(remote_stream, -1);
		ios_set_hold_timeout(local_stream, -1);

		if (very_verbose_mode())
			warning(_("receiving from remote only, "
			     "transmit disabled"));
	}

	if (ca_is_flag_set(attrs, CA_SEND_DATA_ONLY)) {
		/* reading only from the local stream */

		/* close the remote stream for reading */
		ios_shutdown(remote_stream, SHUT_RD);
		/* close the local stream for writing */
		ios_shutdown(local_stream, SHUT_WR);
		/* disable all hold timeouts */
		ios_set_hold_timeout(remote_stream, -1);
		ios_set_hold_timeout(local_stream, -1);

		if (very_verbose_mode())
			warning(_("transmitting to remote only, "
			     "receive disabled"));
	}

	/* run the main read/write loop */
	retval = readwrite(remote_stream, local_stream);

	if (very_verbose_mode())
		warning(_("connection closed (sent %d, rcvd %d)"),
		     ios_bytes_sent(remote_stream),
		     ios_bytes_received(remote_stream));
#ifndef NDEBUG
	if (very_verbose_mode())
		warning("readwrite returned %d", retval);
#endif

	return retval;
}



const char *get_program_name(void)
{
	return program_name;
}



static void i18n_init(void)
{
#ifdef ENABLE_NLS
#ifdef HAVE_SETLOCALE
	setlocale(LC_ALL, "");
	setlocale(LC_MESSAGES, "");
#endif /* HAVE_SETLOCALE */
	bindtextdomain(PACKAGE, LOCALEDIR);
	textdomain(PACKAGE);
#endif /* ENABLE_NLS */
}



/* cleanup any child processes created via --exec */
static void sigchld_handler(int signum)
{
	int pid;

	/* suppress unused attrs warning */
	while (0&&signum);
	assert(signum == SIGCHLD);

	do {
		int status;
		pid = waitpid(WAIT_ANY, &status, WNOHANG);
	} while (pid > 0);
}


syntax highlighted by Code2HTML, v. 0.9.1