mirror of https://github.com/c-ares/c-ares.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
427 lines
12 KiB
427 lines
12 KiB
|
|
/* 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 "ares_setup.h" |
|
|
|
#ifdef HAVE_GETSERVBYPORT_R |
|
# if !defined(GETSERVBYPORT_R_ARGS) || \ |
|
(GETSERVBYPORT_R_ARGS < 4) || (GETSERVBYPORT_R_ARGS > 6) |
|
# error "you MUST specifiy a valid number of arguments for getservbyport_r" |
|
# endif |
|
#endif |
|
|
|
#ifdef HAVE_SYS_SOCKET_H |
|
# include <sys/socket.h> |
|
#endif |
|
#ifdef HAVE_NETINET_IN_H |
|
# include <netinet/in.h> |
|
#endif |
|
#ifdef HAVE_NETDB_H |
|
# include <netdb.h> |
|
#endif |
|
#ifdef HAVE_ARPA_INET_H |
|
# include <arpa/inet.h> |
|
#endif |
|
#ifdef HAVE_ARPA_NAMESER_H |
|
# include <arpa/nameser.h> |
|
#else |
|
# include "nameser.h" |
|
#endif |
|
#ifdef HAVE_ARPA_NAMESER_COMPAT_H |
|
# include <arpa/nameser_compat.h> |
|
#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_ipv6.h" |
|
#include "inet_ntop.h" |
|
#include "ares_nowarn.h" |
|
#include "ares_private.h" |
|
|
|
struct nameinfo_query { |
|
ares_nameinfo_callback callback; |
|
void *arg; |
|
union { |
|
struct sockaddr_in addr4; |
|
struct sockaddr_in6 addr6; |
|
} addr; |
|
int family; |
|
int flags; |
|
int timeouts; |
|
}; |
|
|
|
#ifdef HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID |
|
#define IPBUFSIZ \ |
|
(sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255") + IF_NAMESIZE) |
|
#else |
|
#define IPBUFSIZ \ |
|
(sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255")) |
|
#endif |
|
|
|
static void nameinfo_callback(void *arg, int status, int timeouts, |
|
struct hostent *host); |
|
static char *lookup_service(unsigned short port, int flags, |
|
char *buf, size_t buflen); |
|
#ifdef HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID |
|
static void append_scopeid(struct sockaddr_in6 *addr6, unsigned int scopeid, |
|
char *buf, size_t buflen); |
|
#endif |
|
static char *ares_striendstr(const char *s1, const char *s2); |
|
|
|
void ares_getnameinfo(ares_channel channel, const struct sockaddr *sa, |
|
ares_socklen_t salen, |
|
int flags, ares_nameinfo_callback callback, void *arg) |
|
{ |
|
struct sockaddr_in *addr = NULL; |
|
struct sockaddr_in6 *addr6 = NULL; |
|
struct nameinfo_query *niquery; |
|
unsigned int port = 0; |
|
|
|
/* Validate socket address family and length */ |
|
if ((sa->sa_family == AF_INET) && |
|
(salen == sizeof(struct sockaddr_in))) |
|
{ |
|
addr = (struct sockaddr_in *)sa; |
|
port = addr->sin_port; |
|
} |
|
else if ((sa->sa_family == AF_INET6) && |
|
(salen == sizeof(struct sockaddr_in6))) |
|
{ |
|
addr6 = (struct sockaddr_in6 *)sa; |
|
port = addr6->sin6_port; |
|
} |
|
else |
|
{ |
|
callback(arg, ARES_ENOTIMP, 0, 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; |
|
|
|
service = lookup_service((unsigned short)(port & 0xffff), |
|
flags, buf, sizeof(buf)); |
|
callback(arg, ARES_SUCCESS, 0, 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)) |
|
{ |
|
char ipbuf[IPBUFSIZ]; |
|
char srvbuf[33]; |
|
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, 0, NULL, NULL); |
|
return; |
|
} |
|
if (salen == sizeof(struct sockaddr_in6)) |
|
{ |
|
ares_inet_ntop(AF_INET6, &addr6->sin6_addr, ipbuf, IPBUFSIZ); |
|
/* If the system supports scope IDs, use it */ |
|
#ifdef HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID |
|
append_scopeid(addr6, flags, ipbuf, sizeof(ipbuf)); |
|
#endif |
|
} |
|
else |
|
{ |
|
ares_inet_ntop(AF_INET, &addr->sin_addr, ipbuf, IPBUFSIZ); |
|
} |
|
/* They also want a service */ |
|
if (flags & ARES_NI_LOOKUPSERVICE) |
|
service = lookup_service((unsigned short)(port & 0xffff), |
|
flags, srvbuf, sizeof(srvbuf)); |
|
callback(arg, ARES_SUCCESS, 0, ipbuf, service); |
|
return; |
|
} |
|
/* This is where a DNS lookup becomes necessary */ |
|
else |
|
{ |
|
niquery = malloc(sizeof(struct nameinfo_query)); |
|
if (!niquery) |
|
{ |
|
callback(arg, ARES_ENOMEM, 0, NULL, NULL); |
|
return; |
|
} |
|
niquery->callback = callback; |
|
niquery->arg = arg; |
|
niquery->flags = flags; |
|
niquery->timeouts = 0; |
|
if (sa->sa_family == AF_INET) |
|
{ |
|
niquery->family = AF_INET; |
|
memcpy(&niquery->addr.addr4, addr, sizeof(struct in_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(struct ares_in6_addr)); |
|
ares_gethostbyaddr(channel, &addr6->sin6_addr, |
|
sizeof(struct ares_in6_addr), AF_INET6, |
|
nameinfo_callback, niquery); |
|
} |
|
} |
|
} |
|
} |
|
|
|
static void nameinfo_callback(void *arg, int status, int timeouts, |
|
struct hostent *host) |
|
{ |
|
struct nameinfo_query *niquery = (struct nameinfo_query *) arg; |
|
char srvbuf[33]; |
|
char *service = NULL; |
|
|
|
niquery->timeouts += timeouts; |
|
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, sizeof(srvbuf)); |
|
else |
|
service = lookup_service(niquery->addr.addr6.sin6_port, |
|
niquery->flags, srvbuf, sizeof(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. |
|
*/ |
|
#ifdef HAVE_GETHOSTNAME |
|
if (niquery->flags & ARES_NI_NOFQDN) |
|
{ |
|
char buf[255]; |
|
char *domain; |
|
gethostname(buf, 255); |
|
if ((domain = strchr(buf, '.')) != NULL) |
|
{ |
|
char *end = ares_striendstr(host->h_name, domain); |
|
if (end) |
|
*end = 0; |
|
} |
|
} |
|
#endif |
|
niquery->callback(niquery->arg, ARES_SUCCESS, niquery->timeouts, |
|
(char *)(host->h_name), |
|
service); |
|
free(niquery); |
|
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, |
|
sizeof(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, sizeof(srvbuf)); |
|
else |
|
service = lookup_service(niquery->addr.addr6.sin6_port, |
|
niquery->flags, srvbuf, sizeof(srvbuf)); |
|
} |
|
niquery->callback(niquery->arg, ARES_SUCCESS, niquery->timeouts, ipbuf, |
|
service); |
|
free(niquery); |
|
return; |
|
} |
|
niquery->callback(niquery->arg, status, niquery->timeouts, NULL, NULL); |
|
free(niquery); |
|
} |
|
|
|
static char *lookup_service(unsigned short port, int flags, |
|
char *buf, size_t buflen) |
|
{ |
|
const char *proto; |
|
struct servent *sep; |
|
#ifdef HAVE_GETSERVBYPORT_R |
|
struct servent se; |
|
#endif |
|
char tmpbuf[4096]; |
|
|
|
if (port) |
|
{ |
|
if (flags & ARES_NI_NUMERICSERV) |
|
sep = NULL; |
|
else |
|
{ |
|
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"; |
|
#ifdef HAVE_GETSERVBYPORT_R |
|
sep = &se; |
|
memset(tmpbuf, 0, sizeof(tmpbuf)); |
|
#if GETSERVBYPORT_R_ARGS == 6 |
|
if (getservbyport_r(port, proto, &se, (void *)tmpbuf, |
|
sizeof(tmpbuf), &sep) != 0) |
|
sep = NULL; |
|
#elif GETSERVBYPORT_R_ARGS == 5 |
|
sep = getservbyport_r(port, proto, &se, (void *)tmpbuf, |
|
sizeof(tmpbuf)); |
|
#elif GETSERVBYPORT_R_ARGS == 4 |
|
if (getservbyport_r(port, proto, &se, (void *)tmpbuf) != 0) |
|
sep = NULL; |
|
#else |
|
/* Lets just hope the OS uses TLS! */ |
|
sep = getservbyport(port, proto); |
|
#endif |
|
#else |
|
/* Lets just hope the OS uses TLS! */ |
|
#if (defined(NETWARE) && !defined(__NOVELL_LIBC__)) |
|
sep = getservbyport(port, (char*)proto); |
|
#else |
|
sep = getservbyport(port, proto); |
|
#endif |
|
#endif |
|
} |
|
if (sep && sep->s_name) |
|
/* get service name */ |
|
strcpy(tmpbuf, sep->s_name); |
|
else |
|
/* get port as a string */ |
|
sprintf(tmpbuf, "%u", (unsigned int)ntohs(port)); |
|
if (strlen(tmpbuf) < buflen) |
|
/* return it if buffer big enough */ |
|
strcpy(buf, tmpbuf); |
|
else |
|
/* avoid reusing previous one */ |
|
buf[0] = '\0'; |
|
return buf; |
|
} |
|
buf[0] = '\0'; |
|
return NULL; |
|
} |
|
|
|
#ifdef HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID |
|
static void append_scopeid(struct sockaddr_in6 *addr6, unsigned int flags, |
|
char *buf, size_t buflen) |
|
{ |
|
#ifdef HAVE_IF_INDEXTONAME |
|
int is_ll, is_mcll; |
|
#endif |
|
static const char fmt_u[] = "%u"; |
|
static const char fmt_lu[] = "%lu"; |
|
char tmpbuf[IF_NAMESIZE + 2]; |
|
size_t bufl; |
|
const char *fmt = (sizeof(addr6->sin6_scope_id) > sizeof(unsigned int))? |
|
fmt_lu:fmt_u; |
|
|
|
tmpbuf[0] = '%'; |
|
|
|
#ifdef HAVE_IF_INDEXTONAME |
|
is_ll = IN6_IS_ADDR_LINKLOCAL(&addr6->sin6_addr); |
|
is_mcll = IN6_IS_ADDR_MC_LINKLOCAL(&addr6->sin6_addr); |
|
if ((flags & ARES_NI_NUMERICSCOPE) || |
|
(!is_ll && !is_mcll)) |
|
{ |
|
sprintf(&tmpbuf[1], fmt, addr6->sin6_scope_id); |
|
} |
|
else |
|
{ |
|
if (if_indextoname(addr6->sin6_scope_id, &tmpbuf[1]) == NULL) |
|
sprintf(&tmpbuf[1], fmt, addr6->sin6_scope_id); |
|
} |
|
#else |
|
sprintf(&tmpbuf[1], fmt, addr6->sin6_scope_id); |
|
(void) flags; |
|
#endif |
|
tmpbuf[IF_NAMESIZE + 1] = '\0'; |
|
bufl = strlen(buf); |
|
|
|
if(bufl + strlen(tmpbuf) < buflen) |
|
/* only append the scopeid string if it fits in the target buffer */ |
|
strcpy(&buf[bufl], tmpbuf); |
|
} |
|
#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; |
|
int lo1, lo2; |
|
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) |
|
{ |
|
lo1 = TOLOWER(*c1); |
|
lo2 = TOLOWER(*c2); |
|
if (lo1 != lo2) |
|
return NULL; |
|
else |
|
{ |
|
c1++; |
|
c2++; |
|
} |
|
} |
|
if (c2 == c1 && c2 == NULL) |
|
return (char *)c1_begin; |
|
return NULL; |
|
}
|
|
|