/*
** Copyright 2004-2006 Double Precision, Inc.
** See COPYING for distribution information.
*/

#include	"mksocket.h"

#if HAVE_UNISTD_H
#include	<unistd.h>
#endif
#if HAVE_FCNTL_H
#include	<fcntl.h>
#endif
#include	<stdio.h>
#include	<errno.h>
#include	<string.h>
#include	<stdlib.h>
#include	"soxwrap.h"

static const char rcsid[]="$Id: mksocket.c,v 1.3 2006/04/16 02:24:35 mrsam Exp $";

#define MKS_USEAFINET4 1
#define MKS_ERROK 2

#if	HAVE_SOXWRAP_IPV6
typedef struct in6_addr	NET_ADDR;
typedef struct sockaddr_in6 NET_SOCKADDR;
typedef struct sockaddr_storage NET_NETADDR;
#define NET_ADDRANY		in6addr_any
#else
typedef	struct in_addr	NET_ADDR;
typedef struct sockaddr_in	NET_SOCKADDR;
typedef struct sockaddr		NET_NETADDR;

extern struct in_addr rfc1035_addr_any;
#define NET_ADDRANY		rfc1035_addr_any
#endif

struct recycle_info {
	const struct sockaddr *sin;
	socklen_t sin_len;
	int found_socket;
};

/*
** If caller already has a socket listening on this address, recycle it.
*/

static int try_recycle_socket(int fd, void *voidarg)
{
	struct recycle_info *ri=(struct recycle_info *)voidarg;
	SOCKADDR_STORAGE sa;
	socklen_t salen=sizeof(sa);

	if (getsockname(fd, (struct sockaddr *)&sa, &salen) < 0)
		return 0;
	if (ri->sin->sa_family !=
	    ((struct sockaddr *)&sa)->sa_family)
		return 0;

	switch (ri->sin->sa_family) {
	case AF_INET:
		if (((struct sockaddr_in *)ri->sin)->sin_addr.s_addr !=
		    ((struct sockaddr_in *)&sa)->sin_addr.s_addr ||
		    ((struct sockaddr_in *)ri->sin)->sin_port !=
		    ((struct sockaddr_in *)&sa)->sin_port)
			return 0;
		break;

#if	HAVE_SOXWRAP_IPV6
	case AF_INET6:
		if (memcmp(&((struct sockaddr_in6 *)ri->sin)->sin6_addr,
			   &((struct sockaddr_in6 *)&sa)->sin6_addr,
			   sizeof(((struct sockaddr_in6 *)&sa)->sin6_addr)) ||
		    ((struct sockaddr_in6 *)ri->sin)->sin6_port !=
		    ((struct sockaddr_in6 *)&sa)->sin6_port)
			return 0;
		break;
#endif
	default:
		return 0;
	}
	ri->found_socket=fd;
	return 1;
}

static int mksocket2(const char *ipaddrarg,	/* Host/IP address */
		     const char *servname,	/* Service/port */
		     int socktype,	/* SOCKS_STREAM */
		     int flags,
		     int (*recycle_fd_func)
		     ( int(*)(int, void *), void *, void *),
		     void *voidarg)
{
	struct servent *servptr;
	int	port;
	int	fd;
	struct recycle_info ri;
	const struct sockaddr *sinaddr;
	int	sinaddrlen;

#if	HAVE_SOXWRAP_IPV6
	struct sockaddr_in6	sin6;
#endif

	struct sockaddr_in	sin4;

	int	af;

	servptr=getservbyname(servname, "tcp");
	if (servptr)
		port=servptr->s_port;
	else
	{
		port=atoi(servname);
		if (port <= 0 || port > 65535)
		{
			fprintf(stderr, "Invalid port: %s\n", servname);
			return (-1);
		}
		port=htons(port);
	}

	/* Create an IPv6 or an IPv4 socket */

#if	HAVE_SOXWRAP_IPV6
	if (flags & MKS_USEAFINET4)
	{
		fd=sox_socket(PF_INET, socktype, 0);
		af=AF_INET;
	}
	else
#endif
	{
#if	HAVE_SOXWRAP_IPV6

		if ((fd=sox_socket(PF_INET6, socktype, 0)) >= 0)
			af=AF_INET6;
		else
#endif
		{
			af=AF_INET;
			fd=sox_socket(PF_INET, socktype, 0);
		}
	}

	if (fd < 0)
		return (-1);

	/* Figure out what to bind based on what socket we created */

	if (ipaddrarg && strcmp(ipaddrarg, "0"))
	{

#if	HAVE_SOXWRAP_IPV6
		if (af == AF_INET6)
		{
			memset(&sin6, 0, sizeof(sin6));
			sin6.sin6_family=af;
			if (inet_pton(af, ipaddrarg, &sin6.sin6_addr) <= 0)
			{
				errno=EINVAL;
				close(fd);
				return -1;
			}
			sin6.sin6_port=port;

			sinaddr=(const struct sockaddr *)&sin6;
			sinaddrlen=sizeof(sin6);
		}
		else
#endif
			if (af == AF_INET)
			{
				memset(&sin4, 0, sizeof(sin4));
				sin4.sin_family=AF_INET;
				if (inet_aton(ipaddrarg, &sin4.sin_addr) == 0)
				{
					errno=EINVAL;
					close(fd);
					return -1;
				}
				sin4.sin_port=port;
				sinaddr=(const struct sockaddr *)&sin4;
				sinaddrlen=sizeof(sin4);
			}
			else
			{
				errno=EAFNOSUPPORT;
				close(fd);
				return (-1);
			}
	}
	else	/* Bind default address */
	{
#if	HAVE_SOXWRAP_IPV6
		if (af == AF_INET6)
		{
			memset(&sin6, 0, sizeof(sin6));
			sin6.sin6_family=AF_INET6;
			sin6.sin6_addr=in6addr_any;
			sin6.sin6_port=port;
			sinaddr=(const struct sockaddr *)&sin6;
			sinaddrlen=sizeof(sin6);
		}
		else
#endif
			if (af == AF_INET)
			{
				sin4.sin_family=AF_INET;
				sin4.sin_addr.s_addr=INADDR_ANY;
				sin4.sin_port=port;
				sinaddr=(const struct sockaddr *)&sin4;
				sinaddrlen=sizeof(sin4);
			}
			else
			{
				errno=EAFNOSUPPORT;
				close(fd);
				return (-1);
			}
	}

	ri.found_socket= -1;
	ri.sin=sinaddr;
	ri.sin_len=sinaddrlen;

	if (recycle_fd_func &&
	    (*recycle_fd_func)(&try_recycle_socket, &ri, voidarg) > 0 &&
	    ri.found_socket >= 0)
	{
		close(fd);
		return dup(ri.found_socket);
	}

	{
		int dummy=1;

		setsockopt(fd, SOL_SOCKET, SO_REUSEADDR,
			   (const char *)&dummy, sizeof(dummy));
	}

	if (fcntl(fd, F_SETFD, FD_CLOEXEC))
	{
		close(fd);
		return (-1);
	}

	if (fcntl(fd, F_SETFL, O_NONBLOCK))
	{
		close(fd);
		return (-1);
	}
	{
		int dummy=1;

		setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE,
			   (const char *)&dummy, sizeof(dummy));
	}

	if (sox_bind(fd, sinaddr, sinaddrlen) < 0)
	{
		if (flags & MKS_ERROK)
		{
			close(fd);
			return (-2);
		}
		close(fd);
		return (-1);
	}

	if (sox_listen(fd,
#ifdef	SOMAXCONN
		       SOMAXCONN
#else
		       5
#endif
		       ))
	{
		if (flags && MKS_ERROK)
		{
			close(fd);
			return (-2);
		}
		close(fd);
		return (-1);
	}
	return (fd);
}


int mksocket(const char *address,
	     const char *service,
	     int socktype,
	     int *fd1,
	     int *fd2,
	     int recycle_fd_func( int(*)(int, void *), void *, void *),
	     void *voidarg)
{
	int fd_flag=0;

	*fd1= -1;
	*fd2= -1;

	if (address && strcmp(address, "0"))
	{
#if HAVE_INET_PTON
		SOCKADDR_STORAGE ina;

		if (inet_pton(AF_INET, address, &ina) > 0)
			fd_flag=MKS_USEAFINET4;
#else
		struct in_addr ina;

		if (inet_aton(address, &ina))
			fd_flag=MKS_USEAFINET4;
#endif

	}


	*fd1=mksocket2(address, service, socktype, fd_flag,
		       recycle_fd_func, voidarg);

	/* BSD requires both an IPv6 and an IPv4 socket */

#if	HAVE_SOXWRAP_IPV6

	if (address == 0 || strcmp(address, "0") == 0)
	{
		int fd=mksocket2(address, service, socktype,
				 (MKS_USEAFINET4|MKS_ERROK),
				 recycle_fd_func, voidarg);

		if (fd < 0)
		{
			if (fd != -2)
			{
				if (*fd1 >= 0)
					close(*fd1);
				return -1;
			}
		}

		*fd2=fd;
	}
#endif
	if (*fd1 < 0 && *fd2 < 0)
		return -1;

	if (*fd1 < 0)
	{
		*fd1=*fd2;
		*fd2=-1;
	}
	return 0;
}

#if HAVE_SYS_POLL_H

#else

int poll(struct pollfd *pfd, unsigned int n, int timeout)
{
	fd_set r, w, e;
	int maxfd=-1;
	unsigned int i;
	struct timeval tv;
	int cnt=0;

	FD_ZERO(&r);
	FD_ZERO(&w);
	FD_ZERO(&e);

	for (i=0; i<n; i++)
	{
		if (pfd[i].fd >= maxfd)
			maxfd=pfd[i].fd;

		pfd[i].revents=0;
		if (pfd[i].events & (POLLIN|POLLPRI))
			FD_SET(pfd[i].fd, &r);
		if (pfd[i].events & POLLOUT)
			FD_SET(pfd[i].fd, &w);
		if (pfd[i].events & POLLPRI)
			FD_SET(pfd[i].fd, &e);
	}

	tv.tv_sec=timeout/1000;
	tv.tv_usec=(timeout % 1000) * 1000;

	if (select(maxfd+1, &r, &w, &e, timeout < 0 ?NULL:&tv) < 0)
		return -1;

	for (i=0; i<n; i++)
	{
		if (FD_ISSET(pfd[i].fd, &r))
			pfd[i].revents |= POLLIN;
		if (FD_ISSET(pfd[i].fd, &w))
			pfd[i].revents |= POLLOUT;
		if (FD_ISSET(pfd[i].fd, &e))
			pfd[i].revents |= POLLIN|POLLHUP;

		if (pfd[i].revents)
			++cnt;
	}

	return cnt;
}

#endif
