#include <sys/types.h>
#include <sys/socket.h>
#include <net/if.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <arpa/nameser.h>
#include <netdb.h>
#include <asr.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "asr_private.h"
static int getnameinfo_async_run(struct asr_query *, struct asr_result *);
static int _servname(struct asr_query *);
static int _numerichost(struct asr_query *);
struct asr_query *
getnameinfo_async(const struct sockaddr *sa, socklen_t slen, char *host,
size_t hostlen, char *serv, size_t servlen, int flags, void *asr)
{
struct asr_ctx *ac;
struct asr_query *as;
ac = _asr_use_resolver(asr);
if ((as = _asr_async_new(ac, ASR_GETNAMEINFO)) == NULL)
goto abort;
as->as_run = getnameinfo_async_run;
if (sa->sa_family == AF_INET)
memmove(&as->as.ni.sa.sa, sa, sizeof (as->as.ni.sa.sain));
else if (sa->sa_family == AF_INET6)
memmove(&as->as.ni.sa.sa, sa, sizeof (as->as.ni.sa.sain6));
as->as.ni.sa.sa.sa_len = slen;
as->as.ni.hostname = host;
as->as.ni.hostnamelen = hostlen;
as->as.ni.servname = serv;
as->as.ni.servnamelen = servlen;
as->as.ni.flags = flags;
_asr_ctx_unref(ac);
return (as);
abort:
if (as)
_asr_async_free(as);
_asr_ctx_unref(ac);
return (NULL);
}
DEF_WEAK(getnameinfo_async);
static int
getnameinfo_async_run(struct asr_query *as, struct asr_result *ar)
{
void *addr;
socklen_t addrlen;
int r;
next:
switch (as->as_state) {
case ASR_STATE_INIT:
if (as->as.ni.sa.sa.sa_family != AF_INET &&
as->as.ni.sa.sa.sa_family != AF_INET6) {
ar->ar_gai_errno = EAI_FAMILY;
async_set_state(as, ASR_STATE_HALT);
break;
}
if ((as->as.ni.sa.sa.sa_family == AF_INET &&
(as->as.ni.sa.sa.sa_len != sizeof (as->as.ni.sa.sain))) ||
(as->as.ni.sa.sa.sa_family == AF_INET6 &&
(as->as.ni.sa.sa.sa_len != sizeof (as->as.ni.sa.sain6)))) {
ar->ar_gai_errno = EAI_FAIL;
async_set_state(as, ASR_STATE_HALT);
break;
}
if (_servname(as) == -1) {
ar->ar_gai_errno = EAI_OVERFLOW;
async_set_state(as, ASR_STATE_HALT);
break;
}
if (as->as.ni.hostname == NULL || as->as.ni.hostnamelen == 0) {
ar->ar_gai_errno = 0;
async_set_state(as, ASR_STATE_HALT);
break;
}
if (as->as.ni.flags & NI_NUMERICHOST) {
if (_numerichost(as) == -1) {
if (errno == ENOMEM)
ar->ar_gai_errno = EAI_MEMORY;
else if (errno == ENOSPC)
ar->ar_gai_errno = EAI_OVERFLOW;
else {
ar->ar_errno = errno;
ar->ar_gai_errno = EAI_SYSTEM;
}
} else
ar->ar_gai_errno = 0;
async_set_state(as, ASR_STATE_HALT);
break;
}
if (as->as.ni.sa.sa.sa_family == AF_INET) {
addrlen = sizeof(as->as.ni.sa.sain.sin_addr);
addr = &as->as.ni.sa.sain.sin_addr;
} else {
addrlen = sizeof(as->as.ni.sa.sain6.sin6_addr);
addr = &as->as.ni.sa.sain6.sin6_addr;
}
as->as_subq = _gethostbyaddr_async_ctx(addr, addrlen,
as->as.ni.sa.sa.sa_family,
as->as_ctx);
if (as->as_subq == NULL) {
ar->ar_gai_errno = EAI_MEMORY;
async_set_state(as, ASR_STATE_HALT);
break;
}
async_set_state(as, ASR_STATE_SUBQUERY);
break;
case ASR_STATE_SUBQUERY:
if ((r = asr_run(as->as_subq, ar)) == ASYNC_COND)
return (ASYNC_COND);
as->as_subq = NULL;
if (ar->ar_hostent == NULL) {
if (as->as.ni.flags & NI_NAMEREQD) {
ar->ar_gai_errno = EAI_NONAME;
} else if (_numerichost(as) == -1) {
if (errno == ENOMEM)
ar->ar_gai_errno = EAI_MEMORY;
else if (errno == ENOSPC)
ar->ar_gai_errno = EAI_OVERFLOW;
else {
ar->ar_errno = errno;
ar->ar_gai_errno = EAI_SYSTEM;
}
} else
ar->ar_gai_errno = 0;
} else {
if (strlcpy(as->as.ni.hostname,
ar->ar_hostent->h_name,
as->as.ni.hostnamelen) >= as->as.ni.hostnamelen)
ar->ar_gai_errno = EAI_OVERFLOW;
else
ar->ar_gai_errno = 0;
free(ar->ar_hostent);
}
async_set_state(as, ASR_STATE_HALT);
break;
case ASR_STATE_HALT:
return (ASYNC_DONE);
default:
ar->ar_errno = EOPNOTSUPP;
ar->ar_gai_errno = EAI_SYSTEM;
async_set_state(as, ASR_STATE_HALT);
break;
}
goto next;
}
static int
_servname(struct asr_query *as)
{
struct servent s;
struct servent_data sd;
int port, r;
char *buf = as->as.ni.servname;
size_t n, buflen = as->as.ni.servnamelen;
if (as->as.ni.servname == NULL || as->as.ni.servnamelen == 0)
return (0);
if (as->as.ni.sa.sa.sa_family == AF_INET)
port = as->as.ni.sa.sain.sin_port;
else
port = as->as.ni.sa.sain6.sin6_port;
if (!(as->as.ni.flags & NI_NUMERICSERV)) {
memset(&sd, 0, sizeof (sd));
r = getservbyport_r(port, (as->as.ni.flags & NI_DGRAM) ?
"udp" : "tcp", &s, &sd);
if (r == 0)
n = strlcpy(buf, s.s_name, buflen);
endservent_r(&sd);
if (r == 0) {
if (n >= buflen)
return (-1);
return (0);
}
}
r = snprintf(buf, buflen, "%u", ntohs(port));
if (r < 0 || r >= buflen)
return (-1);
return (0);
}
static int
_numerichost(struct asr_query *as)
{
unsigned int ifidx;
char scope[IF_NAMESIZE + 1], *ifname;
void *addr;
char *buf = as->as.ni.hostname;
size_t buflen = as->as.ni.hostnamelen;
if (as->as.ni.sa.sa.sa_family == AF_INET)
addr = &as->as.ni.sa.sain.sin_addr;
else
addr = &as->as.ni.sa.sain6.sin6_addr;
if (inet_ntop(as->as.ni.sa.sa.sa_family, addr, buf, buflen) == NULL)
return (-1);
if (as->as.ni.sa.sa.sa_family == AF_INET6 &&
as->as.ni.sa.sain6.sin6_scope_id) {
scope[0] = SCOPE_DELIMITER;
scope[1] = '\0';
ifidx = as->as.ni.sa.sain6.sin6_scope_id;
ifname = NULL;
if (IN6_IS_ADDR_LINKLOCAL(&as->as.ni.sa.sain6.sin6_addr) ||
IN6_IS_ADDR_MC_LINKLOCAL(&as->as.ni.sa.sain6.sin6_addr) ||
IN6_IS_ADDR_MC_INTFACELOCAL(&as->as.ni.sa.sain6.sin6_addr))
ifname = if_indextoname(ifidx, scope + 1);
if (ifname == NULL)
snprintf(scope + 1, sizeof(scope) - 1, "%u", ifidx);
strlcat(buf, scope, buflen);
}
return (0);
}