/* * 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 /* size_t */ #include "secstr.h" /* strncpy(), strlen(), memcpy(), memset(), strchr() */ #include /* malloc(), free(), strtoul() */ #include #ifdef HAVE_ARPA_INET_H # include #endif #ifdef HAVE_NETINET_IN_H # include #endif #include #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 */