/* * 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 #endif #include /* stderr */ #include "secstr.h" /* memset(), memcpy(), strcmp() */ #include /* malloc(), free() */ #include /* needed before sys/socket.h on FreeBSD */ #if HAVE_SYS_SOCKET_H # include /* getsockname(), getpeername() */ #endif #if HAVE_SYS_UN_H # include #endif #include #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; }