/*-------------------------------------------------------------------------
 *
 * ip.c
 *	  Handles IPv6
 *
 * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  $Header: /cvsroot/pgsql/src/backend/libpq/ip.c,v 1.1 2003/01/06 03:18:26 momjian Exp $
 *
 * This file and the IPV6 implementation were initially provided by
 * Nigel Kukard <nkukard@lbsd.net>, Linux Based Systems Design
 * http://www.lbsd.net.
 *
 *-------------------------------------------------------------------------
 */

#ifndef FRONTEND
#include "postgres.h"
#else
#include "postgres_fe.h"
#endif

#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/in.h>
#ifdef HAVE_NETINET_TCP_H
#include <netinet/tcp.h>
#endif
#include <arpa/inet.h>
#include <sys/file.h>

#include "libpq/libpq.h"
#include "miscadmin.h"

#ifdef FRONTEND
#define elog fprintf
#define LOG  stderr
#endif

#if defined(HAVE_UNIX_SOCKETS) && defined(HAVE_IPV6)
static int getaddrinfo_unix(const char *path, const struct addrinfo *hintsp,
		struct addrinfo **result);
#endif   /* HAVE_UNIX_SOCKETS */

/*
 *	getaddrinfo2 - get address info for Unix, IPv4 and IPv6 sockets
 */
int
getaddrinfo2(const char *hostname, const char *servname,
#ifdef HAVE_IPV6
			 const struct addrinfo *hintp, struct addrinfo **result)
#else
			 int family, SockAddr *result)
#endif
{
#ifdef HAVE_UNIX_SOCKETS
#ifdef HAVE_IPV6
	if (hintp != NULL && hintp->ai_family == AF_UNIX)
		return getaddrinfo_unix(servname, hintp, result);
#else
	if (family == AF_UNIX)
		return 0;
#endif
	else
	{
#endif   /* HAVE_UNIX_SOCKETS */
#ifdef HAVE_IPV6
		/* NULL has special meaning to getaddrinfo */
		return getaddrinfo((!hostname || hostname[0] == '\0') ? NULL : hostname,
						   servname, hintp, result);
#else
		if (hostname[0] == '\0')
			result->in.sin_addr.s_addr = htonl(INADDR_ANY);
		else
		{
			struct hostent *hp;

			hp = gethostbyname(hostname);
			if ((hp == NULL) || (hp->h_addrtype != AF_INET))
			{
				elog(LOG, "getaddrinfo2: gethostbyname(%s) failed\n", hostname);
				return STATUS_ERROR;
			}
			memmove((char *) &(result->in.sin_addr), (char *) hp->h_addr,
					hp->h_length);
		}

		result->in.sin_port = htons((unsigned short)atoi(servname));
		return 0;
#endif	/* HAVE_IPV6 */

#ifdef HAVE_UNIX_SOCKETS
	}
#endif   /* HAVE_UNIX_SOCKETS */
}


/*
 *	freeaddrinfo2 - free IPv6 addrinfo structures
 */
#ifdef HAVE_IPV6
void
freeaddrinfo2(int hint_ai_family, struct addrinfo *ai)
{
#ifdef HAVE_UNIX_SOCKETS
	if (hint_ai_family == AF_UNIX)
	{
		struct addrinfo *p;

		while (ai != NULL)
		{
			p = ai;
			ai = ai->ai_next;
			free(p->ai_addr);
			free(p);
		}
	}
	else
#endif   /* HAVE_UNIX_SOCKETS */
		freeaddrinfo(ai);
}
#endif


#if defined(HAVE_UNIX_SOCKETS) && defined(HAVE_IPV6)
/* -------
 *	getaddrinfo_unix - get unix socket info using IPv6
 *
 *	Bug:  only one addrinfo is set even though hintsp is NULL or
 *		  ai_socktype is 0
 *		  AI_CANNONNAME does not support.
 * -------
 */
static int
getaddrinfo_unix(const char *path, const struct addrinfo *hintsp,
		struct addrinfo **result)
{
	struct addrinfo hints;
	struct addrinfo *aip;
	struct sockaddr_un *unp;

	MemSet(&hints, 0, sizeof(hints));

	if (hintsp == NULL)
	{
		hints.ai_family = AF_UNIX;
		hints.ai_socktype = SOCK_STREAM;
	}
	else
		memcpy(&hints, hintsp, sizeof(hints));

	if (hints.ai_socktype == 0)
		hints.ai_socktype = SOCK_STREAM;

	if (!(hints.ai_family == AF_UNIX))
	{
		elog(LOG, "hints.ai_family is invalied getaddrinfo_unix()\n");
		return EAI_ADDRFAMILY;
	}

	aip = calloc(1, sizeof(struct addrinfo));
	if (aip == NULL)
		return EAI_MEMORY;

	aip->ai_family = AF_UNIX;
	aip->ai_socktype = hints.ai_socktype;
	aip->ai_protocol = hints.ai_protocol;
	aip->ai_next = NULL;
	aip->ai_canonname = NULL;
	*result = aip;

	unp = calloc(1, sizeof(struct sockaddr_un));
	if (aip == NULL)
		return EAI_MEMORY;

	unp->sun_family = AF_UNIX;
	aip->ai_addr = (struct sockaddr *) unp;
	aip->ai_addrlen = sizeof(struct sockaddr_un);

	if (strlen(path) >= sizeof(unp->sun_path))
		return EAI_SERVICE;
	strcpy(unp->sun_path, path);

#if SALEN
	unp->sun_len = sizeof(struct sockaddr_un);
#endif   /* SALEN */

	if (hints.ai_flags & AI_PASSIVE)
		unlink(unp->sun_path);

	return 0;
}
#endif   /* HAVE_UNIX_SOCKETS && HAVE_IPV6 */

/* ----------
 * SockAddr_ntop - set IP address string from SockAddr
 *
 * parameters...  sa	: SockAddr union
 *				  dst	: buffer for address string
 *				  cnt	: sizeof dst
 *				  v4conv: non-zero: if address is IPv4 mapped IPv6 address then
 *						  convert to IPv4 address.
 * returns... pointer to dst
 * if sa.sa_family is not AF_INET or AF_INET6 dst is set as empy string.
 * ----------
 */
char *
SockAddr_ntop(const SockAddr *sa, char *dst, size_t cnt, int v4conv)
{
	switch (sa->sa.sa_family)
	{
		case AF_INET:
#ifdef HAVE_IPV6
			inet_ntop(AF_INET, &sa->in.sin_addr, dst, cnt);
#else
			StrNCpy(dst, inet_ntoa(sa->in.sin_addr), cnt);
#endif
			break;
#ifdef HAVE_IPV6
		case AF_INET6:
			inet_ntop(AF_INET6, &sa->in6.sin6_addr, dst, cnt);
			if (v4conv && IN6_IS_ADDR_V4MAPPED(&sa->in6.sin6_addr))
				strcpy(dst, dst + 7);
			break;
#endif
		default:
			dst[0] = '\0';
			break;
	}
	return dst;
}


/*
 *	SockAddr_pton - IPv6 pton
 */
int
SockAddr_pton(SockAddr *sa, const char *src)
{
	int			family = AF_INET;
#ifdef HAVE_IPV6
	const char		*ch;

	for (ch = src; *ch != '\0'; ch++)
	{
		if (*ch == ':')
		{
			family = AF_INET6;
			break;
		}
	}
#endif

	sa->sa.sa_family = family;

	switch (family)
	{
		case AF_INET:
#ifdef HAVE_IPV6
			return inet_pton(AF_INET, src, &sa->in.sin_addr);
#else
			return inet_aton(src, &sa->in.sin_addr);
#endif

#ifdef HAVE_IPV6
		case AF_INET6:
			return inet_pton(AF_INET6, src, &sa->in6.sin6_addr);
			break;
#endif
		default:
			return -1;
	}
}




/*
 *	isAF_INETx - check to see if sa is AF_INET or AF_INET6
 */
int
isAF_INETx(const int family)
{
	if (family == AF_INET
#ifdef HAVE_IPV6
		|| family == AF_INET6
#endif
		)
		return 1;
	else
		return 0;
}


int
rangeSockAddr(const SockAddr *addr, const SockAddr *netaddr, const SockAddr *netmask)
{
	if (addr->sa.sa_family == AF_INET)
		return rangeSockAddrAF_INET(addr, netaddr, netmask);
#ifdef HAVE_IPV6
	else if (addr->sa.sa_family == AF_INET6)
		return rangeSockAddrAF_INET6(addr, netaddr, netmask);
#endif
	else
		return 0;
}

int
rangeSockAddrAF_INET(const SockAddr *addr, const SockAddr *netaddr,
					 const SockAddr *netmask)
{
	if (addr->sa.sa_family != AF_INET ||
		netaddr->sa.sa_family != AF_INET ||
		netmask->sa.sa_family != AF_INET)
		return 0;
	if (((addr->in.sin_addr.s_addr ^ netaddr->in.sin_addr.s_addr) &
		 netmask->in.sin_addr.s_addr) == 0)
		return 1;
	else
		return 0;
}

#ifdef HAVE_IPV6
int
rangeSockAddrAF_INET6(const SockAddr *addr, const SockAddr *netaddr,
					  const SockAddr *netmask)
{
	int			i;

	if (IN6_IS_ADDR_V4MAPPED(&addr->in6.sin6_addr))
	{
		SockAddr	addr4;

		convSockAddr6to4(addr, &addr4);
		if (rangeSockAddrAF_INET(&addr4, netaddr, netmask))
			return 1;
	}

	if (netaddr->sa.sa_family != AF_INET6 ||
		netmask->sa.sa_family != AF_INET6)
		return 0;

	for (i = 0; i < 16; i++)
	{
		if (((addr->in6.sin6_addr.s6_addr[i] ^ netaddr->in6.sin6_addr.s6_addr[i]) &
			 netmask->in6.sin6_addr.s6_addr[i]) != 0)
			return 0;
	}

	return 1;
}

void
convSockAddr6to4(const SockAddr *src, SockAddr *dst)
{
	char		addr_str[INET6_ADDRSTRLEN];

	dst->in.sin_family = AF_INET;
	dst->in.sin_port = src->in6.sin6_port;

	dst->in.sin_addr.s_addr = src->in6.sin6_addr.s6_addr32[3];
	SockAddr_ntop(src, addr_str, INET6_ADDRSTRLEN, 0);
}
#endif
