/*
* 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