/*
 *  afindep.c - address family independant networking functions
 * 
 *  nc6 - an advanced netcat clone
 *  Copyright (C) 2002-2006 Chris Leishman <chris _at_ leishman.org>
 *  Copyright (C) 2001-2006 Mauro Tortonesi <mauro _at_ deepspace6.net>
 *
 *  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 "afindep.h"
#include "misc.h"
#include "netsupport.h"
#include "parser.h"

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

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


/* suggested size for argument to getnameinfo_ex */
static const int AI_STR_SIZE = (2 * (NI_MAXHOST + NI_MAXSERV + 2)) + 8;


static bool skip_address(const struct addrinfo *ai);
#ifdef ENABLE_IPV6
static struct addrinfo *order_ipv6_first(struct addrinfo *ai);
#endif
static void getnameinfo_ex(const struct sockaddr *sa, socklen_t len, char *str,
		size_t size, bool numeric_mode);
static bool is_allowed(const struct sockaddr *sa, socklen_t salen,
		const struct addrinfo *hints,
		const char *address, const char *service);



int afindep_connect(const struct addrinfo *hints,
		const char *remote_address, const char *remote_service,
		const char *local_address, const char *local_service,
		set_sockopt_handler_t set_sockopt_handler, void *hdata,
		time_t timeout, int *rt_socktype)
{
	int err, fd = -1;
	struct addrinfo *res = NULL, *ptr;
	bool connect_attempted = false;
	char name_buf[AI_STR_SIZE];

	/* make sure arguments are valid and preconditions are respected */
	assert(hints != NULL);
	assert(remote_address != NULL && strlen(remote_address) > 0);
	assert(remote_service != NULL && strlen(remote_service) > 0);
	assert(local_address == NULL || strlen(local_address) > 0);
	assert(local_service == NULL || strlen(local_service) > 0);

	/* get the address of the remote end of the connection */
	err = getaddrinfo(remote_address, remote_service, hints, &res);
	if (err != 0) {
		warning(_("forward host lookup failed "
		        "for remote endpoint %s: %s"),
		        remote_address, gai_strerror(err));
		return -1;
	}

	/* check the results of getaddrinfo */
	assert(res != NULL);

	/* try connecting to any of the addresses returned by getaddrinfo */
	for (ptr = res; ptr != NULL; ptr = ptr->ai_next) {

		/* only accept results we can handle */
		if (skip_address(ptr) == true) continue;

		/* we are going to try to connect to this address */
		connect_attempted = true;

		/* create the socket */
		fd = socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol);
		if (fd < 0) {
			/* ignore this address if it is not supported */
			if (!unsupported_sock_error(errno))
				continue;
			warning("cannot create the socket: %s",
				strerror(errno));
			return -1;
		}
		
#if defined(ENABLE_IPV6) && defined(IPV6_V6ONLY)
		if (ptr->ai_family == PF_INET6) {
			int on = 1;
			/* in case of error, we will go on anyway... */
			err = setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY,
			                 &on, sizeof(on));
			if (err < 0) 
				warning("error with sockopt IPV6_V6ONLY");
		}
#endif 

		if (set_sockopt_handler != NULL)
			set_sockopt_handler(fd, hdata);

		/* setup name_buf if we're in verbose mode */
		if (verbose_mode())
			getnameinfo_ex(ptr->ai_addr, ptr->ai_addrlen, 
			               name_buf, sizeof(name_buf),
				       (hints->ai_flags & AI_NUMERICHOST));
					
		/* setup local source address and/or service */
		if (local_address != NULL || local_service != NULL) {
			struct addrinfo src_hints, *src_res = NULL, *src_ptr;
		
			/* setup hints structure to be passed to getaddrinfo */
			memset(&src_hints, 0, sizeof(src_hints));
			src_hints.ai_family   = ptr->ai_family;
			src_hints.ai_flags    = AI_PASSIVE;
			src_hints.ai_socktype = ptr->ai_socktype;
			src_hints.ai_protocol = ptr->ai_protocol;
			src_hints.ai_flags    = hints->ai_flags;

			/* get the local IP address of the connection */
			err = getaddrinfo(local_address, local_service,
			                  &src_hints, &src_res);
			if (err != 0) {
				if (verbose_mode()) {
					warning(_("bind to source addr/port "
					     "failed when connecting to "
					     "%s: %s"), name_buf,
					     gai_strerror(err));
				}
				close(fd);
				fd = -1;
				continue;
			}

			/* check the results of getaddrinfo */
			assert(src_res != NULL);

			/* try binding to any of the addresses */
			for (src_ptr = src_res; src_ptr != NULL;
			     src_ptr = src_ptr->ai_next)
			{
				err = bind(fd, src_ptr->ai_addr,
					   src_ptr->ai_addrlen);
				if (err == 0) break;
			}
			
			if (err != 0) {
				/* make sure we have tried all addresses */
				assert(src_ptr == NULL);
				
				if (verbose_mode()) {
					warning(_("bind to source addr/port "
					     "failed when connecting to "
					     "%s: %s"), name_buf,
					     strerror(errno));
				}
				freeaddrinfo(src_res);
				close(fd);
				fd = -1;
				continue;
			}

			freeaddrinfo(src_res);
		}

		/* attempt the connection */
		err = connect_with_timeout(fd,
				ptr->ai_addr, ptr->ai_addrlen, timeout);

		/* exit from the loop if we have a valid connection */
		if (err == 0)
			break;

		/* check error code */
		if (verbose_mode()) {
			/* use different error message for timeout */
			if (errno == ETIMEDOUT) {
				/* connection timed out */
				warning(_("timeout while connecting to %s"),
				        name_buf);
			}
			else {
				/* connection failed */
				warning(_("cannot connect to %s: %s"),
				        name_buf, strerror(errno));
			}
		}
		close(fd);
		fd = -1;
	}

	/* either all possibilities were exahusted, or a connection was made */
	assert(ptr == NULL || fd >= 0);
	
	/* if the connection failed, output an error message */
	if (ptr == NULL) {
		/* if a connection was attempted, an error has been output */
		if (connect_attempted == false) {
			warning(_("forward lookup returned "
			        "no useful socket types"));
		} else {
			warning(_("unable to connect "
			        "to address %s, service %s"), 
			        remote_address, remote_service);
		}
		return -1;
	}

	/* let the user know the connection has been established */
	if (verbose_mode())
		warning(_("%s open"), name_buf);

	/* return the socktype */
	if (rt_socktype != NULL)
		*rt_socktype = ptr->ai_socktype;

	/* cleanup addrinfo structure */
	freeaddrinfo(res);

	return fd;
}



int afindep_listener(const struct addrinfo *hints,
		const char *local_address, const char *local_service,
		const char *remote_address, const char *remote_service,
		set_sockopt_handler_t set_sockopt_handler, void *hdata,
		listen_callback_t callback, void *cdata,
		time_t timeout, int max_accept)
{
	int nfd, i, fd, err, maxfd = -1;
	struct addrinfo *res = NULL, *ptr;
#ifdef ENABLE_IPV6
	bool set_ipv6_only = false;
	bool bound_ipv6_any = false;
#endif
	bound_socket_t *bound_sockets = NULL;
	fd_set accept_fdset;
	char name_buf[AI_STR_SIZE];

	/* make sure arguments are valid and preconditions are respected */
	assert(hints != NULL);
	assert(remote_address == NULL || strlen(remote_address) > 0);
	assert(remote_service == NULL || strlen(remote_service) > 0);
	assert(local_address == NULL || strlen(local_address) > 0);
	assert(local_service != NULL && strlen(local_service) > 0);

	/* if max_accept is 0, just return */
	if (max_accept == 0)
		return 0;

	/* get the IP address of the local end of the connection */
	err = getaddrinfo(local_address, local_service, hints, &res);
	if (err != 0) {
		warning(_("forward host lookup failed "
		        "for local endpoint %s (%s): %s"),
		        local_address? local_address : _("[unspecified]"),
		        local_service, gai_strerror(err));
		return -1;
	}
		
	/* check the results of getaddrinfo */
	assert(res != NULL);

#ifdef ENABLE_IPV6
	/* 
	 * Some systems (notably Linux) with a shared stack for ipv6 and ipv4, 
	 * have a broken bind(2) implementation. In these systems, binding an 
	 * ipv6 socket to ipv6_addr_any (::) will also bind the socket to
	 * the ipv4 unspecified address (0.0.0.0).
	 * 
	 * So when listening on ipv6_addr_any (::) accept(2) will return also
	 * ipv4 connection attemptes, as ipv4 mapped ipv6 addresses.
	 *
	 * This is a problem, since when called with AF_UNSPEC and AI_PASSIVE, 
	 * getaddrinfo will return results for both ipv6 AND ipv4, but if we 
	 * try to bind on the ipv4 unspecified address (0.0.0.0) AFTER we have 
	 * bound our socket to ipv6_addr_any (::), the syscall bind(2) will 
	 * fail and return -1, setting errno to EADDRINUSE.
	 *
	 * The following algorithm is used to work around this error to some
	 * degree:
	 *
	 * Ensure binds to IPv6 addresses are attempted before IPv4 addresses.
	 * On broken systems where IPV6_V6ONLY is not defined or the setsockopt
	 * fails:
	 * 
	 *   - Keep track of whether an IPv6 socket has been bound to 
	 *     in6_addr_any.
	 *   - If a bind to IPv4 fails with EADDRINUSE and an IPv6 socket
	 *     has been bound, then just ignore the error.
	 */

	/* 
	 * TODO: instead of reordering the results, just loop through the
	 * results twice - once for ipv6 then for the rest.
	 */
	res = order_ipv6_first(res);
#endif

	/* initialize accept_fdset */
	FD_ZERO(&accept_fdset);
	
	/* try binding to all of the addresses returned by getaddrinfo */
	nfd = 0;
	for (ptr = res; ptr != NULL; ptr = ptr->ai_next) {

		/* only accept results we can handle */
		if (skip_address(ptr) == true) continue;
		
		/* create the socket */
		fd = socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol);
		if (fd < 0) {
			/* ignore this address if it is not supported */
			if (unsupported_sock_error(errno))
				continue;
			warning("cannot create the socket: %s",
			        strerror(errno));
			return -1;
		}

#if defined(ENABLE_IPV6) && defined(IPV6_V6ONLY)
		if (ptr->ai_family == PF_INET6) {
			int on = 1;
			/* in case of error, we will go on anyway... */
			err = setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY,
			                 &on, sizeof(on));
			if (err < 0)
				warning("error with sockopt IPV6_V6ONLY");
			else
				set_ipv6_only = true;
		}
#endif 

		if (set_sockopt_handler != NULL)
			set_sockopt_handler(fd, hdata);

		/* get the numeric name for this source address */
		getnameinfo_ex(ptr->ai_addr, ptr->ai_addrlen, name_buf, 
		               sizeof(name_buf), true);

		/* bind to the local address */
		err = bind(fd, ptr->ai_addr, ptr->ai_addrlen);
		if (err != 0) {
#ifdef ENABLE_IPV6
			/* suppress ADDRINUSE error for systems that double
			 * bind ipv6 and ipv4 and pretend it succeeded */
			if (errno == EADDRINUSE &&
			    ptr->ai_family == PF_INET &&
			    set_ipv6_only == false &&
			    bound_ipv6_any == true)
			{
				if (verbose_mode())
					warning(_("listening on %s ..."),
						name_buf);
				close(fd);
				continue;
			}
#endif
			warning(_("bind to source %s failed: %s"),
			        name_buf, strerror(errno));
			close(fd);
			continue;
		}

		if (ptr->ai_socktype == SOCK_STREAM) {
			err = listen(fd, SOMAXCONN);
			if (err != 0) {
				warning(_("cannot listen on %s: %s"),
				        name_buf, strerror(errno));
				free_bound_sockets(bound_sockets);
				return -1;
			}
		}

		if (verbose_mode())
			warning(_("listening on %s ..."), name_buf);

#ifdef ENABLE_IPV6
		/* check if this was an IPv6 socket bound to IN6_ADDR_ANY */
		if (ptr->ai_family == PF_INET6 &&
		    memcmp(&((struct sockaddr_in6 *)(ptr->ai_addr))->sin6_addr,
		           &in6addr_any, sizeof(struct in6_addr)) == 0)
		{
			bound_ipv6_any = true;
		}
#endif

		/* add fd to bound_sockets (just add to the head of the list) */
		bound_sockets =	add_bound_socket(bound_sockets, fd, 
				                 ptr->ai_socktype);

		/* add fd to accept_fdset */
		FD_SET(fd, &accept_fdset);
		maxfd = MAX(maxfd, fd);
		nfd++;
	}

	freeaddrinfo(res);
	
	if (nfd == 0) {
		warning(_("failed to bind to any local addr/port"));
		return -1;
	}

	/* enter into the accept loop */
 	for (;;) {
		fd_set tmp_ap_fdset;
		struct timeval tv, *tvp = NULL;
		struct sockaddr_storage dest;
		socklen_t destlen;
		int ns, socktype;
		char c_name_buf[AI_STR_SIZE];

		/* make a copy of accept_fdset before passing to select */
		memcpy(&tmp_ap_fdset, &accept_fdset, sizeof(fd_set));

		/* setup timeout */
		if (timeout > 0) {
			tv.tv_sec = timeout;
			tv.tv_usec = 0;
			tvp = &tv;
		}

		/* wait for an incoming connection */
		err = select(maxfd + 1, &tmp_ap_fdset, NULL, NULL, tvp);

		if (err <= 0) {
			if (err < 0 && errno == EINTR)
				continue;
			if (err == 0)
				warning(_("connection timed out"));
			else
				warning("select error: %s", strerror(errno));
			free_bound_sockets(bound_sockets);
			return -1;
		}
		
		/* find the ready filedescriptor */
		for (fd = 0; fd <= maxfd && !FD_ISSET(fd, &tmp_ap_fdset); ++fd)
			;

		/* if none were ready, loop to select again */
		if (fd > maxfd)
			continue;

		/* find socket type in bound_sockets */
		socktype = get_bound_socket_type(bound_sockets, fd);

		destlen = sizeof(dest);	

		/* for stream sockets we accept a new connection, whereas for
		 * dgram sockets we use MSG_PEEK to determine the sender */
		if (socktype == SOCK_STREAM) {
			ns = accept(fd, (struct sockaddr *)&dest, &destlen);
			if (ns < 0) {
				warning("accept failed: %s", strerror(errno));
				free_bound_sockets(bound_sockets);
				return -1;
			}
		} else {
			/* this is checked when binding listen sockets */
			assert(socktype == SOCK_DGRAM);

			err = recvfrom(fd, NULL, 0, MSG_PEEK,
			               (struct sockaddr *)&dest, &destlen);
			if (err < 0) {
				warning("recvfrom failed: %s", strerror(errno));
				free_bound_sockets(bound_sockets);
				return -1;
			}

			ns = dup(fd);
			if (ns < 0) {
				warning("dup failed: %s", strerror(errno));
				free_bound_sockets(bound_sockets);
				return -1;
			}
		}

		/* get names for each end of the connection */
		if (verbose_mode()) {
			struct sockaddr_storage src;
			socklen_t srclen = sizeof(src);

			/* find out what address the connection was to */
			err = getsockname(ns, (struct sockaddr *)&src, &srclen);
			if (err < 0) {
				warning("getsockname failed: %s",
				        strerror(errno));
				free_bound_sockets(bound_sockets);
				return -1;
			}

			/* get the numeric name for this source */
			getnameinfo_ex((struct sockaddr *)&src, srclen,
			               name_buf, sizeof(name_buf), true);

			/* get the name for this client */
			getnameinfo_ex((struct sockaddr *)&dest, destlen,
			               c_name_buf, sizeof(c_name_buf),
				       (hints->ai_flags & AI_NUMERICHOST));
		}

		/* check if connections from this client are allowed */
		if ((remote_address == NULL && remote_service == NULL) ||
		    (is_allowed((struct sockaddr *)&dest, destlen, hints,
				remote_address, remote_service) == true))
		{

			if (socktype == SOCK_DGRAM) {
				/* connect the socket to ensure we only talk
				 * with this client */
				err = connect(ns, (struct sockaddr *)&dest, 
				              destlen);
				if (err != 0) {
					warning(_("connect failed on "
					        "datagram socket: %s"),
					        strerror(errno));
					free_bound_sockets(bound_sockets);
					return -1;
				}
			}

			if (verbose_mode()) {
				warning(_("connect to %s from %s"),
				        name_buf, c_name_buf);
			}

			callback(ns, socktype, cdata);

			if (max_accept > 0 && --max_accept == 0)
				break;
		} else {
			if (socktype == SOCK_DGRAM) {
				/* the connection wasn't accepted - 
				 * remove the queued packet */
				recvfrom(ns, NULL, 0, 0, NULL, 0);
			}
			close(ns);

			if (verbose_mode()) {
				warning(_("refused connect to %s from %s"),
				        name_buf, c_name_buf);
			}
		}
	}

	/* close the listening sockets */
	for (i = 0; i <= maxfd; ++i) {
		if (FD_ISSET(i, &accept_fdset) != 0) close(i);
	}

	/* free the bound_socket list */
	free_bound_sockets(bound_sockets);
	return 0;
}



static bool skip_address(const struct addrinfo *ai)
{
	/* check arguments */
	assert(ai != NULL);
	
	/* only use socktypes we can handle */
	if (ai->ai_socktype != SOCK_STREAM &&
	    ai->ai_socktype != SOCK_DGRAM) 
		return true;

#ifdef ENABLE_IPV6
	/* 
	 * skip IPv4 mapped addresses returned from getaddrinfo,
	 * for security reasons. see the documents:
	 *
	 * http://playground.iijlab.net/i-d/
	 *       /draft-itojun-ipv6-transition-abuse-01.txt
	 *       
	 * http://playground.iijlab.net/i-d/
	 *       /draft-cmetz-v6ops-v4mapped-api-harmful-00.txt
	 *       
	 * http://playground.iijlab.net/i-d/
	 *       /draft-itojun-v6ops-v4mapped-harmful-01.txt
	 */
	if (is_address_ipv4_mapped(ai->ai_addr))
		return true;
#else
#ifdef PF_INET6
	/* skip IPv6 if disabled */
	if (ai->ai_family == PF_INET6)
		return true;
#endif
#endif
	
	return false;
}



/* 
 * Some systems (notably Linux) will bind to both IPv6 AND IPv4 when
 * listening.  Connections will still be accepted from either IPv6 or
 * IPv4 clients (IPv4 will be mapped into IPv6).  However, this means
 * that we MUST bind the IPv6 address ONLY for these hosts.
 *
 * To handle this, we will ensure that IPv6 sockets are bound first and then
 * attempt to bind the IPv4 ones.  On systems that double bind IPv6/IPv4 the
 * IPv4 bind will simply fail.  This function does the reordering - moving all
 * IPv6 addresses to the start of the getaddrinfo results.
 */
#ifdef ENABLE_IPV6
static struct addrinfo *order_ipv6_first(struct addrinfo *ai)
{
	struct addrinfo *ptr;
	struct addrinfo *lastv6 = NULL;
	struct addrinfo *tmp;

	assert(ai != NULL);

	/* Move all IPv6 addresses to the start of the list - keeping
	 * them in the original order. */

	if (ai->ai_family == PF_INET6)
		lastv6 = ai;

	for (ptr = ai; ptr != NULL && ptr->ai_next != NULL; ptr = ptr->ai_next){
		if (ptr->ai_next->ai_family == PF_INET6) {
			tmp = ptr->ai_next;
			ptr->ai_next = tmp->ai_next;
			if (lastv6) {
				tmp->ai_next = lastv6->ai_next;
				lastv6->ai_next = tmp;
			} else {
				tmp->ai_next = ai;
				ai = tmp;
			}
			lastv6 = tmp;
		}
	}

	return ai;
}
#endif



static void getnameinfo_ex(const struct sockaddr *sa, socklen_t len, char *str,
		size_t size, bool numeric_mode)
{
	int err;
	char hbuf_rev[NI_MAXHOST + 1];
	char hbuf_num[NI_MAXHOST + 1];
	char sbuf_rev[NI_MAXSERV + 1];
	char sbuf_num[NI_MAXSERV + 1];

	/* check arguments */
	assert(sa != NULL);
	assert(len > 0);
	assert(str != NULL);
	assert(size > 0);
	
	/* get the numeric name for this destination as a string */
	err = getnameinfo(sa, len, hbuf_num, sizeof(hbuf_num),
			  sbuf_num, sizeof(sbuf_num),
			  NI_NUMERICHOST | NI_NUMERICSERV);

	/* this should never happen */
	if (err != 0)
		fatal("getnameinfo failed: %s", gai_strerror(err));

	if (numeric_mode == false) {
		/* get the real name for this destination as a string */
		err = getnameinfo(sa, len, hbuf_rev, sizeof(hbuf_rev),
				  sbuf_rev, sizeof(sbuf_rev), 0);
		if (err == 0) {
			snprintf(str, size, "%s (%s) %s [%s]", hbuf_rev, 
			         hbuf_num, sbuf_num, sbuf_rev);
		} else {
			warning(_("inverse lookup failed for %s: %s"),
			        hbuf_num, gai_strerror(err));
			
			snprintf(str, size, "%s %s", hbuf_num, sbuf_num);
		}
	} else {
		snprintf(str, size, "%s %s", hbuf_num, sbuf_num);
	}
}



/* returns true if sa corresponds to the address/port specified in addr */
static bool is_allowed(const struct sockaddr *sa, socklen_t salen,
		const struct addrinfo *hints,
		const char *address, const char *service)
{
	struct addrinfo *res = NULL, *ptr;
	int err;
	bool ret;

	assert(sa != NULL);
	assert(hints != NULL);
	assert(address == NULL || strlen(address) > 0);
	assert(service == NULL || strlen(service) > 0);

	/* if no address or service is supplied, match everything */
	if (address == NULL && service == NULL) return true;
		
	err = getaddrinfo(address, service, hints, &res);
	if (err != 0) {
		/* some errors just indicate that the address wasn't suitable */
		switch (err) {
#ifdef HAVE_GETADDRINFO_EAI_NODATA 
  		case EAI_NODATA:
#endif
#ifdef HAVE_GETADDRINFO_EAI_ADDRFAMILY
  		case EAI_ADDRFAMILY:
#endif
  		case EAI_FAMILY:
		case EAI_SERVICE:
		case EAI_SOCKTYPE:
			return false;
		default:
			fatal("getaddrinfo error: %s", gai_strerror(err));
		}
	}

	ret = false;
	
	for (ptr = res; ptr != NULL; ptr = ptr->ai_next) {

#ifdef ENABLE_IPV6
		/* skip IPv4 mapped addresses returned from getaddrinfo */
		if (is_address_ipv4_mapped(ptr->ai_addr))
			continue;
#endif

		if (sockaddr_compare(sa, salen,
				     ptr->ai_addr, ptr->ai_addrlen) == true)
		{
			ret = true;
			break;
		}
	}

	freeaddrinfo(res);

	return ret;
}


syntax highlighted by Code2HTML, v. 0.9.1