/*
 * getaddrinfo.c - replacement functions for getaddrinfo (& co.)
 * This is not thread-safe!!!
 * $Id: getaddrinfo.c,v 1.6 2005/06/16 20:08:53 rdenisc Exp $
 */

/***********************************************************************
 *  Copyright (C) 2002-2004 Remi Denis-Courmont.                       *
 *  This program is free software; you can redistribute and/or modify  *
 *  it under the terms of the GNU General Public License as published  *
 *  by the Free Software Foundation; version 2 of the license.         *
 *                                                                     *
 *  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 Punlic License  *
 *  along with this program; if not, you can get it from:              *
 *  http://www.gnu.org/copyleft/gpl.html                               *
 ***********************************************************************/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#ifdef WIN32
# ifdef HAVE_GETADDRINFO
#  undef getaddrinfo
#  define getaddrinfo stub_getaddrinfo
#  undef freeaddrinfo
#  define freeaddrinfo stub_freeaddrinfo
# endif
# ifdef HAVE_GETNAMEINFO
#  undef getnameinfo
#  define getnameinfo stub_getnameinfo
# endif
#endif

#include "getaddrinfo.h"

#include <stddef.h> /* size_t */
#include "secstr.h" /* strncpy(), strlen(), memcpy(), memset(), strchr() */
#include <stdlib.h> /* malloc(), free(), strtoul() */
#include <sys/types.h>
#ifdef HAVE_ARPA_INET_H
# include <arpa/inet.h>
#endif
#ifdef HAVE_NETINET_IN_H
# include <netinet/in.h>
#endif
#include <errno.h>

#ifndef HAVE_GAI_STRERROR
static struct
{
	int code;
	const char *msg;
} const __gai_errlist[] =
{
	{ 0,			"Error 0" },
	{ EAI_BADFLAGS,		"Invalid flag used" },
	{ EAI_NONAME,		"Host or service not found" },
	{ EAI_AGAIN,		"Temporary name service failure" },
	{ EAI_FAIL,		"Non-recoverable name service failure" },
	{ EAI_NODATA,		"No data for host name" },
	{ EAI_FAMILY,		"Unsupported address family" },
	{ EAI_SOCKTYPE,		"Unsupported socket type" },
	{ EAI_SERVICE,		"Incompatible service for socket type" },
	{ EAI_ADDRFAMILY,	"Unavailable address family for host name" },
	{ EAI_MEMORY,		"Memory allocation failure" },
	{ EAI_SYSTEM,		"System error" },
	{ 0, 			NULL }
};

static const char *__gai_unknownerr = "Unrecognized error number";

/*
 * Converts an EAI_* error code into human readable english text.
 */
const char *
gai_strerror (int errnum)
{
	int i;

	for (i = 0; __gai_errlist[i].msg != NULL; i++)
		if (errnum == __gai_errlist[i].code)
			return __gai_errlist[i].msg;

	return __gai_unknownerr;
}
# undef _EAI_POSITIVE_MAX
#endif /* ifndef HAVE_GAI_STRERROR */

#if !(defined (HAVE_GETNAMEINFO) && defined (HAVE_GETADDRINFO))
/*
 * Converts the current herrno error value into an EAI_* error code.
 * That error code is normally returned by getnameinfo() or getaddrinfo().
 */
static int
gai_error_from_herrno (void)
{
	switch(h_errno)
	{
		case HOST_NOT_FOUND:
			return EAI_NONAME;

		case NO_ADDRESS:
# if (NO_ADDRESS != NO_DATA)
		case NO_DATA:
# endif
			return EAI_NODATA;

		case NO_RECOVERY:
			return EAI_FAIL;

		case TRY_AGAIN:
			return EAI_AGAIN;
	}
	return EAI_SYSTEM;
}
#endif /* if !(HAVE_GETNAMEINFO && HAVE_GETADDRINFO) */

#ifndef HAVE_GETNAMEINFO
/*
 * getnameinfo() non-thread-safe IPv4-only implementation,
 * Address-family-independant address to hostname translation
 * (reverse DNS lookup in case of IPv4).
 *
 * This is meant for use on old IP-enabled systems that are not IPv6-aware,
 * and probably do not have getnameinfo(), but have the old gethostbyaddr()
 * function.
 *
 * GNU C library 2.0.x is known to lack this function, even though it defines
 * getaddrinfo().
 */
int
getnameinfo (const struct sockaddr *sa, int salen,
	     char *host, int hostlen, char *serv, int servlen, int flags)
{
	if (((unsigned)salen < sizeof (struct sockaddr_in))
	 || (sa->sa_family != AF_INET))
		return EAI_FAMILY;
	else if (flags & (~_NI_MASK))
		return EAI_BADFLAGS;
	else
	{
		const struct sockaddr_in *addr;

		addr = (const struct sockaddr_in *)sa;

		if (host != NULL)
		{
			int solved = 0;

			/* host name resolution */
			if (!(flags & NI_NUMERICHOST))
			{
				struct hostent *hent;

				hent = gethostbyaddr (
						(const void*)&addr->sin_addr,
						4, AF_INET);

				if (hent != NULL)
				{
					secure_strncpy (host, hent->h_name,
							hostlen);

					/*
					 * only keep first part of hostname
					 * if user don't want fully qualified
					 * domain name
					 */
					if (flags & NI_NOFQDN)
					{
						char *ptr;

						ptr = strchr (host, '.');
						if (ptr != NULL)
							*ptr = 0;
					}

					solved = 1;
				}
				else if (flags & NI_NAMEREQD)
					return gai_error_from_herrno ();
			}

			if (!solved)
				/* inet_ntoa() can't fail */
				secure_strncpy (host,
						inet_ntoa (addr->sin_addr),
						hostlen);
		}

		if (serv != NULL)
		{
			int solved = 0;
			struct servent *sent = NULL;

			/* service name resolution */
			if (!(flags & NI_NUMERICSERV))
			{

				sent = getservbyport(addr->sin_port,
						     (flags & NI_DGRAM)
						      ? "udp" : "tcp");
				if (sent != NULL)
				{
					secure_strncpy (serv, sent->s_name,
							servlen);
					solved = 1;
				}
			}
			if (sent == NULL)
			{
				snprintf (serv, servlen, "%u",
					  (unsigned int)ntohs (addr->sin_port));
				serv[servlen - 1] = '\0';
			}
		}
	}
	return 0;
}
#endif /* if !HAVE_GETNAMEINFO */


#ifndef HAVE_GETADDRINFO
/*
 * This functions must be used to free the memory allocated by getaddrinfo().
 */
void
freeaddrinfo (struct addrinfo *res)
{
	if (res != NULL)
	{
		if (res->ai_canonname != NULL)
			free (res->ai_canonname);
		if (res->ai_addr != NULL)
			free (res->ai_addr);
		if (res->ai_next != NULL)
			free (res->ai_next);
		free (res);
	}
}


/*
 * Internal function that builds an addrinfo struct.
 */
static struct addrinfo *
makeaddrinfo (int af, int type, int proto,
		const struct sockaddr *addr, size_t addrlen,
		const char *canonname)
{
	struct addrinfo *res;

	res = (struct addrinfo *)malloc (sizeof (struct addrinfo));
	if (res != NULL)
	{
		res->ai_flags = 0;
		res->ai_family = af;
		res->ai_socktype = type;
		res->ai_protocol = proto;
		res->ai_addrlen = addrlen;
		res->ai_addr = malloc (addrlen);
		res->ai_canonname = NULL;
		res->ai_next = NULL;

		if (res->ai_addr != NULL)
		{
			memcpy (res->ai_addr, addr, addrlen);

			if (canonname != NULL)
			{
				res->ai_canonname = strdup (canonname);
				if (res->ai_canonname != NULL)
					return res; /* success ! */
			}
			else
				return res;
		}
	}
	/* failsafe */
	freeaddrinfo (res);
	return NULL;
}


static struct addrinfo *
makeipv4info (int type, int proto, u_long ip, u_short port, const char *name)
{
	struct sockaddr_in addr;

	memset (&addr, 0, sizeof (addr));
	addr.sin_family = AF_INET;
# ifdef HAVE_SA_LEN
	addr.sin_len = sizeof (addr);
# endif
	addr.sin_port = port;
	addr.sin_addr.s_addr = ip;

	return makeaddrinfo (PF_INET, type, proto,
				(struct sockaddr*)&addr, sizeof (addr), name);
}


/*
 * getaddrinfo() non-thread-safe IPv4-only implementation
 * Address-family-independant hostname to address resolution.
 *
 * This is meant for IPv6-unaware systems that do probably not provide
 * getaddrinfo(), but still have old function gethostbyname().
 *
 * Only UDP and TCP over IPv4 are supported here.
 */
int getaddrinfo (const char *node, const char *service,
		const struct addrinfo *hints, struct addrinfo **res)
{
	struct addrinfo *info;
	u_long ip;
	u_short port;
	int protocol = 0, flags = 0;
	const char *name = NULL;

	if (hints != NULL)
	{
		flags = hints->ai_flags;

		if (flags & ~_AI_MASK)
			return EAI_BADFLAGS;
		/* only accept AF_INET and AF_UNSPEC */
		if (hints->ai_family && (hints->ai_family != AF_INET))
			return EAI_FAMILY;

		/* protocol sanity check */
		switch (hints->ai_socktype)
		{
			case SOCK_STREAM:
				protocol = IPPROTO_TCP;
				break;

			case SOCK_DGRAM:
				protocol = IPPROTO_UDP;
				break;

			case SOCK_RAW:
			case 0:
				break;

			default:
				return EAI_SOCKTYPE;
		}
		if (hints->ai_protocol && protocol
		 && (protocol != hints->ai_protocol))
			return EAI_SERVICE;
	}

	*res = NULL;

	/* default values */
	if (node == NULL)
	{
		if (flags & AI_PASSIVE)
			ip = htonl (INADDR_ANY);
		else
			ip = htonl (INADDR_LOOPBACK);
	}
	else
	if ((ip = inet_addr (node)) == INADDR_NONE)
	{
		struct hostent *entry = NULL;

		/* hostname resolution */
		if (!(flags & AI_NUMERICHOST))
			entry = gethostbyname (node);

		if (entry == NULL)
			return EAI_NONAME;

		if ((entry->h_length != 4) || (entry->h_addrtype != AF_INET))
			return EAI_FAMILY;

		ip = *((u_long *) entry->h_addr);
		if (flags & AI_CANONNAME)
			name = entry->h_name;
	}

	if ((flags & AI_CANONNAME) && (name == NULL))
		name = node;

	/* service resolution */
	if (service == NULL)
		port = 0;
	else
	{
		long d;
		char *end;

		d = strtoul (service, &end, 0);
		if (end[0] /* service is not a number */
		 || (d > 65535))
		{
			struct servent *entry;
			const char *protoname;

			switch (protocol)
			{
				case IPPROTO_TCP:
					protoname = "tcp";
					break;

				case IPPROTO_UDP:
					protoname = "udp";
					break;

				default:
					protoname = NULL;
			}

			entry = getservbyname (service, protoname);
			if (entry == NULL)
				return EAI_SERVICE;

			port = entry->s_port;
		}
		else
			port = htons ((u_short)d);
	}

	/* building results... */
	if ((!protocol) || (protocol == IPPROTO_UDP))
	{
		info = makeipv4info (SOCK_DGRAM, IPPROTO_UDP, ip, port, name);
		if (info == NULL)
		{
			errno = ENOMEM;
			return EAI_SYSTEM;
		}
		if (flags & AI_PASSIVE)
			info->ai_flags |= AI_PASSIVE;
		*res = info;
	}
	if ((!protocol) || (protocol == IPPROTO_TCP))
	{
		info = makeipv4info (SOCK_STREAM, IPPROTO_TCP, ip, port, name);
		if (info == NULL)
		{
			errno = ENOMEM;
			return EAI_SYSTEM;
		}
		info->ai_next = *res;
		if (flags & AI_PASSIVE)
			info->ai_flags |= AI_PASSIVE;
		*res = info;
	}

	return 0;
}
#endif /* if !HAVE_GETADDRINFO */



syntax highlighted by Code2HTML, v. 0.9.1