/*
 * solve.c - Secure RFC-2553-based IP resolution functions
 * $Id: solve.c,v 1.4 2004/06/05 15:15:17 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 Public 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

#include <stdio.h> /* stderr */
#include "secstr.h" /* memset(), memcpy(), strcmp() */
#include <stdlib.h> /* malloc(), free() */

#include <sys/types.h> /* needed before sys/socket.h on FreeBSD */
#if HAVE_SYS_SOCKET_H
# include <sys/socket.h> /* getsockname(), getpeername() */
#endif
#if HAVE_SYS_UN_H
# include <sys/un.h>
#endif
#include <errno.h>
#include "solve.h"


/*
 * Compares 2 socket addresses. Return 0 if they are identical,
 * a positive if they are different, a negative on error. It is assumed
 * that they are of the same size (otherwise, YOU know they are
 * different anyway, don't you?).
 *
 * Only nodes are compared, services are not.
 */
static int
sockaddrcmp (const struct sockaddr *a1, size_t a1len,
		 const struct sockaddr *a2, size_t a2len)
{
	char n1[NI_MAXHOST], n2[NI_MAXHOST];
	/* Normally, we'd compare addr and res->ai_addr, but there is
	no address family independant way to do this (memcmp() won't
	work in many case (at least Linux/IPv4).

	Instead, we do compare numerical address strings. This requires
	yet another (but fortunately non-blocking) call of getnameinfo.

	It is moreover assumed that service names cannot be spoofed
	(Service Name Service has not been invented, right?).
	*/
	if ((a1->sa_family != a2->sa_family)
	 || getnameinfo (a1, a1len, n1, sizeof (n1), NULL, 0, NI_NUMERICHOST)
	 || getnameinfo (a2, a2len, n2, sizeof (n2), NULL, 0, NI_NUMERICHOST))
		return -1;

	return (strcmp (n1, n2) == 0) ? 0 : 1;
}

/*
 * Secure reverse DNS resolution.
 * NI_NOFQDN (flags option) will fail unless addr is on the same domain
 * as we are (this is absolutely normal). All other flags should work
 * correctly.
 *
 * In case of error, if *servbuf is true, the service name is ok.
 */
static int
secure_getnameinfo(const struct sockaddr *addr, size_t addrlen,
			char *namebuf, size_t namelen,
			char *servbuf, size_t servlen, int flags)
{
	int check;

	/* Gets service name once and for all */
	check = getnameinfo (addr, addrlen, NULL, 0, servbuf, servlen,
			flags);
	if (check != 0)
		return check;

	/* Reverse DNS request */
	check = getnameinfo (addr, addrlen, namebuf, namelen, NULL, 0, flags);
	if ((check != 0) || (flags & NI_NUMERICHOST))
		return check; /* If numeric host name is requested, done. */
	else
	{
		struct addrinfo hints, *res, *info;

		/* Hostname DNS request (to prevent malicious users
		 * from DNS spoofing us). */
		memset (&hints, 0, sizeof (hints));
		hints.ai_family = addr->sa_family;
		check = getaddrinfo (namebuf, NULL, &hints, &res);

		if (check == 0)
		{
			for (info = res; info != NULL; info = info->ai_next)
				if (!sockaddrcmp (addr, addrlen, info->ai_addr,
							info->ai_addrlen))
				{
		 			freeaddrinfo(res);
				 	return 0;
				}

			/* DNS spoofing detected: use numeric address only */
			freeaddrinfo (res);
		}
	}
	return getnameinfo (addr, addrlen, namebuf, namelen, NULL, 0,
				flags|NI_NUMERICHOST);
}


/*** Generic struct addrinfo handling ***/
void
freeai (struct addrinfo *res)
{
	if (res != NULL)
	{
		freeai (res->ai_next);
		if (res->ai_addr != NULL)
			free (res->ai_addr);
		free (res);
	}
}


struct addrinfo *
makeai (const struct sockaddr *addr, socklen_t addrlen)
{
	struct addrinfo *res = (struct addrinfo *)
				malloc (sizeof (struct addrinfo));
	if (res == NULL)
		return NULL;

	memset (res, 0, sizeof (struct addrinfo));
	if (addr != NULL)
	{
		struct sockaddr *ad = (struct sockaddr *)
					malloc (addrlen);
		if (ad == NULL)
		{
			int errb = errno;
			free (res);
			errno = errb;
			return NULL;
		}
		memcpy (ad, addr, addrlen);
		res->ai_addr = ad;
	}
	res->ai_addrlen = addrlen;

	return res;
}


struct addrinfo *
copyai (const struct addrinfo *src)
{
	if (src != NULL)
	{
		struct addrinfo *res;

		res = (struct addrinfo *)malloc (sizeof (struct addrinfo));
		if (res == NULL)
			return NULL;
		memcpy (res, src, sizeof (struct addrinfo));

		if (src->ai_next != NULL)
		{
			res->ai_next = copyai (src->ai_next);
			if (res->ai_next == NULL)
			{
				int errb = errno;
				free (res);
				errno = errb;
				return NULL;
			}
		}

		if (src->ai_addr != NULL)
		{
			res->ai_addr =
				(struct sockaddr *)malloc (src->ai_addrlen);
			if (res->ai_addr == NULL)
			{
				int errb = errno;
				freeai (res->ai_next);
				free (res);
				errno = errb;
				return NULL;
			}

			memcpy (res->ai_addr, src->ai_addr, src->ai_addrlen);
		}

		return res;
	}
	return NULL;
}


#if HAVE_SYS_UN_H
/*** Unix (a.k.a. "local")  addresses ***/
static int
unix_getaddrinfo (const char *path, const struct addrinfo *hints,
			struct addrinfo **res)
{
	if (path != NULL)
	{
		struct sockaddr_un addr;
		struct addrinfo *ret;

		memset (&addr, 0, sizeof (addr));
		addr.sun_family = AF_LOCAL;
# ifdef HAVE_SA_LEN
		addr.sun_len = sizeof (addr);
# endif
		strncpy (addr.sun_path, path, sizeof (addr.sun_path));
		if (addr.sun_path[sizeof (addr.sun_path) - 1])
			return EAI_NONAME;

		ret = makeai ((struct sockaddr *)&addr, sizeof (addr));
		if (ret == NULL)
			return EAI_MEMORY;
		ret->ai_family = PF_LOCAL;

		ret->ai_socktype = hints->ai_socktype ?: SOCK_DGRAM;
		*res = ret;
		return 0;
	}
	return EAI_NONAME;
}
#endif


/*** Protocols family-independant addresses resolution ***/
int getnamebyaddr (const struct sockaddr *addr, size_t addrlen,
			char *nodename, size_t nlen, char *service,
			size_t slen, int flags)
{
	switch (addr->sa_family)
	{
		case AF_INET:
#ifdef AF_INET6
		case AF_INET6:
#endif
			return secure_getnameinfo (addr, addrlen, nodename,
						nlen, service, slen, flags);

#if HAVE_SYS_UN_H
		case AF_LOCAL:
			*nodename = 0;
			secure_strncpy (service,
					((struct sockaddr_un *)addr)->sun_path,
					slen);
			return 0;
#endif
	}

	return EAI_FAMILY;
}


int
getaddrbyname (const char *node, const char *service,
		const struct addrinfo *hints, struct addrinfo **res)
{
	switch ((hints != NULL) ? hints->ai_family : 0)
	{
		case 0:
		case PF_INET:
#ifdef PF_INET6
		case PF_INET6:
#endif
		{
			int check;
			struct addrinfo *inet_res, inet_hints;

			if (hints != NULL)
				memcpy (&inet_hints, hints,
					sizeof (struct addrinfo));
			else
				memset (&inet_hints, 0,
					sizeof (struct addrinfo));
			inet_hints.ai_flags |= AI_IDN;

			// Avoids unknown service error
			if ((node == NULL) && (service == NULL))
					service = "0";

			check = getaddrinfo (node, service, &inet_hints,
						&inet_res);
			if (check)
				return check;

			*res = copyai (inet_res);
			freeaddrinfo (inet_res);
			return (*res == NULL) ? EAI_SYSTEM : 0;
		}

#if HAVE_SYS_UN_H
		case PF_LOCAL:
			return (node != NULL) ? EAI_SERVICE
				: unix_getaddrinfo (service, hints, res);
#endif
	}

	return EAI_FAMILY;
}


syntax highlighted by Code2HTML, v. 0.9.1