openbsd-compat/libasr/getnameinfo_async.c
Rama master · réplica de lectura del repositorio original.
/* $OpenBSD: getnameinfo_async.c,v 1.15 2020/12/21 09:40:35 eric Exp $ */
/*
* Copyright (c) 2012 Eric Faurot <eric@openbsd.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "includes.h"
#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; /* errno set */
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));
#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN
as->as.ni.sa.sa.sa_len = slen;
#endif
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:
/* Make sure the parameters are all valid. */
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 &&
(SA_LEN(&as->as.ni.sa.sa) != sizeof (as->as.ni.sa.sain))) ||
(as->as.ni.sa.sa.sa_family == AF_INET6 &&
(SA_LEN(&as->as.ni.sa.sa) != sizeof (as->as.ni.sa.sain6)))) {
ar->ar_gai_errno = EAI_FAIL;
async_set_state(as, ASR_STATE_HALT);
break;
}
/* Set the service name first, if needed. */
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;
}
/*
* Create a subquery to lookup the address.
*/
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);
/*
* Request done.
*/
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;
}
/*
* Set the service name on the result buffer is not NULL.
* return (-1) if the buffer is too small.
*/
static int
_servname(struct asr_query *as)
{
struct servent s;
#ifdef HAVE_STRUCT_SERVENT_DATA
struct servent_data sd;
#endif
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)) {
#ifdef HAVE_STRUCT_SERVENT_DATA
memset(&sd, 0, sizeof (sd));
#endif
#ifdef HAVE_GETSERVBYPORT_R_4_ARGS
r = getservbyport_r(port, (as->as.ni.flags & NI_DGRAM) ?
"udp" : "tcp", &s, &sd);
if (r == 0)
n = strlcpy(buf, s.s_name, buflen);
#else
r = -1;
#endif
#ifdef HAVE_ENDSERVENT_R
endservent_r(&sd);
#endif
if (r == 0) {
if (n >= buflen)
return (-1);
return (0);
}
}
r = snprintf(buf, buflen, "%u", ntohs(port));
if (r < 0 || (size_t)r >= buflen)
return (-1);
return (0);
}
/*
* Write the numeric address
*/
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); /* errno set */
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);
}