/*
 *  netsupport.c - networking support module - implementation
 * 
 *  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 "misc.h"
#include "netsupport.h"

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

#if HAVE_ALLOCA_H
#include <alloca.h>
#else
#ifdef _AIX
#pragma alloca
#else
#ifndef alloca /* predefined by HP cc +Olibcalls */
char *alloca();
#endif
#endif
#endif

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



/* call 'connect' in non-blocking mode and use select to await a timeout */
int connect_with_timeout(int fd, const struct sockaddr *sa,
		socklen_t salen, int timeout)
{
	int err;
	struct timeval tv, *tvp = NULL;
	fd_set connect_fdset;
	socklen_t len;
	int optval;
	
	assert(sa != NULL);
	assert(salen > 0);
	
	/* set connect timeout */
	if (timeout > 0) {
		tv.tv_sec = (time_t)timeout;
		tv.tv_usec = 0;
		tvp = &tv;
	}

	/* set fd to nonblocking */
	nonblock(fd);
	
	/* attempt the connection */
	err = connect(fd, sa, salen);
	
	if (err != 0 && errno == EINPROGRESS) {
		/* connection is proceeding
		 * it is complete (or failed) when select returns */

		/* initialize connect_fdset */
		FD_ZERO(&connect_fdset);
		FD_SET(fd, &connect_fdset);

		/* call select */
		do {
			err = select(fd + 1, NULL, &connect_fdset, 
				     NULL, tvp);
		} while (err < 0 && errno == EINTR);

		/* select error */
		if (err < 0)
			return -1;
	
		/* we have reached a timeout */
		if (err == 0) 
		{
			errno = ETIMEDOUT;
			return -1;
		}
		
		/* select returned successfully, but we must test socket 
		 * error for result */
		len = sizeof(optval);
		err = getsockopt(fd, SOL_SOCKET, SO_ERROR, &optval, &len);
		if (err != 0)
			return err;
		
		/* if getsockopt indicates a failure occured, then store the
		 * error code into errno and return a failure */
		if (optval != 0)
		{
			errno = optval;
			return -1;
		}
	}

	return 0;
}



#ifdef ENABLE_IPV6
/* returns true if a represents an ipv4-mapped address */
bool is_address_ipv4_mapped(const struct sockaddr *a)
{
	bool ret = false;
	const struct sockaddr_in6 *tmp = (const struct sockaddr_in6 *)a;
	
	assert(a != NULL);
	
	if ((a->sa_family == AF_INET6) && 
	    IN6_IS_ADDR_V4MAPPED(&(tmp->sin6_addr)))
	{
		ret = true;
	}
			
	return ret;
}
#endif



/* compare two sockaddr structs to see if they represent the same address */
bool sockaddr_compare(const struct sockaddr *a, socklen_t a_len,
		const struct sockaddr *b, socklen_t b_len)
{
	assert(a != NULL);
	assert(b != NULL);
	assert(a_len > 0);
	assert(b_len > 0);
	
#ifdef ENABLE_IPV6
	/* we have to handle IPv6 IPV4MAPPED addresses - convert them to IPv4 */
	if (is_address_ipv4_mapped(a)) {
		const struct sockaddr_in6 *a6;
		struct sockaddr_in *a_in;

		a6 = (const struct sockaddr_in6 *)a;
		a_in = (struct sockaddr_in *)alloca(sizeof(struct sockaddr_in));

		memset(a_in, 0, sizeof(struct sockaddr_in));
		memcpy(&(a_in->sin_addr.s_addr), &(a6->sin6_addr.s6_addr[12]),
		       sizeof(struct in_addr));
		a_in->sin_port = a6->sin6_port;
		a = (const struct sockaddr *)a_in;
	}

	if (is_address_ipv4_mapped(b)) {
		const struct sockaddr_in6 *b6;
		struct sockaddr_in *b_in;

		b6 = (const struct sockaddr_in6 *)b;
		b_in = (struct sockaddr_in *)alloca(sizeof(struct sockaddr_in));

		memset(b_in, 0, sizeof(struct sockaddr_in));
		memcpy(&(b_in->sin_addr.s_addr), &(b6->sin6_addr.s6_addr[12]),
		       sizeof(struct in_addr));
		b_in->sin_port = b6->sin6_port;
		b = (const struct sockaddr *)b_in;
	}
#endif

	/* now we can perform the comparison */

	/* check family is the same */
	if (a->sa_family != b->sa_family)
		return false;

	/* the comparison is unique for each real sockaddr family */

#ifdef ENABLE_IPV6
	if (a->sa_family == AF_INET6) {
		const struct sockaddr_in6 *a6 = (const struct sockaddr_in6 *)a;
		const struct sockaddr_in6 *b6 = (const struct sockaddr_in6 *)b;

#ifdef HAVE_SOCKADDR_IN6_SCOPE_ID
		/* compare scope */
		if (a6->sin6_scope_id && b6->sin6_scope_id &&
		    (a6->sin6_scope_id != b6->sin6_scope_id))
		{
			return false;
		}
#endif
		/* compare address part 
		 * either may be IN6ADDR_ANY, resulting in a good match */
		if ((memcmp(&(a6->sin6_addr), &in6addr_any,
		            sizeof(struct in6_addr)) != 0) &&
		    (memcmp(&(b6->sin6_addr), &in6addr_any,
			    sizeof(struct in6_addr)) != 0) &&
		    (memcmp(&(a6->sin6_addr), &(b6->sin6_addr), 
			    sizeof(struct in6_addr)) != 0))
		{
			return false;
		}

		/* compare port part 
		 * either port may be 0(any), resulting in a good match */
		return ((a6->sin6_port == 0) || (b6->sin6_port == 0) ||
		        (a6->sin6_port == b6->sin6_port))? true : false;
	}
#endif

	if (a->sa_family == AF_INET) { 
		const struct sockaddr_in *a_in = (const struct sockaddr_in *)a;
		const struct sockaddr_in *b_in = (const struct sockaddr_in *)b;

		/* compare address part
		 * either may be INADDR_ANY, resulting in a good match */
		if ((a_in->sin_addr.s_addr != INADDR_ANY) &&
		    (b_in->sin_addr.s_addr != INADDR_ANY) &&
		    (a_in->sin_addr.s_addr != b_in->sin_addr.s_addr))
		{
			return false;
		}

		/* compare port part */
		/* either port may be 0(any), resulting in a good match */
		return ((a_in->sin_port == 0) || (b_in->sin_port == 0) ||
		        (a_in->sin_port == b_in->sin_port))? true : false;
	}

#ifdef ENABLE_BLUEZ
	if (a->sa_family == AF_BLUETOOTH) {
		/* just compare the sockaddr structures directly */
		return ((a_len == b_len) && memcmp(a, b, a_len) == 0)?
			true : false;
	}
#endif
	
	/* for all other socket types, return false */
	return false;
}



/* On some systems, getaddrinfo will return results that can't actually be
 * used - resulting in a failure when trying to create the socket.
 * This function checks for all the different error codes that indicate this
 * situation */
bool unsupported_sock_error(int err)
{
	return (err == EPFNOSUPPORT ||
	        err == EAFNOSUPPORT ||
	        err == EPROTONOSUPPORT ||
		err == ESOCKTNOSUPPORT ||
	        err == ENOPROTOOPT)?
		true : false;
}



/* add a new fd/socktype pair to the list */
bound_socket_t *add_bound_socket(bound_socket_t *list,
                                 int fd, int socktype)
{
	bound_socket_t *fdnew;
	
	fdnew = (bound_socket_t *)xmalloc(sizeof(bound_socket_t));
	
	fdnew->fd = fd;
	fdnew->socktype = socktype;
	
	/* prepend to the start of the list */
	fdnew->next = list;
	
	return fdnew;
}



/* get the socktype for a given fd in a list */
int get_bound_socket_type(const bound_socket_t *list, int fd)
{
	assert(list != NULL);
	
	while (list != NULL && list->fd != fd)
		list = list->next;
	
	return ((list != NULL) ? list->socktype : -1);
}



/* free a bound socket list */
void free_bound_sockets(bound_socket_t *list)
{
	bound_socket_t *tmp;
	
	while (list != NULL) {
		tmp = list;
		list = list->next;
		free(tmp);
	}
}



/* close all bound sockets in a list and free the list */
void close_and_destroy_bound_sockets(bound_socket_t *list)
{
	bound_socket_t *tmp;
	
	while (list != NULL) {
		tmp = list;
		list = list->next;
		close(tmp->fd);
		free(tmp);
	}
}


syntax highlighted by Code2HTML, v. 0.9.1