/*
 *  network.c - common networking functions module - implementation
 * 
 *  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 "network.h"
#include "connection.h"
#include "afindep.h"
#ifdef ENABLE_BLUEZ
#include "bluez.h"
#endif/*ENABLE_BLUEZ*/

#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>

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


/* cddata argument for the listen callback proxy */
typedef struct callback_proxy_data {
	const connection_attributes_t *attrs;
	established_callback_t callback;
	void *cdata;
} callback_proxy_data_t;



static int net_connect(const connection_attributes_t *attrs,
		established_callback_t callback, void *cdata);
static int net_listen(const connection_attributes_t *attrs,
		established_callback_t callback, void *cdata);
static void callback_proxy(int fd, int socktype, void *cdata);
static void set_sockopt_handler(int sock, void *hdata);
static void warn_socket_details(const connection_attributes_t *attrs,
		int sock, int socktype);



int net_establish(const connection_attributes_t *attrs,
		established_callback_t callback, void *cdata)
{
	/* establish remote connection */
	if (ca_is_flag_set(attrs, CA_CONNECT_MODE)) {
		return net_connect(attrs, callback, cdata);
	} else if (ca_is_flag_set(attrs, CA_LISTEN_MODE)) {
		return net_listen(attrs, callback, cdata);
	} else {
		fatal_internal("unknown connection mode");
	}

	/* not reached */
	abort();
}



static int net_connect(const connection_attributes_t *attrs,
		established_callback_t callback, void *cdata)
{
	struct addrinfo hints;
	const address_t *remote, *local;
	time_t timeout;
	int fd, socktype;
	callback_proxy_data_t proxy_data;

	assert(attrs != NULL);

	/* setup getaddrinfo hints */
	memset(&hints, 0, sizeof(hints));
	ca_to_addrinfo(&hints, attrs);
#ifdef HAVE_GETADDRINFO_AI_ADDRCONFIG
	/* make calls to getaddrinfo send AAAA queries only if at least one
	 * IPv6 interface is configured */
	hints.ai_flags |= AI_ADDRCONFIG;
#endif
	if (ca_is_flag_set(attrs, CA_NUMERIC_MODE))
		hints.ai_flags |= AI_NUMERICHOST;

	/* get addresses */
	remote = ca_remote_address(attrs);
	local = ca_local_address(attrs);

	/* get timeout */
	timeout = ca_connect_timeout(attrs);

	/* store requested callback and cdata */
	proxy_data.attrs = attrs;
	proxy_data.callback = callback;
	proxy_data.cdata = cdata;

	/* invoke the appropriate connector for the protocol family */
	switch (ca_family(attrs)) {
#ifdef ENABLE_BLUEZ
	case PROTO_BLUEZ:
		fd = bluez_connect(&hints,
				remote->address, remote->service,
				set_sockopt_handler, &attrs,
				timeout, &socktype);
		break;
#endif/*ENABLE_BLUEZ*/
	default:
		fd = afindep_connect(&hints,
				remote->address, remote->service,
				local->address, local->service,
				set_sockopt_handler, &attrs,
				timeout, &socktype);
		break;
	}

	/* return errors immediately */
	if (fd < 0)
		return fd;

	/* only support a single connect, so issue callback directly */
	callback_proxy(fd, socktype, &proxy_data);
	return 0;
}



static int net_listen(const connection_attributes_t *attrs,
		established_callback_t callback, void *cdata)
{
	struct addrinfo hints;
	const address_t *remote, *local;
	time_t timeout;
	int max_accept;
	callback_proxy_data_t proxy_data;

	/* make sure that attrs is a valid pointer */
	assert(attrs != NULL);

	/* setup getaddrinfo hints */
	memset(&hints, 0, sizeof(hints));
	ca_to_addrinfo(&hints, attrs);
	hints.ai_flags = AI_PASSIVE;
#ifdef HAVE_GETADDRINFO_AI_ADDRCONFIG
	hints.ai_flags |= AI_ADDRCONFIG;
#endif
	if (ca_is_flag_set(attrs, CA_NUMERIC_MODE))
		hints.ai_flags |= AI_NUMERICHOST;

	/* get addresses */
	remote = ca_remote_address(attrs);
	local = ca_local_address(attrs);

	/* get timeout */
	timeout = ca_connect_timeout(attrs);

	/* get maximum accepted connection (currently either 1 or infinite) */
	max_accept = ca_is_flag_set(attrs, CA_CONTINUOUS_ACCEPT)? -1 : 1;

	/* store requested callback and cdata */
	proxy_data.attrs = attrs;
	proxy_data.callback = callback;
	proxy_data.cdata = cdata;

	/* invoke the appropriate listener for the protocol family */
	switch (ca_family(attrs)) {
#ifdef ENABLE_BLUEZ
	case PROTO_BLUEZ:
		return bluez_listener(&hints,
				local->address, local->service,
				remote->address, remote->service,
				set_sockopt_handler, &attrs,
				callback_proxy, &proxy_data,
				timeout, max_accept);
#endif/*ENABLE_BLUEZ*/
	default:
		return afindep_listener(&hints,
				local->address, local->service,
				remote->address, remote->service,
				set_sockopt_handler, &attrs,
				callback_proxy, &proxy_data,
				timeout, max_accept);
	}

	/* never reached */
	abort();
}



/* proxy for the actual callback, to keep track of the connection attributes */
static void callback_proxy(int fd, int socktype, void *cdata)
{
	callback_proxy_data_t *proxy_data = (callback_proxy_data_t *)cdata;
	const connection_attributes_t *attrs;

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

	attrs = proxy_data->attrs;

	warn_socket_details(attrs, fd, socktype);

	proxy_data->callback(attrs, fd, socktype, proxy_data->cdata);
}



/* handler function to set socket options on newly created sockets */
static void set_sockopt_handler(int sock, void *hdata)
{
	int on, err;
	const connection_attributes_t *attrs =
		*((const connection_attributes_t **)hdata);

	/* check arguments */
	assert(attrs != NULL);
	assert(sock >= 0);
	
	/* set the reuse address socket option */
	if (!ca_is_flag_set(attrs, CA_DONT_REUSE_ADDR)) {
		on = 1;
		/* in case of error, we will go on anyway... */
		err = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
				&on, sizeof(on));
		if (err < 0)
			warning("error with setsockopt SO_REUSEADDR: %s",
			    strerror(errno));
	}

	/* disable the nagle option for TCP sockets */
	if (ca_is_flag_set(attrs, CA_DISABLE_NAGLE)) {
		on = 1;
		/* in case of error, we will go on anyway... */
		err = setsockopt(sock, IPPROTO_TCP, TCP_NODELAY,
				&on, sizeof(on));
		/* ignore error if this socket does not use TCP */
		if (err < 0 && errno != ENOPROTOOPT) {
			warning("error with setsockopt TCP_NODELAY: %s",
			    strerror(errno));
		}
	}

	/* setup the kernel sndbuf size */
	if ((on = ca_sndbuf_size(attrs)) > 0) {
		/* in case of error, we will go on anyway... */
		err = setsockopt(sock, SOL_SOCKET, SO_SNDBUF, &on, sizeof(on));
		if (err < 0)
			warning("error with setsockopt SO_SNDBUF: %s",
			    strerror(errno));
	}

	/* setup the kernel rcvbuf size */
	if ((on = ca_rcvbuf_size(attrs)) > 0) {
		/* in case of error, we will go on anyway... */
		err = setsockopt(sock, SOL_SOCKET, SO_RCVBUF, &on, sizeof(on));
		if (err < 0)
			warning("error with setsockopt SO_RCVBUF: %s",
			    strerror(errno));
	}
}



static void warn_socket_details(const connection_attributes_t *attrs,
		int sock, int socktype)
{
	int n;
	socklen_t nlen;

	/* check arguments */
	assert(attrs != NULL);
	assert(sock >= 0);
	
	/* announce the socket in very verbose mode */
	switch (socktype) {
	case SOCK_STREAM:
		warning(_("using stream socket"));
		break;
	case SOCK_DGRAM:
		warning(_("using datagram socket"));
		break;
	case SOCK_SEQPACKET:
		warning(_("using seqpacket socket"));
		break;
	default:
		fatal_internal("unsupported socket type %d", socktype);
	}

	/* announce the real sndbuf size */
	if (ca_sndbuf_size(attrs) > 0) {
		nlen = sizeof(n);
		if (getsockopt(sock, SOL_SOCKET, SO_SNDBUF, &n, &nlen) < 0)
			warning("error with getsockopt SO_SNDBUF: %s",
			     strerror(errno));
		else
			warning(_("using socket sndbuf size of %d"), n);
	}

	/* announce the real rcvbuf size */
	if (ca_rcvbuf_size(attrs) > 0) {
		nlen = sizeof(n);
		if (getsockopt(sock, SOL_SOCKET, SO_RCVBUF, &n, &nlen) < 0)
			warning("error with getsockopt SO_RCVBUF: %s",
			    strerror(errno));
		else
			warning(_("using socket rcvbuf size of %d"), n);
	}
}


syntax highlighted by Code2HTML, v. 0.9.1