| /* Copyright 2005 by Dominick Meglio |
| * |
| * Permission to use, copy, modify, and distribute this |
| * software and its documentation for any purpose and without |
| * fee is hereby granted, provided that the above copyright |
| * notice appear in all copies and that both that copyright |
| * notice and this permission notice appear in supporting |
| * documentation, and that the name of M.I.T. not be used in |
| * advertising or publicity pertaining to distribution of the |
| * software without specific, written prior permission. |
| * M.I.T. makes no representations about the suitability of |
| * this software for any purpose. It is provided "as is" |
| * without express or implied warranty. |
| */ |
| #include "setup.h" |
| #include <sys/types.h> |
| #include <ctype.h> |
| |
| #if defined(WIN32) && !defined(WATT32) |
| #include "nameser.h" |
| #else |
| #include <sys/socket.h> |
| #include <netinet/in.h> |
| #include <netdb.h> |
| #include <arpa/inet.h> |
| #include <arpa/nameser.h> |
| #ifdef HAVE_ARPA_NAMESER_COMPAT_H |
| #include <arpa/nameser_compat.h> |
| #endif |
| #endif |
| |
| #ifdef HAVE_NET_IF_H |
| #include <net/if.h> |
| #endif |
| |
| #ifdef HAVE_UNISTD_H |
| #include <unistd.h> |
| #endif |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| |
| #include "ares.h" |
| #include "ares_private.h" |
| #include "ares_ipv6.h" |
| #include "inet_ntop.h" |
| |
| #ifdef WATT32 |
| #undef WIN32 |
| #endif |
| |
| struct nameinfo_query { |
| ares_nameinfo_callback callback; |
| void *arg; |
| union { |
| struct sockaddr_in addr4; |
| struct sockaddr_in6 addr6; |
| } addr; |
| int family; |
| int flags; |
| }; |
| |
| #ifdef HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID |
| #define IPBUFSIZ 40+IF_NAMESIZE |
| #else |
| #define IPBUFSIZ 40 |
| #endif |
| |
| static void nameinfo_callback(void *arg, int status, struct hostent *host); |
| static char *lookup_service(unsigned short port, int flags, char *buf); |
| #ifdef HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID |
| static char *append_scopeid(struct sockaddr_in6 *addr6, unsigned int scopeid, char *buf); |
| #endif |
| static char *ares_striendstr(const char *s1, const char *s2); |
| |
| void ares_getnameinfo(ares_channel channel, const struct sockaddr *sa, socklen_t salen, |
| int flags, ares_nameinfo_callback callback, void *arg) |
| { |
| struct sockaddr_in *addr; |
| struct sockaddr_in6 *addr6; |
| struct nameinfo_query *niquery; |
| |
| /* Verify the buffer size */ |
| if (salen == sizeof(struct sockaddr_in)) |
| addr = (struct sockaddr_in *)sa; |
| else if (salen == sizeof(struct sockaddr_in6)) |
| addr6 = (struct sockaddr_in6 *)sa; |
| else |
| { |
| callback(arg, ARES_ENOTIMP, NULL, NULL); |
| return; |
| } |
| |
| /* If neither, assume they want a host */ |
| if (!(flags & ARES_NI_LOOKUPSERVICE) && !(flags & ARES_NI_LOOKUPHOST)) |
| flags |= ARES_NI_LOOKUPHOST; |
| |
| /* All they want is a service, no need for DNS */ |
| if ((flags & ARES_NI_LOOKUPSERVICE) && !(flags & ARES_NI_LOOKUPHOST)) |
| { |
| char buf[33], *service; |
| unsigned int port = 0; |
| |
| if (salen == sizeof(struct sockaddr_in)) |
| port = addr->sin_port; |
| else |
| port = addr6->sin6_port; |
| service = lookup_service(port, flags, buf); |
| callback(arg, ARES_SUCCESS, NULL, service); |
| return; |
| } |
| |
| /* They want a host lookup */ |
| if ((flags & ARES_NI_LOOKUPHOST)) |
| { |
| /* A numeric host can be handled without DNS */ |
| if ((flags & ARES_NI_NUMERICHOST)) |
| { |
| unsigned int port = 0; |
| char ipbuf[IPBUFSIZ]; |
| char srvbuf[32]; |
| char *service = NULL; |
| ipbuf[0] = 0; |
| |
| /* Specifying not to lookup a host, but then saying a host |
| * is required has to be illegal. |
| */ |
| if (flags & ARES_NI_NAMEREQD) |
| { |
| callback(arg, ARES_EBADFLAGS, NULL, NULL); |
| return; |
| } |
| if (salen == sizeof(struct sockaddr_in6)) |
| { |
| ares_inet_ntop(AF_INET6, &addr6->sin6_addr, ipbuf, IPBUFSIZ); |
| port = addr6->sin6_port; |
| /* If the system supports scope IDs, use it */ |
| #ifdef HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID |
| append_scopeid(addr6, flags, ipbuf); |
| #endif |
| } |
| else |
| { |
| ares_inet_ntop(AF_INET, &addr->sin_addr, ipbuf, IPBUFSIZ); |
| port = addr->sin_port; |
| } |
| /* They also want a service */ |
| if (flags & ARES_NI_LOOKUPSERVICE) |
| service = lookup_service(port, flags, srvbuf); |
| callback(arg, ARES_SUCCESS, ipbuf, service); |
| return; |
| } |
| /* This is where a DNS lookup becomes necessary */ |
| else |
| { |
| niquery = malloc(sizeof(struct nameinfo_query)); |
| if (!niquery) |
| { |
| callback(arg, ARES_ENOMEM, NULL, NULL); |
| return; |
| } |
| niquery->callback = callback; |
| niquery->arg = arg; |
| niquery->flags = flags; |
| if (sa->sa_family == AF_INET) |
| { |
| niquery->family = AF_INET; |
| memcpy(&niquery->addr.addr4, addr, sizeof(addr)); |
| ares_gethostbyaddr(channel, &addr->sin_addr, sizeof(struct in_addr), AF_INET, |
| nameinfo_callback, niquery); |
| } |
| else |
| { |
| niquery->family = AF_INET6; |
| memcpy(&niquery->addr.addr6, addr6, sizeof(addr6)); |
| ares_gethostbyaddr(channel, &addr6->sin6_addr, sizeof(struct in6_addr), AF_INET6, |
| nameinfo_callback, niquery); |
| } |
| } |
| } |
| } |
| |
| static void nameinfo_callback(void *arg, int status, struct hostent *host) |
| { |
| struct nameinfo_query *niquery = (struct nameinfo_query *) arg; |
| char srvbuf[33]; |
| char *service = NULL; |
| |
| |
| if (status == ARES_SUCCESS) |
| { |
| /* They want a service too */ |
| if (niquery->flags & ARES_NI_LOOKUPSERVICE) |
| { |
| if (niquery->family == AF_INET) |
| service = lookup_service(niquery->addr.addr4.sin_port, niquery->flags, srvbuf); |
| else |
| service = lookup_service(niquery->addr.addr6.sin6_port, niquery->flags, srvbuf); |
| } |
| /* NOFQDN means we have to strip off the domain name portion. |
| We do this by determining our own domain name, then searching the string |
| for this domain name and removing it. |
| */ |
| if (niquery->flags & ARES_NI_NOFQDN) |
| { |
| char buf[255]; |
| char *domain; |
| gethostname(buf, 255); |
| if ((domain = strchr(buf, '.'))) |
| { |
| char *end = ares_striendstr(host->h_name, domain); |
| if (end) |
| *end = 0; |
| } |
| } |
| niquery->callback(niquery->arg, ARES_SUCCESS, host->h_name, service); |
| return; |
| } |
| /* We couldn't find the host, but it's OK, we can use the IP */ |
| else if (status == ARES_ENOTFOUND && !(niquery->flags & ARES_NI_NAMEREQD)) |
| { |
| char ipbuf[IPBUFSIZ]; |
| if (niquery->family == AF_INET) |
| ares_inet_ntop(AF_INET, &niquery->addr.addr4.sin_addr, ipbuf, IPBUFSIZ); |
| else |
| { |
| ares_inet_ntop(AF_INET6, &niquery->addr.addr6.sin6_addr, ipbuf, IPBUFSIZ); |
| #ifdef HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID |
| append_scopeid(&niquery->addr.addr6, niquery->flags, ipbuf); |
| #endif |
| } |
| /* They want a service too */ |
| if (niquery->flags & ARES_NI_LOOKUPSERVICE) |
| { |
| if (niquery->family == AF_INET) |
| service = lookup_service(niquery->addr.addr4.sin_port, niquery->flags, srvbuf); |
| else |
| service = lookup_service(niquery->addr.addr6.sin6_port, niquery->flags, srvbuf); |
| } |
| niquery->callback(niquery->arg, ARES_SUCCESS, ipbuf, service); |
| return; |
| } |
| niquery->callback(niquery->arg, status, NULL, NULL); |
| free(niquery); |
| } |
| |
| static char *lookup_service(unsigned short port, int flags, char *buf) |
| { |
| if (port) |
| { |
| /* Just return the port as a string */ |
| if (flags & ARES_NI_NUMERICSERV) |
| sprintf(buf, "%u", ntohs(port)); |
| else |
| { |
| struct servent *se; |
| const char *proto; |
| |
| if (flags & ARES_NI_UDP) |
| proto = "udp"; |
| else if (flags & ARES_NI_SCTP) |
| proto = "sctp"; |
| else if (flags & ARES_NI_DCCP) |
| proto = "dccp"; |
| else |
| proto = "tcp"; |
| se = getservbyport(port, proto); |
| if (se && se->s_name) |
| strcpy(buf, se->s_name); |
| else |
| sprintf(buf, "%u", ntohs(port)); |
| } |
| return buf; |
| } |
| return NULL; |
| } |
| |
| #ifdef HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID |
| static char *append_scopeid(struct sockaddr_in6 *addr6, unsigned int flags, char *buf) |
| { |
| char tmpbuf[IF_NAMESIZE + 1]; |
| |
| tmpbuf[0] = '%'; |
| #ifdef HAVE_IF_INDEXTONAME |
| if ((flags & ARES_NI_NUMERICSCOPE) || (!IN6_IS_ADDR_LINKLOCAL(&addr6->sin6_addr) |
| && !IN6_IS_ADDR_MC_LINKLOCAL(&addr6->sin6_addr))) |
| { |
| sprintf(&tmpbuf[1], "%u", addr6->sin6_scope_id); |
| } |
| else |
| { |
| if (if_indextoname(addr6->sin6_scope_id, &tmpbuf[1]) == NULL) |
| sprintf(&tmpbuf[1], "%u", addr6->sin6_scope_id); |
| } |
| #else |
| sprintf(&tmpbuf[1], "%u", addr6->sin6_scope_id); |
| #endif |
| strcat(buf, tmpbuf); |
| return buf; |
| } |
| #endif |
| |
| /* Determines if s1 ends with the string in s2 (case-insensitive) */ |
| static char *ares_striendstr(const char *s1, const char *s2) |
| { |
| const char *c1, *c2, *c1_begin; |
| size_t s1_len = strlen(s1), s2_len = strlen(s2); |
| |
| /* If the substr is longer than the full str, it can't match */ |
| if (s2_len > s1_len) |
| return NULL; |
| |
| /* Jump to the end of s1 minus the length of s2 */ |
| c1_begin = s1+s1_len-s2_len; |
| c1 = (const char *)c1_begin; |
| c2 = s2; |
| while (c2 < s2+s2_len) |
| { |
| if (tolower(*c1) != tolower(*c2)) |
| return NULL; |
| else |
| { |
| c1++; |
| c2++; |
| } |
| } |
| if (c2 == c1 && c2 == NULL) |
| return (char *)c1_begin; |
| return NULL; |
| } |