diff options
Diffstat (limited to 'openbsd-compat/libasr/gethostnamadr_async.c')
-rw-r--r-- | openbsd-compat/libasr/gethostnamadr_async.c | 676 |
1 files changed, 676 insertions, 0 deletions
diff --git a/openbsd-compat/libasr/gethostnamadr_async.c b/openbsd-compat/libasr/gethostnamadr_async.c new file mode 100644 index 00000000..2636e848 --- /dev/null +++ b/openbsd-compat/libasr/gethostnamadr_async.c @@ -0,0 +1,676 @@ +/* $OpenBSD: gethostnamadr_async.c,v 1.45 2019/06/27 05:26:37 martijn 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 <netinet/in.h> +#include <arpa/inet.h> +#include <arpa/nameser.h> +#ifdef HAVE_ARPA_NAMESER_COMPAT_H +#include <arpa/nameser_compat.h> +#endif +#include <netdb.h> + +#include <asr.h> +#include <ctype.h> +#include <errno.h> +#include <resolv.h> /* for res_hnok */ +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <limits.h> + +#include "asr_private.h" + +#define MAXALIASES 35 +#define MAXADDRS 35 + +struct hostent_ext { + struct hostent h; + char *aliases[MAXALIASES + 1]; + char *addrs[MAXADDRS + 1]; + char *end; + char *pos; +}; + +struct netent_ext { + struct netent n; + char *aliases[MAXALIASES + 1]; + char *end; + char *pos; +}; + +static int gethostnamadr_async_run(struct asr_query *, struct asr_result *); +static struct hostent_ext *hostent_alloc(int); +static int hostent_set_cname(struct hostent_ext *, const char *, int); +static int hostent_add_alias(struct hostent_ext *, const char *, int); +static int hostent_add_addr(struct hostent_ext *, const void *, size_t); +static struct hostent_ext *hostent_from_addr(int, const char *, const char *); +static struct hostent_ext *hostent_file_match(FILE *, int, int, const char *, + int); +static struct hostent_ext *hostent_from_packet(int, int, char *, size_t); +static void netent_from_hostent(struct asr_result *ar); + +struct asr_query * +gethostbyname_async(const char *name, void *asr) +{ + return gethostbyname2_async(name, AF_INET, asr); +} +DEF_WEAK(gethostbyname_async); + +struct asr_query * +gethostbyname2_async(const char *name, int af, void *asr) +{ + struct asr_ctx *ac; + struct asr_query *as; + + /* the original segfaults */ + if (name == NULL) { + errno = EINVAL; + return (NULL); + } + + ac = _asr_use_resolver(asr); + if ((as = _asr_async_new(ac, ASR_GETHOSTBYNAME)) == NULL) + goto abort; /* errno set */ + as->as_run = gethostnamadr_async_run; + + as->as.hostnamadr.family = af; + if (af == AF_INET) + as->as.hostnamadr.addrlen = INADDRSZ; + else if (af == AF_INET6) + as->as.hostnamadr.addrlen = IN6ADDRSZ; + as->as.hostnamadr.name = strdup(name); + if (as->as.hostnamadr.name == NULL) + goto abort; /* errno set */ + + _asr_ctx_unref(ac); + return (as); + + abort: + if (as) + _asr_async_free(as); + _asr_ctx_unref(ac); + return (NULL); +} +DEF_WEAK(gethostbyname2_async); + +struct asr_query * +gethostbyaddr_async(const void *addr, socklen_t len, int af, void *asr) +{ + struct asr_ctx *ac; + struct asr_query *as; + + ac = _asr_use_resolver(asr); + as = _gethostbyaddr_async_ctx(addr, len, af, ac); + _asr_ctx_unref(ac); + + return (as); +} +DEF_WEAK(gethostbyaddr_async); + +struct asr_query * +_gethostbyaddr_async_ctx(const void *addr, socklen_t len, int af, + struct asr_ctx *ac) +{ + struct asr_query *as; + + if ((as = _asr_async_new(ac, ASR_GETHOSTBYADDR)) == NULL) + goto abort; /* errno set */ + as->as_run = gethostnamadr_async_run; + + as->as.hostnamadr.family = af; + as->as.hostnamadr.addrlen = len; + if (len > 0) + memmove(as->as.hostnamadr.addr, addr, (len > 16) ? 16 : len); + + return (as); + + abort: + if (as) + _asr_async_free(as); + return (NULL); +} + +static int +gethostnamadr_async_run(struct asr_query *as, struct asr_result *ar) +{ + struct hostent_ext *h; + int r, type, saved_errno; + FILE *f; + char name[MAXDNAME], *data, addr[16], *c; + + next: + switch (as->as_state) { + + case ASR_STATE_INIT: + + if (as->as.hostnamadr.family != AF_INET && + as->as.hostnamadr.family != AF_INET6) { + ar->ar_h_errno = NETDB_INTERNAL; + ar->ar_errno = EAFNOSUPPORT; + async_set_state(as, ASR_STATE_HALT); + break; + } + + if ((as->as.hostnamadr.family == AF_INET && + as->as.hostnamadr.addrlen != INADDRSZ) || + (as->as.hostnamadr.family == AF_INET6 && + as->as.hostnamadr.addrlen != IN6ADDRSZ)) { + ar->ar_h_errno = NETDB_INTERNAL; + ar->ar_errno = EINVAL; + async_set_state(as, ASR_STATE_HALT); + break; + } + + if (as->as_type == ASR_GETHOSTBYNAME) { + + if (as->as.hostnamadr.name[0] == '\0') { + ar->ar_h_errno = NO_DATA; + async_set_state(as, ASR_STATE_HALT); + break; + } + + /* Name might be an IP address string */ + for (c = as->as.hostnamadr.name; *c; c++) + if (!isdigit((unsigned char)*c) && + *c != '.' && *c != ':') + break; + if (*c == 0 && + inet_pton(as->as.hostnamadr.family, + as->as.hostnamadr.name, addr) == 1) { + h = hostent_from_addr(as->as.hostnamadr.family, + as->as.hostnamadr.name, addr); + if (h == NULL) { + ar->ar_errno = errno; + ar->ar_h_errno = NETDB_INTERNAL; + } + else { + ar->ar_hostent = &h->h; + ar->ar_h_errno = NETDB_SUCCESS; + } + async_set_state(as, ASR_STATE_HALT); + break; + } + } + async_set_state(as, ASR_STATE_NEXT_DB); + break; + + case ASR_STATE_NEXT_DB: + + if (_asr_iter_db(as) == -1) { + async_set_state(as, ASR_STATE_NOT_FOUND); + break; + } + + switch (AS_DB(as)) { + + case ASR_DB_DNS: + + /* Create a subquery to do the DNS lookup */ + + if (as->as_type == ASR_GETHOSTBYNAME) { + type = (as->as.hostnamadr.family == AF_INET) ? + T_A : T_AAAA; + as->as_subq = _res_search_async_ctx( + as->as.hostnamadr.name, + C_IN, type, as->as_ctx); + } else { + _asr_addr_as_fqdn(as->as.hostnamadr.addr, + as->as.hostnamadr.family, + name, sizeof(name)); + as->as_subq = _res_query_async_ctx( + name, C_IN, T_PTR, as->as_ctx); + } + + if (as->as_subq == NULL) { + ar->ar_errno = errno; + ar->ar_h_errno = NETDB_INTERNAL; + async_set_state(as, ASR_STATE_HALT); + break; + } + + async_set_state(as, ASR_STATE_SUBQUERY); + break; + + case ASR_DB_FILE: + + /* Try to find a match in the host file */ + + if ((f = fopen(_PATH_HOSTS, "re")) == NULL) + break; + + if (as->as_type == ASR_GETHOSTBYNAME) + data = as->as.hostnamadr.name; + else + data = as->as.hostnamadr.addr; + + h = hostent_file_match(f, as->as_type, + as->as.hostnamadr.family, data, + as->as.hostnamadr.addrlen); + saved_errno = errno; + fclose(f); + errno = saved_errno; + + if (h == NULL) { + if (errno) { + ar->ar_errno = errno; + ar->ar_h_errno = NETDB_INTERNAL; + async_set_state(as, ASR_STATE_HALT); + } + /* otherwise not found */ + break; + } + ar->ar_hostent = &h->h; + ar->ar_h_errno = NETDB_SUCCESS; + async_set_state(as, ASR_STATE_HALT); + break; + } + break; + + case ASR_STATE_SUBQUERY: + + /* Run the DNS subquery. */ + + if ((r = asr_run(as->as_subq, ar)) == ASYNC_COND) + return (ASYNC_COND); + + /* Done. */ + as->as_subq = NULL; + + /* + * We either got no packet or a packet without an answer. + * Saveguard the h_errno and use the next DB. + */ + if (ar->ar_count == 0) { + free(ar->ar_data); + as->as.hostnamadr.subq_h_errno = ar->ar_h_errno; + async_set_state(as, ASR_STATE_NEXT_DB); + break; + } + + /* Read the hostent from the packet. */ + + h = hostent_from_packet(as->as_type, + as->as.hostnamadr.family, ar->ar_data, ar->ar_datalen); + free(ar->ar_data); + if (h == NULL) { + ar->ar_errno = errno; + ar->ar_h_errno = NETDB_INTERNAL; + async_set_state(as, ASR_STATE_HALT); + break; + } + + if (as->as_type == ASR_GETHOSTBYADDR) { + if (hostent_add_addr(h, as->as.hostnamadr.addr, + as->as.hostnamadr.addrlen) == -1) { + free(h); + ar->ar_errno = errno; + ar->ar_h_errno = NETDB_INTERNAL; + async_set_state(as, ASR_STATE_HALT); + break; + } + } + + /* + * No valid hostname or address found in the dns packet. + * Ignore it. + */ + if ((as->as_type == ASR_GETHOSTBYNAME && + h->h.h_addr_list[0] == NULL) || + h->h.h_name == NULL) { + free(h); + async_set_state(as, ASR_STATE_NEXT_DB); + break; + } + + ar->ar_hostent = &h->h; + ar->ar_h_errno = NETDB_SUCCESS; + async_set_state(as, ASR_STATE_HALT); + break; + + case ASR_STATE_NOT_FOUND: + ar->ar_errno = 0; + if (as->as.hostnamadr.subq_h_errno) + ar->ar_h_errno = as->as.hostnamadr.subq_h_errno; + else + ar->ar_h_errno = HOST_NOT_FOUND; + async_set_state(as, ASR_STATE_HALT); + break; + + case ASR_STATE_HALT: + if (ar->ar_h_errno == NETDB_SUCCESS && + as->as_flags & ASYNC_GETNET) + netent_from_hostent(ar); + if (ar->ar_h_errno) { + ar->ar_hostent = NULL; + ar->ar_netent = NULL; + } else + ar->ar_errno = 0; + return (ASYNC_DONE); + + default: + ar->ar_errno = EOPNOTSUPP; + ar->ar_h_errno = NETDB_INTERNAL; + ar->ar_gai_errno = EAI_SYSTEM; + async_set_state(as, ASR_STATE_HALT); + break; + } + goto next; +} + +/* + * Create a hostent from a numeric address string. + */ +static struct hostent_ext * +hostent_from_addr(int family, const char *name, const char *addr) +{ + struct hostent_ext *h; + + if ((h = hostent_alloc(family)) == NULL) + return (NULL); + if (hostent_set_cname(h, name, 0) == -1) + goto fail; + if (hostent_add_addr(h, addr, h->h.h_length) == -1) + goto fail; + return (h); +fail: + free(h); + return (NULL); +} + +/* + * Lookup the first matching entry in the hostfile, either by address or by + * name depending on reqtype, and build a hostent from the line. + */ +static struct hostent_ext * +hostent_file_match(FILE *f, int reqtype, int family, const char *data, + int datalen) +{ + char *tokens[MAXTOKEN], addr[16], buf[ASR_BUFSIZ + 1]; + struct hostent_ext *h; + int n, i; + + for (;;) { + n = _asr_parse_namedb_line(f, tokens, MAXTOKEN, buf, sizeof(buf)); + if (n == -1) { + errno = 0; /* ignore errors reading the file */ + return (NULL); + } + + /* there must be an address and at least one name */ + if (n < 2) + continue; + + if (reqtype == ASR_GETHOSTBYNAME) { + for (i = 1; i < n; i++) { + if (strcasecmp(data, tokens[i])) + continue; + if (inet_pton(family, tokens[0], addr) == 1) + goto found; + } + } else { + if (inet_pton(family, tokens[0], addr) == 1 && + memcmp(addr, data, datalen) == 0) + goto found; + } + } + +found: + if ((h = hostent_alloc(family)) == NULL) + return (NULL); + if (hostent_set_cname(h, tokens[1], 0) == -1) + goto fail; + for (i = 2; i < n; i ++) + if (hostent_add_alias(h, tokens[i], 0) == -1) + goto fail; + if (hostent_add_addr(h, addr, h->h.h_length) == -1) + goto fail; + return (h); +fail: + free(h); + return (NULL); +} + +/* + * Fill the hostent from the given DNS packet. + */ +static struct hostent_ext * +hostent_from_packet(int reqtype, int family, char *pkt, size_t pktlen) +{ + struct hostent_ext *h; + struct asr_unpack p; + struct asr_dns_header hdr; + struct asr_dns_query q; + struct asr_dns_rr rr; + char dname[MAXDNAME]; + + if ((h = hostent_alloc(family)) == NULL) + return (NULL); + + _asr_unpack_init(&p, pkt, pktlen); + _asr_unpack_header(&p, &hdr); + for (; hdr.qdcount; hdr.qdcount--) + _asr_unpack_query(&p, &q); + strlcpy(dname, q.q_dname, sizeof(dname)); + + for (; hdr.ancount; hdr.ancount--) { + _asr_unpack_rr(&p, &rr); + if (rr.rr_class != C_IN) + continue; + switch (rr.rr_type) { + + case T_CNAME: + if (reqtype == ASR_GETHOSTBYNAME) { + if (hostent_add_alias(h, rr.rr_dname, 1) == -1) + goto fail; + } else { + if (strcasecmp(rr.rr_dname, dname) == 0) + strlcpy(dname, rr.rr.cname.cname, + sizeof(dname)); + } + break; + + case T_PTR: + if (reqtype != ASR_GETHOSTBYADDR) + break; + if (strcasecmp(rr.rr_dname, dname) != 0) + continue; + if (hostent_set_cname(h, rr.rr.ptr.ptrname, 1) == -1) + hostent_add_alias(h, rr.rr.ptr.ptrname, 1); + break; + + case T_A: + if (reqtype != ASR_GETHOSTBYNAME) + break; + if (family != AF_INET) + break; + if (hostent_set_cname(h, rr.rr_dname, 1) == -1) + ; + if (hostent_add_addr(h, &rr.rr.in_a.addr, 4) == -1) + goto fail; + break; + + case T_AAAA: + if (reqtype != ASR_GETHOSTBYNAME) + break; + if (family != AF_INET6) + break; + if (hostent_set_cname(h, rr.rr_dname, 1) == -1) + ; + if (hostent_add_addr(h, &rr.rr.in_aaaa.addr6, 16) == -1) + goto fail; + break; + } + } + + return (h); +fail: + free(h); + return (NULL); +} + +static struct hostent_ext * +hostent_alloc(int family) +{ + struct hostent_ext *h; + size_t alloc; + + alloc = sizeof(*h) + 1024; + if ((h = calloc(1, alloc)) == NULL) + return (NULL); + + h->h.h_addrtype = family; + h->h.h_length = (family == AF_INET) ? 4 : 16; + h->h.h_aliases = h->aliases; + h->h.h_addr_list = h->addrs; + h->pos = (char *)(h) + sizeof(*h); + h->end = h->pos + 1024; + + return (h); +} + +static int +hostent_set_cname(struct hostent_ext *h, const char *name, int isdname) +{ + char buf[MAXDNAME]; + size_t n; + + if (h->h.h_name) + return (-1); + + if (isdname) { + _asr_strdname(name, buf, sizeof buf); + buf[strlen(buf) - 1] = '\0'; + if (!res_hnok(buf)) + return (-1); + name = buf; + } + + n = strlen(name) + 1; + if (h->pos + n >= h->end) + return (-1); + + h->h.h_name = h->pos; + memmove(h->pos, name, n); + h->pos += n; + return (0); +} + +static int +hostent_add_alias(struct hostent_ext *h, const char *name, int isdname) +{ + char buf[MAXDNAME]; + size_t i, n; + + for (i = 0; i < MAXALIASES; i++) + if (h->aliases[i] == NULL) + break; + if (i == MAXALIASES) + return (0); + + if (isdname) { + _asr_strdname(name, buf, sizeof buf); + buf[strlen(buf)-1] = '\0'; + if (!res_hnok(buf)) + return (-1); + name = buf; + } + + n = strlen(name) + 1; + if (h->pos + n >= h->end) + return (0); + + h->aliases[i] = h->pos; + memmove(h->pos, name, n); + h->pos += n; + return (0); +} + +static int +hostent_add_addr(struct hostent_ext *h, const void *addr, size_t size) +{ + int i; + + for (i = 0; i < MAXADDRS; i++) + if (h->addrs[i] == NULL) + break; + if (i == MAXADDRS) + return (0); + + if (h->pos + size >= h->end) + return (0); + + h->addrs[i] = h->pos; + memmove(h->pos, addr, size); + h->pos += size; + return (0); +} + +static void +netent_from_hostent(struct asr_result *ar) +{ + struct in_addr *addr; + struct netent_ext *n; + struct hostent_ext *h; + char **na, **ha; + size_t sz; + + /* Allocate and initialize the output. */ + if ((n = calloc(1, sizeof(*n) + 1024)) == NULL) { + ar->ar_h_errno = NETDB_INTERNAL; + ar->ar_errno = errno; + goto out; + } + n->pos = (char *)(n) + sizeof(*n); + n->end = n->pos + 1024; + n->n.n_name = n->pos; + n->n.n_aliases = n->aliases; + + /* Copy the fixed-size data. */ + h = (struct hostent_ext *)ar->ar_hostent; + addr = (struct in_addr *)h->h.h_addr; + n->n.n_net = ntohl(addr->s_addr); + n->n.n_addrtype = h->h.h_addrtype; + + /* Copy the network name. */ + sz = strlen(h->h.h_name) + 1; + memcpy(n->pos, h->h.h_name, sz); + n->pos += sz; + + /* + * Copy the aliases. + * No overflow check is needed because we are merely copying + * a part of the data from a structure of the same size. + */ + na = n->aliases; + for (ha = h->aliases; *ha != NULL; ha++) { + sz = strlen(*ha) + 1; + memcpy(n->pos, *ha, sz); + *na++ = n->pos; + n->pos += sz; + } + *na = NULL; + + /* Handle the return values. */ + ar->ar_netent = &n->n; +out: + free(ar->ar_hostent); + ar->ar_hostent = NULL; +} |